Spring中的循環(huán)依賴

首先看一下出問題的代碼片段:

@Service
publicclass TestService1 {

    @Autowired
    private TestService2 testService2;

    @Async
    public void test1() {
    }
}
@Service
publicclass TestService2 {

    @Autowired
    private TestService1 testService1;

    public void test2() {
    }
}

這兩段代碼中定義了兩個Service類:TestService1和TestService2,在TestService1中注入了TestService2的實例,同時在TestService2中注入了TestService1的實例,這里構(gòu)成了循環(huán)依賴。
只不過,這不是普通的循環(huán)依賴,因為TestService1的test1方法上加了一個@Async注解。程序運(yùn)行結(jié)果:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService1': Bean with name 'testService1' has been injected into other beans [testService2] 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.

但如果把@Async注解去掉,又能正常啟動。其實就算去掉@Async注解也已經(jīng)構(gòu)成循環(huán)依賴,但為什么還能正常啟動呢?因為Spring的內(nèi)部機(jī)制已經(jīng)幫我們解決了這個問題,spring內(nèi)部有三級緩存:

  • singletonObjects 一級緩存,用于保存實例化、注入、初始化完成的bean實例
  • earlySingletonObjects 二級緩存,用于保存實例化完成的bean實例
  • singletonFactories 三級緩存,用于保存bean創(chuàng)建工廠,以便于后面擴(kuò)展有機(jī)會創(chuàng)建代理對象

但Spring只能解決單例類型的循環(huán),其解決辦法就是提前暴露ObjectFactory,將未填充完屬性的bean提前暴露出來。

for (String beanName : beanNames) {
            RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
            if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                if (isFactoryBean(beanName)) {
                    Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
                    if (bean instanceof FactoryBean) {
                        FactoryBean<?> factory = (FactoryBean<?>) bean;
                        boolean isEagerInit;
                        if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                            isEagerInit = AccessController.doPrivileged(
                                    (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
                                    getAccessControlContext());
                        }
                        else {
                            isEagerInit = (factory instanceof SmartFactoryBean &&
                                    ((SmartFactoryBean<?>) factory).isEagerInit());
                        }
                        if (isEagerInit) {
                            getBean(beanName);
                        }
                    }
                }
                else {
                    getBean(beanName);
                }
            }
        }

由此可以看到,只有非抽象、單例 并且非懶加載的類才能被提前初始bean。(所以多例模式也是可以正常啟動的,不會被提前初始化bean;如果想讓它提前初始化bean,只要再定義一個單例,注入多例依賴即可,其啟動就會報循環(huán)依賴的錯誤)


image.png
image.png

這里為什么要用第二級緩存呢?
試想一下,如果出現(xiàn)以下這種情況,我們要如何處理?
TestService1依賴于TestService2和TestService3,而TestService2依賴于TestService1,同時TestService3也依賴于TestService1。
按照上圖的流程可以把TestService1注入到TestService2,并且TestService1的實例是從第三級緩存中獲取的。
假設(shè)不用第二級緩存,TestService1注入到TestService3的流程如圖:


image.png

TestService1注入到TestService3又需要從第三級緩存中獲取實例,而第三級緩存里保存的并非真正的實例對象,而是ObjectFactory對象。說白了,兩次從三級緩存中獲取都是ObjectFactory對象,而通過它創(chuàng)建的實例對象每次可能都不一樣的。
這樣不是有問題?
為了解決這個問題,spring引入的第二級緩存。上面圖1其實TestService1對象的實例已經(jīng)被添加到第二級緩存中了,而在TestService1注入到TestService3時,只用從第二級緩存中獲取該對象即可。


image.png

還有個問題,第三級緩存中為什么要添加ObjectFactory對象,直接保存實例對象不行嗎?
答:不行,因為假如你想對添加到三級緩存中的實例對象進(jìn)行增強(qiáng),直接用實例對象是行不通的。
針對這種場景spring是怎么做的呢?
答案就在AbstractAutowireCapableBeanFactory類doCreateBean方法的這段代碼中:
if (earlySingletonExposure) {
      if (logger.isTraceEnabled()) {
         logger.trace("Eagerly caching bean '" + beanName +
               "' to allow for resolving potential circular references");
      }
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }

它定義了一個匿名內(nèi)部類,通過getEarlyBeanReference方法獲取代理對象,其實底層是通過AbstractAutoProxyCreator類的getEarlyBeanReference生成代理對象。

回到問題中來,@Async注解的場景,會通過AOP自動生成代理對象:


image.png

說白了,bean初始化完成之后,后面還有一步去檢查:第二級緩存 和 原始對象 是否相等。由于它對前面流程來說無關(guān)緊要,所以前面的流程圖中省略了,但是在這里是關(guān)鍵點。

如果這時候把TestService1改個名字,改成:TestService6,其他的都不變:

@Service
publicclass TestService6 {

    @Autowired
    private TestService2 testService2;

    @Async
    public void test1() {
    }
}

再重新啟動一下程序,神奇般的好了。
這就要從spring的bean加載順序說起了,默認(rèn)情況下,spring是按照文件完整路徑遞歸查找的,按路徑+文件名排序,排在前面的先加載。所以TestService1比TestService2先加載,而改了文件名稱之后,TestService2比TestService6先加載。
為什么TestService2比TestService6先加載就沒問題呢?
答案在下面這張圖中:


image.png

這種情況testService6中其實第二級緩存是空的,不需要跟原始對象判斷,所以不會拋出循環(huán)依賴。


解決這類問題一般使用:

  • 使用@Lazy注解,延遲加載
  • 使用@DependsOn注解,指定加載先后關(guān)系
  • 修改文件名稱,改變循環(huán)依賴類的加載順序

轉(zhuǎn)載整理自:https://mp.weixin.qq.com/s/fX4ze5YuLI5cqVaZhxq1tw

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容