前言
問:Spring如何解決循環(huán)依賴?
答:Spring通過提前曝光機制,利用三級緩存解決循環(huán)依賴(這原理還是挺簡單的,參考:三級緩存、圖解循環(huán)依賴原理)
再問:Spring通過提前曝光,直接曝光到二級緩存已經(jīng)可以解決循環(huán)依賴問題了,為什么一定要三級緩存?
再細問:如果循環(huán)依賴的時候,所有類又都需要Spring AOP自動代理,那Spring如何提前曝光?曝光的是原始bean還是代理后的bean?
這些問題算是Spring源碼的壓軸題了,如果這些問題都弄明白,恭喜你順利結(jié)業(yè)Spring源碼了。就單單對Spring這一塊的理解,不夸張的說可以達到阿里水準了
源碼分析
進入正題,在Spring創(chuàng)建Bean的核心代碼doGetBean中,在實例化bean之前,會先嘗試從三級緩存獲取bean,這也是Spring解決循環(huán)依賴的開始
(一) 緩存中獲取bean
// AbstractBeanFactory.javaprotected T doGetBean(final String name, @Nullable final Class requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean; // 2. 嘗試從緩存中獲取bean Object sharedInstance = getSingleton(beanName); …}
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 從一級緩存獲取,key=beanName value=bean Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 從二級緩存獲取,key=beanName value=bean singletonObject = this.earlySingletonObjects.get(beanName); // 是否允許循環(huán)引用 if (singletonObject == null && allowEarlyReference) { /** * 三級緩存獲取,key=beanName value=objectFactory,objectFactory中存儲getObject()方法用于獲取提前曝光的實例 * * 而為什么不直接將實例緩存到二級緩存,而要多此一舉將實例先封裝到objectFactory中? * 主要關鍵點在getObject()方法并非直接返回實例,而是對實例又使用 * SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法對bean進行處理 * * 也就是說,當spring中存在該后置處理器,所有的單例bean在實例化后都會被進行提前曝光到三級緩存中, * 但是并不是所有的bean都存在循環(huán)依賴,也就是三級緩存到二級緩存的步驟不一定都會被執(zhí)行,有可能曝光后直接創(chuàng)建完成,沒被提前引用過, * 就直接被加入到一級緩存中。因此可以確保只有提前曝光且被引用的bean才會進行該后置處理 */ ObjectFactory singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { /** * 通過getObject()方法獲取bean,通過此方法獲取到的實例不單單是提前曝光出來的實例, * 它還經(jīng)過了SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法處理過。 * 這也正是三級緩存存在的意義,可以通過重寫該后置處理器對提前曝光的實例,在被提前引用時進行一些操作 */ singletonObject = singletonFactory.getObject(); // 將三級緩存生產(chǎn)的bean放入二級緩存中 this.earlySingletonObjects.put(beanName, singletonObject); // 刪除三級緩存 this.singletonFactories.remove(beanName); } } } } return singletonObject; }
三級緩存分別是:
- singletonObject:一級緩存,該緩存key = beanName, value = bean;這里的bean是已經(jīng)創(chuàng)建完成的,該bean經(jīng)歷過實例化->屬性填充->初始化以及各類的后置處理。因此,一旦需要獲取bean時,我們第一時間就會尋找一級緩存
- earlySingletonObjects:二級緩存,該緩存key = beanName, value = bean;這里跟一級緩存的區(qū)別在于,該緩存所獲取到的bean是提前曝光出來的,是還沒創(chuàng)建完成的。也就是說獲取到的bean只能確保已經(jīng)進行了實例化,但是屬性填充跟初始化肯定還沒有做完,因此該bean還沒創(chuàng)建完成,僅僅能作為指針提前曝光,被其他bean所引用
- singletonFactories:三級緩存,該緩存key = beanName, value = beanFactory;在bean實例化完之后,屬性填充以及初始化之前,如果允許提前曝光,spring會將實例化后的bean提前曝光,也就是把該bean轉(zhuǎn)換成beanFactory并加入到三級緩存。
在需要引用提前曝光對象時再通過singletonFactory.getObject()獲取。
這里拋出問題,如果我們直接將提前曝光的對象放到二級緩存earlySingletonObjects,Spring循環(huán)依賴時直接取就可以解決循環(huán)依賴了,為什么還要三級緩存singletonFactory然后再通過getObject()來獲取呢?這是不是多此一舉?
(二) 三級緩存的添加
我們回到添加三級緩存,添加SingletonFactory的地方,看看getObject()到底做了什么操作
this.addSingletonFactory(beanName, () -> { return this.getEarlyBeanReference(beanName, mbd, bean); });
可以看到在返回getObject()時,多做了一步getEarlyBeanReference操作,這步操作是BeanPostProcess的一種,也就是給子類重寫的一個后處理器,目的是用于被提前引用時進行拓展。即:曝光的時候并不調(diào)用該后置處理器,只有曝光,且被提前引用的時候才調(diào)用,確保了被提前引用這個時機觸發(fā)。
(三) 提前曝光代理earlyProxyReferences
因此所有的重點都落到了getEarlyBeanReference上,getEarlyBeanReference方法是SmartInstantiationAwareBeanPostProcessor所規(guī)定的接口。再通過UML的類圖查看實現(xiàn)類,僅有AbstractAutoProxyCreator進行了實現(xiàn)。也就是說,除了用戶在子類重寫,否則僅有AbstractAutoProxyCreator一種情況
// AbstractAutoProxyCreator.java public Object getEarlyBeanReference(Object bean, String beanName) { // 緩存當前bean,表示該bean被提前代理了 Object cacheKey = getCacheKey(bean.getClass(), beanName); this.earlyProxyReferences.put(cacheKey, bean); // 對bean進行提前Spring AOP代理 return wrapIfNecessary(bean, beanName, cacheKey); }
wrapIfNecessary是用于Spring AOP自動代理的。Spring將當前bean緩存到earlyProxyReferences中標識提前曝光的bean在被提前引用之前,然后進行了Spring AOP代理。
但是經(jīng)過Spring AOP代理后的bean就已經(jīng)不再是原來的bean了,經(jīng)過代理后的bean是一個全新的bean,也就是說代理前后的2個bean連內(nèi)存地址都不一樣了。這時將再引出新的問題:B提前引用A將引用到A的代理,這是符合常理的,但是最原始的bean A在B完成創(chuàng)建后將繼續(xù)創(chuàng)建,那么Spring Ioc最后返回的Bean是Bean A呢還是經(jīng)過代理后的Bean呢?
這個問題我們得回到Spring AOP代理,Spring AOP代理時機有2個:
第一種情況就沒什么好探究的了,直接短路了,根本沒有后續(xù)操作。而我們關心的是第二種情況,在Spring初始化后置處理器中發(fā)生的Spring AOP代理
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { // 調(diào)用bean初始化后置處理器處理 Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; } // AbstractAutoProxyCreator.java public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean != null) { // 獲取緩存key Object cacheKey = getCacheKey(bean.getClass(), beanName); // 查看該bean是否被Spring AOP提前代理!而緩存的是原始的bean,因此如果bean被提前代理過,這此處會跳過 // 如果bean沒有被提前代理過,則進入AOP代理 if (this.earlyProxyReferences.remove(cacheKey) != bean) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }
earlyProxyReferences是不是有點熟悉,是的,這就是我們剛剛提前曝光并且進行Spring AOP提前代理時緩存的原始bean,如果緩存的原始bean跟當前的bean是一致的,那么就不進行Spring AOP代理了!返回原始的bean
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { try { // /** * 4. 填充屬性 * 如果@Autowired注解屬性,則在上方完成解析后,在這里完成注入 * * @Autowired * private Inner inner; */ populateBean(beanName, mbd, instanceWrapper); // 5. 初始化 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), beanName, “Initialization of bean failed”, ex); } } // 6. 存在提前曝光情況下 if (earlySingletonExposure) { // earlySingletonReference:二級緩存,緩存的是經(jīng)過提前曝光提前Spring AOP代理的bean Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { // exposedObject跟bean一樣,說明初始化操作沒用應用Initialization后置處理器(指AOP操作)改變exposedObject // 主要是因為exposedObject如果提前代理過,就會跳過Spring AOP代理,所以exposedObject沒被改變,也就等于bean了 if (exposedObject == bean) { // 將二級緩存中的提前AOP代理的bean賦值給exposedObject,并返回 exposedObject = earlySingletonReference; } // 引用都不相等了,也就是現(xiàn)在的bean已經(jīng)不是當時提前曝光的bean了 else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { // dependentBeans也就是B, C, D String[] dependentBeans = getDependentBeans(beanName); Set actualDependentBeans = new LinkedHashSet(dependentBeans.length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } // 被依賴檢測異常 if (!actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(beanName, “Bean with name ‘” + beanName + “‘ has been injected into other beans [” + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + “] in its raw version as part of a circular reference, but has eventually been ” + “wrapped. This means that said other beans do not use the final version of the ” + “bean. This is often the result of over-eager type matching – consider using ” + “‘getBeanNamesOfType’ with the ‘allowEagerInit’ flag turned off, for example.”); } } } }
這個時候我們需要理清一下3個變量
理清這3個變量以后,就會發(fā)現(xiàn),exposedObject = earlySingletonReference;
AOP代理過的Bean賦值給了exposedObject并返回,這時候用戶拿到的bean就是AOP代理過后的bean了,一切皆大歡喜了。
但是中間還有一個問題!提前曝光的bean在提前引用時被Spring AOP代理了,但是此時的bean只是經(jīng)過了實例化的bean,還沒有進行@Autowire的注入啊!也就是說此時代理的bean里面自動注入的屬性是空的!
(四) 提前AOP代理對象的 屬性填充、初始化
是的,確實在Spring AOP提前代理后沒有經(jīng)過屬性填充和初始化。那么這個代理又是如何保證依賴屬性的注入的呢?
答案回到Spring AOP最早最早講的JDK動態(tài)代理上找,JDK動態(tài)代理時,會將目標對象target保存在最后生成的代理中,當調(diào)用proxy方法時會回調(diào)h.invoke,而h.invoke又會回調(diào)目標對象target的原始方法。
因此,其實在Spring AOP動態(tài)代理時,原始bean已經(jīng)被保存在提前曝光代理中了。而后原始Bean繼續(xù)完成屬性填充和初始化操作。
因為AOP代理$proxy中保存著traget也就是是原始bean的引用,因此后續(xù)原始bean的完善,也就相當于Spring AOP中的target的完善,這樣就保證了Spring AOP的屬性填充與初始化了!
(五) 循環(huán)依賴遇上Spring AOP 圖解
為了幫助大家理解,這里靈魂畫手畫張流程圖幫助大家理解
在這里插入圖片描述
首先又bean A,bean B,他們循環(huán)依賴注入,同時bean A還需要被Spring AOP代理,例如事務管理或者日志之類的操作。
原始bean A,bean B圖中用a,b表示,而代理后的bean A我們用aop.a表示
最少必要面試題
【Java基礎】10道不得不會的Java基礎面試題
【Java并發(fā)】10道不得不會的Java并發(fā)基礎面試題
【MySQL】10道不得不會的MySQL基礎面試題
【ElasticSearch】10道不得不會的ElasticSearch面試題
【JVM】10道不得不會的JVM面試題
【Spring】10道不得不會的Spring面試題