spring解决循环依赖思路

news/2025/1/17 7:59:57/

代理

AProxy对象—>A代理对象—>A代理对象的target = A普通对象

A—>推断构造方法—>普通对象—>依赖注入—>初始化前—>初始化—>初始化后(AOP)—>代理对象—>放入单例池(三级缓冲的第一级缓冲)—>bean对象

@Component
public class UserService {@Autowiredprivate OrderService orderService;public void test() {System.out.println(orderService);}
}class AProxy extends A {A target;public void test() {//切面逻辑//xxxtarget.test();// A普通对象.test()打印orderService,可以正常打印出来orderService}
}public static void main() {AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);	//这里拿到的是userService的代理对象,cglb代理是继承实现的,所以代理对象可以强转为UserServiceUserService userService = (UserService)ac.getBean(UserService.class);userService.test();
}

注意:

1、普通对象是经过了依赖注入的,所以target.test()方法,执行正常

2、但是代理对象并没有进行依赖注入,没有必要

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LgcFPdl6-1679884802018)(../Pictures/typora-图片/循环依赖/image-20230326232024246.png)]

先执行”切面逻辑“,可以看到实际是代理对象的切面逻辑

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3UTyjw9K-1679884802019)(../Pictures/typora-图片/循环依赖/image-20230326232953616.png)]

普通对象的test()在打印orderService

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jT8uT8aF-1679884802019)(../Pictures/typora-图片/循环依赖/image-20230326233151506.png)]

bean生命周期简图

A—>无参构造方法—>普通对象—>依赖注入—>初始化前—>初始化—>初始化后(AOP)—>放入单例池(三级缓冲的第一级缓冲)—>bean对象

第三级:singletonFactories Map<String, ObjectFactory<?>>:目的就是打破循环的

第二级:earlySingletonObjects Map<String, Object>:保存提前需要给其他还没有经过完整生命周期bean(B、C)用的对象(可能是代理可能是普通)

第一级:singletonObjects Map<String, Object>:单例池,保存经过完整生命周期的bean

earlyProxyReferences:判断是否进行了AOP,避免第4步重复AOP

creatingSet:保存正在创建过程中的bean,判断是否出现循环依赖

第一步

A的bean生命周期:

  • 1、实例化—>A的普通对象

  • 2、填充B—>单例池—>创建B

    • B的生命周期
      • 2.1、实例化—>B普通对象
      • 2.2、填充A—>单例池—>创建A (循环依赖了)
      • 2.3、填充其他属性
      • 2.4、其他步骤(包括AOP)
      • 2.5、加入单例池
  • 3、填充其他属性

  • 4、其他步骤(包括AOP)

  • 5、加入单例池中

第二步

在创建B时,发现缺少A,发生了循环依赖,要解决就必须打破循环,要想打破循环,就必须在创建B时,A属性可以在一个地方被找到就打破了,所以要有一个地方要能找到A的对象,在创建B时能用。所以在创建A普通对象时,引入ZhouyuMap<beanName, Object>并放入,在创建B时,单例池找不到,就从ZhouyuMap找肯定就找到了,从而打破循环。

A的bean生命周期:

  • 1、实例化—>A的普通对象—>放入ZhouyuMap<beanName, Object>

  • 2、填充B—>单例池—>创建B

    • B的生命周期
      • 2.1、实例化—>B普通对象
      • 2.2、填充A—>单例池—>ZhouyuMap—>A普通对象
      • 2.3、填充其他属性
      • 2.4、其他步骤(包括AOP)
      • 2.5、加入单例池
  • 3、填充其他属性

  • 4、其他步骤(包括AOP)

  • 5、加入单例池中

(此时ZhouyuMap,到底算是第几级缓存,还不清楚)

第三步

上面虽然打破了循环,但是当A需要AOP时,就需要进行代理,产生代理对象,我们知道在spring中,经过AOP的bean,在单例池中是<beanName, 代理对象Proxy>形式存在的,而且代理对象的target属性指向A普通对象。此时上一步就存在问题,单例池中A是代理对象,但是实际赋值给B的确实A普通对象。

