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的方法放在新的类中,从根本上消除循环依赖

 

posted on 2021-02-09 17:57  家有四只胖加菲  阅读(7753)  评论(0编辑  收藏  举报

导航