• Spring循环依赖


    1.什么是循环依赖

    循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图:
    在这里插入图片描述

    2.spring是否支持循环依赖

    先说结论:spring仅支持单例对象属性注入依赖。

    在Spring中有:
    构造器循环依赖
    field属性注入循环依赖

    2.1 构造器循环依赖(项目启动报错)

    @Service
    public class A {  
        public A(B b) {  }
    }
    
    @Service
    public class B {  
        public B(C c) {  
        }
    }
    
    @Service
    public class C {  
        public C(A a) {  }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    结果项目启动失败,发现了一个cycle在这里插入图片描述

    2.2 field属性注入循环依赖(prototype)

    @Service
    @Scope("prototype")
    public class A1 {  
        @Autowired  
        private B1 b1;
    }
    
    @Service
    @Scope("prototype")
    public class B1 {  
        @Autowired  
        public C1 c1;
    }
    
    @Service
    @Scope("prototype")
    public class C1 {  
        @Autowired  public A1 a1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    结果:项目启动失败,发现了一个cycle。

    2.3 field属性注入循环依赖(默认单例)

    @Service
    public class A1 {  
        @Autowired  
        private B1 b1;
    }
    
    @Service
    public class B1 {  
        @Autowired  
        public C1 c1;
    }
    
    @Service
    public class C1 {  
        @Autowired  public A1 a1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    结果:正常启动。

    总结

    spring中不支持构造器注入和prototype类型的属性注入的循环依赖。因为spring中的类默认是单例的,单例的属性注入是可以成功的。

    3. spring三级缓存

    singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例
    earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例
    singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。

    下面用一张图告诉你,spring是如何解决循环依赖的:
    在这里插入图片描述

    4. 面试题

    4.1 为什么要用第二级缓存呢

    @Service
    public class TestService1 {
    
        @Autowired
        private TestService2 testService2;
        @Autowired
        private TestService3 testService3;
    
        public void test1() {
        }
    }
    
    @Service
    public class TestService2 {
    
        @Autowired
        private TestService1 testService1;
    
        public void test2() {
        }
    }
    
    @Service
    public class TestService3 {
    
        @Autowired
        private TestService1 testService1;
    
        public void test3() {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    TestService1依赖于TestService2和TestService3,而TestService2依赖于TestService1,同时TestService3也依赖于TestService1。

    1. TestService1注入到TestService2,并且TestService1的实例是从第三级缓存中获取的。
    2. TestService1注入到TestService3,流程如下
      在这里插入图片描述
      TestService1注入到TestService3又需要从第三级缓存中获取实例,而第三级缓存里保存的并非真正的实例对象,而是ObjectFactory对象。说白了,两次从三级缓存中获取都是ObjectFactory对象,而通过它创建的实例对象每次可能都不一样的。
      为了解决这个问题,spring引入的第二级缓存。上面图1其实TestService1对象的实例已经被添加到第二级缓存中了,而在TestService1注入到TestService3时,只用从第二级缓存中获取该对象即可。
      在这里插入图片描述

    4.2 第三级缓存中为什么要添加ObjectFactory对象,直接保存实例对象不行吗

    不行。因为假如你想对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的。

    针对这种场景spring是怎么做的呢?
    答案就在AbstractAutowireCapableBeanFactory类doCreateBean方法的这段代码中:
    在这里插入图片描述
    它定义了一个匿名内部类,通过getEarlyBeanReference方法获取代理对象,其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。

    4.3 构造器注入

    @Service
    public class TestService1 {
    
        public TestService1(TestService2 testService2) {
        }
    }
    
    @Service
    public class TestService2 {
    
        public TestService2(TestService1 testService1) {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    启动报错!
    在这里插入图片描述
    从图中的流程看出构造器注入没能添加到三级缓存,也没有使用缓存,所以也无法解决循环依赖问题。

    来源:Spring 是如何解决循环依赖的? - 苏三说技术的回答 - 知乎
    https://www.zhihu.com/question/438247718/answer/1730527725

  • 相关阅读:
    Flask狼书笔记 | 06_电子邮件
    SpringBoot——》引入Redis
    跟着DW学习大语言模型-什么是知识库,如何构建知识库
    NZ系列工具NZ06:VBA创建PDF文件说明
    go语言中的读写操作以及文件的复制
    springcloud22:sentinal的使用
    一个简单的注册页面,如有错误请指正(2.css)
    TS封装小程序wx.showModal弹窗及调用
    官宣!软考机考模拟练习平台于10月16日至11月3日开放
    Oracle 查询 SQL 语句
  • 原文地址:https://blog.csdn.net/yzx3105/article/details/127736683