前言
循環依賴:就是N個類循環(嵌套)引用。 通俗的講就是N個Bean互相引用對方,最終形成閉環
。用一副經典的圖示可以表示成這樣(A、B、C都代表對象,虛線代表引用關系):

注意:其實可以N=1,也就是極限情況的循環依賴:
自己依賴自己
另需注意:這里指的循環引用不是方法之間的循環調用,而是對象的相互依賴關系。(方法之間循環調用若有出口也是能夠正常work的)
可以設想一下這個場景:如果在日常開發中我們用new對象的方式,若構造函數之間發生這種循環依賴的話,程序會在運行時一直循環調用最終導致內存溢出,示例代碼如下:
public class Main { public static void main(String[] args) throws Exception { System.out.println(new A()); } } class A { public A() { new B(); } } class B { public B() { new A(); } }
運行報錯:
Exception in thread "main" java.lang.StackOverflowError
這是一個典型的循環依賴問題。本文說一下Spring
是如果巧妙的解決平時我們會遇到的三大循環依賴問題
的~
Spring Bean的循環依賴
談到Spring Bean
的循環依賴,有的小伙伴可能比較陌生,畢竟開發過程中好像對循環依賴
這個概念無感知。其實不然,你有這種錯覺,權是因為你工作在Spring的襁褓
中,從而讓你“高枕無憂”~ 我十分堅信,小伙伴們在平時業務開發中一定一定寫過如下結構的代碼:
@Service
public class AServiceImpl implements AService { @Autowired private BService bService; ... } @Service public class BServiceImpl implements BService { @Autowired private AService aService; ... }
這其實就是Spring環境下典型的循環依賴場景。但是很顯然,這種循環依賴場景,Spring已經完美的幫我們解決和規避了問題。所以即使平時我們這樣循環引用,也能夠整成進行我們的coding之旅~
Spring中三大循環依賴場景
演示
在Spring環境中,因為我們的Bean的實例化、初始化都是交給了容器,因此它的循環依賴主要表現為下面三種場景。為了方便演示,我准備了如下兩個類:

1、構造器注入循環依賴
@Service
public class A { public A(B b) { } } @Service public class B { public B(A a) { } }
結果:項目啟動失敗拋出異常BeanCurrentlyInCreationException
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference? at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
構造器注入構成的循環依賴,此種循環依賴方式是無法解決的,只能拋出
BeanCurrentlyInCreationException
異常表示循環依賴。這也是構造器注入的最大劣勢(它有很多獨特的優勢,請小伙伴自行發掘)
根本原因
:Spring解決循環依賴依靠的是Bean的“中間態”這個概念,而這個中間態指的是已經實例化
,但還沒初始化的狀態。而構造器是完成實例化的東東,所以構造器的循環依賴無法解決~~~
2、field屬性注入(setter方法注入)循環依賴
這種方式是我們最最最最為常用的依賴注入方式(所以猜都能猜到它肯定不會有問題啦):
@Service
public class A { @Autowired private B b; } @Service public class B { @Autowired private A a; }
結果:項目啟動成功,能夠正常work
備注:setter方法注入方式因為原理和字段注入方式類似,此處不多加演示
2、prototype
field屬性注入循環依賴
prototype
在平時使用情況較少,但是也並不是不會使用到,因此此種方式也需要引起重視。
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Service public class A { @Autowired private B b; } @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Service public class B { @Autowired private A a; }
結果:需要注意的是本例中啟動時是不會報錯的(因為非單例Bean默認
不會初始化,而是使用時才會初始化),所以很簡單咱們只需要手動getBean()
或者在一個單例Bean內@Autowired
一下它即可
// 在單例Bean內注入
@Autowired
private A a;
這樣子啟動就報錯:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'mytest.TestSpringBean': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a': Unsatisfied dependency expressed through field 'b'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference? at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374)
如何解決??? 可能有的小伙伴看到網上有說使用@Lazy
注解解決:
@Lazy
@Autowired
private A a;
此處負責任的告訴你這樣是解決不了問題的(可能會掩蓋問題),@Lazy
只是延遲初始化而已,當你真正使用到它(初始化)的時候,依舊會報如上異常。
對於Spring循環依賴的情況總結如下:
- 不能解決的情況: 1. 構造器注入循環依賴 2.
prototype
field屬性注入循環依賴 - 能解決的情況: 1. field屬性注入(setter方法注入)循環依賴
Spring解決循環依賴的原理分析
在這之前需要明白java中所謂的引用傳遞
和值傳遞
的區別。
說明:看到這句話可能有小伙伴就想噴我了。java中明明都是傳遞啊,這是我初學java時背了100遍的面試題,怎么可能有錯??? 這就是我做這個申明的必要性:伙計,你的說法是正確的,
java中只有值傳遞
。但是本文借用引用傳遞
來輔助講解,希望小伙伴明白我想表達的意思~
Spring的循環依賴的理論依據基於Java的引用傳遞
,當獲得對象的引用時,對象的屬性是可以延后設置的。(但是構造器必須是在獲取引用之前,畢竟你的引用是靠構造器給你生成的,兒子能先於爹出生?哈哈)
Spring創建Bean的流程
首先需要了解是Spring它創建Bean的流程,我把它的大致調用棧繪圖如下:

