聊聊Spring循環(huán)依賴三級(jí)緩存是否可以減少為二級(jí)緩存的情況
基于Spring-5.1.5.RELEASE
問題
都知道Spring通過三級(jí)緩存來解決循環(huán)依賴的問題。但是是不是必須三級(jí)緩存才能解決,二級(jí)緩存不能解決嗎?
要分析是不是可以去掉其中一級(jí)緩存,就先過一遍Spring是如何通過三級(jí)緩存來解決循環(huán)依賴的。
循環(huán)依賴
所謂的循環(huán)依賴,就是兩個(gè)或則兩個(gè)以上的bean互相依賴對(duì)方,最終形成閉環(huán)。比如“A對(duì)象依賴B對(duì)象,而B對(duì)象也依賴A對(duì)象”,或者“A對(duì)象依賴B對(duì)象,B對(duì)象依賴C對(duì)象,C對(duì)象依賴A對(duì)象”;類似以下代碼:
public class A { private B b; } public class B { private A a; }
常規(guī)情況下,會(huì)出現(xiàn)以下情況:
通過構(gòu)建函數(shù)創(chuàng)建A對(duì)象(A對(duì)象是半成品,還沒注入屬性和調(diào)用init方法)。
A對(duì)象需要注入B對(duì)象,發(fā)現(xiàn)對(duì)象池(緩存)里還沒有B對(duì)象(對(duì)象在創(chuàng)建并且注入屬性和初始化完成之后,會(huì)放入對(duì)象緩存里)。
通過構(gòu)建函數(shù)創(chuàng)建B對(duì)象(B對(duì)象是半成品,還沒注入屬性和調(diào)用init方法)。
B對(duì)象需要注入A對(duì)象,發(fā)現(xiàn)對(duì)象池里還沒有A對(duì)象。
創(chuàng)建A對(duì)象,循環(huán)以上步驟。
三級(jí)緩存
Spring解決循環(huán)依賴的核心思想在于提前曝光:
通過構(gòu)建函數(shù)創(chuàng)建A對(duì)象(A對(duì)象是半成品,還沒注入屬性和調(diào)用init方法)。
A對(duì)象需要注入B對(duì)象,發(fā)現(xiàn)緩存里還沒有B對(duì)象,將半成品對(duì)象A放入半成品緩存。
通過構(gòu)建函數(shù)創(chuàng)建B對(duì)象(B對(duì)象是半成品,還沒注入屬性和調(diào)用init方法)。
B對(duì)象需要注入A對(duì)象,從半成品緩存里取到半成品對(duì)象A。
B對(duì)象繼續(xù)注入其他屬性和初始化,之后將完成品B對(duì)象放入完成品緩存。
A對(duì)象繼續(xù)注入屬性,從完成品緩存中取到完成品B對(duì)象并注入。
A對(duì)象繼續(xù)注入其他屬性和初始化,之后將完成品A對(duì)象放入完成品緩存。
其中緩存有三級(jí):
/** Cache of singleton objects: bean name to bean instance. */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of early singleton objects: bean name to bean instance. */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); /** Cache of singleton factories: bean name to ObjectFactory. */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
緩存 | 說明 |
---|---|
singletonObjects | 第一級(jí)緩存,存放可用的成品Bean。 |
earlySingletonObjects | 第二級(jí)緩存,存放半成品的Bean,半成品的Bean是已創(chuàng)建對(duì)象,但是未注入屬性和初始化。用以解決循環(huán)依賴。 |
singletonFactories | 第三級(jí)緩存,存的是Bean工廠對(duì)象,用來生成半成品的Bean并放入到二級(jí)緩存中。用以解決循環(huán)依賴。 |
要了解原理,最好的方法就是閱讀源碼,從創(chuàng)建Bean的方法AbstractAutowireCapableBeanFactor.doCreateBean入手。
1. 在構(gòu)造Bean對(duì)象之后,將對(duì)象提前曝光到緩存中,這時(shí)候曝光的對(duì)象僅僅是構(gòu)造完成,還沒注入屬性和初始化。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { …… // 是否提前曝光 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } …… } }
2. 提前曝光的對(duì)象被放入Map<String, ObjectFactory<?>> singletonFactories緩存中,這里并不是直接將Bean放入緩存,而是包裝成ObjectFactory對(duì)象再放入。
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { // 一級(jí)緩存 if (!this.singletonObjects.containsKey(beanName)) { // 三級(jí)緩存 this.singletonFactories.put(beanName, singletonFactory); // 二級(jí)緩存 this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } } } public interface ObjectFactory<T> { T getObject() throws BeansException; }
3. 為什么要包裝一層ObjectFactory對(duì)象?
如果創(chuàng)建的Bean有對(duì)應(yīng)的代理,那其他對(duì)象注入時(shí),注入的應(yīng)該是對(duì)應(yīng)的代理對(duì)象;但是Spring無法提前知道這個(gè)對(duì)象是不是有循環(huán)依賴的情況,而正常情況下(沒有循環(huán)依賴情況),Spring都是在創(chuàng)建好完成品Bean之后才創(chuàng)建對(duì)應(yīng)的代理。這時(shí)候Spring有兩個(gè)選擇:
不管有沒有循環(huán)依賴,都提前創(chuàng)建好代理對(duì)象,并將代理對(duì)象放入緩存,出現(xiàn)循環(huán)依賴時(shí),其他對(duì)象直接就可以取到代理對(duì)象并注入。
不提前創(chuàng)建好代理對(duì)象,在出現(xiàn)循環(huán)依賴被其他對(duì)象注入時(shí),才實(shí)時(shí)生成代理對(duì)象。這樣在沒有循環(huán)依賴的情況下,Bean就可以按著Spring設(shè)計(jì)原則的步驟來創(chuàng)建。
Spring選擇了第二種方式,那怎么做到提前曝光對(duì)象而又不生成代理呢?
Spring就是在對(duì)象外面包一層ObjectFactory,提前曝光的是ObjectFactory對(duì)象,在被注入時(shí)才在ObjectFactory.getObject方式內(nèi)實(shí)時(shí)生成代理對(duì)象,并將生成好的代理對(duì)象放入到第二級(jí)緩存Map<String, Object> earlySingletonObjects。
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));:
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { 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; } }
為了防止對(duì)象在后面的初始化(init)時(shí)重復(fù)代理,在創(chuàng)建代理時(shí),earlyProxyReferences緩存會(huì)記錄已代理的對(duì)象。
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware { private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16); @Override public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); this.earlyProxyReferences.put(cacheKey, bean); return wrapIfNecessary(bean, beanName, cacheKey); } }
4. 注入屬性和初始化
提前曝光之后:
通過populateBean方法注入屬性,在注入其他Bean對(duì)象時(shí),會(huì)先去緩存里取,如果緩存沒有,就創(chuàng)建該對(duì)象并注入。
通過initializeBean方法初始化對(duì)象,包含創(chuàng)建代理。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { …… // Initialize the bean instance. Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); 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); } } …… } } // 獲取要注入的對(duì)象 public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 一級(jí)緩存 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 二級(jí)緩存 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 三級(jí)緩存 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; } }
5. 放入已完成創(chuàng)建的單例緩存
在經(jīng)歷了以下步驟之后,最終通過addSingleton方法將最終生成的可用的Bean放入到單例緩存里。
AbstractBeanFactory.doGetBean -> DefaultSingletonBeanRegistry.getSingleton -> AbstractAutowireCapableBeanFactory.createBean -> AbstractAutowireCapableBeanFactory.doCreateBean -> DefaultSingletonBeanRegistry.addSingleton public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { /** Cache of singleton objects: bean name to bean instance. */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of singleton factories: bean name to ObjectFactory. */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name to bean instance. */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
二級(jí)緩存
上面第三步《為什么要包裝一層ObjectFactory對(duì)象?》里講到有兩種選擇:
不管有沒有循環(huán)依賴,都提前創(chuàng)建好代理對(duì)象,并將代理對(duì)象放入緩存,出現(xiàn)循環(huán)依賴時(shí),其他對(duì)象直接就可以取到代理對(duì)象并注入。
不提前創(chuàng)建好代理對(duì)象,在出現(xiàn)循環(huán)依賴被其他對(duì)象注入時(shí),才實(shí)時(shí)生成代理對(duì)象。這樣在沒有循環(huán)依賴的情況下,Bean就可以按著Spring設(shè)計(jì)原則的步驟來創(chuàng)建。
Sping選擇了第二種,如果是第一種,就會(huì)有以下不同的處理邏輯:
在提前曝光半成品時(shí),直接執(zhí)行g(shù)etEarlyBeanReference創(chuàng)建到代理,并放入到緩存earlySingletonObjects中。
有了上一步,那就不需要通過ObjectFactory來延遲執(zhí)行g(shù)etEarlyBeanReference,也就不需要singletonFactories這一級(jí)緩存。
這種處理方式可行嗎?
這里做個(gè)試驗(yàn),對(duì)AbstractAutowireCapableBeanFactory做個(gè)小改造,在放入三級(jí)緩存之后立刻取出并放入二級(jí)緩存,這樣三級(jí)緩存的作用就完全被忽略掉,就相當(dāng)于只有二級(jí)緩存。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { …… // 是否提前曝光 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); // 立刻從三級(jí)緩存取出放入二級(jí)緩存 getSingleton(beanName, true); } …… } }
測(cè)試結(jié)果是可以的,并且從源碼上分析可以得出兩種方式性能是一樣的,并不會(huì)影響到Sping啟動(dòng)速度。那為什么Sping不選擇二級(jí)緩存方式,而是要額外加一層緩存?
如果要使用二級(jí)緩存解決循環(huán)依賴,意味著Bean在構(gòu)造完后就創(chuàng)建代理對(duì)象,這樣違背了Spring設(shè)計(jì)原則。
Spring結(jié)合AOP跟Bean的生命周期,是在Bean創(chuàng)建完全之后通過AnnotationAwareAspectJAutoProxyCreator這個(gè)后置處理器來完成的,在這個(gè)后置處理的postProcessAfterInitialization方法中對(duì)初始化后的Bean完成AOP代理。
如果出現(xiàn)了循環(huán)依賴,那沒有辦法,只有給Bean先創(chuàng)建代理,但是沒有出現(xiàn)循環(huán)依賴的情況下,設(shè)計(jì)之初就是讓Bean在生命周期的最后一步完成代理而不是在實(shí)例化后就立馬完成代理。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
SpringBoot整合Redis正確的實(shí)現(xiàn)分布式鎖的示例代碼
這篇文章主要介紹了SpringBoot整合Redis正確的實(shí)現(xiàn)分布式鎖的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07Java數(shù)據(jù)結(jié)構(gòu)之紅黑樹的真正理解
這篇文章主要為大家詳細(xì)介紹了Java數(shù)據(jù)結(jié)構(gòu)之紅黑樹的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11Java編程Socket實(shí)現(xiàn)多個(gè)客戶端連接同一個(gè)服務(wù)端代碼
這篇文章主要介紹了Java編程Socket實(shí)現(xiàn)多個(gè)客戶端連接同一個(gè)服務(wù)端代碼,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11windows下java環(huán)境變量的設(shè)置方法
在“系統(tǒng)變量”中,設(shè)置3項(xiàng)屬性,JAVA_HOME,PATH,CLASSPATH(大小寫無所謂),若已存在則點(diǎn)擊“編輯”,不存在則點(diǎn)擊“新建”2013-09-09SpringBoot中@EnableAutoConfiguration和@Configuration的區(qū)別
這篇文章主要介紹了SpringBoot中@EnableAutoConfiguration和@Configuration的區(qū)別,@SpringBootApplication相當(dāng)于@EnableAutoConfiguration,@ComponentScan,@Configuration三者的集合,需要的朋友可以參考下2023-08-08Java 實(shí)現(xiàn)Excel文檔添加超鏈接的代碼
超鏈接即內(nèi)容鏈接,通過給特定對(duì)象設(shè)置超鏈接,可實(shí)現(xiàn)載體與特定網(wǎng)頁(yè)、文件、郵件、網(wǎng)絡(luò)等的鏈接,點(diǎn)擊鏈接載體可打開鏈接目標(biāo),在文檔處理中是一種比較常用的功能,本文將介紹通過Java程序給Excel文檔添加超鏈接的方法,感興趣的朋友一起看看吧2020-02-02淺談在Spring中如何使用數(shù)據(jù)源(DBCP、C3P0、JNDI)
這篇文章主要介紹了淺談在Spring中如何使用數(shù)據(jù)源(DBCP、C3P0、JNDI),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-10-10