动态

详情 返回 返回

spring使用@Async註解導致循環依賴問題異常的排查 - 动态 详情

因為我用到了@async來實現異步操作,在本地跑的時候一直沒有報錯,可是當我打包到服務器啓動的時候卻報了一個BeanCurrentlyInCreationException

Bean with name 'xxx' has been injected into other beans [xxx2] 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 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.

出現循環依賴報錯了,於是我看了一下代碼,的確有循環依賴的情況(這個是不好的習慣不要學,這裏我之前沒注意到,後面可以改一下),但是我記得明明spring是能夠處理使用@Autowired註解形成的循環的,並不會導致報錯,而且我本地也一直復現不了,非常的奇怪。於是我寫了一個spring循環依賴使用@Async的demo並調試,異常代碼的發生點在spring的doCreateBean方法

if (earlySingletonExposure) {
    Object earlySingletonReference = getSingleton(beanName, false);
    if (earlySingletonReference != null) {
        if (exposedObject == bean) { 
            exposedObject = earlySingletonReference;
        } else {
            //如果代理後的對象不等於原始對象就會拋異常了
        }
@Component
public class CircularTest {

    @Autowired
    CircularTest2 circularTest2;

}

@Component
public class CircularTest2 {

    @Autowired
    CircularTest CircularTest;

    @Async
    public void async() {

    }

}

開始我的代碼是上面這樣,spring初始化bean的順序是先CircularTest再CircularTest2,每到初始化CircularTest2執行異常處代碼的時候,直接earlySingletonReference返回的是null,所以一直復現不了,這時候我突然想到是不是spring初始化順序的原因,因為之前也遇到過spring順序不確定的問題,於是我改了一下代碼

@Component
public class CircularTest {

    @Autowired
    CircularTest2 circularTest2;

    @Async
    public void async() {

    }

}

@Component
public class CircularTest2 {

    @Autowired
    CircularTest CircularTest;

}

我把@Async方法換了個位置,這次竟然真的就復現了該異常,所以看來我本地的和服務器上的雖然代碼一致,但是最終跑起來的spring的bean加載順序變成不一樣了,因為spring容器載入bean的順序是不確定的,框架本身也沒有約定特定順序的邏輯規範,不過利用@Ordered,@Dependon等等註解自己實現依賴順序那是另外一回事了,嗯復現bug了,下面調試一下spring循環依賴使用@Async註解為啥會報錯

遍歷beanDefinitionNames 執行createBean(circularTest),先初始化circularTest,生成java的原始對象,執行populateBean(circularTest) -> AutowiredAnnotationBeanPostProcessor.postProcessProperties,發現circularTest類有@Autowired註解的circularTest2屬性,於是去初始化circularTest2,初始化circularTest2同樣是執行getBean(circularTest2)->createBean(circularTest2),發現有circularTest2類有@Autowired註解的circularTest屬性,於是去三層緩存裏找,在第三層緩存factories裏找到了circularTest,於是執行objectFactory.getObject(),實際執行的是getEarlyBeanReference(原始的circularTest);拿到了最終的circularTest的對象,circularTest2創建完成,繼續回到了circularTest的創建過程,執行initializeBean(circularTest),因為我們用到了@Async註解,所以會執行AsyncAnnotationBeanPostProcessor.postProcessAfterInitialization,而這個會把原始的circularTest傳進去並生成一個cglib的代理對象並返回,這時候這個生成的代理對象exposedObject和原始的對象bean就已經不是同一個對象了,就來到了我們報錯的地方,當exposedObject != bean時,spring會找有誰依賴了circularTest這個名字的對象,如果有,就拋出了異常,我們這裏有circularTest2依賴了,所以就拋出了異常。

這裏奇怪的是為什麼async會返回一個不同的對象呢,同樣是aop,怎麼自己寫aop生成代理類不會報這個錯誤,在參考了網上一些文章同時自己也進行了調試,發現使用Aop時的AbstractAutoProxyCreator類繼承了SmartInstantiationAwareBeanPostProcessor,它有個方法getEarlyBeanReference,和前面的就連上了,aop重寫了getEarlyBeanReference,在進行初始化的時候就已經生成代理對象了,而不是返回的原始對象,所以引用的對象是一致的,就不會報錯了

搞不懂為啥async不重寫getEarlyBeanReference...

user avatar sofastack 头像 u_16502039 头像 seazhan 头像 u_16769727 头像 chuanghongdengdeqingwa_eoxet2 头像 aipaobudezuoyeben 头像 nianqingyouweidenangua 头像 java_3y 头像 dengjijie 头像 xiaoxiansheng_5e75673e1ae30 头像 lpc63szb 头像 enaium 头像
点赞 40 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.