zoukankan      html  css  js  c++  java
  • Spring:解决因@Async引起的循环依赖报错

    最近项目中使用@Async注解在方法上引起了循环依赖报错:

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

    先提几个问题:

    1.@Async是什么

    2.什么是循环依赖

    3.为什么使用@async会引起循环依赖

    4.如何解决

    一、@Async

      从Spring3开始提供了@Async注解,该注解可以被标注在方法上(也可以标注在类上,代表所有方法都异步调用),以便异步地调用该方法。调用者将在调用时立即返回,方法的实际执行将提交给Spring TaskExecutor的任务中,由指定的线程池中的线程执行。

      看定义很简单,其实就是新起一条线程执行注解下的方法,所以可以立即返回结果无需等待,常用于调用外部接口。

    二、循环依赖

    简单讲就是有A,B两个服务,A依赖B,B依赖A,这样就是一个死循环。如下图:

    用代码表示:

    @component
    public class A{
        @Autowired
        private B b;
    }
    
    @component
    public class B{
        @Autowired
        private A a;
    }
    

      

    三、为什么使用@async会引起循环依赖

    其实当我们使用Spring的时候,默认是会解决循环依赖,但当使用了@Async注解方法后,处理循环依赖就失效了。

    @Component
    public class A{
         @Autowired
        private B b;
    
        @Async
        @Override
        public void testA() {
    
        }
    }
    
    @Component
    public class ClassB{
        @Autowired
        private A a;
    
        @Override
        public void testB() {
            a.testA();
        }
    }

    如果要知道底层的前因后果,需要去分析源码,这里我用别人总结的话简单说一下:

    1.context.getBean(A)开始创建A,A实例化完成后给A的依赖属性b开始赋值
    2.context.getBean(B)开始创建B,B实例化完成后给B的依赖属性a开始赋值
    3.重点:此时因为A支持循环依赖,所以会执行A的getEarlyBeanReference方法得到它的早期引用。而执行getEarlyBeanReference()的时候因为@Async根本还没执行,所以最终返回的仍旧是原始对象的地址
    4.B完成初始化、完成属性的赋值,此时属性field持有的是Bean A原始类型的引用
    5.完成了A的属性的赋值(此时已持有B的实例的引用),继续执行初始化方法initializeBean(...),在此处会解析@Aysnc注解,从而生成一个代理对象,所以最终exposedObject是一个代理对象(而非原始对象)最终加入到容器里
    6.尴尬场面出现了:B引用的属性A是个原始对象,而此处准备return的实例A竟然是个代理对象,也就是说B引用的并非是最终对象(不是最终放进容器里的对象)
    7.执行自检程序:由于allowRawInjectionDespiteWrapping默认值是false,表示不允许上面不一致的情况发生,so最终就抛错了

    四、如何解决

    1.使用@Lazy或者 @ComponentScan(lazyInit = true)

    @Component
    public class ClassB{
    
        @Autowired
        @Lazy
        private A a;
    
        @Override
        public void testB() {
            a.testA();
        }
    }

    2.使用setter注入

    @Component
    public class ClassA{
    
        private B b;
    
        public void setB(B b) {
            this.b = b;
        }
    
       @Async @Override
    public void testA() { b.testb(); } }
    @Component
    public class ClassB{
    
        private A a;
    
        public void setA(A a) {
            this.a = a;
        }
    
        @Override
        public void testB() {
            a.testA();
        }
    }

    3.使用@Autowired注解

    @Component
    public class A{
         
        private B b;
    
        @Autowired
        public void SetB(B b) {
            this.b= b;
        }
    
    
        @Async
        @Override
        public void testA() {
            //TODO
        }
    }    

    4.重构方法

    重新建class,把@Async的方法放在新的类中,从根本上消除循环依赖

  • 相关阅读:
    java Map集合学习
    java web 中的WEB-INF文件夹
    java web 通过前台输入的数据(name-value)保存到后台 xml文件中
    java web前端调试手段
    java学习之多线程
    java 反射学习
    java web前端easyui(layout+tree+双tabs)布局+树+2个选项卡tabs
    java socket编程(也是学习多线程的例子)详细版----转
    rman
    oracle 分区 查询
  • 原文地址:https://www.cnblogs.com/iceggboom/p/14393725.html
Copyright © 2011-2022 走看看