Spring - 三级缓存 + 循环依赖circular reference解决

回到顶部(go to top)

详细文章

https://blog.csdn.net/a745233700/article/details/110914620

回到顶部(go to top)

零、三层循环

Spring中有三个缓存,用于存储单例的Bean实例,这三个缓存是彼此互斥的,不会针对同一个Bean的实例同时存储。如果调用getBean,则需要从三个缓存中依次获取指定的Bean实例。 读取顺序依次是一级缓存 ==> 二级缓存 ==> 三级缓存。

 

 

回到顶部(go to top)

一、循环依赖问题的类型

循环依赖问题在Spring中主要有三种情况:

  • (1)通过构造方法进行依赖注入时产生的循环依赖问题。
  • (2)通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
  • (3)通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。

在Spring中,只有第(3)种方式的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常。其实也很好解释:

  • 第(1)种构造方法注入的情况下,在new对象的时候就会堵塞住了,其实也就是”先有鸡还是先有蛋“的历史难题。
  • 第(2)种setter方法(多例)的情况下,每一次getBean()时,都会产生一个新的Bean,如此反复下去就会有无穷无尽的Bean产生了,最终就会导致OOM问题的出现。

 

回到顶部(go to top)

二、如何解决循环依赖问题?

 

1-代码

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
//----------------------------
 
@Service
public class A{
    public A(){
        System.out.println("init A");
    
 
    @Autowired
    private B instanceB;
 
}
 
//----------------------------
 
@Service
public class B{
    public B(){
        System.out.println("init B");
    }
 
    @Autowired
    private A instanceA;
 
}
 
//----------------------------

  

2-解决循环依赖详细流程【重点】

1-开始实例化bean A (因为有@Service注解),先尝试从一级+二级+三级缓存找,没找到...

2-调用A的无参数构造函数

3-将A对象加入三级缓存(此时的A对象只是分配了堆空间,其属性B还未赋值)

4-发现属性B有@Autowire注解,开始IOC注入属性B,并调用其getBean()方法

5-开始实例化bean B,先尝试从一级+二级+三级缓存找,没找到..

6-调用B 的无参构造函数

7-将B对象加入三级缓存(此时的B对象只是分配了堆空间,其属性A还未初始化完成)

8-发现属性A有@Autowire注解,开始IOC注入属性A,并调用其getBean()方法

9-开始实例化bean A,先尝试从一级+二级+三级缓存找...

10-从三级缓存找到了A (如果在三级缓存找到的话,会把它升级到二级缓存)

11-bean B实例化完成。先把beanName B从singletonsCurrentlyCreation(存放正在实例化ing的bean的集合)中删去,然后存入一级缓存,从二、三级缓存删除

12-bean A实例化完成。先把beanName A从singletonsCurrentlyCreation(存放正在实例化ing的bean的集合)中删去,然后存入一级缓存,从二、三级缓存删除

 

 

 

3-为何step10需要从三级升级到二级缓存?

需要强调的是,如果不升级到二级缓存,功能上OK;从三升到二,是为了性能考虑。注意: 从三级缓存容器singletonFactories取出来的不是一个bean object,而是一个beanFactory,还要靠循环所有的beanPostProcessors,调用他们的getEarlyBeanReferece()去获取bean object,比较耗时。

 

尤其如下情况,在实例化B,C,..时会多次在三级缓存里寻找A

当A -->B,C,D,E,F,G....  B->A, C->A,D->A, E->A...

 

4-循环依赖的错误示范

以下例子,把循环依赖从属性注解,改为了注解构造函数的参数。

 

这种就会抛出异常,因为走的代码和属性注解的不同。这种方式(注解构造函数的参数)的getBean方法是先于往三级缓存保存的操作,因此每次getBean都从三级缓存拿不到,因此都会尝试生成bean A, 并往singletonsCurrentlyInCreation (会保存正在实例化ing的beanName) 加入 A。那么,当在第二次实例化A对象时(就是在实例化B的构造参数A),就会重复,因为抛出异常。

 

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
//----------------------------
 
@Service
public class A{
 
    @Autowired
    public A(B instanceB){
        System.out.println("init A");
    
 
}
 
//----------------------------
 
@Service
public class B{
 
    @Autowired
    public B(A instanceA){
        System.out.println("init B");
    }
 
}
 
//----------------------------

 

回到顶部(go to top)

四、QA

为什么第三级缓存要使用ObjectFactory?

如果仅仅是解决循环依赖问题,使用二级缓存就可以了,但是如果对象实现了AOP,那么注入到其他bean的时候,并不是最终的代理对象,而是原始的。这时就需要通过三级缓存的ObjectFactory才能提前产生最终的需要代理的对象。

 

什么时候将Bean的引用提前暴露给第三级缓存的ObjectFactory持有?

时机就是在第一步实例化之后,第二步依赖注入之前,完成此操作。

 

解决构造函数相互注入造成的循环依赖?

前面说Spring可以自动解决单例模式下通过setter()方法进行依赖注入产生的循环依赖问题。而对于通过构造方法进行依赖注入时产生的循环依赖问题没办法自动解决,那针对这种情况,我们可以使用@Lazy注解来解决。

也就是说,对于类A和类B都是通过构造器注入的情况,可以在A或者B的构造函数的形参上加个@Lazy注解实现延迟加载。@Lazy实现原理是,当实例化对象时,如果发现参数或者属性有@Lazy注解修饰,那么就不直接创建所依赖的对象了,而是使用动态代理创建一个代理类。

比如,类A的创建:A a=new A(B),需要依赖对象B,发现构造函数的形参上有@Lazy注解,那么就不直接创建B了,而是使用动态代理创建了一个代理类B1,此时A跟B就不是相互依赖了,变成了A依赖一个代理类B1,B依赖A。但因为在注入依赖时,类A并没有完全的初始化完,实际上注入的是一个代理对象,只有当他首次被使用的时候才会被完全的初始化。

 

posted on   frank_cui  阅读(554)  评论(0编辑  收藏  举报

编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5

导航

统计

levels of contents
点击右上角即可分享
微信分享提示