文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Spring 动态代理时是如何解决循环依赖的?为什么要使用三级缓存?

2024-12-03 12:33

关注

前言

在研究 『 Spring 是如何解决循环依赖的 』 的时候,了解到 Spring 是借助三级缓存来解决循环依赖的。

同样在上一节留下了疑问:

  1. 循环依赖为什么要使用三级缓存?而不是使用二级缓存?
  2. AOP 动态代理对循环依赖的有没有什么影响?

本篇文章也是围绕上面的内容进行展开。

笔记也在不断整理,之前可能会有点杂乱。

循序渐进,看一看什么是循环依赖?

先来回顾一下三级缓存的概念。

Bean 的创建过程

  1. @Service 
  2. public class CircularServiceA { 
  3.     private String fieldA = "字段 A"

单例 Bean 的创建过程

 

通过上面的流程,可以看出 Spring 在创建 Bean 的过程中重点是在 AbstractAutowireCapableBeanFactory 中的以下三个步骤:

  1. 实例化 createBeanInstance: 其中实例化 Bean 并对 Bean 进行赋值,像例子中的 fieldA 字段在这里就会赋值。
  2. 属性注入 populateBean: 可以理解为对 Bean 里面的属性进行赋值。(会依赖其他 Bean)
  3. 初始化 initializeBean: 执行初始化和 Bean 的后置处理器。

实例化赋值源码可以阅读:

BeanUtils.instantiateClass(constructorToUse)

如果要依赖其他 Bean 呢?

那如果 CircularServiceA 依赖了其他 Bean 呢?

A 依赖了 B

 

当 A 依赖了 B 的时候,在 createBeanInstance 这一步,并不会对 B 进行属性赋值。

而是在 populatedBean 这里查找依赖项,并创建 B。

循环依赖下的创建过程

循环依赖的场景,在上一篇文章已经有所讲解,这里仅仅画图说明一下。

  1. @Service 
  2. public class CircularServiceA { 
  3.  
  4.     private String fieldA = "字段 A"
  5.  
  6.     @Autowired 
  7.     private CircularServiceB circularServiceB; 
  8.  
  9. @Service 
  10. public class CircularServiceB { 
  11.     @Autowired 
  12.     private CircularServiceA circularServiceA; 

 

 


A B 循环依赖

 

 

在 A 和 B 循环依赖的场景中:

B populatedBean 查找依赖项 A 的时候,从一级缓存中虽然未获取到 A,但是发现 A 在创建中。

此时,从三级缓存中获取 A 的 singletonFactory 调用工厂方法,创建 getEarlyBeanReference A 的早期引用并返回。

B 引用到 A ,B 就可以初始化完毕,然后 A 同样也可以初始化完毕了。

二级缓存能否解决循环依赖

通过上面的图,仔细分析一下,其实把二级缓存拿掉,在 B 尝试获取 A 的时候直接返回 A 的实例,是不是也是可以的?

答案是:可以的!

但是为什么还是用三级缓存呢?

网上的很多资料说是和动态代理有关系,那就从动态代理的方面继续往下分析分析。

动态代理的场景

在 JavaConfig(配置类) 上添加 @EnableAspectJAutoProxy 注解,开启 AOP ,通过 Debug 循序渐进看一看动态代理对循环依赖的影响。

动态代理下,Bean 的创建过程

  1. @Service 
  2. public class CircularServiceA { 
  3.     private String fieldA = "字段 A"
  4.  
  5.     public void methodA() { 
  6.  
  7.         System.out.println("方法 A 执行"); 
  8.  
  9.     } 
  10. @Aspect 
  11. @Component 
  12. public class AspectA { 
  13.  
  14.     @Before("execution(public void com.liuzhihang.circular.CircularServiceA.methodA())"
  15.     public void beforeA() { 
  16.         System.out.println("beforeA 执行"); 
  17.     } 

只有 A 的情况下,给 A 添加切面,开始 Debug。

前面的流程都相同,在 initializeBean 开始出现差异。

这一步需要初始化 Bean 并执行 Bean 的后置处理器。

 


执行后置处理器

 

 

其中有一个处理器为:AnnotationAwareAspectJAutoProxyCreator 其实就是加的注解切面,会跳转到 AbstractAutoProxyCreator 类的 postProcessAfterInitialization 方法

 

 


postProcessAfterInitialization

 

 

如图所示:wrapIfNecessary 方法会判断是否满足代理条件,是的话返回一个代理对象,否则返回当前 Bean。

后续调用 getProxy 、createAopProxy 等等,最终执行到下面一部分。

 

最终会执行到这里,AOP 代理相关的就不细看了。

一路放行,直到 initializeBean 执行结束。

A 被替换为了代理对象

 

此时发现:A 被替换为了代理对象。

所以 doCreateBean 返回,以及后面放到一级缓存中的都是代理对象。

红框部分为差异

 

有循环依赖的动态代理

这一次把循环依赖打开:

  1. @Service 
  2. public class CircularServiceA { 
  3.  
  4.     private String fieldA = "字段 A"
  5.  
  6.     @Autowired 
  7.     private CircularServiceB circularServiceB; 
  8.  
  9.     public void methodA() { 
  10.  
  11.         System.out.println("方法 A 执行"); 
  12.  
  13.     } 
  14. @Aspect 
  15. @Component 
  16. public class AspectA { 
  17.  
  18.     @Before("execution(public void com.liuzhihang.circular.CircularServiceA.methodA())"
  19.     public void beforeA() { 
  20.  
  21.         System.out.println("beforeA 执行"); 
  22.  
  23.     } 
  24.  
  25. @Service 
  26. public class CircularServiceB { 
  27.  
  28.     @Autowired 
  29.     private CircularServiceA circularServiceA; 
  30.  
  31.     public void methodB() { 
  32.  
  33.     } 
  34. @Aspect 
  35. @Component 
  36. public class AspectB { 
  37.      
  38.     @Before("execution(public void com.liuzhihang.circular.CircularServiceB.methodB())"
  39.     public void beforeB() { 
  40.  
  41.         System.out.println("beforeB 执行"); 
  42.  
  43.     } 
  44.  

开始 Debug,前面的一些列流程,都和正常的没有什么区别。而唯一的区别在于,创建 B 的时候,需要从三级缓存获取 A。

此时在 getSingleton 方法中会调用:singletonObject = singletonFactory.getObject();

B 属性赋值时,从三级缓存获取 A

 

有时会比较疑惑 singletonFactory.getObject() 调用的是哪里?

 


三级缓存获取对象

 

 

所以这一块调用的是 getEarlyBeanReference,开始遍历执行 BeanPostProcessor。

getEarlyBeanReference

 

 


getEarlyBeanReference

 

 

看到 wrapIfNecessary 就明白了吧!这块会获取一个代理对象。

也就是说此时返回,并放到二级缓存的是一个 A 的代理对象。

这样 B 就创建完毕了!

到 A 开始初始化并执行后置处理器了!因为 A 也有代理,所以 A 也会执行到 postProcessAfterInitialization 这一部分!

 


判断二级缓存

 

 

但是在执行 wrapIfNecessary 之前,会先判断二级缓存是否有 A 了。

  1. this.earlyProxyReferences.remove(cacheKey) != bean 

但是这块获取到的是 A 的代理对象。肯定是 false 。所以不会再生成一次 A 的代理对象。

代理 - 循环依赖

 

总结

可以看到,循环依赖下,有没有代理情况下的区别就在:

  1. singletonObject = singletonFactory.getObject(); 

在循环依赖发生的情况下 B 中的 A 赋值时:

  1. 无代理:getObject 直接返回原来的 Bean
  2. 有代理:getObject 返回的是代理对象

然后都放到二级缓存。

为什么要三级缓存?

假设去掉三级缓存

去掉三级缓存之后,Bean 直接创建 earlySingletonObjects, 看着好像也可以。

如果有代理的时候,在 earlySingletonObjects 直接放代理对象就行了。

但是会导致一个问题:在实例化阶段就得执行后置处理器,判断有 AnnotationAwareAspectJAutoProxyCreator 并创建代理对象。

这么一想,是不是会对 Bean 的生命周期有影响。

同样,先创建 singletonFactory 的好处就是:在真正需要实例化的时候,再使用 singletonFactory.getObject() 获取 Bean 或者 Bean 的代理。相当于是延迟实例化。

假设去掉二级缓存

如果去掉了二级缓存,则需要直接在 singletonFactory.getObject() 阶段初始化完毕,并放到一级缓存中。

 


B 和 C 都依赖 A

 

 

那有这么一种场景,B 和 C 都依赖了 A。

要知道在有代理的情况下 singletonFactory.getObject() 获取的是代理对象。

 

 


多次获取代理对象不同

 

 

而多次调用 singletonFactory.getObject() 返回的代理对象是不同的,就会导致 B 和 C 依赖了不同的 A。

那如果获取 B 到之后直接放到一级缓存,然后 C 再获取呢?

 

一级缓存放的是已经初始化完毕的 Bean,要知道 A 依赖了 B 和 C ,A 这时候还没有初始化完毕。

小结

循环依赖的场景有很多,本文只是通过 Debug ,来了解到循环依赖和 AOP 之间的关系,以及了解到为什么要用三级缓存。

当然,Spring 设计之初是什么样子的?如何一步一步发展成现在这种的?

肯定是不能慢慢去研究了,所以只能以现在的版本,去揣测作者的意图。

不足之处,多多指正。

本文转载自微信公众号「程序员小航」,可以通过以下二维码关注。转载本文请联系程序员小航公众号。

 

来源:程序员小航内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