A的bean生命周期:

  • 1、实例化—>A的普通对象—>放入ZhouyuMap<beanName, Object>

  • 2、填充B—>单例池—>创建B

    • B的生命周期
      • 2.1、实例化—>B普通对象
      • 2.2、填充A—>单例池—>ZhouyuMap—>A普通对象
      • 2.3、填充其他属性
      • 2.4、其他步骤(包括AOP)
      • 2.5、加入单例池
  • 3、填充其他属性

  • 4、其他步骤(包括AOP)—>A代理对象

  • 5、加入单例池中

要想解决这个问题,也好办,那就是在2.2步从ZhouyuMap<beanName, Object>拿到是A的代理对象赋值给B即可,也就是说我们应该在第1步就创建A的代理对象,放入ZhouyuMap<beanName, Object>,且在第4步要能判断出来A是否进行过,至少AOP不能重复不能漏掉啊!

A的bean生命周期:

  • 1、实例化—>A的普通对象—>AOP—>A代理对象—>放入ZhouyuMap<beanName, Object>

  • 2、填充B—>单例池—>创建B

    • B的生命周期
      • 2.1、实例化—>B普通对象
      • 2.2、填充A—>单例池—>ZhouyuMap—>A代理对象
      • 2.3、填充其他属性
      • 2.4、其他步骤(包括AOP)
      • 2.5、加入单例池
  • 3、填充其他属性

  • 4、其他步骤(包括AOP)—>A代理对象

  • 5、加入单例池中

第四步

上步中“提前AOP“是在解决出现了循环依赖,还又要进行AOP想到的办法,但是不是每一个bean都要“提前进行AOP”,也就是说出现了循环依赖,才“提前进行AOP”

A的bean生命周期:

  • 1、实例化—>A的普通对象—>循环依赖—>AOP—>A代理对象—>放入ZhouyuMap<beanName, Object>

  • 2、填充B—>单例池—>创建B

    • B的生命周期
      • 2.1、实例化—>B普通对象
      • 2.2、填充A—>单例池—>ZhouyuMap—>A代理对象
      • 2.3、填充其他属性
      • 2.4、其他步骤(包括AOP)
      • 2.5、加入单例池
  • 3、填充其他属性

  • 4、其他步骤(包括AOP)—>A代理对象

  • 5、加入单例池中

现在问题就是,怎么判断出现了循环依赖?这里很明显是在2.2步发现了出现了循环依赖,这里就是”正在创建的bean,被别人依赖了“,A在“创建过程中”被B依赖了,就说明发生了循环依赖呗!所以我们要记录”正在创建的bean“,引入creatingSet记录正在创建的bean,在2.2步中,从单例池中没有获取到A,而且还在creatingSet中得知A正在创建,那肯定就发生了循环依赖。

A的bean生命周期:

  • 0、creatingSet[A]

  • 1、实例化—>A的普通对象

  • 2、填充B—>单例池—>创建B

    • B的生命周期
      • 2.1、实例化—>B普通对象
      • 2.2、填充A—>单例池—>creatingSet—>发生了循环依赖—>又要进行AOP则—>A代理对象
      • 2.3、填充其他属性
      • 2.4、其他步骤(包括AOP)
      • 2.5、加入单例池
  • 3、填充其他属性

  • 4、其他步骤(包括AOP)—>A代理对象

  • 5、加入单例池中

但是问题来了,假如此时A还有C依赖A呢?

第五步

A的bean生命周期:

  • 0、creatingSet[A]

  • 1、实例化—>A的普通对象

  • 2、填充B—>单例池—>创建B

    • B的生命周期
      • 2.1、实例化—>B普通对象
      • 2.2、填充A—>单例池—>creatingSet—>发生了循环依赖—>又要进行AOP则—>A代理对象
      • 2.3、填充其他属性
      • 2.4、其他步骤(包括AOP)
      • 2.5、加入单例池
    • C的生命周期
      • 3.1、实例化—>B普通对象
      • 3.2、填充A—>单例池—>creatingSet—>发生了循环依赖—>又要进行AOP则—>A代理对象
      • 3.3、填充其他属性
      • 3.4、其他步骤(包括AOP)
      • 3.5、加入单例池
  • 3、填充其他属性

  • 4、其他步骤(包括AOP)—>A代理对象

  • 5、加入单例池中

