結合Spring Bean加載流程,本文對Spring單例構造器循環依賴及Field循環依賴進行分析。對於構造器循環依賴,目前Spring是沒法解決的;Field循環依賴,Spring經過提早暴露實例化Bean及緩存不一樣階段的bean(三級緩存)進行依賴排除。網上也有很多一些關於這方面的文章,但做者想從緩存生命週期及多例Bean循環依賴這方面另闢蹊徑,深刻理解下Spring Ioc的精髓。這是第二篇博文,但願能養成梳理筆記的好習慣。web
循環依賴,簡單地說,就是循環引用,兩個或者多個 bean 相互之間的持有對方,造成一個閉環。如,A 依賴 B,B 又依賴 A,它們之間造成了循環依賴,又或者是 A 依賴 B,B 依賴 C,C 又依賴 A。能夠用一張簡圖描述這種依賴關係。spring
Spring循環依賴的理論依據實際上是Java基於引用傳遞,當咱們獲取到對象的引用時,對象的field或者或屬性是能夠延後設置的。接下來,將經過構造器循環依賴及Field循環依賴進行闡述。設計模式
在分析循環依賴以前咱們先回顧下Spring Bean加載的流程。緩存
1)項目啓動時建立ServletContext實例,將context-param中鍵值對值存入ServletContext中;bash
2)當建立Context LoaderListener時,因爲監聽器實現了ServletContextListener接口,而ServletContextListener提供了監聽web容器啓動時,初始化ServletContext後的事件監聽及銷燬ServletContext前的事件監聽;所以,contextLoaderListener默認實現contextInitialized和contextDestroyed這兩個方法;容器的初始化就是從contextInitialized開始的;app
3)首先先建立WebApplicationContext的實例,若是配置了contextClass屬性值,則表明配置了相應的WebApplicationContext容器實現類,若是沒有配置,默認建立的實例對象是XmlWebApplicationContext;ide
4)經過contextConfigLocation獲取容器加載的配置文件,循環遍歷configLocation,調用AbstractBeanDefinitionReader的loadDefinitionBeans方法進行解析並註冊,解析的過程主要有如下幾個步驟:函數
將xml轉換爲Document對象,最終調用DefaultBeanDefinitionDocumentReader中的parseBeanDefinitions方法;post
解析Document中的Node節點,若是是默認的bean標籤直接註冊(調用的是org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition方法,若是是自定義的命名空間標籤,得到命名空間後,拿到對應的NamespaceHandler(從spring中的jar包中的meta-inf/spring.handlers屬性文件中獲取),調用其parse方法進行解析;ui
調用NamespaceHandler的init方法註冊每一個標籤對應的解析器;
根據標籤名稱得到對應的解析器,解析具體的標籤; 解析註冊這些步驟最終將解析所得的BeanDefinition放入一個map中,這時並無進行注入。
5)實例化 入口是AbstractApplicationContext#finishBeanFactoryInitialization方法,以getBean方法爲入口,先從緩存中獲取,若是拿不到時,經過工廠方法或構造器實例化一個Bean,對於構造器咱們能夠指定構造參數。
6)依賴注入(populateBean) 裝配bean依賴,項目中大都使用@Autowired註解,org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues,這個過程可能進行遞歸進行依賴注入;最終經過反射將字段設置到bean中。
7)初始化:經過後置處理器完成對bean的一些設置,如判斷否實現intializingBean,若是實現調用afterPropertiesSet方法,建立代理對象等;
8)最終將根上下文設置到servletContext屬性中;
Spring bean的加載最主要的過程集中在5,6,7這三個步驟中,對應着createBean、populateBean及intializeBean這三個方法上,循環依賴產生在createBean和populateBean這兩個方法中。
對於構造器循環依賴,其依賴產生在實例化Bean上,也就是在createBean這個方法。對於這種循環依賴Spring是沒有辦法解決的。
<bean id = "aService" class="com.yfty.eagle.service.AService">
<constructor-arg index="0" ref="bService"/>
</bean>
<bean id = "bService" class="com.yfty.eagle.service.BService">
<constructor-arg index="0" ref="cService"/>
</bean>
<bean id = "cService" class="com.yfty.eagle.service.CService">
<constructor-arg index="0" ref="aService"/>
</bean>
複製代碼
/**
* Callback before singleton creation.
* <p>The default implementation register the singleton as currently in creation.
* @param beanName the name of the singleton about to be created
* @see #isSingletonCurrentlyInCreation
*/
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
複製代碼
在xml文件配置property屬性或者使用@Autowired註解,其實這兩種同屬於一類。下面分析Spring是如何經過提早曝光機制+三級緩存來排除bean之間依賴的。
/** Cache of singleton objects: bean name --> bean instance */
一級緩存:維護着全部建立完成的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of early singleton objects: bean name --> bean instance */
二級緩存:維護早期暴露的Bean(只進行了實例化,並未進行屬性注入)
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
/** Cache of singleton factories: bean name --> ObjectFactory */
三級緩存:維護建立中Bean的ObjectFactory(解決循環依賴的關鍵)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
複製代碼
由Spring Bean建立的過程,首先Spring會嘗試從緩存中獲取,這個緩存就是指singletonObjects,主要調用的方法是getSingleton;若是緩存中沒有,則調下Spring bean建立過程當中,最重要的一個方法doCreateBean。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 從一級緩存中獲取
Object singletonObject = this.singletonObjects.get(beanName);
// 若是一級緩存沒有,而且bean在建立中,會從二級緩存中獲取
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
// 二級緩存不存在,而且容許從singletonFactories中經過getObject拿到對象
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);
}
複製代碼
分析: Spring首先從singletonObjects(一級緩存)中嘗試獲取,若是獲取不到而且對象在建立中,則嘗試從earlySingletonObjects(二級緩存)中獲取,若是仍是獲取不到而且容許從singletonFactories經過getObject獲取,則經過三級緩存獲取,即經過singletonFactory.getObject()。若是獲取到了,將其存入二級緩存,並清除三級緩存。
若是緩存中沒有bean對象,那麼Spring會建立Bean對象,將實例化的bean提早曝光,而且加入緩存中。
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
......
if (instanceWrapper == null) {
//這個是實例化Bean的方法,會調用構造方法,生成一個原始類型的Bean
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 提早曝光這個實例化的Bean,方便其餘Bean使用
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
// 知足單例 + allowCircularReferences默認爲true + bean在singletonsCurrentlyInCreation集合中時,earlySingletonExposure爲true
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 將bean加入三級緩存中
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 屬性注入,這裏可能發生循環依賴
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
// 初始化bean
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
// 因爲AService提早暴露,會走這段代碼
if (earlySingletonExposure) {
// 從二級緩存中拿出AService(這個對象其實ObjectFactory.getObject()得來的,多是個包裝類,
而exposedObject可能依然是實例化的那個bean,這時爲保證最終BService中的AService屬性與AService自己
持有的引用一直,故再次進行exposedObject的賦值操做,保證beanName對應實例惟一性。)
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
}
// ...........
return exposedObject;
}
複製代碼
分析: 當經過無參構造,得到一個實例化bean時,Spring會將其提早曝光,即在實例化後注入屬性前將其加入三級緩存中。下面以AService和BService相互依賴爲例,說明依賴排除過程。
從上述分析可知,singletonFactories即三級緩存纔是解決循環依賴的關鍵,它是一個橋樑。當AService初始化後,會從二級緩存中獲取提早暴露的對象,而且賦值給exposedObject。這主要是二級緩存的對象earlySingletonReference多是包裝類,BService持有的引用就是這個earlySingletonReference,賦值後保證beanName對應實例惟一性,這點耐人尋味。
咱們已經知道了Spring如何解決循環依賴了,可是對於Spring爲何這麼設計,總感受雲裏霧裏,網上的博文大都沒有講這點。下面做者談下本身的觀點。
三級緩存採用工廠設計模式,經過getObject方法獲取bean,就循環依賴而言,當BService經過populateBean注入AService時,要保證BService中的AService內存地址a1和AService最終初始化後的地址a2一致,而此時AService纔剛剛實例化,a1與a2不必定相等,經過三級緩存能夠獲取到最終引用地址,這保證了在循環依賴能獲取到真正的依賴。
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
複製代碼
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
if (exposedObject == null) {
return null;
}
}
}
}
return exposedObject;
}
複製代碼
實例化的bean是什麼時候加入緩存中,又是什麼時候將其刪除的,它們之間有什麼區別呢?接下來,本文會一一做答。
當earlySingletonExposure屬性爲true時,將beanFactory加入緩存;當經過getSingleton從三級緩存中取出實例化的原始bean時或者完成初始化後,並清除singletonFactories中bean的緩存。
當earlySingletonExposure屬性爲true時,將beanFactory加入緩存,當經過getSingleton從三級緩存中取出實例化的原始bean時,此時,將獲取的bean加入二級緩存。當完成bean初始化,將bean加入一級緩存後,清除二級緩存;
當完成bean初始化,經過addSingleton將bean加入一級緩存singletonObjects中,而且這個緩存是常駐內存中的。
從上述分析可知,三級緩存和二級緩存是不共存的,且其在Spring完成初始化,都會被清除掉。
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
複製代碼