溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Spring處理@Async導致的循環(huán)依賴失敗問題怎么解決

發(fā)布時間:2022-07-11 10:12:10 來源:億速云 閱讀:276 作者:iii 欄目:開發(fā)技術

本文小編為大家詳細介紹“Spring處理@Async導致的循環(huán)依賴失敗問題怎么解決”,內容詳細,步驟清晰,細節(jié)處理妥當,希望這篇“Spring處理@Async導致的循環(huán)依賴失敗問題怎么解決”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。

    簡介

    說明

    本文介紹SpringBoot中的@Async導致循環(huán)依賴失敗的原因及其解決方案。

    概述

    我們知道,Spring解決了循環(huán)依賴問題,但Spring的異步(@Async)會使得循環(huán)依賴失敗。本文將用實例來介紹其原因和解決方案。

    問題復現(xiàn)

    啟動類

    啟動類添加@EnableAsync以啟用異步功能。

    package com.knife;
     
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.scheduling.annotation.EnableAsync;
     
    @EnableAsync
    @SpringBootApplication
    public class DemoApplication {
     
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
     
    }

    Service

    A

    package com.knife.service;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Component;
     
    @Component
    public class A {
        @Autowired
        private B b;
     
        @Async
        public void print() {
            System.out.println("Hello World");
        }
    }

    B

    package com.knife.service;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
     
    @Component
    public class B {
        @Autowired
        private A a;
    }

    Controller

    package com.knife.controller;
     
    import com.knife.service.A;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
     
    @RestController
    public class HelloController {
        @Autowired
        private A a;
     
        @GetMapping("/test")
        public String test() {
            a.print();
            return "test success";
        }
    }

    啟動:(報錯)

    Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Bean with name 'a' has been injected into other beans [b] 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.
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:624) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1306) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        ... 20 common frames omitted

    原因分析

    @EnableAsync開啟時向容器內注入AsyncAnnotationBeanPostProcessor,它是一個BeanPostProcessor,實現(xiàn)了postProcessAfterInitialization方法。創(chuàng)建代理的動作在抽象父類AbstractAdvisingBeanPostProcessor上:

        // 這個方法主要是為有@Async 注解的 bean 生成代理對象
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) {
            if (this.advisor == null || bean instanceof AopInfrastructureBean) {
                // Ignore AOP infrastructure such as scoped proxies.
                return bean;
            }
     
            // 如果此Bean已經(jīng)被代理了(比如已經(jīng)被事務那邊給代理了~~)
            if (bean instanceof Advised) {
                Advised advised = (Advised) bean;
            
                if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
                    // Add our local Advisor to the existing proxy's Advisor chain...
                    // beforeExistingAdvisors決定這該advisor最先執(zhí)行還是最后執(zhí)行
                    // 此處的advisor為:AsyncAnnotationAdvisor  它切入Class和Method標注有@Aysnc注解的地方~~~
                    if (this.beforeExistingAdvisors) {
                        advised.addAdvisor(0, this.advisor);
                    } else {
                        advised.addAdvisor(this.advisor);
                    }
                    return bean;
                }
            }
            // 若不是代理對象,則進行處理
            if (isEligible(bean, beanName)) {
                //copy屬性 proxyFactory.copyFrom(this); 工廠模式生成一個新的 ProxyFactory
                ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
                // 如果沒有采用CGLIB,就去探測它的接口
                if (!proxyFactory.isProxyTargetClass()) {
                    evaluateProxyInterfaces(bean.getClass(), proxyFactory);
                }
                // 切入切面并創(chuàng)建一個getProxy 代理對象
                proxyFactory.addAdvisor(this.advisor);
                customizeProxyFactory(proxyFactory);
                return proxyFactory.getProxy(getProxyClassLoader());
            }
     
            // No proxy needed.
            return bean;
        }
     
        protected boolean isEligible(Object bean, String beanName) {
            return isEligible(bean.getClass());
        }
     
        protected boolean isEligible(Class<?> targetClass) {
            //首次從 eligibleBeans 這個 map 中獲取值肯定為 null
            Boolean eligible = this.eligibleBeans.get(targetClass);
            if (eligible != null) {
                return eligible;
            }
            //如果沒有配置 advisor(即:切面),返回 false
            if (this.advisor == null) {
                return false;
            }
            
            // 若類或方法有 @Aysnc 注解,AopUtils.canApply 會判斷為 true
            eligible = AopUtils.canApply(this.advisor, targetClass);
            this.eligibleBeans.put(targetClass, eligible);
            return eligible;
        }
    1. 創(chuàng)建A,A實例化完成后將自己放入第三級緩存,然后給A的依賴屬性b賦值

    2. 創(chuàng)建B,B實例化后給B的依賴屬性a賦值

    3. 從第三級緩存中獲得A(執(zhí)行A的getEarlyBeanReference方法)。執(zhí)行getEarlyBeanReference()時@Async根本還被掃描,所以返回的是原始類型地址(沒被代理的對象地址)。

    4. B完成初始化、屬性的賦值,此時持有A原始類型引用(沒被代理)

    5. 完成A的屬性的賦值(此時持有B的引用),繼續(xù)執(zhí)行初始化方法initializeBean(...),解析@Aysnc注解,生成一個代理對象,exposedObject是一個代理對象(而非原始對象),加入到容器里。

    6. 問題出現(xiàn)了:B的屬性A是個原始對象,而此處的實例A卻是個代理對象。(即:B里的A不是最終對象(不是最終放進容器的對象))

    7. 執(zhí)行自檢程序:由于allowRawInjectionDespiteWrapping默認值是false,表示不允許上面不一致的情況發(fā)生,就報錯了

    解決方案

    有三種方案:

    懶加載:使用@Lazy或者@ComponentScan(lazyInit = true)

    不要讓@Async的Bean參與循環(huán)依賴

    將allowRawInjectionDespiteWrapping設置為true

    方案1:懶加載

    說明

    建議使用@Lazy。

    不建議使用@ComponentScan(lazyInit = true),因為它是全局的,容易產(chǎn)生誤傷。

    實例

    這兩個方法都是可以的:

    • 法1. 將@Lazy放到A類的b成員上邊

    • 法2: 將@Lazy放到B類的a成員上邊

    法1:將@Lazy放到A類的b成員上邊

    package com.knife.service;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Component;
     
    @Component
    public class A {
        @Lazy
        @Autowired
        private B b;
     
        @Async
        public void print() {
            System.out.println("Hello World");
        }
    }

    法2:將@Lazy放到B類的a成員上邊

    package com.knife.service;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.stereotype.Component;
     
    @Component
    public class B {
        @Lazy
        @Autowired
        private A a;
    }

    這樣啟動就能成功。

    原理分析

    以這種寫法為例進行分析:@Lazy放到A類的b成員上邊。

    即:

    package com.knife.service;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Component;
     
    @Component
    public class A {
        @Lazy
        @Autowired
        private B b;
     
        @Async
        public void print() {
            System.out.println("Hello World");
        }
    }

    假設 A 先加載,在創(chuàng)建 A 的實例時,會觸發(fā)依賴屬性 B 的加載,在加載 B 時發(fā)現(xiàn)它是一個被 @Lazy 標記過的屬性。那么就不會去直接加載 B,而是產(chǎn)生一個代理對象注入到了 A 中,這樣 A 就能正常的初始化完成放入一級緩存了。

    B 加載時,將前邊生成的B代理對象取出,再注入 A 就能直接從一級緩存中獲取到 A,這樣 B 也能正常初始化完成了。所以,循環(huán)依賴的問題就解決了。

    方案2:不讓@Async的類有循環(huán)依賴

    略。

    方案3:allowRawInjectionDespiteWrapping設置為true

    說明

    本方法不建議使用。

    這樣配置后,容器啟動不報錯了。但是:Bean A的@Aysnc方法不起作用了。因為Bean B里面依賴的a是個原始對象,所以它不能執(zhí)行異步操作(即使容器內的a是個代理對象)。

    方法

    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
    import org.springframework.stereotype.Component;
     
    @Component
    public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
        }
    }

    為什么@Transactional不會導致失敗

    概述

    同為創(chuàng)建動態(tài)代理對象,同為一個注解標注在類/方法上,為何@Transactional就不會出現(xiàn)這種啟動報錯呢?

    原因是,它們代理的創(chuàng)建的方式不同:

    @Transactional創(chuàng)建代理的方式:使用自動代理創(chuàng)建器InfrastructureAdvisorAutoProxyCreator(AbstractAutoProxyCreator的子類),它實現(xiàn)了getEarlyBeanReference()方法從而很好的對循環(huán)依賴提供了支持

    @Async創(chuàng)建代理的方式:使用AsyncAnnotationBeanPostProcessor單獨的后置處理器。它只在一處postProcessAfterInitialization()實現(xiàn)了對代理對象的創(chuàng)建,因此若它被循環(huán)依賴了,就會報錯

    詳解

    Spring處理@Async導致的循環(huán)依賴失敗問題怎么解決

    處理@Transactional注解的是InfrastructureAdvisorAutoProxyCreator,它是SmartInstantiationAwareBeanPostProcessor的子類。AbstractAutoProxyCreator對SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法進行了覆寫:

    AbstractAutoProxyCreator# getEarlyBeanReference

    public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
            implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
            
        // 其他代碼
        
        @Override
        public Object getEarlyBeanReference(Object bean, String beanName) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            this.earlyProxyReferences.put(cacheKey, bean);
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
        
    }

    AbstractAutoProxyCreator#postProcessAfterInitialization方法中,判斷是否代理過,是的話,直接返回:

    public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
            implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
            
        // 其他代碼
        
        @Override
        public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
            if (bean != null) {
                Object cacheKey = getCacheKey(bean.getClass(), beanName);
                if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                    return wrapIfNecessary(bean, beanName, cacheKey);
                }
            }
            return bean;
        }
        
    }

    讀到這里,這篇“Spring處理@Async導致的循環(huán)依賴失敗問題怎么解決”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業(yè)資訊頻道。

    向AI問一下細節(jié)

    免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

    AI