发现B、C分别创建了不同的A代理对象,这是有问题的。此时考虑是否可以将2.2中A的代理对象放入单例池中呢,创建C时从单例池中去除A的代理对象赋值给C,保证一个A代理对象,肯定是不能的,因为A的创建才走到第2步,而单例池中的对象是经过了完整的bean生命周期动作的对象,那又需要一个Map保证单例,那就新加一个Map呗,earlySingletonObjects(根据名称”提前单例对象“)。

A的bean生命周期:

  • 0、creatingSet[A]

  • 1、实例化—>A的普通对象

  • 2、填充B—>单例池—>创建B

    • B的生命周期
      • 2.1、实例化—>B普通对象
      • 2.2、填充A—>单例池—>creatingSet—>发生了循环依赖—>earlySingletonObjects(第一次肯定找不到)—>进行AOP—>A代理对象—>放入earlySingletonObjects—>赋值跟B
      • 2.3、填充其他属性
      • 2.4、其他步骤(包括AOP)
      • 2.5、加入单例池
    • C的生命周期
      • 3.1、实例化—>B普通对象
      • 3.2、填充A—>单例池—>creatingSet—>发生了循环依赖—>earlySingletonObjects(找到了)—>A代理对象—>赋值给C
      • 3.3、填充其他属性
      • 3.4、其他步骤(包括AOP)
      • 3.5、加入单例池
  • 3、填充其他属性

  • 4、其他步骤(包括AOP)—>A代理对象

  • 5、加入单例池中

第六步

我们发现B、C的A属性赋的值,都是从earlySingletonObjectsMap中拿的,earlySingletonObjectsMap中拿的对象是自己还没创建完,已经赋值给其他对象用了,所以叫”提前暴露“,earlySingletonObjectsMap就是为了”保存出现了循环依赖,提前给其他bean去用的代理bean“

但是还有个小问题,其实在2.2中A的AOP生成A的代理对象时,是需要A的普通对象的,否则代理是完不成的,因为代理对象的target指向普通对象,这么说循环还是没有解决,没有打破,所以还得有一个缓存去缓存A的普通对象去彻底打破循环,第三级缓冲singletonFactories,先简单理解为singletonFactoriesMap<String, A普通对象>,所以第三级缓冲作用就是打破循环。

A的bean生命周期:

  • 0、creatingSet[A]

  • 1、实例化—>A的普通对象—>singletonFactoriesMap<String, A普通对象>

  • 2、填充B—>单例池—>创建B

    • B的生命周期
      • 2.1、实例化—>B普通对象
      • 2.2、填充A—>单例池—>creatingSet—>发生了循环依赖—>earlySingletonObjects—>singletonFactoriesMap—>进行AOP—>A代理对象—>放入earlySingletonObjects—>赋值跟B
      • 2.3、填充其他属性
      • 2.4、其他步骤(包括AOP)
      • 2.5、加入单例池
    • C的生命周期
      • 3.1、实例化—>B普通对象
      • 3.2、填充A—>单例池—>creatingSet—>发生了循环依赖—>earlySingletonObjects(找到了)—>A代理对象—>赋值给C
      • 3.3、填充其他属性
      • 3.4、其他步骤(包括AOP)
      • 3.5、加入单例池
  • 3、填充其他属性

  • 4、其他步骤(包括AOP)—>A代理对象

  • 5、加入单例池中

但是实际第三级缓存存储的不是A的普通对象,而是一个ObjectFactory,这是一个函数式接口lamda表达式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p5QXI3T6-1679884802019)(../Pictures/typora-图片/循环依赖/image-20230326221137099.png)]

