Spring循环依赖大全

本博客挑出出现大部分情况的循环依赖场景进行分析,分析启动会不会报循环依赖的错误!

一、常规的A依赖B,B依赖A,代码如下:

@Component
public class A {
    @Resource
    private B b;
}
@Component
public class B {
    @Resource
    private A a;
}

case1分析

答案是不会报错!!!因为sping已经通过三级缓存解决了这种循环依赖
Spring循环依赖大全_第1张图片
将上面的抽象逻辑转化一下,其核心原理是通过无参构造方法 + set方法:
A a = new A();
B b = new B();
b.setA(a);
a.setB(b);

二、A和B都只有参构造

@Component
public class A {
    @Resource
    private B b;
    public A(B b){
       this.b = b;
    }
}
@Component
public class B {
    @Resource
    private A a;
    public B(A a){
       this.a = a;
    }
}

case2分析

答案是会报错!因为A和B的默认构造方法都被自定义的构造方法覆盖了!Spring就不会用clazz.getConstructor().newInstance()这个方法new出原始对象;他会进行构造器的推断,发现想创建A必须传入B,就会在IOC容器中找B对象,但b对象肯定没有就会尝试尝试创建b,创建b的时候又会被提示创建b,必须得先创建a…其实再第二次尝试创建a的时候就报错了!
Spring循环依赖大全_第2张图片
Spring循环依赖大全_第3张图片
将上面的抽象逻辑转化一下,陷入无限套娃模式,你发现你自己永远创建不了这个对象:
A a = new A( new B(new A( …) ) )
有点类似上面的感觉,但spring默认是单例肯定不会无限new

三、有一个类没有无参构造

@Component
public class A {
    @Resource
    private B b;
    public A(B b){
       this.b = b;
    }
    public A(){}
}
@Component
public class B {
    @Resource
    private A a;
    public B(A a){
       this.a = a;
    }
}

case3分析

得看创建顺序!如果先创建A,不会报错!如果先创建B,会爆循环依赖的错误!

1.先创建a的情况(没问题):

A a = new A();
B b = new B(a);
a.setB(b);

2.先创建b的情况:

new B(??) → 发现创建B之前得先有a 阻塞住!!!!
A a = new A() ;
//下面的两句spring不会执行
a.setB(new B(a));
b.setA(a);
虽然直观感觉可以往后走,但经过打断点哈,发现是在执行populateBean中的bean后置处理器报错了,为什么?????
具体的方法栈是:
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessProperties(这就是@Resource注解的后置处理器)
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.ResourceElement#getResourceToInject(false条件的语句)
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeanByName
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
Spring循环依赖大全_第4张图片
报错的根本原因a注入b属性的时候,发现123级缓存中都没有b,就会再次创建一个B,这对于单例Bean来说是不允许的!!!!你创建两个B对象,那么B对象就不是单例啊
在这里插入图片描述

四、@Async注解导致的循环依赖

@Component
public class A {
    @Autowired
    private B b;
    @Async //!!!!!!!
    public void test(){}
}
@Component
public class B {
    @Autowired
    private A a;
}

case4分析

1.先创建A会报错,下面是具体分析

Spring循环依赖大全_第5张图片

下面这段代码就是再doCreateBean的最下方,是第一种情况的报错点!
spring在这里报错合理吗,大家思考一下:

2.先创建B就正常运行,下面是具体分析

Spring循环依赖大全_第6张图片

如何解决这种循环依赖?????
方案1:在A类的b属性加上@Lazy注解,或者在B类的a属性加上@Lazy注解
方案2:让A类的scope指定为 prototype (不推荐!)

五、自定义AOP切面产生的循环依赖

@Component
public class A {
    @Autowired
    private B b;
    public void aoptest(){}
}
@Component
public class B {
    @Autowired
    private A a;
}
@Aspect
@Component
public class LzlAspect {
    @Pointcut("execution(public * com.lzl.circleRefrence.bean.A.aoptest())")
    public void pointCut(){
    }
    @Before("pointCut()")
    public void beforeInfo(){
        System.out.println("aaaa");
    }
}

case5分析

答案是不会报错!!
是不是感觉很奇怪?为什么case4有报错的情况,而这个也会产生aop代理对象,为什么不报错???
答案是:spring的三级缓存中存储的lamda表达式如下所示,该lamda表达式只会在出现循环依赖的时候执行,在这里可以提前进行aop,让二级缓存直接塞入代理对象
Spring循环依赖大全_第7张图片
看到这里,是不是又出现一个新的问题:为什么自己写的切面可以提前aop,@Async注解的切面为什么不提前aop?????
我认为哈,这是Spring的一个遗憾,在上面这块代码中Async的后置处理器 不属于 SmartInstantiationAwareBeanPostProcessor 类,压根不会在这里进行aop代理

六、原型Bean的循环依赖

@Component
@Scope("prototype")
public class A {
    @Resource
    private B b;
}
@Component
@Scope("prototype")
public class B {
    @Resource
    private A a;
}

case6分析

答案是会报错!
在这里插入图片描述
1.A进行生命周期,实例化A
2.A依赖了B,进入到B的生命周期,B进行实例化
3.B又依赖了A,而且A又是个原型的bean,所以又要再创建一个A的bean进行注入
4.创建A的bean时,又遇到了跟B一样的问题,一直无穷无尽了
如何解决?????两个类中的属性都加上@Lazy
1.A进行生命周期,实例化A
2.A依赖了B,但是B是个懒加载的bean,创建一个代理返回给A
3.A的生命周期结束
4.B进行生命周期,实例化B
5.B依赖了A,但是A也是个懒加载对象,创建一个代理返回给B
6.B的生命周期结束
7.在A真正使用到B/B真正使用到A的时候,代理对象调用他的getObject方法,返回真正单例池里的真正对象,进行执行

你可能感兴趣的:(spring,java,后端)