一、問題描述
剛一看標(biāo)題相信大家都有點(diǎn)懵逼,具體問題是這樣的:今天一個(gè)群友在群里貼出了一個(gè)他遇到的問題,他在使用spring的時(shí)候,配置方式是java方式配置,其中的一個(gè)配置類大概如下:
他這個(gè)時(shí)候發(fā)現(xiàn)environment沒有被注入進(jìn)來,然后他把MapperScannerConfigurer的定義去掉后發(fā)現(xiàn),environment可以正常被注入??吹剿岢龅倪@個(gè)問題后,我寫了一個(gè)demo測(cè)試了一下,果然如他描述,當(dāng)配置類中定義了MapperScannerConfigurer,這個(gè)配置類中Environment是注入不進(jìn)來的。因?yàn)槠綍r(shí)喜歡看spring的源碼,并且對(duì)spring的原理有一定的理解,所以我打算幫他找出導(dǎo)致這個(gè)問題的原因。
二、初步分析
為什么加了MapperScannerConfigurer的定義就會(huì)出現(xiàn)這種注入不了的問題?MapperScannerConfigurer到底有什么特殊之處,我點(diǎn)開了MapperScannerConfigurer的源碼,看了一下:

這個(gè)時(shí)候我一眼看到了MapperScannerConfigurer實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor,XXXPostProcessor是spring生命周期中提供的各類接口,這里我貼出MapperScannerConfigurer的類圖,看一下MapperScannerConfigurer的具體繼承關(guān)系:

原來BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的一個(gè)子接口,BeanFactoryPostProcessor這個(gè)接口的作用是:spring在所有的Bean的定義都被解析完BeanDefinition后,調(diào)用所有實(shí)現(xiàn)了BeanFactoryPostProcessor接口的類的postProcessBeanFactory方法,來給用戶提供最后一個(gè)改變BeanDefinition的機(jī)會(huì)。到這里,我們可以大概有了一個(gè)分析的方向。(關(guān)于Spring的生命周期,請(qǐng)參考我之前的一篇文章:《BeanPostProcessor和BeanFactoryProcessor淺析》)
三、詳細(xì)分析
我們先從springboot的啟動(dòng)入口開始分析:SpringBoot項(xiàng)目的啟動(dòng)入口為SpringApplication的run方法,我們一步一步進(jìn)入可以發(fā)現(xiàn)最終執(zhí)行的是SpringApplication的public ConfigurableApplicationContext run(String... args)這個(gè)方法。我們把代碼拿出來看一下:

由于本文的重點(diǎn)是排查問題,所以不會(huì)詳細(xì)解釋整個(gè)啟動(dòng)過程的源碼,這里只簡單的說一下每一步spring做了什么操作,有興趣的同學(xué)可以自己查閱源碼查看細(xì)節(jié)。
首先,我們可以看到environment初始化和賦值在applicationContext創(chuàng)建的時(shí)候就已經(jīng)完成了,如圖中1標(biāo)記處,environment初始化完成。所以這里可以排除引起標(biāo)題中說道的問題的一個(gè)可能性:MapperScannerConfigurer初始化先于environment。
接著,圖中2標(biāo)記處,創(chuàng)建了applicationContext對(duì)象,這里只是創(chuàng)建了一個(gè)空的applicationContext對(duì)象。
然后,圖中3標(biāo)記處,prepareContext()方法向applicationContext中設(shè)置了一些定義性的屬性:如設(shè)置environment、設(shè)置applicationContext的配置定義入口XXApplication(就是我們spring項(xiàng)目中的帶main方法的那個(gè)入口類)。
最后就是圖中標(biāo)記的第四步,這里是整個(gè)springboot項(xiàng)目啟動(dòng)的核心方法。在這一步,spring會(huì)加載bean的定義,執(zhí)行對(duì)外開放的生命周期接口。我們進(jìn)入這個(gè)方法,會(huì)發(fā)現(xiàn)這個(gè)方法最后是執(zhí)行applicationContext的refresh方法。在之前的文章中我們分析過applicationContext的refresh方法。這里,我們以AbstractApplicationContext的refresh方法為例,結(jié)合現(xiàn)在的問題再次分析一下這個(gè)方法:

圖中1處,做刷新applicationContext的準(zhǔn)備,這里可以不用特別關(guān)心。
圖中2處,獲取當(dāng)前applicationContext持有的BeanFactory對(duì)象。
圖中3處,prepareBeanFactory方法給BeanFactory設(shè)置了一些屬性,我們也可以忽略。
圖中4處,postProcessBeanFactory方法,是一個(gè)空方法,提供給子類重寫,來作為一個(gè)開放點(diǎn)。
圖中5處,是本篇文章的關(guān)鍵處,invokeBeanFactoryPostProcessors是干什么的呢?這個(gè)方法的作用是執(zhí)行所有的BeanFactoryPostProcessor接口的postProcessBeanFactory方法。這里有一點(diǎn)我們需要知道,首先不同的Application的子類在圖2處的獲取BeanFactory的方法處有不同的實(shí)現(xiàn)邏輯,有的子類在obtainFreshBeanFactory這個(gè)方法中就已經(jīng)加載了所有的Bean的定義,有的方法沒有在此加載Bean的定義,而是將加載Bean的定義放在invokeBeanFactoryPostProcessors中,通過ConfigurationClassPostProcessor這個(gè)BeanFactoryPostProcessor的實(shí)現(xiàn)類來進(jìn)行Bean定義的讀取。不過不管Bean定義的讀取發(fā)生在何處,可以確定的是在Bean定義讀取完之后會(huì)執(zhí)行所有的BeanFactoryPostProcessor接口提供的生命周期方法。
所以問題就來了,我們把MapperScannerConfigurer定義在我們自己的JAVA配置類中,如果要在圖中5處,也就是invokeBeanFactoryPostProcessors方法中被執(zhí)行,就必須在此之前創(chuàng)建我們的JAVA配置類的實(shí)例,也就是我們的栗子中的TestConfig。這個(gè)也就是出現(xiàn)我們標(biāo)題中的問題的關(guān)鍵所在?。。∥覀円肋@樣一個(gè)事情:@Autowired注入屬性,是通過BeanPostProcessor的一個(gè)子類AutowiredAnnotationBeanPostProcessor來實(shí)現(xiàn)的,看過之前的《BeanPostProcessor和BeanFactoryProcessor淺析》這篇文章的朋友會(huì)知道,BeanPostProcessor這個(gè)Spring生命周期中開放的接口是在Bean實(shí)例化的時(shí)候調(diào)用的(postProcessBeforeInitialization方法是在所有的bean的InitializingBean的afterPropertiesSet方法之前執(zhí)行而postProcessAfterInitialization方法則是在所有的bean的InitializingBean的afterPropertiesSet方法之后執(zhí)行的),而我們從圖中6處可以看到,BeanPostProcessor的注冊(cè)是發(fā)生在BeanFactoryPostProcessor接口被調(diào)用之后。而我們的例子中,MapperScannerConfigurer這個(gè)類是BeanFactoryPostProcessor的子類,如果想要被創(chuàng)建,并且被應(yīng)用到圖中5處的方法中,就必須先創(chuàng)建TestConfig的實(shí)例,而這個(gè)時(shí)候,BeanPostProcessor接口是沒有被注冊(cè)的,所以這個(gè)時(shí)候,TestConfig的實(shí)例想要通過@Autowired來注入屬性對(duì)象是不可能的。
四、問題解決
那么問題來了,如果我們?cè)谶@種情況下想要使用Environment怎么辦?這里我先把答案給出來:讓配置類實(shí)現(xiàn)EnvironmentAware接口,XXXAware也是Spring生命周期中提供的一系列接口,作用是注入一些spring的關(guān)鍵對(duì)象,比如說ApplicationContext、Environment等對(duì)象。注入的原理也是通過BeanPostProcessor的一個(gè)子類ApplicationContextAwareProcessor實(shí)現(xiàn)的,在bean被創(chuàng)建的時(shí)候如果發(fā)現(xiàn)這個(gè)bean實(shí)現(xiàn)了Aware接口,就調(diào)用Aware提供的對(duì)應(yīng)的注入方法,我們可以看一下具體代碼:

細(xì)心的朋友也許會(huì)問,BeanPostProcessor不是在圖中6出注冊(cè)的嗎?應(yīng)該不會(huì)被調(diào)用呀。其實(shí)是這樣的ApplicationContextAwareProcessor作為Spring內(nèi)部的一個(gè)比較重要的類,早在圖中3處prepareBeanFactory這個(gè)方法中注冊(cè)了:

那么問題又來了,這個(gè)時(shí)候如果我想在例子中的TestConfig配置類中注入其他普通的bean可以嗎?答案是不可以?。?!我們只能通過實(shí)現(xiàn)Aware接口這種手段注入一些Spring給我們提供的特殊對(duì)象?;蛘咄ㄟ^ApplicationContextAware注入applicationContext,從applicationContext中獲取需要的bean,但是要注意,盡管applicationContext可以被注入進(jìn)來,但是在類似MapperScannerConfigurer這種實(shí)現(xiàn)了BeanPostProcessor的類的定義方法中,通過applicationContext獲取的對(duì)象是有問題的,這些對(duì)象如果有通過@Autowired注入屬性對(duì)象,這些屬性對(duì)象都將是空,甚至直到Spring容器初始化完成之后,這些屬性也都是空,所以通過這種方法獲取對(duì)象,帶來的影響太惡劣了,有些得不償失!
今天的分析就到這里,希望大家能通過本篇,對(duì)Spring的生命周期有更深的理解!