其实这里就能想通了,上面我们也说了,并不是每一个bean都需要AOP的,那么这个lamba表达式就是用来判断是否需要AOP的,在第1步放进去时,就只是singletonFactoriesMap.put进去一个表达式,并不会立即执行,在2.2步中从第三级缓冲中取出对应A的lamda表达式判断是否需要AOP。可以搜搜wraIfNecessary、getarlyBeanReference方法再详细看看。

第七步

A的bean生命周期:

  • 0、creatingSet[A]

  • 1、实例化—>A的普通对象—>singletonFactoriesMap<String, A的lamda>

  • 2、填充B—>单例池—>创建B

    • B的生命周期
      • 2.1、实例化—>B普通对象
      • 2.2、填充A—>单例池—>creatingSet—>发生了循环依赖—>earlySingletonObjects—>singletonFactoriesMap—>lamda
        • —>进行AOP—>A代理对象—>放入earlySingletonObjects—>赋值跟B
        • —>不进行AOP—>A普通对象—>放入earlySingletonObjects—>赋值跟B
      • 2.3、填充其他属性
      • 2.4、其他步骤(包括AOP)
      • 2.5、加入单例池
    • C的生命周期
      • 3.1、实例化—>B普通对象
      • 3.2、填充A—>单例池—>creatingSet—>发生了循环依赖—>earlySingletonObjects(找到了)—>A(这里就不用纠结找到的A是代理还是普通对象了)—>赋值给C
      • 3.3、填充其他属性
      • 3.4、其他步骤(包括AOP)
      • 3.5、加入单例池
  • 3、填充其他属性

  • 4、其他步骤(包括AOP,注意执行完lamda表达式后singletonFactories.remove了,就不会重复AOP了)

  • 5、earlySingletonObjects.get(A),加入单例池中

通过源码,发现singletonFactory.getObject()执行完lamda后,singletonObject对象可能是代理对象也可能是普通对象,会在singletonFactories.remove掉这个lamda表达式,也就是这个lamda表达式是一次性的,执行一次就够了,避免在第4步AOP重复。

AOP其实是一个插件,用户可以根据需求选择是否使用AOP,需要用时就在配置类中加上@EnableAspectJAutoProxy,然后在需要AOP增强的地方配置切点相关的即可使用了。所以第4步判断是否需要AOP,其实不是Spring做的事情,而是这个”插件AOP“去做的事情,

在这里插入图片描述

A的bean生命周期:

  • 0、creatingSet[A]

  • 1、实例化—>A的普通对象—>singletonFactoriesMap<String, A的lamda>

  • 2、填充B—>单例池—>创建B

    • B的生命周期
      • 2.1、实例化—>B普通对象
      • 2.2、填充A—>单例池—>creatingSet—>发生了循环依赖—>earlySingletonObjects—>singletonFactoriesMap—>lamda
        • —>进行AOP—>A代理对象—>放入earlySingletonObjects—>赋值跟B
        • —>不进行AOP—>A普通对象—>放入earlySingletonObjects—>赋值跟B
      • 2.3、填充其他属性
      • 2.4、其他步骤(包括AOP)
      • 2.5、加入单例池
    • C的生命周期
      • 3.1、实例化—>B普通对象
      • 3.2、填充A—>单例池—>creatingSet—>发生了循环依赖—>earlySingletonObjects(找到了)—>A(这里就不用纠结找到的A是代理还是普通对象了)—>赋值给C
      • 3.3、填充其他属性
      • 3.4、其他步骤(包括AOP)
      • 3.5、加入单例池
  • 3、填充其他属性

  • 4、其他步骤(包括AOP)—> earlyProxyReferences

  • 5、earlySingletonObjects.get(A),加入单例池中

@Lazy解决方式

问题

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

像上面这种情况,属性上没有@Autowired注解的,并且提供了有参构造(覆盖无参构造),此时发生循环依赖,A的普通对象都创建不出来的。

解决方式:

1、因为有参构造覆盖无参构造,所以另外提供一个无参构造方法即可

