• Spring循环依赖大全


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

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

    @Component
    public class A {
        @Resource
        private B b;
    }
    @Component
    public class B {
        @Resource
        private A a;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    case1分析

    答案是不会报错!!!因为sping已经通过三级缓存解决了这种循环依赖
    在这里插入图片描述
    将上面的抽象逻辑转化一下,其核心原理是通过无参构造方法 + 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    case2分析

    答案是会报错!因为A和B的默认构造方法都被自定义的构造方法覆盖了!Spring就不会用clazz.getConstructor().newInstance()这个方法new出原始对象;他会进行构造器的推断,发现想创建A必须传入B,就会在IOC容器中找B对象,但b对象肯定没有就会尝试尝试创建b,创建b的时候又会被提示创建b,必须得先创建a…其实再第二次尝试创建a的时候就报错了!
    在这里插入图片描述
    在这里插入图片描述
    将上面的抽象逻辑转化一下,陷入无限套娃模式,你发现你自己永远创建不了这个对象:
    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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
    在这里插入图片描述
    报错的根本原因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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    case4分析

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

    在这里插入图片描述

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

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

    在这里插入图片描述

    如何解决这种循环依赖?????
    方案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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    case5分析

    答案是不会报错!!
    是不是感觉很奇怪?为什么case4有报错的情况,而这个也会产生aop代理对象,为什么不报错???
    答案是:spring的三级缓存中存储的lamda表达式如下所示,该lamda表达式只会在出现循环依赖的时候执行,在这里可以提前进行aop,让二级缓存直接塞入代理对象
    在这里插入图片描述
    看到这里,是不是又出现一个新的问题:为什么自己写的切面可以提前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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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方法,返回真正单例池里的真正对象,进行执行

  • 相关阅读:
    TiDB Lightning 故障处理
    Rewrite the Stars
    webpack打包vue项目添加混淆方式,解决缓存问题
    Java实战指南|幂等性-公共幂等组件实现
    医学访问学者申请四点规划建议
    Talk预告 | FAIR研究科学家刘壮:高效和可扩展的视觉神经网络架构
    亚马逊买家号白号批量注册怎么做?
    String字符串类型详解
    【跨境电商】WhatsApp营销保姆级教程!
    Java自动装箱与自动拆箱
  • 原文地址:https://blog.csdn.net/MoastAll/article/details/133217734