一、
在spring傳統項目中,我們的bean定義信息是存放在xml中的,在項目啟動的時候,需要將xml傳遞給容器
但是到了springboot中,普遍使用javaConfig來定義bean,使用@Component、@Configuration、@ComponentScan這些基礎注解實現的配置
還有在自動配置中,一些按照約定注入的bean是寫在spring.factories文件的,這些bean是通過@Import注解批量注冊到容器中的
但是在springboot啟動的時候,我們是沒有任何顯式的操作,這些bean就會自動注冊到容器中了,那么接下來就找找這個bean加載的源頭。
二、
在開始之前,先復習一下,那些以@Component注解為基礎的注解是需要配合@ComponentScan來掃描的,也就是說,我們至少需要知道掃描的范圍
那么在springboot中,掃描的范圍是在@SpringBootApplication --> @EnableAutoConfiguration --> @AutoConfigurationPackage --> @Import(AutoConfigurationPackages.Registrar.class) 確定的
注意到,方法參數中,已經可以確定包名了
然后通過注解的元信息,首先加入了basePackages、basePackageClasses的信息
然后如果這兩個屬性都沒有值,也就是我們使用的是默認值,那么默認的packageNames就是這個注解標注的類所在的包,也就是我們的啟動類
那么現在拿到了包的路徑,也就是掃描的范圍之后,就會將信息放在類中,無論bean定義信息中是不是已經存在,都會將這次的包信息加入到bean中
以beanName=AutoConfigurationPackages, 具體類是BasePackages.class的形式
三、
在知道了我們掃描的范圍之后,就可以開始找找到底是從哪里開始,我們自定義的bean以及那些自動配置的類,是什么時候被加載成beanDefinition的
我們主要是找出ConfigurationClassPostProcessor這個類,是什么時候,在哪里加載的
這里一個題外話,sources是什么
其實是我們的啟動類
這個看名字就很像了,加載beanDefinition的
掃描之后讀取,在spring中也是這兩種接口完成的功能
將ConfigurationClassPostProcessor注冊為beanName=internalConfigurationAnnotationProcessor
四、
這樣,對於配置類的后置處理器的加載,就找到了,接下來就可以看看這個類的執行時機,就可以知道我們自定義的bean啥時候加載了
首先看看這個類的繼承樹
可以看到,BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor接口都有實現,不過從名字看出來,我們想看的是BeanDefinitionRegistryPostProcessor這個接口的實現
知道接口后,根據容器的生命周期,就知道這個類的執行時機了
是在容器刷新中的invokeBeanFactoryPostProcessors方法中被執行
在印象中,在這個方法之前的類,都是從spring.factories中直接讀出來再反射生成,並沒有通過beanFactory
在這個之后,我們就可以直接通過beanFactory拿到bean了
方法的第一段,將已經有的BeanDefinitionRegistryPostProcessor首先執行,順便一提,這些最早存在的BeanDefinitionRegistryPostProcessor是通過spring.factories文件里面的initializer創建的
接下來就是將還沒有實例化的BeanDefinitionRegistryPostProcessor實例化,會區分優先級去獲取,然后排序,最后執行
之后按照這個套路對BeanFactoryPostProcessor接口再做一次
那么可以知道我們的ConfigurationClassPostProcessor是最高優先級,所以會最先執行postProcessBeanDefinitionRegistry方法, 然后是postProcessBeanFactory方法
知道了執行時機,接下來就可以查看這兩個方法到底是如何為我們注冊bean的
由於方法較長,一段段看
首先是將所有的beanDefinition拿出來,然后查看這些bean是不是有被標注了@Configuration
而且會檢查是不是有重復處理的,如果沒有被處理過,會被加入候選列表
如果沒有被標注的類,就直接返回
排序
看看是不是有自定義的beanName生成規則,
然后開始解析bean,方法很長,先不進去具體的方法看,先從整體的邏輯去分析,這一大段代碼做了什么
1、實例化一個 ConfigurationClassParser ,從名字猜測,就是專門解析我們的配置類的,然后是 parser.parse(candidates);
那我們知道,在一開始的時候,beanDefinition里面除了那些約定的類,也就只有我們自己的啟動類了,而我們的啟動類也是有@Configuration的,所以一開始其實就是解析我們的啟動類
2、然后在解析啟動類之后,將解析出來的所有類減去已經解析的類
3、實例化一個 ConfigurationClassBeanDefinitionReader ,隨后 this.reader.loadBeanDefinitions(configClasses); 很明顯,就是將我們解析出來的類注冊成BeanDefinition ,然后去除已經解析的類
4、接着,清除了候選列表,判斷現階段注冊的beanDefinition是不是比一開始拿出來的要多了,多了也就是證明這一個@Configuration配合@ComponentScan或者@Bean注冊了許多bean
然后接下來就重復一開始的步驟,檢查新注冊的bean是不是還有@Configuration標注的,如果有就會繼續加入候選列表
然后這個do...while循環一直到候選列表中沒有可以解析的bean了,也就代表所有的@Configuration都被解析完成
5、最后注冊了一個bean暫時不知道用處,注釋看不懂,還有就是將元信息讀取工廠的緩存清除了
然后整個解析的邏輯我們就已經清楚了,接下來重點就在那些bean是如何被掃描出來的,也就是bean的元信息和路徑信息,有了這些才能給reader封裝為beanDefinition以供后續實例化
分為三類解析,有注解的,有抽象的,有正常的,不過都是一個方法
遞歸檢查是不是注解或者是不是@Controller等注解,然后進入真正的解析(這個遞歸檢查暫時不太理解具體作用)
又是一個較長方法,不過此處已經接近核心解析代碼,還是先看整體
1、解析@Component
2、解析@PropertySources
帶有@ComponentScan注解的,會使用componentScanParser去解析,大概過程是將注解的元信息basePackage、過濾規則結合,然后將讀取的類封裝,具體的執行邏輯暫時不做閱讀
1、處理@import注解
2、處理@importResource注解
3、處理帶有@Bean注解的方法
4、處理接口中的默認方法
處理父類
五、
在上面的過程中,根據不同的注解會出現許多分支,那么在我們啟動過程中,這個被解析的類就是我們的啟動類
回去翻查啟動類的注解中,具有的元信息是,有一個@ComponentScan定義了過濾規則,但是沒有定義basePackage
但是如果看了@ComponentScan的處理邏輯中,會發現,如果標注的@ComponentScan沒有定義basePackage,那么會將標注的類所在包作為basePackage
(具體的代碼可以到 this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); 查看)
這時候就會出現一個疑問,因為啟動類上還有一個@AutoConfigurationPackage,這個注解內部是@Import ,最終會注冊一個 BasePackages.class ,里面也是帶有以當前標注類的所在包為basePackage的信息
然后在查找該類是何時被使用的時候,發現它的使用場景多是數據庫一側,目前沒有接觸這一方面的源碼,但是可以看到這個注解我們暫時沒有使用
我們自定義的bean是通過@ComponentScan被掃描的
六、
至此,對於beanDefinition的加載過程,做了一個簡述,具體到每一個注解的具體分析,就留到以后再做下一層次的源碼閱讀。
如有錯漏,歡迎指正。