您现在的位置是:群英 > 开发技术 > 编程语言
Spring怎样解决@Async导致的循环依赖失败
Admin发表于 2022-10-12 17:55:17644 次浏览
这篇文章主要给大家介绍“Spring怎样解决@Async导致的循环依赖失败”的相关知识,下文通过实际案例向大家展示操作过程,内容简单清晰,易于学习,有这方面学习需要的朋友可以参考,希望这篇“Spring怎样解决@Async导致的循环依赖失败”文章能对大家有所帮助。



    简介

    说明

    本文介绍SpringBoot中的@Async导致循环依赖失败的原因及其解决方案。

    概述

    我们知道,Spring解决了循环依赖问题,但Spring的异步(@Async)会使得循环依赖失败。本文将用实例来介绍其原因和解决方案。

    问题复现

    启动类

    启动类添加@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,实现了postProcessAfterInitialization方法。创建代理的动作在抽象父类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已经被代理了(比如已经被事务那边给代理了~~)
            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最先执行还是最后执行
                    // 此处的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);
                }
                // 切入切面并创建一个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. 创建A,A实例化完成后将自己放入第三级缓存,然后给A的依赖属性b赋值
    2. 创建B,B实例化后给B的依赖属性a赋值
    3. 从第三级缓存中获得A(执行A的getEarlyBeanReference方法)。执行getEarlyBeanReference()时@Async根本还被扫描,所以返回的是原始类型地址(没被代理的对象地址)。
    4. B完成初始化、属性的赋值,此时持有A原始类型引用(没被代理)
    5. 完成A的属性的赋值(此时持有B的引用),继续执行初始化方法initializeBean(...),解析@Aysnc注解,生成一个代理对象,exposedObject是一个代理对象(而非原始对象),加入到容器里。
    6. 问题出现了:B的属性A是个原始对象,而此处的实例A却是个代理对象。(即:B里的A不是最终对象(不是最终放进容器的对象))
    7. 执行自检程序:由于allowRawInjectionDespiteWrapping默认值是false,表示不允许上面不一致的情况发生,就报错了

    解决方案

    有三种方案:

    懒加载:使用@Lazy或者@ComponentScan(lazyInit = true)

    不要让@Async的Bean参与循环依赖

    将allowRawInjectionDespiteWrapping设置为true

    方案1:懒加载

    说明

    建议使用@Lazy。

    不建议使用@ComponentScan(lazyInit = true),因为它是全局的,容易产生误伤。

    实例

    这两个方法都是可以的:

    • 法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 先加载,在创建 A 的实例时,会触发依赖属性 B 的加载,在加载 B 时发现它是一个被 @Lazy 标记过的属性。那么就不会去直接加载 B,而是产生一个代理对象注入到了 A 中,这样 A 就能正常的初始化完成放入一级缓存了。

    B 加载时,将前边生成的B代理对象取出,再注入 A 就能直接从一级缓存中获取到 A,这样 B 也能正常初始化完成了。所以,循环依赖的问题就解决了。

    方案2:不让@Async的类有循环依赖

    略。

    方案3:allowRawInjectionDespiteWrapping设置为true

    说明

    本方法不建议使用。

    这样配置后,容器启动不报错了。但是:Bean A的@Aysnc方法不起作用了。因为Bean B里面依赖的a是个原始对象,所以它不能执行异步操作(即使容器内的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不会导致失败

    概述

    同为创建动态代理对象,同为一个注解标注在类/方法上,为何@Transactional就不会出现这种启动报错呢?

    原因是,它们代理的创建的方式不同:

    @Transactional创建代理的方式:使用自动代理创建器InfrastructureAdvisorAutoProxyCreator(AbstractAutoProxyCreator的子类),它实现了getEarlyBeanReference()方法从而很好的对循环依赖提供了支持

    @Async创建代理的方式:使用AsyncAnnotationBeanPostProcessor单独的后置处理器。它只在一处postProcessAfterInitialization()实现了对代理对象的创建,因此若它被循环依赖了,就会报错

    详解

    处理@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导致的循环依赖失败”已经有了进一步的了解,更多相关的问题,欢迎关注群英网络或到群英官网咨询客服。

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。

    相关信息推荐
    2021-11-04 17:40:41 
    摘要:这篇文章给大家分享的是一个简单的PHP示例,也就是实现三个数求和的问题,这个问题很多PHP初学者都会遇到,因此分享给大家参考一下,文中示例代码介绍的非常详细,感兴趣的朋友接下来跟随小编一起学习一下吧。
    2022-05-20 17:08:02 
    摘要:下面由golang教程栏目给大家介绍golang编写PHP扩展方法,希望对需要的朋友有所帮助!
    2021-11-23 17:41:19 
    摘要:这篇文章我们来了解python中局部变量和全局变量的相关内容,本文对新手理解python中局部变量和全局变量的区别有一定的帮助,有需要的朋友可以参考,接下来就跟随小编来了解看看吧!
    云活动
    推荐内容
    热门关键词
    热门信息
    群英网络助力开启安全的云计算之旅
    立即注册,领取新人大礼包
    • 联系我们
    • 24小时售后:4006784567
    • 24小时TEL :0668-2555666
    • 售前咨询TEL:400-678-4567

    • 官方微信

      官方微信
    Copyright  ©  QY  Network  Company  Ltd. All  Rights  Reserved. 2003-2019  群英网络  版权所有   茂名市群英网络有限公司
    增值电信经营许可证 : B1.B2-20140078   粤ICP备09006778号
    免费拨打  400-678-4567
    免费拨打  400-678-4567 免费拨打 400-678-4567 或 0668-2555555
    微信公众号
    返回顶部
    返回顶部 返回顶部