作者丨低調(diào)的JVM
來(lái)自丨CSDN
https://blog.csdn.net/qq_27529917/article/details/79329809
在使用Spring時(shí),Bean之間會(huì)有些依賴,比如一個(gè)Bean A實(shí)例化時(shí)需要用到Bean B,那么B應(yīng)該在A之前實(shí)例化好。很多時(shí)候Spring智能地為我們做好了這些工作,但某些情況下可能不是,比如Springboot的@AutoConfigureAfter注解,手動(dòng)的指定Bean的實(shí)例化順序。
了解Spring內(nèi)Bean的解析,加載和實(shí)例化順序機(jī)制有助于我們更好的使用Spring/Springboot,避免手動(dòng)的去干預(yù)Bean的加載過(guò)程,搭建更優(yōu)雅的框架。
Spring容器在實(shí)例化時(shí)會(huì)加載容器內(nèi)所有非延遲加載的單例類(lèi)型Bean,看如下源碼:
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext, DisposableBean {
//刷新Spring容器,相當(dāng)于初始化
public void refresh() throws BeansException, IllegalStateException {
。。。。。。
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
}
}
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
/** List of bean definition names, in registration order */
private volatile List《String》 beanDefinitionNames = new ArrayList《String》(256);
public void preInstantiateSingletons() throws BeansException {
List《String》 beanNames = new ArrayList《String》(this.beanDefinitionNames);
for (String beanName : beanNames) {
。。。。。。
getBean(beanName); //實(shí)例化Bean
}
}
}
ApplicationContext內(nèi)置一個(gè)BeanFactory對(duì)象,作為實(shí)際的Bean工廠,和Bean相關(guān)業(yè)務(wù)都交給BeanFactory去處理。
在BeanFactory實(shí)例化所有非延遲加載的單例Bean時(shí),遍歷beanDefinitionNames 集合,按順序?qū)嵗付Q的Bean。beanDefinitionNames 屬性是Spring在加載Bean Class生成的BeanDefinition時(shí),為這些Bean預(yù)先定義好的名稱,看如下代碼:
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
。。。。。。
this.beanDefinitionNames.add(beanName);
}
}
BeanFactory在加載一個(gè)BeanDefinition(也就是加載Bean Class)時(shí),將相應(yīng)的beanName存入beanDefinitionNames屬性中,在加載完所有的BeanDefinition后,執(zhí)行Bean實(shí)例化工作,此時(shí)會(huì)依據(jù)beanDefinitionNames的順序來(lái)有序?qū)嵗疊ean,也就是說(shuō)Spring容器內(nèi)Bean的加載和實(shí)例化是有順序的,而且近似一致,當(dāng)然僅是近似。
Spring在初始化容器時(shí),會(huì)先解析和加載所有的Bean Class,如果符合要求則通過(guò)Class生成BeanDefinition,存入BeanFactory中,在加載完所有Bean Class后,開(kāi)始有序的通過(guò)BeanDefinition實(shí)例化Bean。
我們先看加載Bean Class過(guò)程,零配置下Spring Bean的加載起始于ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry(BeanDefinitionRegistry)方法,我總結(jié)了下其加載解析Bean Class的流程:
配置類(lèi)可以是Spring容器的起始配置類(lèi),也可以是通過(guò)@ComponentScan掃描得到的類(lèi),也可以是通過(guò)@Import引入的類(lèi)。如果這個(gè)類(lèi)上含有@Configuration,@Component,@ComponentScan,@Import,@ImportResource注解中的一個(gè),或者內(nèi)部含有@Bean標(biāo)識(shí)的方法,那么這個(gè)類(lèi)就是一個(gè)配置類(lèi),Spring就會(huì)按照一定流程去解析這個(gè)類(lèi)上的信息。
在解析的第一步會(huì)校驗(yàn)當(dāng)前類(lèi)是否已經(jīng)被解析過(guò)了,如果是,那么需要按照一定的規(guī)則處理(@ComponentScan得到的Bean能覆蓋@Import得到的Bean,@Bean定義的優(yōu)先級(jí)最高)。
如果未解析過(guò),那么開(kāi)始解析:
解析內(nèi)部類(lèi),查看內(nèi)部類(lèi)是否應(yīng)該被定義成一個(gè)Bean,如果是,遞歸解析。
解析@PropertySource,也就是解析被引入的Properties文件。
解析配置類(lèi)上是否有@ComponentScan注解,如果有則執(zhí)行掃描動(dòng)作,通過(guò)掃描得到的Bean Class會(huì)被立即解析成BeanDefinition,添加進(jìn)beanDefinitionNames屬性中。之后查看掃描到的Bean Class是否是一個(gè)配置類(lèi)(大部分情況是,因?yàn)闃?biāo)識(shí)@Component注解),如果是則遞歸解析這個(gè)Bean Class。
解析@Import引入的類(lèi),如果這個(gè)類(lèi)是一個(gè)配置類(lèi),則遞歸解析。
解析@Bean標(biāo)識(shí)的方法,此種形式定義的Bean Class不會(huì)被遞歸解析
解析父類(lèi)上的@ComponentScan,@Import,@Bean,父類(lèi)不會(huì)被再次實(shí)例化,因?yàn)槠渥宇?lèi)能夠做父類(lèi)的工作,不需要額外的Bean了。
在1,3,4,6中都有遞歸操作,也就是在解析一個(gè)Bean Class A時(shí),發(fā)現(xiàn)其上能夠獲取到其他Bean Class B信息,此時(shí)會(huì)遞歸的解析Bean Class B,在解析完Bean Class B后再接著解析Bean Class A,可能在解析B時(shí)能夠獲取到C,那么也會(huì)先解析C再解析B,就這樣不斷的遞歸解析。
在第3步中,通過(guò)@ComponentScan掃描直接得到的Bean Class會(huì)被立即加載入beanDefinitionNames中,但@Import和@Bean形式定義的Bean Class則不會(huì),也就是說(shuō)正常情況下面@ComponentScan直接得到的Bean其實(shí)例化時(shí)機(jī)比其他兩種形式的要早。
通過(guò)@Bean和@Import形式定義的Bean Class不會(huì)立即加載,他們會(huì)被放入一個(gè)ConfigurationClass類(lèi)中,然后按照解析的順序有序排列,就是圖片上的 “將配置類(lèi)有序排列”。一個(gè)ConfigurationClass代表一個(gè)配置類(lèi),這個(gè)類(lèi)可能是被@ComponentScan掃描到的,則此類(lèi)已經(jīng)被加載過(guò)了;也可能是被@Import引入的,則此類(lèi)還未被加載;此類(lèi)中可能含有@Bean標(biāo)識(shí)的方法。
Spring在解析完了所有Bean Class后,開(kāi)始加載ConfigurationClass。如果這個(gè)ConfigurationClass是被Import的,也就是說(shuō)在加載@ComponentScan時(shí)其未被加載,那么此時(shí)加載ConfigurationClass代表的Bean Class。然后加載ConfigurationClass內(nèi)的@Bean方法。
順序總結(jié):@ComponentScan 》 @Import 》 @Bean
Bean Class的結(jié)構(gòu)圖如上所示,A是配置類(lèi)的入口,通過(guò)A能直接或間接的引入一個(gè)模塊。
此時(shí)啟動(dòng)Spring容器,將A引入容器內(nèi)。
如果A是通過(guò)@ComponentScan掃描到的,那么此時(shí)的加載順序是:
A 》 D 》 F 》 B 》 E 》 G 》 C
如果A是通過(guò)@Import形式引入的,那么此時(shí)的加載順訊是:
D 》 F 》 B 》 E 》 G 》 A 》 C
當(dāng)然以上僅僅代表著加載Bean Class的順序,實(shí)際實(shí)例化Bean的順序和加載順序大體相同,但還是會(huì)有一些差別。
Spring在通過(guò)getBean(beanName)形式實(shí)例化Bean時(shí),會(huì)通過(guò)BeanDefinition去生成Bean對(duì)象。在這個(gè)過(guò)程中,如果BeanDefinition的DependsOn不為空,從字面理解就是依賴某個(gè)什么,其值一般是某個(gè)或多個(gè)beanName,也就是說(shuō)依賴于其他Bean。
此時(shí)Spring會(huì)將DependsOn指定的這些名稱的Bean先實(shí)例化,也就是先調(diào)用getBean(dependsOn)方法。我們可以通過(guò)在Bean Class或者@Bean的方法上標(biāo)識(shí)**@DependsOn**注解,來(lái)指定當(dāng)前Bean實(shí)例化時(shí)需要觸發(fā)哪些Bean的提前實(shí)例化。
當(dāng)一個(gè)Bean A內(nèi)部通過(guò)@Autowired或者@Resource注入Bean B,那么在實(shí)例化A時(shí)會(huì)觸發(fā)B的提前實(shí)例化,此時(shí)會(huì)注冊(cè)A》B的dependsOn依賴關(guān)系,實(shí)質(zhì)和@DependsOn一樣,這個(gè)是Spring自動(dòng)為我們處理好的。
了解Spring Bean的解析,加載及實(shí)例化的順序機(jī)制能夠加深對(duì)Spring的理解,搭建更優(yōu)雅簡(jiǎn)介的Spring框架。
編輯:jq
-
spring
+關(guān)注
關(guān)注
0文章
335瀏覽量
14259
原文標(biāo)題:Spring解析,加載及實(shí)例化Bean的順序(零配置)
文章出處:【微信號(hào):harmonyos_developer,微信公眾號(hào):harmonyos_developer】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論