對Bean的創建最為核心三個方法解釋如下:
createBeanInstance
:例化,其實也就是調用對象的構造方法實例化對象populateBean
:填充屬性,這一步主要是對bean的依賴屬性進行注入(@Autowired
)initializeBean
:回到一些形如initMethod
、InitializingBean
等方法
從對單例Bean
的初始化可以看出,循環依賴主要發生在第二步(populateBean),也就是field屬性注入的處理。
Spring容器的'三級緩存'
在Spring容器的整個聲明周期中,單例Bean有且僅有一個對象。這很容易讓人想到可以用緩存來加速訪問。 從源碼中也可以看出Spring大量運用了Cache的手段,在循環依賴問題的解決過程中甚至不惜使用了“三級緩存”,這也便是它設計的精妙之處~
三級緩存
其實它更像是Spring容器工廠的內的術語
,采用三級緩存模式來解決循環依賴問題,這三級緩存分別指:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { ... // 從上至下 分表代表這“三級緩存” 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); // 三級緩存 ... /** Names of beans that are currently in creation. */ // 這個緩存也十分重要:它表示bean創建過程中都會在里面呆着~ // 它在Bean開始創建時放值,創建完成時會將其移出~ private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); /** Names of beans that have already been created at least once. */ // 當這個Bean被創建完成后,會標記為這個 注意:這里是set集合 不會重復 // 至少被創建了一次的 都會放進這里~~~~ private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256)); }
注:AbstractBeanFactory
繼承自DefaultSingletonBeanRegistry
~
singletonObjects
:用於存放完全初始化好的 bean,從該緩存中取出的 bean 可以直接使用earlySingletonObjects
:提前曝光的單例對象的cache,存放原始的 bean 對象(尚未填充屬性),用於解決循環依賴singletonFactories
:單例對象工廠的cache,存放 bean 工廠對象,用於解決循環依賴
獲取單例Bean的源碼如下:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { ... @Override @Nullable public Object getSingleton(String beanName) { return getSingleton(beanName, true); } @Nullable 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; } ... public boolean isSingletonCurrentlyInCreation(String beanName) { return this.singletonsCurrentlyInCreation.contains(beanName); } protected boolean isActuallyInCreation(String beanName) { return isSingletonCurrentlyInCreation(beanName); } ... }
- 先從
一級緩存singletonObjects
中去獲取。(如果獲取到就直接return) - 如果獲取不到或者對象正在創建中(
isSingletonCurrentlyInCreation()
),那就再從二級緩存earlySingletonObjects
中獲取。(如果獲取到就直接return) - 如果還是獲取不到,且允許singletonFactories(allowEarlyReference=true)通過
getObject()
獲取。就從三級緩存singletonFactory
.getObject()獲取。(如果獲取到了就從singletonFactories
中移除,並且放進earlySingletonObjects
。其實也就是從三級緩存移動(是剪切、不是復制哦~)
到了二級緩存)
加入
singletonFactories
三級緩存的前提是執行了構造器,所以構造器的循環依賴沒法解決
getSingleton()
從緩存里獲取單例對象步驟分析可知,Spring解決循環依賴的訣竅:就在於singletonFactories這個三級緩存。這個Cache里面都是ObjectFactory
,它是解決問題的關鍵。
// 它可以將創建對象的步驟封裝到ObjectFactory中 交給自定義的Scope來選擇是否需要創建對象來靈活的實現scope。 具體參見Scope接口
@FunctionalInterface
public interface ObjectFactory<T> { T getObject() throws BeansException; }
經過ObjectFactory.getObject()后,此時放進了二級緩存
earlySingletonObjects
內。這個時候對象已經實例化了,雖然還不完美
,但是對象的引用已經可以被其它引用了。
此處說一下二級緩存earlySingletonObjects
它里面的數據什么時候添加什么移除???
添加:向里面添加數據只有一個地方,就是上面說的getSingleton()
里從三級緩存里挪過來 移除:addSingleton、addSingletonFactory、removeSingleton
從語義中可以看出添加單例、添加單例工廠ObjectFactory
的時候都會刪除二級緩存里面對應的緩存值,是互斥的
源碼解析
Spring
容器會將每一個正在創建的Bean 標識符放在一個“當前創建Bean池”中,Bean標識符在創建過程中將一直保持在這個池中,而對於創建完畢的Bean將從當前創建Bean池
中清除掉。 這個“當前創建Bean池”指的是上面提到的singletonsCurrentlyInCreation
那個集合。
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { ... protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { ... // Eagerly check singleton cache for manually registered singletons. // 先去獲取一次,如果不為null,此處就會走緩存了~~ Object sharedInstance = getSingleton(beanName); ... // 如果不是只檢查類型,那就標記這個Bean被創建了~~添加到緩存里 也就是所謂的 當前創建Bean池 if (!typeCheckOnly) { markBeanAsCreated(beanName); } ... // Create bean instance. if (mbd.isSingleton()) { // 這個getSingleton方法不是SingletonBeanRegistry的接口方法 屬於實現類DefaultSingletonBeanRegistry的一個public重載方法~~~ // 它的特點是在執行singletonFactory.getObject();前后會執行beforeSingletonCreation(beanName);和afterSingletonCreation(beanName); // 也就是保證這個Bean在創建過程中,放入正在創建的緩存池里 可以看到它實際創建bean調用的是我們的createBean方法~~~~ sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } } ... } // 抽象方法createBean所在地 這個接口方法是屬於抽象父類AbstractBeanFactory的 實現在這個抽象類里 public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { ... protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { ... // 創建Bean對象,並且將對象包裹在BeanWrapper 中 instanceWrapper = createBeanInstance(beanName, mbd, args); // 再從Wrapper中把Bean原始對象(非代理~~~) 這個時候這個Bean就有地址值了,就能被引用了~~~ // 注意:此處是原始對象,這點非常的重要 final Object bean = instanceWrapper.getWrappedInstance(); ... // earlySingletonExposure 用於表示是否”提前暴露“原始對象的引用,用於解決循環依賴。 // 對於單例Bean,該變量一般為 true 但你也可以通過屬性allowCircularReferences = false來關閉循環引用 // isSingletonCurrentlyInCreation(beanName) 表示當前bean必須在創建中才行 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"); } // 上面講過調用此方法放進一個ObjectFactory,二級緩存會對應刪除的 // getEarlyBeanReference的作用:調用SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()這個方法 否則啥都不做 // 也就是給調用者個機會,自己去實現暴露這個bean的應用的邏輯~~~ // 比如在getEarlyBeanReference()里可以實現AOP的邏輯~~~ 參考自動代理創建器AbstractAutoProxyCreator 實現了這個方法來創建代理對象 // 若不需要執行AOP的邏輯,直接返回Bean addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } Object exposedObject = bean; //exposedObject 是最終返回的對象 ... // 填充屬於,解決@Autowired依賴~ populateBean(beanName, mbd, instanceWrapper); // 執行初始化回調方法們~~~ exposedObject = initializeBean(beanName, exposedObject, mbd); // earlySingletonExposure:如果你的bean允許被早期暴露出去 也就是說可以被循環引用 那這里就會進行檢查 // 此段代碼非常重要~~~~~但大多數人都忽略了它 if (earlySingletonExposure) { // 此時一級緩存肯定還沒數據,但是呢此時候二級緩存earlySingletonObjects也沒數據 //注意,注意:第二參數為false 表示不會再去三級緩存里查了~~~ // 此處非常巧妙的一點:::因為上面各式各樣的實例化、初始化的后置處理器都執行了,如果你在上面執行了這一句 // ((ConfigurableListableBeanFactory)this.beanFactory).registerSingleton(beanName, bean); // 那么此處得到的earlySingletonReference 的引用最終會是你手動放進去的Bean最終返回,完美的實現了"偷天換日" 特別適合中間件的設計 // 我們知道,執行完此doCreateBean后執行addSingleton() 其實就是把自己再添加一次 **再一次強調,完美實現偷天換日** Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { // 這個意思是如果經過了initializeBean()后,exposedObject還是木有變,那就可以大膽放心的返回了 // initializeBean會調用后置處理器,這個時候可以生成一個代理對象,那這個時候它哥倆就不會相等了 走else去判斷吧 if (exposedObject == bean