7. IOC初始化流程
IoC容器的初始化就是含有BeanDefinition信息的Resource的定位、載入、解析、註冊四個過程,最終我們配置的bean,以beanDefinition的數據結構存在於IoC容器即內存中。這裏並不涉及bean的依賴注入,只是bean定義的載入。但有例外,在使用Ioc容器時有一個預實例化的配置,即bean定義中的設置了lazyinit屬性,那麼這個bean在Ioc容器初始化時就預先加載,不需要等到Ioc整個初始化後,第一次getBean時纔會觸發。其中refresh()啓動對Ioc容器的初始化。
主要分爲如下三個步驟:
- Resource定位過程。
這個Resource定位指的是BeanDefinition的資源定位,它由ResourceLoader通過統一的Resource接口來完成,這個Resource對各種形式的BeanDefinition的使用提供了統一接口。對於這些BeanDefinition的存在形式,相信大家都不會感到陌生。比如說,在文件系統中的Bean定義信息可以使用FileSystemResource來進行抽象;在類路徑中可以使用前面提到的ClassPathResource來使用,等等。這個過程類似於容器尋找數據的過程,就像用水桶裝水先要把水找到一樣。
- BeanDefinition的載入
該載入過程把用戶定義好的Bean表示成IoC容器內部的數據結構,而這個容器內部的數據結構就是BeanDefinition,下面可以看到這個數據結構的詳細定義。總地說來,這個BeanDefinition實際上就是POJO對象在IoC容器中的抽象,這個BeanDefinition定義了一系列的數據來使得IoC容器能夠方便地對POJO對象也就是Spring的Bean進行管理。即BeanDefinition就是Spring的領域對象。
- 向IoC容器註冊這些BeanDefinition的過程
這個過程是通過調用BeanDefinitionRegistry接口的實現來完成的,這個註冊過程把載入過程中解析得到的BeanDefinition向IoC容器進行註冊。可以看到,在IoC容器內部將BeanDefinition注入到一個HashMap中去,Ioc容器是通過這個HashMap來持有這些BeanDefinition數據的。整個過程可以理解爲容器的初始化過程。
容器的初始化是通過AbstractApplicationContext的refresh()實現的,下面將會對這個函數進行詳細介紹。
7.1 refresh函數
Spring中會經常使用到AnnotationConfigApplicationContext
作爲IOC容器的操作入口,可以利用該context進行Bean的管理,從下面的構造函數可以看到,refresh函數中完成了IOC容器的初始化,因此弄清楚refresh函數就理解了IOC的初始化流程。
下面對每個函數仔細分析。
1. prepareRefresh() 預處理
- initPropertySources()初始化一些屬性設置;子類自定義個性化的屬性設置方法
- getEnvironment().validateRequiredProperties();校驗屬性的合法等
- earlyApplicationEvents = new LinkedHashSet
() 保存容器中一些早期的事件
2. obtainFreshBeanFactory() 獲取BeanFactory
這一步重點是refreshBeanFactory(),
- refreshBeanFactory()
刷新【創建】容器,創建了一個this.beanFactory = new DefaultListableBeanFactory();並設置序列化id。 - getBeanFactory();
返回上一步創建的beanFactory對象 - 將創建BeanFactory【DefaultListableBeanFactory】返回
3. prepareBeanFactory(beanFactory) BeanFactory的預準備工作
- 設置beanFactory的類加載器、支持表達式解析器
- 添加部分ApplicationContextAwareProcessor
- 設置忽略的自動裝配的接口EnvironmentAware、EmbeddedValueResolverAware等等
- 註冊可以解析的自動裝配;我們能直接在任何組件中自動注入
- BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext
- 添加BeanPostProcessor【ApplicationListenerDetector】
- 添加編譯時的AspectJ
- 給BeanFactory中註冊一些能用的組件 environment【ConfigurableEnvironment】、systemProperties【Map<String, Object>】、systemEnvironment【Map<String, Object>】
4. postProcessBeanFactory(beanFactory) beanFactory準備工作完成後進行的後置處理
子類通過重寫這個方法來在beanFactory創建並預準備完成以後做進一步的設置,以上4步是beanFactory的創建以及預準備工作。
5. invokeBeanFactoryPostProcessors(beanFactory) 調用beanFactory後置處理器
這部分主要執行實現瞭如下兩個接口的類方法:BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor。
1. 先執行BeanDefinitionRegistryPostProcessor的方法
1)獲取所有的BeanDefinitionRegistryPostProcessor
2)先執行實現了PriorityOrdered接口的獲取所有的BeanDefinitionRegistryPostProcessor
postProcessor.postProcessBeanDefinitionRegistry(registry)
3)在執行實現了Ordered順序接口的BeanDefinitionRegistryPostProcessor
4)最後執行沒有任何優先級或者是順序接口的BeanDefinitionRegistryPostProcessor
2. 在執行BeanFactoryPostProcessor的方法
1)獲取所有的BeanFactoryPostProcessor
2)看先執行實現了PriorityOrdered接口的獲取所有的BeanFactoryPostProcessor
3)在執行實現了Ordered順序接口的BeanFactoryPostProcessor
4)最後執行沒有任何優先級或者是順序接口的BeanFactoryPostProcessor
6. registerBeanPostProcessors(beanFactory);註冊BeanPostProcessor(bean的後置處理器)
將不同類型的BeanPostProcessor加入到BeanFactory中,注意到這裏是依據優先級依次註冊。
1)獲取所有的BeanPostProcessor;後置處理器都默認可以通過PriorityOrdered、Ordered接口執行優先級。
2)先註冊PriorityOrdered優先級的BeanPostProcessor;把每一個BeanPostProcessor添加到BeanFactory中。
3)接着註冊Ordered接口的。
4)然後註冊沒有任何優先級接口的。
5)註冊一個ApplicationListenerDetector;來在bean創建完成後檢查是否是ApplicationListener如果是監聽器。
這個步驟中的所有後置處理器,都是通過下面的getBean方法來進行實例化的,具體流程在之前AOP中有介紹。實例化之後,在後續註冊Bean的時候,就可以對Bean的生成進行定製化。
7. initMessageSource() 初始化messageSource組件
1)獲取BeanFactory
2)看容器中是否有id爲messageSource的,類型是MessageSource的組件
如果有複製給messageSource,如果沒有創建一個DelegatingMessageSource
3)把創建好的MessageSource註冊到容器中,以後獲取國際化配置文件的值的時候可以自動注入MessageSource
beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
8. initApplicationEventMulticaster()
1)獲取BeanFactory
2)從BeanFactory中獲取ApplicationEventMulticaster
3)如果沒有上一步配置,那就創建一個SimpleApplicationEventMulticaster
4)將創建的ApplicationEventMulticaster添加到BeanFactory中,以後其他組件直接自動注入
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
9. onRefresh()
空函數,子類可以重寫這個方法,在容器刷新的時候可以自定義邏輯(比如增加組件)
10. registerListeners()
1)從容器中拿到所有的ApplicationListener。
2)將每個監聽器添加到時間派發器中
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
3)派發之前步驟產生的事件
11. finishBeanFactoryInitialization(beanFactory);初始化所有剩下的單實例bean
這是所有步驟中,最重要,最複雜的一步,之前AOP中對這部分有過仔細介紹,這裏重點梳理正常Bean的初始化流程。
12. finishRefresh() 完成BeanFactory的初始化創建工作,IOC容器就創建完成
1.initLifecycleProcessor()初始化和生命週期有關的後置處理器;
LifecycleProcessor默認從容器中找是否有LifecycleProcessor的組件
2.getLifecycleProcessor().onRefresh();
拿到前面定義的生命週期處理器(BeanFactory) 回調onRefresh
3.publishEvent(new ContextRefreshedEvent(this));發佈容器刷新完成事件
4.LiveBeansView.registerApplicationContext(this);
7.2 Spring-bean的循環依賴以及解決方式
什麼是循環依賴?
循環依賴其實就是循環引用,也就是兩個或則兩個以上的bean互相持有對方,最終形成閉環。比如A依賴於B,B依賴於C,C又依賴於A。如下圖:
@w=250
注意,這裏不是函數的循環調用,是對象的相互依賴關係。循環調用其實就是一個死循環,除非有終結條件。
Spring中循環依賴場景有:
(1)構造器的循環依賴
(2)field屬性或者setter的循環依賴。
下面的例子中, 會發生這種循環依賴的情況:
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public CircularDependencyA(CircularDependencyB circB) {
this.circB = circB;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
@Autowired
public CircularDependencyB(CircularDependencyA circA) {
this.circA = circA;
}
}
怎麼檢測是否存在循環依賴?
檢測循環依賴相對比較容易,Bean在創建的時候可以給該Bean加標記,如果遞歸調用回來發現正在創建中的話,即說明了循環依賴了。
Spring怎麼解決field屬性和setter的循環依賴?
Spring的循環依賴的理論依據其實是基於Java的引用傳遞,當我們獲取到對象的引用時,對象的field或則屬性是可以延後設置的(但是構造器必須是在獲取引用之前)。
Spring的單例對象的初始化主要分爲三步:
(1)createBeanInstance:實例化,其實也就是調用對象的構造方法實例化對象
(2)populateBean:填充屬性,這一步主要是多bean的依賴屬性進行填充
(3)initializeBean:調用spring xml中的init 方法。
從上面講述的單例bean初始化步驟我們可以知道,循環依賴主要發生在第一、第二部。也就是構造器循環依賴和field循環依賴。
那麼我們要解決循環引用也應該從初始化過程着手,對於單例來說,在Spring容器整個生命週期內,有且只有一個對象,所以很容易想到這個對象應該存在Cache中,Spring爲了解決單例的循環依賴問題,使用了三級緩存。
三級緩存主要指:
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
這三級緩存分別指:
singletonFactories : 單例對象工廠的cache
earlySingletonObjects :提前曝光的單例對象的Cache
singletonObjects:單例對象的cache
我們在創建bean的時候,首先想到的是從cache中獲取這個單例的bean,這個緩存就是singletonObjects。主要調用方法就就是:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
上面的代碼需要解釋兩個參數:
- isSingletonCurrentlyInCreation()判斷當前單例bean是否正在創建中,也就是沒有初始化完成(比如A的構造器依賴了B對象所以得先去創建B對象, 或則在A的populateBean過程中依賴了B對象,得先去創建B對象,這時的A就是處於創建中的狀態。)
- allowEarlyReference 是否允許從singletonFactories中通過getObject拿到對象
分析getSingleton()的整個過程,Spring首先從一級緩存singletonObjects中獲取。如果獲取不到,並且對象正在創建中,就再從二級緩存earlySingletonObjects中獲取。如果還是獲取不到且允許singletonFactories通過getObject()獲取,就從三級緩存singletonFactory.getObject()(三級緩存)獲取,如果獲取到了則:
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
從singletonFactories中移除,並放入earlySingletonObjects中。其實也就是從三級緩存移動到了二級緩存。
從上面三級緩存的分析,我們可以知道,Spring解決循環依賴的訣竅就在於singletonFactories這個三級cache。這個cache的類型是ObjectFactory,定義如下:
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
這個接口在下面被引用
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
這裏就是解決循環依賴的關鍵,這段代碼發生在createBeanInstance之後,也就是說單例對象此時已經被創建出來(調用了構造器)。這個對象已經被生產出來了,雖然還不完美(還沒有進行初始化的第二步和第三步),但是已經能被人認出來了(根據對象引用能定位到堆中的對象),所以Spring此時將這個對象提前曝光出來讓大家認識,讓大家使用。
下面是整段代碼:
這樣做有什麼好處呢?讓我們來分析一下“A的某個field或者setter依賴了B的實例對象,同時B的某個field或者setter依賴了A的實例對象”這種循環依賴的情況。A首先完成了初始化的第一步,並且將自己提前曝光到singletonFactories中,此時進行初始化的第二步,發現自己依賴對象B,此時就嘗試去get(B),發現B還沒有被create,所以走create流程,B在初始化第一步的時候發現自己依賴了對象A,於是嘗試get(A),嘗試一級緩存singletonObjects(肯定沒有,因爲A還沒初始化完全),嘗試二級緩存earlySingletonObjects(也沒有),嘗試三級緩存singletonFactories,由於A通過ObjectFactory將自己提前曝光了,所以B能夠通過ObjectFactory.getObject拿到A對象(雖然A還沒有初始化完全,但是總比沒有好呀),B拿到A對象後順利完成了初始化階段1、2、3,完全初始化之後將自己放入到一級緩存singletonObjects中。此時返回A中,A此時能拿到B的對象順利完成自己的初始化階段2、3,最終A也完成了初始化,進去了一級緩存singletonObjects中,而且更加幸運的是,由於B拿到了A的對象引用,所以B現在hold住的A對象完成了初始化。
總結來講,Spring不能解決“A的構造方法中依賴了B的實例對象,同時B的構造方法中依賴了A的實例對象”這類問題了!因爲加入singletonFactories三級緩存的前提是執行了構造器,所以構造器的循環依賴沒法解決。
如何解決構造器中的循環依賴?
1. 使用@Lazy註解
最簡單的方法是使用@Lazy聲明其中的一個Bean,這樣的話Spring將會創建代理對象,並注入到其他依賴於它的Bean中,這個注入的Bean將會在第一次被使用的時候初始化。
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public CircularDependencyA(@Lazy CircularDependencyB circB) {
this.circB = circB;
}
}
2. 替換構造器依賴,改爲setter/Field注入
上面提到了Spring可以解決setter/Field中的循環依賴,因此可以將構造器中的依賴Bean,改爲在setter/Field中進行注入,例子如下:
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public void setCircB(CircularDependencyB circB) {
this.circB = circB;
}
public CircularDependencyB getCircB() {
return circB;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
private String message = "Hi!";
@Autowired
public void setCircA(CircularDependencyA circA) {
this.circA = circA;
}
public String getMessage() {
return message;
}
}
3. 實現ApplicationContextAware和InitializingBean
讓其中的一個類實現ApplicationContextAware和InitializingBean,來手動設置依賴的Bean。
ApplicationContextAware
發生在調用初始化方法之前,也就是下面的第二步,因此可以獲取到ApplicationContext。
實現InitializingBean
需要重寫其的afterPropertiesSet
方法,這發生在第3步,此時類中已經有了ApplicationContext,所以直接拿到對應的Bean實例即可。
因爲這兩個方法都發生在createBeanInstance之後,所以在緩存singletonFactories中拿到對應的Bean。
具體例子如下:
@Component
public class CircularDependencyA implements ApplicationContextAware, InitializingBean {
private CircularDependencyB circB;
private ApplicationContext context;
public CircularDependencyB getCircB() {
return circB;
}
@Override
public void afterPropertiesSet() throws Exception {
circB = context.getBean(CircularDependencyB.class);
}
@Override
public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
context = ctx;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
private String message = "Hi!";
@Autowired
public void setCircA(CircularDependencyA circA) {
this.circA = circA;
}
public String getMessage() {
return message;
}
}
參考:
- https://blog.csdn.net/u010853261/article/details/77940767
- https://www.baeldung.com/circular-dependencies-in-spring
7.3 spring依賴注入註解的實現原理
7.3.1 @Autowired的工作原理
以下是@Autowired註解的源碼,從源碼中看到它可以被標註在構造函數、屬性、setter方法或配置方法上,用於實現依賴自動注入。
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
/**
* Declares whether the annotated dependency is required.
* <p>Defaults to {@code true}.
*/
boolean required() default true;
}
@Autowired註解的作用是由AutowiredAnnotationBeanPostProcessor
實現的,查看該類的源碼會發現它實現了MergedBeanDefinitionPostProcessor接口,進而實現了接口中的postProcessMergedBeanDefinition方法,@Autowired註解正是通過這個方法實現注入類型的預解析,將需要依賴注入的屬性信息封裝到InjectionMetadata類中,InjectionMetadata類中包含了哪些需要注入的元素及元素要注入到哪個目標類中。
public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware
@Autowired
發生在refresh方法的finishBeanFactoryInitialization
(beanFactory)階段,在此之前,在registerBeanPostProcessors(beanFactory)已經完成了對AutowiredAnnotationBeanPostProcessor的註冊。
在doCreateBean方法,首先會調用applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName),實質上就是調用AutowiredAnnotationBeanPostProcessor類的postProcessMergedBeanDefinition方法,也就是開頭介紹的在這個方法中完成了對注入元素註解的預解析。接着,在doCreateBean方法中執行populateBean方法實現對屬性的注入。
深入分析populateBean方法,下面是關鍵部分,這段代碼中會遍歷所有註冊過的BeanPostProcessor接口實現類的實例,如果實例屬於InstantiationAwareBeanPostProcessor類型的,則執行實例類的postProcessPropertyValues方法。
從下面的類繼承關係可以看到,這裏會執行AutowiredAnnotationBeanPostProcessor類的postProcessPropertyValues方法,
具體代碼如下:
metadata.inject(bean, beanName, pvs)代碼的執行會進入如下inject方法中,在這裏完成依賴的注入。
上面的InjectedElement有兩個子類,分別是AutowiredFieldElement和AutowiredMethodElement,AutowiredFieldElement用於對標註在屬性上的注入,AutowiredMethodElement用於對標註在方法上的注入。
兩種方式的注入過程都差不多,根據需要注入的元素的描述信息,按類型或名稱查找需要的依賴值,如果依賴沒有實例化先實例化依賴,然後使用反射進行賦值。
7.3.2 @Resource和@Inject的工作原理
兩者的實現原理與@Autowired類似,不過這兩者是JDK中提供的annotation,是通過BeanPostProcessor接口的實現類CommonAnnotationBeanPostProcessor來實現的,其中如名字所述,即公共註解CommonAnotation,CommonAnnotationBeanPostProcessor是spring中統一處理JDK中定義的註解的一個BeanPostProcessor。該類會處理的註解還包括@PostConstruct,@PreDestroy等。
7.3.3 註解處理器的激活條件
AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor添加到spring容器的BeanPostProcessor的條件,即激活這些處理器的條件如下:
- 基於xml的spring配置
-
在對應的spring容器的配置xml文件中,如applicationContext.xml,添加<context:annotation-config />和<context:component-scan />,或者只使用<context:component-scan />。
-
兩者的區別是<context:annotation-config />只查找並激活已經存在的bean,如通過xml文件的bean標籤生成加載到spring容器的,而不會去掃描如@Controller等註解的bean,查找到之後進行注入;而<context:component-scan />除了具有<context:annotation-config />的功能之外,還會去加載通過basePackages屬性指定的包下面的,默認爲掃描@Controller,@Service,@Component,@Repository註解的類。不指定basePackages則是類路徑下面,或者如果使用註解@ComponentScan方式,則是當前類所在包及其子包下面。
- 基於配置類的spring配置
-
如果是基於配置類而不是基於applicationContext.xml來對spring進行配置,如SpringBoot,則在內部使用的IOC容器實現爲AnnotationConfigApplicationContext或者其派生類,在AnnotationConfigApplicationContext內部會自動創建和激活以上的BeanPostProcessor。
-
如果同時存在基於xml的配置和配置類的配置,而在注入時間方面,基於註解的注入先於基於XML的注入,所以基於XML的注入會覆蓋基於註解的注入。
7.3.4 總結
- @Autowired是Spring自帶的,@Inject和@Resource都是JDK提供的,其中@Inject是JSR330規範實現的,@Resource是JSR250規範實現的,而Spring通過BeanPostProcessor來提供對JDK規範的支持。
- @Autowired、@Inject用法基本一樣,不同之處爲@Autowired有一個required屬性,表示該注入是否是必須的,即如果爲必須的,則如果找不到對應的bean,就無法注入,無法創建當前bean。
- @Autowired、@Inject是默認按照類型匹配的,@Resource是按照名稱匹配的。如在spring-boot-data項目中自動生成的redisTemplate的bean,是需要通過byName來注入的。如果需要注入該默認的,則需要使用@Resource來注入,而不是@Autowired。
- 對於@Autowire和@Inject,如果同一類型存在多個bean實例,則需要指定注入的beanName。@Autowired和@Qualifier一起使用,@Inject和@Name一起使用。
參考:
本文由『後端精進之路』原創,首發於博客 http://teckee.github.io/ , 轉載請註明出處
搜索『後端精進之路』關注公衆號,立刻獲取最新文章和價值2000元的BATJ精品面試課程。