Spring三級緩存思想解決循環(huán)依賴總結(jié)分析
三級緩存思想
Spring 解決循環(huán)依賴的核心就是提前暴露對象,而提前暴露的對象就是放置于第二級緩存中。下表是三級緩存的說明:
所有被 Spring 管理的 Bean,最終都會存放在 singletonObjects 中,這里面存放的 Bean 是經(jīng)歷了所有生命周期的(除了銷毀的生命周期),完整的,可以給用戶使用的。
earlySingletonObjects 存放的是已經(jīng)被實例化,但是還沒有注入屬性和執(zhí)行 init 方法的 Bean。
singletonFactories 存放的是生產(chǎn) Bean 的工廠。
解決循環(huán)依賴
Spring 是如何通過上面介紹的三級緩存來解決循環(huán)依賴的呢?這里只用 A,B 形成的循環(huán)依賴來舉例:
- 實例化 A,此時 A 還未完成屬性填充和初始化方法(@PostConstruct)的執(zhí)行,A 只是一個半成品。
- 為 A 創(chuàng)建一個 Bean 工廠,并放入到 singletonFactories 中。
- 發(fā)現(xiàn) A 需要注入 B 對象,但是一級、二級、三級緩存均為發(fā)現(xiàn)對象 B。
- 實例化 B,此時 B 還未完成屬性填充和初始化方法(@PostConstruct)的執(zhí)行,B 只是一個半成品。
- 為 B 創(chuàng)建一個 Bean 工廠,并放入到 singletonFactories 中。
- 發(fā)現(xiàn) B 需要注入 A 對象,此時在一級、二級未發(fā)現(xiàn)對象 A,但是在三級緩存中發(fā)現(xiàn)了對象 A,從三級緩存中得到對象 A,并將對象 A 放入二級緩存中,同時刪除三級緩存中的對象 A。(注意,此時的 A 還是一個半成品,并沒有完成屬性填充和執(zhí)行初始化方法)
- 將對象 A 注入到對象 B 中。
- 對象 B 完成屬性填充,執(zhí)行初始化方法,并放入到一級緩存中,同時刪除二級緩存中的對象 B。(此時對象 B 已經(jīng)是一個成品)
- 對象 A 得到對象 B,將對象 B 注入到對象 A 中。(對象 A 得到的是一個完整的對象 B)
- 對象 A 完成屬性填充,執(zhí)行初始化方法,并放入到一級緩存中,同時刪除二級緩存中的對象 A。
我們從源碼中來分析整個過程:
創(chuàng)建 Bean 的方法在 AbstractAutowireCapableBeanFactory::doCreateBean()
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; if (instanceWrapper == null) { // ① 實例化對象 instanceWrapper = this.createBeanInstance(beanName, mbd, args); } final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null; Class<?> beanType = instanceWrapper != null ? instanceWrapper.getWrappedClass() : null; // ② 判斷是否允許提前暴露對象,如果允許,則直接添加一個 ObjectFactory 到三級緩存 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 添加三級緩存的方法詳情在下方 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // ③ 填充屬性 this.populateBean(beanName, mbd, instanceWrapper); // ④ 執(zhí)行初始化方法,并創(chuàng)建代理 exposedObject = initializeBean(beanName, exposedObject, mbd); return exposedObject; }
添加三級緩存的方法如下:
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); } } } @FunctionalInterface public interface ObjectFactory<T> { T getObject() throws BeansException; }
通過這段代碼,我們可以知道 Spring 在實例化對象之后,就會為其創(chuàng)建一個 Bean 工廠,并將此工廠加入到三級緩存中。
因此,Spring 一開始提前暴露的并不是實例化的 Bean,而是將 Bean 包裝起來的 ObjectFactory。為什么要這么做呢?
這實際上涉及到 AOP,如果創(chuàng)建的 Bean 是有代理的,那么注入的就應該是代理 Bean,而不是原始的 Bean。但是 Spring 一開始并不知道 Bean 是否會有循環(huán)依賴,通常情況下(沒有循環(huán)依賴的情況下),Spring 都會在完成填充屬性,并且執(zhí)行完初始化方法之后再為其創(chuàng)建代理。但是,如果出現(xiàn)了循環(huán)依賴的話,Spring 就不得不為其提前創(chuàng)建代理對象,否則注入的就是一個原始對象,而不是代理對象。因此,這里就涉及到應該在哪里提前創(chuàng)建代理對象?
Spring 的做法就是在 ObjectFactory 中去提前創(chuàng)建代理對象。它會執(zhí)行 getObject() 方法來獲取到 Bean。實際上,它真正執(zhí)行的方法如下:
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; }
因為提前進行了代理,避免對后面重復創(chuàng)建代理對象,會在 earlyProxyReferences 中記錄已被代理的對象。
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware { @Override public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); // 記錄已被代理的對象 this.earlyProxyReferences.put(cacheKey, bean); return wrapIfNecessary(bean, beanName, cacheKey); } }
通過上面的解析,我們可以知道 Spring 需要三級緩存的目的是為了在沒有循環(huán)依賴的情況下,延遲代理對象的創(chuàng)建,使 Bean 的創(chuàng)建符合 Spring 的設計原則。
如何獲取依賴
通過一個 getSingleton() 方法去獲取所需要的 Bean 的。
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) { // Bean 工廠中獲取 Bean singletonObject = singletonFactory.getObject(); // 放入到二級緩存中 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
當 Spring 為某個 Bean 填充屬性的時候,它首先會尋找需要注入對象的名稱,然后依次執(zhí)行 getSingleton() 方法得到所需注入的對象,而獲取對象的過程就是先從一級緩存中獲取,一級緩存中沒有就從二級緩存中獲取,二級緩存中沒有就從三級緩存中獲取,如果三級緩存中也沒有,那么就會去執(zhí)行 doCreateBean() 方法創(chuàng)建這個 Bean。
二級緩存能夠解決循環(huán)依賴嗎
第三級緩存的目的是為了延遲代理對象的創(chuàng)建,因為如果沒有依賴循環(huán)的話,那么就不需要為其提前創(chuàng)建代理,可以將它延遲到初始化完成之后再創(chuàng)建。
既然目的只是延遲的話,那么我們是不是可以不延遲創(chuàng)建,而是在實例化完成之后,就為其創(chuàng)建代理對象,這樣我們就不需要第三級緩存了。因此,我們可以將addSingletonFactory() 方法進行改造。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { // 判斷一級緩存中不存在此對象 object o = singletonFactory.getObject(); // 直接從工廠中獲取 Bean this.earlySingletonObjects.put(beanName, o); // 添加至二級緩存中 this.registeredSingletons.add(beanName); } } }
這樣的話,每次實例化完 Bean 之后就直接去創(chuàng)建代理對象,并添加到二級緩存中。測試結(jié)果是完全正常的,Spring 的初始化時間應該也是不會有太大的影響,因為如果 Bean 本身不需要代理的話,是直接返回原始 Bean 的,并不需要走復雜的創(chuàng)建代理 Bean 的流程。
測試證明,二級緩存也是可以解決循環(huán)依賴的。為什么 Spring 不選擇二級緩存,而要額外多添加一層緩存呢?
如果 Spring 選擇二級緩存來解決循環(huán)依賴的話,那么就意味著所有 Bean 都需要在實例化完成之后就立馬為其創(chuàng)建代理,而 Spring 的設計原則是在 Bean 初始化完成之后才為其創(chuàng)建代理。所以,Spring 選擇了三級緩存。但是因為循環(huán)依賴的出現(xiàn),導致了 Spring 不得不提前去創(chuàng)建代理,因為如果不提前創(chuàng)建代理對象,那么注入的就是原始對象,這樣就會產(chǎn)生錯誤。
解決循環(huán)依賴總結(jié)
Spring設計了三級緩存來解決循環(huán)依賴問題。
- 第一級緩存里面存儲完整的bean實例,這些實例是可以直接被使用的;
- 第二級緩存里面存儲的實例化以后但是還沒有設置屬性值的bean實例,也就是bean里面的依賴注入還沒有做;
- 第三級緩存用來存放bean工廠,它主要用來生成原始bean對象,并且放到第二個緩存里面。
三級緩存的核心思想就是把bean的實例化和bean里面的依賴注入進行分離,采用一級緩存存儲完整的bean實例,采用二級緩存來儲存不完整的bean實例。通過不完整的bean實例作為突破口,解決循環(huán)依賴問題。至于第三級緩存,主要是解決代理對象的循環(huán)依賴問題。
spring無法解決的循環(huán)依賴場景
- 多實例的Setter注入導致的循環(huán)依賴,需要把bean改成單例。
- 構(gòu)造器注入導致的循環(huán)依賴,可以通過@Lazy注解。
- DependsOn導致的循環(huán)依賴,找到注解循環(huán)依賴的地方,使它不循環(huán)依賴。
- 單例代理對象Setter注入導致的循環(huán)依賴,可以使用 @Lazy注解;或者使用 @DependsOn注解指定加載先后關系。
多例、構(gòu)造器注入為什么不能解決循環(huán)依賴?
因為循環(huán)依賴的原理是實例化后提前暴露的引用,這兩種情況還沒實例化
以上就是Spring三級緩存思想解決循環(huán)依賴總結(jié)分析的詳細內(nèi)容,更多關于Spring解決循環(huán)依賴的資料請關注腳本之家其它相關文章!
相關文章
使用SpringBoot開發(fā)Restful服務實現(xiàn)增刪改查功能
Spring Boot是由Pivotal團隊提供的全新框架,其設計目的是用來簡化新Spring應用的初始搭建以及開發(fā)過程。這篇文章主要介紹了基于SpringBoot開發(fā)一個Restful服務,實現(xiàn)增刪改查功能,需要的朋友可以參考下2018-01-01