讓阿里面試官都讚不絕口的Spring解讀,沒人比我更懂迴圈依賴!
一、前言
這一篇博文主要講一下我們spring
是怎麼解決迴圈依賴的問題的。
二、什麼是迴圈依賴
首先我們需要明確,什麼是迴圈依賴呢?這裡舉一個簡單的例子:
@Service public class A { @Autowired private B b; } @Service public class B { @Autowired private A a; }
以這個例子來看,我們聲明瞭a
、b
兩個bean
,且a
中需要注入一個b
,b
中需要注入一個a
。
結合我們上篇博文的bean
生命週期的知識,我們來模擬一下這兩個bean
建立的流程:
如果沒有快取的設計,我們的虛線所示的分支將永遠無法到達,導致出現無法解決的迴圈依賴問題....
三、三級快取設計
1. 自己解決迴圈依賴問題
現在,假如我們是spring
的架構師,我們應該怎麼解決這個迴圈依賴問題呢?
1.1. 流程設計
首先如果要解決這個問題,我們的目標應該是要把之前的級聯的無限建立流程切到,也就是說我們的流程要變為如下所示:
也就是說,我們需要在B
例項建立後,注入A
的時候,能夠拿到A
的例項,這樣才能打破無限建立例項的情況。
而B
例項的初始化流程,是在A
例項建立之後,在populateBean
方法中進行依賴注入時觸發的。那麼如果我們B
例項化過程中,想要拿到A
的例項,那麼A
例項必須在createBeanInstance
建立例項後(例項都沒有就啥也別說了)、populateBean
B
能通過getBean
獲取到!(同學們認真想一下這個流程,在現有的流程下改造,是不是隻能夠這樣操作?自己先想清楚這個流程,再去結合spring
原始碼驗證,這一塊的知識點你以後想忘都忘不掉)
那麼結合我們的思路,我們再修改一下流程圖:
1.2. 虛擬碼實現
流程已經設計好了,那麼我們其實也可以出一下這個流程的虛擬碼(虛擬碼就不寫加鎖那些流程了):
// 正真已經初始化完成的map private Map<String, Object> singleMap = new ConcurrentHashMap<>(16); // 快取的map privateMap<String, Object> cacheMap = new ConcurrentHashMap<>(16); protected Object getBean(final String beanName) { // 先看一下目標bean是否完全初始化完了,完全初始化完直接返回 Object single = singleMap.get(beanName); if (single != null) { return single; } // 再看一下目標bean例項是否已經建立,已經建立直接返回 single = cacheMap.get(beanName); if (single != null) { return single; } // 建立例項 Object beanInstance = createBeanInstance(beanName); // 例項建立之後,放入快取 // 因為已經建立例項了,這個時候這個例項的引用暴露出去已經沒問題了 // 之後的屬性注入等邏輯還是在這個例項上做的 cacheMap.put(beanName, beanInstance); // 依賴注入,會觸發依賴的bean的getBean方法 populateBean(beanName, beanInstance); // 初始化方法呼叫 initializeBean(beanName, beanInstance); // 從快取移除,放入例項map singleMap.put(beanName, beanInstance); cacheMap.remove(beanName) return beanInstance; }
可以看到,如果我們自己實現一個快取結構來解決迴圈依賴的問題的話,可能只需要兩層結構就可以了,但是spring
卻使用了3級快取,它有哪些不一樣的考量呢?
2. Spring
原始碼
我們已經知道該怎麼解決迴圈依賴問題了,那麼現在我們就一起看一下spring
原始碼,看一下我們的分析是否正確。
由於之前我們已經詳細講過整個bean
的生命週期了,所以這裡就只挑三級快取相關的程式碼段來講了,會跳過比較多的程式碼,同學們如果有點懵,可以溫習一下萬字長文講透bean的生命週期。
2.1. Spring
的三級快取設計
2.1.1. 三級快取原始碼
首先,在我們的AbstractBeanFactory#doGetBean
的邏輯中:
// 初始化是通過getBean觸發bean建立的,依賴注入最終也會使用getBean獲取依賴的bean的例項 public Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false); } protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean; // 獲取bean例項 Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { // beanFactory相關,之後再講 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // 跳過一些程式碼 // 建立bean的邏輯 if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } // 跳過一些程式碼 } // 跳過一些程式碼 // 返回bean例項 return (T) bean; }
可以看到,如果我們使用getSingleton(beanName)
直接獲取到bean例項了,是會直接把bean例項返回的,我們一起看一下這個方法(這個方法屬於DefaultSingletonBeanRegistry
):
// 一級快取,快取正常的bean例項 /** Cache of singleton objects: bean name to bean instance. */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二級快取,快取還未進行依賴注入和初始化方法呼叫的bean例項 /** Cache of early singleton objects: bean name to bean instance. */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 三級快取,快取bean例項的ObjectFactory /** Cache of singleton factories: bean name to ObjectFactory. */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); public Object getSingleton(String beanName) { return getSingleton(beanName, true); } protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 先嚐試中一級快取獲取 Object singletonObject = this.singletonObjects.get(beanName); // 獲取不到,並且當前需要獲取的bean正在建立中 // 第一次容器初始化觸發getBean(A)的時候,這個isSingletonCurrentlyInCreation判斷一定為false // 這個時候就會去走建立bean的流程,建立bean之前會先把這個bean標記為正在建立 // 然後A例項化之後,依賴注入B,觸發B的例項化,B再注入A的時候,會再次觸發getBean(A) // 此時isSingletonCurrentlyInCreation就會返回true了 // 當前需要獲取的bean正在建立中時,代表出現了迴圈依賴(或者一前一後併發獲取這個bean) // 這個時候才需要去看二、三級快取 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 加鎖了 synchronized (this.singletonObjects) { // 從二級快取獲取 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 二級快取也沒有,並且允許獲取早期引用的話 - allowEarlyReference傳進來是true ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); // 從三級快取獲取ObjectFactory if (singletonFactory != null) { // 通過ObjectFactory獲取bean例項 singletonObject = singletonFactory.getObject(); // 放入二級快取 this.earlySingletonObjects.put(beanName, singletonObject); // 從三級快取刪除 // 也就是說對於一個單例bean,ObjectFactory#getObject只會呼叫到一次 // 獲取到早期bean例項之後,就把這個bean例項從三級快取升級到二級快取了 this.singletonFactories.remove(beanName); } } } } // 不管從哪裡獲取到的bean例項,都會返回 return singletonObject; }
一二級快取都好理解,其實就可以理解為我們虛擬碼裡面的那兩個Map
,但是這個三級快取是怎麼回事?ObjectFactory
又是個什麼東西?我們就先看一下這個ObjectFactory
的結構:
@FunctionalInterface public interface ObjectFactory<T> { // 好吧,就是簡簡單單的一個獲取例項的函式介面而已 T getObject() throws BeansException; }
我們回到這個三級快取的結構,二級快取是是在getSingleton
方法中put
進去的,這跟我們之前分析的,建立bean
例項之後放入,好像不太一樣?那我們是不是可以推斷一下,其實建立bean
例項之後,是放入三級快取的呢(總之例項建立之後是需要放入快取的)?我們來跟一下bean
例項化的程式碼,主要看一下上一篇時刻意忽略掉的地方:
// 程式碼做了很多刪減,只把主要的邏輯放出來的 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // 建立bean例項 BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args); final Object bean = instanceWrapper.getWrappedInstance(); // beanPostProcessor埋點呼叫 applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); // 重點是這裡了,如果是單例bean&&允許迴圈依賴&&當前bean正在建立 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 加入三級快取 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } Object exposedObject = bean; try { // 依賴注入 populateBean(beanName, mbd, instanceWrapper); // 初始化方法呼叫 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { throw new BeanCreationException(...); } if (earlySingletonExposure) { // 第二個引數傳false是不會從三級快取中取值的 Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { // 如果發現二級快取中有值了 - 說明出現了迴圈依賴 if (exposedObject == bean) { // 並且initializeBean沒有改變bean的引用 // 則把二級快取中的bean例項返回出去 exposedObject = earlySingletonReference; } } } try { // 註冊銷燬邏輯 registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException(...); } return exposedObject; }
可以看到,初始化一個bean
是,建立bean
例項之後,如果這個bean是單例bean
&&允許迴圈依賴&&當前bean
正在建立,那麼將會呼叫addSingletonFactory
加入三級快取:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { // 加入三級快取 this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
也就是說我們虛擬碼中的這一段有了:
// 建立例項 Object beanInstance = createBeanInstance(beanName); // 例項建立之後,放入快取 // 因為已經建立例項了,這個時候這個例項的引用暴露出去已經沒問題了 // 之後的屬性注入等邏輯還是在這個例項上做的 cacheMap.put(beanName, beanInstance); 複製程式碼 那麼接下來,完全例項化完成的bean又是什麼時候塞入我們的例項Map(一級快取)singletonObjects的呢? 這個時候我們就要回到呼叫createBean方法的這一塊的邏輯了: if (mbd.isSingleton()) { // 我們回到這個位置 sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }
可以看到,我們的createBean
建立邏輯是通過一個lamdba
語法傳入getSingleton
方法了,我們進入這個方法看一下:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { // 一級快取拿不到 // 注意一下這個方法,這裡會標記這個bean正在建立 beforeSingletonCreation(beanName); boolean newSingleton = false; try { // 呼叫外部傳入的lamdba,即createBean邏輯 // 獲取到完全例項化好的bean // 需要注意的是,這個時候這個bean的例項已經在二級快取或者三級快取中了 // 三級快取:bean例項建立後放入的,如果沒有迴圈依賴/併發獲取這個bean,那會一直在三級快取中 // 二級快取:如果出現迴圈依賴,第二次進入getBean->getSingleton的時候,會從三級快取升級到二級快取 singletonObject = singletonFactory.getObject(); // 標記一下 newSingleton = true; } catch (IllegalStateException ex) { singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { throw ex; } } catch (BeanCreationException ex) { throw ex; } finally { // 這裡是從正在建立的列表移除,到這裡這個bean要麼已經完全初始化完成了 // 要麼就是初始化失敗,都需要移除的 afterSingletonCreation(beanName); } if (newSingleton) { // 如果是新初始化了一個單例bean,加入一級快取 addSingleton(beanName, singletonObject); } } return singletonObject; } }
哈哈,加入例項Map
(一級快取)singletonObjects
的邏輯明顯就是在這個addSingleton
中了:
protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { // 這個邏輯應該一點也不意外吧 // 放入一級快取,從二、三級快取刪除,這裡就用判斷當前bean具體是在哪個快取了 // 反正都要刪的 this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }
也就是說,我們虛擬碼的這一塊在spring
裡面也有對應的體現,完美:
// 初始化方法呼叫 initializeBean(beanName, beanInstance); // 從快取移除,放入例項map singleMap.put(beanName, beanInstance); cacheMap.remove(beanName)
就這樣,spring通過快取設計解決了迴圈依賴的問題。
2.1.2. 三級快取解決迴圈依賴流程圖
什麼,看完程式碼之後還是有點模糊?那麼把我們的流程圖再改一下,按照spring
的流程來:
2.1.3. 三級快取解決迴圈依賴虛擬碼
看完圖還覺得不清晰的話,我們把所有spring
中三級快取相關的程式碼彙總到一起,用虛擬碼的方式,拍平成一個方法,大家應該感覺會更清晰了:
// 一級快取 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二級快取 private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 三級快取 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); protected Object getBean(final String beanName) { // !以下為getSingleton邏輯! // 先從一級快取獲取 Object single = singletonObjects.get(beanName); if (single != null) { return single; } // 再從二級快取獲取 single = earlySingletonObjects.get(beanName); if (single != null) { return single; } // 從三級快取獲取objectFactory ObjectFactory<?> objectFactory = singletonFactories.get(beanName); if (objectFactory != null) { single = objectFactory.get(); // 升到二級快取 earlySingletonObjects.put(beanName, single); singletonFactories.remove(beanName); return single; } // !以上為getSingleton邏輯! // !以下為doCreateBean邏輯 // 快取完全拿不到,需要建立 // 建立例項 Object beanInstance = createBeanInstance(beanName); // 例項建立之後,放入三級快取 singletonFactories.put(beanName, () -> return beanInstance); // 依賴注入,會觸發依賴的bean的getBean方法 populateBean(beanName, beanInstance); // 初始化方法呼叫 initializeBean(beanName, beanInstance); // 依賴注入完之後,如果二級快取有值,說明出現了迴圈依賴 // 這個時候直接取二級快取中的bean例項 Object earlySingletonReference = earlySingletonObjects.get(beanName); if (earlySingletonReference != null) { beanInstance = earlySingletonObject; } // !以上為doCreateBean邏輯 // 從二三快取移除,放入一級快取 singletonObjects.put(beanName, beanInstance); earlySingletonObjects.remove(beanName); singletonFactories.remove(beanName); return beanInstance; }
把所有邏輯放到一起之後會清晰很多,同學們只需要自行模擬一遍,再populateBean
中再次呼叫getBean
邏輯進行依賴注入,應該就能捋清楚了。
2.1.4. 標記當前bean正在建立
在我們剛剛看到的將bean
例項封裝成ObjectFactory
並放入三級快取的流程中,有一個判斷是當前bean是正在建立,這個狀態又是怎麼判斷的呢:
// 重點是這裡了,如果是單例bean&&允許迴圈依賴&&當前bean正在建立 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 加入三級快取 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }
我們看一下這個isSingletonCurrentlyInCreation
的邏輯:
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); public boolean isSingletonCurrentlyInCreation(String beanName) { return this.singletonsCurrentlyInCreation.contains(beanName); }
可以看到額,其實就是判斷當前beanName
是不是在這個singletonsCurrentlyInCreation
容器中,那麼這個容器中的值又是什麼時候操作的呢?
希望同學們還記得getSingleton(beanName, singletonFactory)
中有呼叫的beforeSingletonCreation
和afterSingletonCreation
:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { // 一級快取拿不到 // 注意一下這個方法,這裡會標記這個bean正在建立 beforeSingletonCreation(beanName); boolean newSingleton = false; try { // 呼叫外部傳入的lamdba,即createBean邏輯 singletonObject = singletonFactory.getObject(); // 標記一下 newSingleton = true; } catch (BeanCreationException ex) { throw ex; } finally { // 這裡是從正在建立的列表移除,到這裡這個bean要麼已經完全初始化完成了 // 要麼就是初始化失敗,都需要移除的 afterSingletonCreation(beanName); } if (newSingleton) { // 如果是新初始化了一個單例bean,加入一級快取 addSingleton(beanName, singletonObject); } } return singletonObject; } }
我們現在來看一下這兩個方法的邏輯:
protected void beforeSingletonCreation(String beanName) { // 加入singletonsCurrentlyInCreation,由於singletonsCurrentlyInCreation是一個set // 如果加入失敗的話,說明在建立兩次這個bean // 這個時候會丟擲迴圈依賴異常 if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } } protected void afterSingletonCreation(String beanName) { // 從singletonsCurrentlyInCreation中刪除 if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) { throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation"); } }
可以看到,我們這兩個方法主要就是對singletonsCurrentlyInCreation
容器進行操作的,inCreationCheckExclusions
這個容器可以不用管它,這名稱一看就是一些白名單之類的配置。
這裡需要主要的是beforeSingletonCreation
中,如果singletonsCurrentlyInCreation.add(beanName)
失敗的話,是會丟擲BeanCurrentlyInCreationException
的,這代表spring
遇到了無法解決的迴圈依賴問題,此時會丟擲異常中斷初始化流程,畢竟單例的bean
不允許被建立兩次。
2.2. 為什麼要設計為三級結構?
2.2.1. 只做兩級快取會有什麼問題?
其實到這裡,我們已經清楚,三級快取的設計已經成功的解決了迴圈依賴的問題。
可是按我們自己的設計思路,明明只需要兩級快取就可以解決,spring
卻使用了三級快取,難道是為了炫技麼?
這個時候,就需要我們再細緻的看一下bean初始化過程了:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // ... if (earlySingletonExposure) { // 放入三級快取 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); // 這裡這個引用被替換了 exposedObject = initializeBean(beanName, exposedObject, mbd); } // ... return exposedObject; }
仔細觀察,initializeBean
方法是可能返回一個新的物件,從而把createBeanInstance
建立的bean例項替換掉的:
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { // 呼叫aware介面 invokeAwareMethods(beanName, bean); Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { // 埋點 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { // 初始化方法 invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException(...); } if (mbd == null || !mbd.isSynthetic()) { // 埋點 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }
可以看到,我們的postProcessBeforeInitialization
和postProcessAfterInitialization
的埋點方法都是有可能把我們的bean
替換掉的。
那麼結合整個流程來看,由於我們放入快取之後,initializeBean
方法中可能存在替換bean
的情況,如果只有兩級快取的話:
這會導致B
中注入的A
例項與singletonObjects
中儲存的AA
例項不一致,而之後其他的例項注入a
時,卻會拿到singletonObjects
中的AA
例項,這樣肯定是不符合預期的。
2.2.2. 三級快取是如何解決問題的
那麼這個問題應該怎麼解決呢?
這個時候我們就要回到新增三級快取的地方看一下了。addSingletonFactory
的第二個引數就是一個ObjectFactory
,並且這個ObjectFactory
最終將會放入三級快取,現在我們再回頭看呼叫addSingletonFactory
的地方:
// 加入三級快取 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
熟悉lamdba
語法的同學都知道,getEarlyBeanReference
其實就是放入三級快取中的ObjectFactory
的getObject
方法的邏輯了,那我們一起來看一下,這個方法是做了什麼:
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; // 呼叫了beanPostProcessor的一個埋點方法 exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } // 返回的是埋點替換的bean return exposedObject; }
咦,這裡也有個埋點,可以替換掉bean
的引用。
原來為了解決initializeBean
可能替換bean
引用的問題,spring
就設計了這個三級快取,他在第三級裡儲存了一個ObjectFactory
,其實具體就是getEarlyBeanReference
的呼叫,其中提供了一個getEarlyBeanReference
的埋點方法,通過這個埋點方法,它允許開發人員把需要替換的bean
,提早替換出來。
比如說如果在initializeBean
方法中希望把A
換成AA
(這個邏輯肯定是通過某個beanPostProcessor
來做的),那麼你這個beanPostProcessor
可以同時提供getEarlyBeanReference
方法,在出現迴圈依賴的時候,可以提前把A->AA
這個邏輯做了,並且initializeBean
方法不再做這個A->AA
的邏輯,並且,當我們的迴圈依賴邏輯走完,A
建立->注入B
->觸發B
初始化->注入A
->執行快取邏輯獲取AA
例項並放入二級快取->B
初始化完成->回到A
的初始化邏輯時,通過以下程式碼:
protected Object doCreateBean(...) { populateBean(beanName, mbd, instanceWrapper); Object exposedObject = initializeBean(beanName, exposedObject, mbd); if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { // 如果二級快取存在,直接使用二級快取 exposedObject = earlySingletonReference; } } } return exposedObject; }
這樣就能保證當前bean
中注入的AA
和singletonObjects
中的AA
例項是同一個物件了。
將會把二級快取中的AA
直接返回,這是就能保證B
中注入的AA
例項與spring
管理起來的最終的AA
例項是同一個了。
整個流程梳理一下就是這樣:
2.2.3. 三級快取的實際應用
既然設計了這個三級快取,那麼肯定是有實際需求的,我們上面分析了一大堆,現在正好舉一個例子看一下,為什麼spring
需要三級快取。
我們都知道,Spring
的AOP
功能,是通過生成動態代理類來實現的,而最後我們使用的也都是代理類例項而不是原始類例項。而AOP
代理類的建立,就是在initializeBean
方法的postProcessAfterInitialization
埋點中,我們直接看一下getEarlyBeanReference
和postProcessAfterInitialization
這兩個埋點吧(具體類是AbstractAutoProxyCreator
,之後講AOP
的時候會細講):
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware { private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16); // 如果出現迴圈依賴,getEarlyBeanReference會先被呼叫到 public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); // 這個時候把當前類放入earlyProxyReferences this.earlyProxyReferences.put(cacheKey, bean); // 直接返回了一個代理例項 return wrapIfNecessary(bean, beanName, cacheKey); } public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); // 注意這個判斷,如果出現了迴圈依賴,這個if塊是進不去的 if (this.earlyProxyReferences.remove(cacheKey) != bean) { // 如果沒有出現迴圈依賴,會在這裡建立代理類 return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; } }
就這樣,Spring
巧妙的使用三級快取來解決了這個不同例項的問題。當然,如果我們需要自己開發類似代理之類的可能改變bean
引用的功能時,也需要遵循getEarlyBeanReference
方法的埋點邏輯,學習AbstractAutoProxyCreator
中的方式,才能讓spring
按照我們的預期來工作。
四、三級快取無法解決的問題
1. 構造器迴圈依賴
剛剛講了很多三級快取的實現,以及它是怎麼解決迴圈依賴的問題的。
但是,是不是使用了三級快取,就能解決所有的迴圈依賴問題呢?
當然不是的,有一個特殊的迴圈依賴,由於java
語言特性的原因,是永遠無法解決的,那就是構造器迴圈依賴。
比如以下兩個類:
public class A { private final B b; public A(final B b) { this.b = b; } } public class B { private final A a; public B(final A a) { this.a = a; } }
拋開Spring
來講,同學們你們有辦法讓這兩個類例項化成功麼?
該不會有同學說,這有何難看我的:
// 你看,這樣不行麼~ final A a = new A(new B(a));
不好意思,這個真的不行,不信可以去試試。從語法上來講,java
的語言特性決定了不允許使用未初始化完成的變數。我們只能無限套娃:
// 這樣明顯就沒有解決問題,是個無限套娃的死迴圈 final A a = new A(new B(new A(new B(new A(new B(...))))));
所以,連我們都無法解決的問題,就不應該強求spring
來解決了吧~
@Service public class A { private final B b; public A(final B b) { this.b = b; } } @Service public class B { private final A a; public B(final A a) { this.a = a; } }
啟動之後,果然報錯了:
Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
2. Spring
真的對構造器迴圈依賴束手無策麼?
難道,spring
對於這種迴圈依賴真的束手無策了麼?其實不是的,spring
還有@Lazy
這個大殺器...只需要我們對剛剛那兩個類小小的改造一下:
@Service public class A { private final B b; public A(final B b) { this.b = b; } public void prt() { System.out.println("in a prt"); } } @Service public class B { private final A a; public B(@Lazy final A a) { this.a = a; } public void prt() { a.prt(); } } // 啟動 @Test public void test() { applicationContext = new ClassPathXmlApplicationContext("spring.xml"); B bean = applicationContext.getBean(B.class); bean.prt(); }
都說了成功了,執行結果同學們也能猜到了吧:
in a prt
(同學們也可以自己嘗試一下~
3. @Lazy
原理
這個時候我們必須要想一下,spring
是怎麼通過 @Lazy
來繞過我們剛剛解決不了的無限套娃問題了。
因為這裡涉及到之前沒有細節的引數注入時候的引數解析問題,我這邊就不帶大家從入口處一步一步深入了,這邊直接空降到目的碼DefaultListableBeanFactory#resolveDependency
:
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { // 跳過... // 這個地方是我們獲取依賴的地方 // 嘗試獲取一個懶載入代理 Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary( descriptor, requestingBeanName); if (result == null) { // 如果沒獲取到懶載入代理,就直接去獲取bean例項了,這裡最終會呼叫getBean result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); } return result; }
我們直接看一下這個getLazyResolutionProxyIfNecessary
,這個方法就是獲取LazyProxy
的地方了:
public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotationAutowireCandidateResolver { @Override @Nullable public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) { // 如果是懶載入的,就構建一個懶載入的代理 return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null); } // 判斷是否是懶載入的,主要就是判斷@Lazy註解,簡單看下就好了 protected boolean isLazy(DependencyDescriptor descriptor) { for (Annotation ann : descriptor.getAnnotations()) { Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class); if (lazy != null && lazy.value()) { return true; } } MethodParameter methodParam = descriptor.getMethodParameter(); if (methodParam != null) { Method method = methodParam.getMethod(); if (method == null || void.class == method.getReturnType()) { Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class); if (lazy != null && lazy.value()) { return true; } } } return false; } protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) { final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory(); // 構造了一個TargetSource TargetSource ts = new TargetSource() { @Override public Class<?> getTargetClass() { return descriptor.getDependencyType(); } @Override public boolean isStatic() { return false; } @Override public Object getTarget() { // 再對應的getTarget方法裡,才會去正真載入依賴,進而呼叫getBean方法 Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null); if (target == null) { Class<?> type = getTargetClass(); if (Map.class == type) { return Collections.emptyMap(); } else if (List.class == type) { return Collections.emptyList(); } else if (Set.class == type || Collection.class == type) { return Collections.emptySet(); } throw new NoSuchBeanDefinitionException(...); } return target; } @Override public void releaseTarget(Object target) { } }; // 建立代理工廠ProxyFactory ProxyFactory pf = new ProxyFactory(); pf.setTargetSource(ts); Class<?> dependencyType = descriptor.getDependencyType(); if (dependencyType.isInterface()) { pf.addInterface(dependencyType); } // 建立返回代理類 return pf.getProxy(beanFactory.getBeanClassLoader()); } }
同學們可能對TargetSource
和ProxyFactory
這些不熟悉,沒關係,這不妨礙我們理解邏輯。
從原始碼我們可以看到,對於@Lazy
的依賴,我們其實是返回了一個代理類(以下稱為LazyProxy
)而不是正真通過getBean
拿到目標bean
注入。而真正的獲取bean
的邏輯,被封裝到了一個TargetSource
類的getTarget
方法中,而這個TargetSource
類最終被用來生成LazyProxy
了,那麼我們是不是可以推測,LazyProxy
應該持有這個TargetSource
物件。
而從我們懶載入的語意來講,是說真正使用到這個bean
(呼叫這個bean
的某個方法時)的時候,才對這個屬性進行注入/初始化。
那麼對於當前這個例子來講,就是說其實B
建立的時候,並沒有去呼叫getBean("a")
去獲取構造器的引數,而是直接生成了一個LazyProxy
來做B
構造器的引數,而B
之後正真呼叫到A
的方法時,才會去呼叫TargetSource
中的getTarget
獲取A
例項,即呼叫getBean("a")
,這個時候A
早就例項化好了,所以也就不會有迴圈依賴問題了。
4. 虛擬碼描述
還是同樣,我們可以用虛擬碼來描述一下這個流程,虛擬碼我們就直接用靜態代理來描述了:
public class A { private final B b; public A(final B b) { this.b = b; } public void prt() { System.out.println("in a prt"); } } public class B { private final A a; public B(final A a) { this.a = a; } public void prt() { a.prt(); } } // A的懶載入代理類 public class LazyProxyA extends A { private A source; private final Map<String, Object> ioc; private final String beanName; public LazyProxyA(Map<String, Object> ioc, String beanName) { super(null); this.ioc = ioc; this.beanName = beanName; } @Override public void prt() { if (source == null) { source = (A) ioc.get(beanName); } source.prt(); } }
那麼整個初始化的流程簡單來描述就是:
Map<String, Object> ioc = new HashMap<>(); void init() { B b = new B(new LazyProxyA(ioc, "a")); ioc.put("b", b); A a = new A((B)ioc.get("b")); ioc.put("a", a); }
我們也模擬一下執行:
void test() { // 容器初始化 init(); B b = (B)ioc.get("b"); b.prt(); }
當然是能成功列印的:
in a prt
六、總結
關於迴圈依賴的問題,Spring提供了通過設計快取的方式來解決的,而設計為三級快取,主要是為了解決bean
初始化過程中,例項被放入快取之後,例項的引用還可能在呼叫initializeBean
方法時被替換的問題。
對於構造器的迴圈依賴,三級快取設計是無法解決的,這屬於java
語言的約束;但是spring
提供了一種使用@Lazy
的方式,繞過這個限制,使得構造器的迴圈依賴在特定情況下(迴圈鏈中的某個注入打上@Lazy
註解)也能解決。
版權所屬:歸原作者所有
聲援博主:您的肯定就是我進步的動力。如果你感覺還不錯,就請鼓勵一下吧!記得隨手點波推薦不要忘記哦!!!
別忘了點推薦留下您來過的痕跡
交流看我公告
作者:小希子
連結:https://juejin.im/post/6860785780307492871
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。