2、在这个有参构造方法上面加上@Lazy注解,大致解决思路就是:给B生成一个代理对象(注意这个代理和AOP代理没有任何关系),这个代理对象也是CGLB继承式的继承了B,然后把这个代理对象B赋值给A的B属性,从而A对象创建完成,放入单例池,然后创建B,从单例池取出A赋值给B的A属性。直接没有发生循环依赖;你可能会说此时beanB和B的代理对象不是一个对象啊?是的,不是一个对象,但是实际上,不影响真实调用的,例如执行beanA的方法用到了b属性的方法,此时b代理对象中执行时是先从容器中找到beanB对象,再去执行beanB的方法。

class BProxy extends B {//这个代理和AOP代理不同,没有targetpublic void test() {//从容器中找beanB//执行beanB的test()beanB.test();}
}

总结

在这里插入图片描述

第三级:singletonFactories Map<String, ObjectFactory<?>>:目的就是打破循环的

第二级:earlySingletonObjects Map<String, Object>:保存提前需要给其他还没有经过完整生命周期bean(B、C)用的对象(可能是代理可能是普通)

第一级:singletonObjects Map<String, Object>:单例池,保存经过完整生命周期的bean

earlyProxyReferences:判断是否进行了AOP,避免第4步重复AOP

creatingSet:保存正在创建过程中的bean,判断是否出现循环依赖


http://www.ppmy.cn/news/38605.html

相关文章

springAOP注解开发

使用注解&#xff0c;可以减少代码。更加简便 springAOP纯注解开发 创建一个项目&#xff0c;导入jar包 按以下顺序创建包层 advice层&#xff1a; config层&#xff1a; 该层是配置类 service impl层&#xff1a; 接口&#xff1a; 测试类servlet&#xff1a; 这里就不是…

优漫动游UI设计师和美工区别

UI即UserInterface&#xff08;用户界面&#xff09;的简称。泛指用户的操作界面&#xff0c;包含移动APP&#xff0c;网页&#xff0c;智能穿戴设备等。互联网新科技兴起。   美工服务的是甲方客户&#xff0c;作品是否可以通过&#xff0c;每个人的审美都不同&#xff0c…

2023年全国最新安全员精选真题及答案45

百分百题库提供安全员考试试题、建筑安全员考试预测题、建筑安全员ABC考试真题、安全员证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 121.&#xff08;单选题&#xff09;依据《建筑施工扣件式钢管脚手架安全技术规范》&a…

图解 SQL 执行顺序,通俗易懂!

​这是一条标准的查询语句: 这是我们实际上SQL执行顺序&#xff1a; 我们先执行from,join来确定表之间的连接关系&#xff0c;得到初步的数据where对数据进行普通的初步的筛选group by 分组各组分别执行having中的普通筛选或者聚合函数筛选。然后把再根据我们要的数据进行sele…

【Redis学习】Redis哨兵(sentinel)

理论简介 定义 吹哨人巡查监控后台master主机是否故障&#xff0c;如果故障了根据投票数自动将某一个从库转换为新主库&#xff0c;继续对外服务。 作用&#xff1a; 监控redis运行状态&#xff0c;包括master和slave 当master down机&#xff0c;能自动将slave切换成新mas…

Leetcode.111 二叉树的最小深度

题目链接 Leetcode.111 二叉树的最小深度 easy 题目描述 给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从 根节点 到 最近叶子节点 的 最短路径上的节点数量。 说明: 叶子节点是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,nul…

多个音频轨道合并成一个

AudioContext 文档 一、音频 tracks 合并 const audioContext new AudioContext() const dest audioContext.createMediaStreamDestination()// 多个tracks 创建 多个 streamconst tracksList [tracks, tracks, tracks]tracksList.forEach((tracks) > {const stream new…

开心档之C++ 重载运算符和重载函数

C 重载运算符和重载函数 目录 C 重载运算符和重载函数 C 中的函数重载 实例 C 中的运算符重载 实例 可重载运算符/不可重载运算符 运算符重载实例 {#examples} C 允许在同一作用域中的某个函数 和运算符 指定多个定义&#xff0c;分别称为函数重载 和运算符重载。 重载…