聊聊Spring循環(huán)依賴三級緩存是否可以減少為二級緩存的情況
基于Spring-5.1.5.RELEASE
問題
都知道Spring通過三級緩存來解決循環(huán)依賴的問題。但是是不是必須三級緩存才能解決,二級緩存不能解決嗎?
要分析是不是可以去掉其中一級緩存,就先過一遍Spring是如何通過三級緩存來解決循環(huán)依賴的。
循環(huán)依賴
所謂的循環(huán)依賴,就是兩個或則兩個以上的bean互相依賴對方,最終形成閉環(huán)。比如“A對象依賴B對象,而B對象也依賴A對象”,或者“A對象依賴B對象,B對象依賴C對象,C對象依賴A對象”;類似以下代碼:
public class A {
private B b;
}
public class B {
private A a;
}
常規(guī)情況下,會出現(xiàn)以下情況:
通過構(gòu)建函數(shù)創(chuàng)建A對象(A對象是半成品,還沒注入屬性和調(diào)用init方法)。
A對象需要注入B對象,發(fā)現(xiàn)對象池(緩存)里還沒有B對象(對象在創(chuàng)建并且注入屬性和初始化完成之后,會放入對象緩存里)。
通過構(gòu)建函數(shù)創(chuàng)建B對象(B對象是半成品,還沒注入屬性和調(diào)用init方法)。
B對象需要注入A對象,發(fā)現(xiàn)對象池里還沒有A對象。
創(chuàng)建A對象,循環(huán)以上步驟。
三級緩存
Spring解決循環(huán)依賴的核心思想在于提前曝光:
通過構(gòu)建函數(shù)創(chuàng)建A對象(A對象是半成品,還沒注入屬性和調(diào)用init方法)。
A對象需要注入B對象,發(fā)現(xiàn)緩存里還沒有B對象,將半成品對象A放入半成品緩存。
通過構(gòu)建函數(shù)創(chuàng)建B對象(B對象是半成品,還沒注入屬性和調(diào)用init方法)。
B對象需要注入A對象,從半成品緩存里取到半成品對象A。
B對象繼續(xù)注入其他屬性和初始化,之后將完成品B對象放入完成品緩存。
A對象繼續(xù)注入屬性,從完成品緩存中取到完成品B對象并注入。
A對象繼續(xù)注入其他屬性和初始化,之后將完成品A對象放入完成品緩存。
其中緩存有三級:
/** 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 | 第一級緩存,存放可用的成品Bean。 |
| earlySingletonObjects | 第二級緩存,存放半成品的Bean,半成品的Bean是已創(chuàng)建對象,但是未注入屬性和初始化。用以解決循環(huán)依賴。 |
| singletonFactories | 第三級緩存,存的是Bean工廠對象,用來生成半成品的Bean并放入到二級緩存中。用以解決循環(huán)依賴。 |
要了解原理,最好的方法就是閱讀源碼,從創(chuàng)建Bean的方法AbstractAutowireCapableBeanFactor.doCreateBean入手。
1. 在構(gòu)造Bean對象之后,將對象提前曝光到緩存中,這時候曝光的對象僅僅是構(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. 提前曝光的對象被放入Map<String, ObjectFactory<?>> singletonFactories緩存中,這里并不是直接將Bean放入緩存,而是包裝成ObjectFactory對象再放入。
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) {
// 一級緩存
if (!this.singletonObjects.containsKey(beanName)) {
// 三級緩存
this.singletonFactories.put(beanName, singletonFactory);
// 二級緩存
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
}
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
3. 為什么要包裝一層ObjectFactory對象?
如果創(chuàng)建的Bean有對應(yīng)的代理,那其他對象注入時,注入的應(yīng)該是對應(yīng)的代理對象;但是Spring無法提前知道這個對象是不是有循環(huán)依賴的情況,而正常情況下(沒有循環(huán)依賴情況),Spring都是在創(chuàng)建好完成品Bean之后才創(chuàng)建對應(yīng)的代理。這時候Spring有兩個選擇:
不管有沒有循環(huán)依賴,都提前創(chuàng)建好代理對象,并將代理對象放入緩存,出現(xiàn)循環(huán)依賴時,其他對象直接就可以取到代理對象并注入。
不提前創(chuàng)建好代理對象,在出現(xiàn)循環(huán)依賴被其他對象注入時,才實時生成代理對象。這樣在沒有循環(huán)依賴的情況下,Bean就可以按著Spring設(shè)計原則的步驟來創(chuàng)建。
Spring選擇了第二種方式,那怎么做到提前曝光對象而又不生成代理呢?
Spring就是在對象外面包一層ObjectFactory,提前曝光的是ObjectFactory對象,在被注入時才在ObjectFactory.getObject方式內(nèi)實時生成代理對象,并將生成好的代理對象放入到第二級緩存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;
}
}
為了防止對象在后面的初始化(init)時重復(fù)代理,在創(chuàng)建代理時,earlyProxyReferences緩存會記錄已代理的對象。
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對象時,會先去緩存里取,如果緩存沒有,就創(chuàng)建該對象并注入。
通過initializeBean方法初始化對象,包含創(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);
}
}
……
}
}
// 獲取要注入的對象
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
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) {
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);
}
}
}
二級緩存
上面第三步《為什么要包裝一層ObjectFactory對象?》里講到有兩種選擇:
不管有沒有循環(huán)依賴,都提前創(chuàng)建好代理對象,并將代理對象放入緩存,出現(xiàn)循環(huán)依賴時,其他對象直接就可以取到代理對象并注入。
不提前創(chuàng)建好代理對象,在出現(xiàn)循環(huán)依賴被其他對象注入時,才實時生成代理對象。這樣在沒有循環(huán)依賴的情況下,Bean就可以按著Spring設(shè)計原則的步驟來創(chuàng)建。
Sping選擇了第二種,如果是第一種,就會有以下不同的處理邏輯:
在提前曝光半成品時,直接執(zhí)行g(shù)etEarlyBeanReference創(chuàng)建到代理,并放入到緩存earlySingletonObjects中。
有了上一步,那就不需要通過ObjectFactory來延遲執(zhí)行g(shù)etEarlyBeanReference,也就不需要singletonFactories這一級緩存。
這種處理方式可行嗎?

這里做個試驗,對AbstractAutowireCapableBeanFactory做個小改造,在放入三級緩存之后立刻取出并放入二級緩存,這樣三級緩存的作用就完全被忽略掉,就相當于只有二級緩存。
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));
// 立刻從三級緩存取出放入二級緩存
getSingleton(beanName, true);
}
……
}
}
測試結(jié)果是可以的,并且從源碼上分析可以得出兩種方式性能是一樣的,并不會影響到Sping啟動速度。那為什么Sping不選擇二級緩存方式,而是要額外加一層緩存?
如果要使用二級緩存解決循環(huán)依賴,意味著Bean在構(gòu)造完后就創(chuàng)建代理對象,這樣違背了Spring設(shè)計原則。
Spring結(jié)合AOP跟Bean的生命周期,是在Bean創(chuàng)建完全之后通過AnnotationAwareAspectJAutoProxyCreator這個后置處理器來完成的,在這個后置處理的postProcessAfterInitialization方法中對初始化后的Bean完成AOP代理。
如果出現(xiàn)了循環(huán)依賴,那沒有辦法,只有給Bean先創(chuàng)建代理,但是沒有出現(xiàn)循環(huán)依賴的情況下,設(shè)計之初就是讓Bean在生命周期的最后一步完成代理而不是在實例化后就立馬完成代理。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。如有錯誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
Java遞歸讀取文件例子_動力節(jié)點Java學(xué)院整理
本文通過一段示例代碼給大家介紹了java遞歸讀取文件的方法,代碼簡單易懂,非常不錯,具有參考借鑒價值,需要的朋友參考下吧2017-05-05
springboot如何使用yml文件方式配置shardingsphere
這篇文章主要介紹了springboot如何使用yml文件方式配置shardingsphere問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09
springboot循環(huán)依賴問題案例代碼及解決辦法
在 Spring Boot 中,如果兩個或多個 Bean之間存在循環(huán)依賴(即 Bean A 依賴 Bean B,而 Bean B 又依賴 Bean A),會導(dǎo)致 Spring 的依賴注入機制無法正確處理,從而拋出異常,下面給大家介紹springboot循環(huán)依賴問題及其解決辦法,感興趣的朋友一起看看吧2025-04-04
SpringBoot利用注解來實現(xiàn)Redis分布式鎖
有些業(yè)務(wù)請求,屬于耗時操作,需要加鎖,防止后續(xù)的并發(fā)操作,同時對數(shù)據(jù)庫的數(shù)據(jù)進行操作,需要避免對之前的業(yè)務(wù)造成影響。本文將利用注解來實現(xiàn)Redis分布式鎖,需要的可以參考一下2022-09-09

