文章參考:
面試講解思路:
- 什么是循環依賴?
- Spring怎么解決循環依賴
- Spring對於循環依賴無法解決的場景
- 怎么檢測循環依賴
什么是循環依賴?
循環依賴其實就是循環引用,也就是兩個或則兩個以上的bean互相持有對方,最終形成閉環。比如A依賴於B,B依賴於C,C又依賴於A。類似於死鎖
Spring中循環依賴場景有:
(1)構造器的循環依賴 (不能被解決,加入singletonFactories
三級緩存的前提是執行了構造器,所以構造器的循環依賴沒法解決
(2)field屬性的循環依賴,也就是setter注入的循環依賴。(可以通過三級緩存來解決)
Spring對於循環依賴的解決
Spring循環依賴的理論依據其實是Java基於引用傳遞,當我們獲取到對象的引用時,對象的field或者或屬性是可以延后設置的。
Spring單例對象的初始化其實可以分為三步:
- createBeanInstance, 實例化,其實也就是調用對象的構造方法實例化對象
- populateBean,填充屬性,這一步主要是對bean的依賴屬性進行注入(
@Autowired
) - initializeBean,調用spring xml中指定的init方法,比如一些形如
initMethod
、InitializingBean
等方法
/** 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,沒有經過后置處理且沒有初始化完全的對象的工廠。加入
singletonFactories
三級緩存的前提是執行了構造器,所以構造器的循環依賴沒法解決 ,工廠對象中,有3個field,一個是beanName,一個是RootBeanDefinition,一個是已經創建好的,但還沒有注入屬性的bean,如果在2級緩存中,還是沒找到,則在3級緩存中查找對應的工廠對象,利用拿到的工廠對象,去獲取包裝后的bean,或者說,代理后的bean。
// allowEarlyReference: 是否允許從singletonFactories中通過getObject拿到對象 protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); // isSingletonCurrentlyInCreation()判斷當前單例bean是否正在創建中,也就是沒有初始化完成 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); }
比如A、B兩個bean相互依賴,他們的注入過程如下:
原型模式(prototype) 循環依賴
因為原型模式每次都是重新生成一個全新的bean,根本沒有緩存一說。這將導致實例化A完,填充發現需要B,實例化B完又發現需要A,而每次的A又都要不一樣,所以死循環的依賴下去。唯一的做法就是利用循環依賴檢測
,發現原型模式下存在循環依賴並拋出異常
Spring對於循環依賴無法解決的場景
構造器注入沒有將自己提前曝光在第三級緩存中,所以對其他依賴它的對象來說不可見,所以這種循環依賴問題不能被解決,而原型模式的循環依賴則沒有用到緩存機制,更不能解決循環依賴問題,兩種情況都會拋出循環依賴異常。怎么檢測是否存在循環依賴
可以 Bean在創建的時候給其打個標記,如果遞歸調用回來發現正在創建中的話--->即可說明循環依賴。
為什么要三級緩存,一級不行嗎
一級緩存的問題在於,就1個map,里面既有完整的已經ready的bean,也有不完整的,尚未設置field的bean。
如果這時候,有其他線程去這個map里獲取bean來用怎么辦?拿到的bean,不完整,怎么辦呢?屬性都是null,直接空指針了。
所以,我們就要加一個map,這個map,用來存放那種不完整的bean。
為什么要三級緩存,兩級不行嗎
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }
getEarlyBeanReference的作用:調用SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()這個方法 否則啥都不做,也就是給調用者個機會,自己去實現暴露這個bean的應用的邏輯~~~,比如在getEarlyBeanReference()里可以實現AOP的邏輯~~~ 參考自動代理創建器AbstractAutoProxyCreator 實現了這個方法來創建代理對象
Spring
容器會將每一個正在創建的Bean 標識符放在一個 singletonsCurrentlyInCreation“當前創建Bean池”中,Bean標識符在創建過程中將一直保持在這個池中,而對於創建完畢的Bean將從當前創建Bean池
中清除掉。