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

程序员小航 2021-02-23 16:43:02
代理 spring 解决 何解 动态


前言

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

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

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

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

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

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

开始先简单回顾一下 Bean 的创建过程,当然小伙伴也可以直接阅读『 单例 Bean 的创建 』这篇文章。

不过考虑到阅读本文前再阅读上一篇文章、Debug 等等,会比较耗时,所以本篇文章前面一小部分会先对之前的文章内容做简要概括,也相当于对我自己学习的知识进行一个总结。

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

singletonObjects: 一级缓存,存储单例对象,Bean 已经实例化,初始化完成。 earlySingletonObjects: 二级缓存,存储 singletonObject,这个 Bean 实例化了,还没有初始化。 singletonFactories: 三级缓存,存储 singletonFactory。

Bean 的创建过程

@Service
public class CircularServiceA {
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 呢?

@Service
public class CircularServiceA {
private String fieldA = "字段 A";
@Autowired
private CircularServiceB circularServiceB;
}
@Service
public class CircularServiceB {
}

A 依赖了 B

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

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

循环依赖下的创建过程

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

@Service
public class CircularServiceA {
private String fieldA = "字段 A";
@Autowired
private CircularServiceB circularServiceB;
}
@Service
public class CircularServiceB {
@Autowired
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 的创建过程

@Service
public class CircularServiceA {
private String fieldA = "字段 A";
public void methodA() {
System.out.println("方法 A 执行");
}
}
@Aspect
@Component
public class AspectA {
@Before("execution(public void com.liuzhihang.circular.CircularServiceA.methodA())")
public void beforeA() {
System.out.println("beforeA 执行");
}
}

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

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

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

执行后置处理器

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

postProcessAfterInitialization

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

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

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

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

A 被替换为了代理对象

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

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

红框部分为差异

有循环依赖的动态代理

这一次把循环依赖打开:

@Service
public class CircularServiceA {
private String fieldA = "字段 A";
@Autowired
private CircularServiceB circularServiceB;
public void methodA() {
System.out.println("方法 A 执行");
}
}
@Aspect
@Component
public class AspectA {
@Before("execution(public void com.liuzhihang.circular.CircularServiceA.methodA())")
public void beforeA() {
System.out.println("beforeA 执行");
}
}
@Service
public class CircularServiceB {
@Autowired
private CircularServiceA circularServiceA;
public void methodB() {
}
}
@Aspect
@Component
public class AspectB {
@Before("execution(public void com.liuzhihang.circular.CircularServiceB.methodB())")
public void beforeB() {
System.out.println("beforeB 执行");
}
}

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

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

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

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

三级缓存获取对象

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

getEarlyBeanReference

getEarlyBeanReference

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

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

这样 B 就创建完毕了!

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

判断二级缓存

但是在执行 wrapIfNecessary 之前,会先判断代理对象的标记缓存是否有 A 了。

this.earlyProxyReferences.remove(cacheKey) != bean

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

代理 - 循环依赖

总结

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

singletonObject = singletonFactory.getObject();

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

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

然后都放到二级缓存

为什么要三级缓存?

  1. 假设去掉三级缓存

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

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

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

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

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

  1. 假设去掉二级缓存

如果去掉了二级缓存,则需要直接在 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 设计之初是什么样子的?如何一步一步发展成现在这种的?

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

不足之处,多多指正。

- <End /> -

本文分享自微信公众号 - 程序员小航(gh_liuzhihang) ,作者:刘志航

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间: 2021-01-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

版权声明
本文为[程序员小航]所创,转载请带上原文链接,感谢
https://cloud.tencent.com/developer/article/1791902

  1. J2EE
  2. Vue uses SDK to upload seven cows
  3. k8s-dns
  4. JavaScript mailbox verification - regular verification
  5. k8s-dashboard
  6. How many questions can you answer?
  7. Spring annotation -- transactional
  8. [k8s cluster] construction steps
  9. k8s-kubeadm
  10. k8s-etcd
  11. Using HashMap to improve search performance in Java
  12. There is no class problem when Maven publishes jar package
  13. JavaScriptBOM操作
  14. J2EE
  15. k8s-prometheus-memory
  16. k8s-prometheus disk
  17. k8s-prometheus
  18. JavaScript BOM operation
  19. k8s-prometheus-memory
  20. k8s-prometheus disk
  21. k8s-prometheus
  22. Linux Disk Command
  23. Linux FS
  24. 使用docker-compose &WordPress建站
  25. Linux Command
  26. This time, thoroughly grasp the depth of JavaScript copy
  27. Linux Disk Command
  28. Linux FS
  29. Using docker compose & WordPress to build a website
  30. Linux Command
  31. 摊牌了,我 HTTP 功底贼好!
  32. shiro 报 Submitted credentials for token
  33. It's a showdown. I'm good at it!
  34. Shiro submitted credentials for token
  35. Linux Stress test
  36. Linux Root Disk Extension
  37. Linux Stress test
  38. Linux Root Disk Extension
  39. Redis高级客户端Lettuce详解
  40. springboot学习-综合运用(一)
  41. 忘记云服务器上MySQL数据库的root密码时如何重置密码?
  42. Detailed explanation of lettuce, an advanced client of redis
  43. Springboot learning integrated application (1)
  44. Linux File Recover
  45. Linux-Security
  46. How to reset the password when you forget the root password of MySQL database on the cloud server?
  47. Linux File Recover
  48. Linux-Security
  49. LiteOS:盘点那些重要的数据结构
  50. Linux Memory
  51. Liteos: inventory those important data structures
  52. Linux Memory
  53. 手把手教你使用IDEA2020创建SpringBoot项目
  54. Hand in hand to teach you how to create a springboot project with idea2020
  55. spring boot 整合swagger2生成API文档
  56. Spring boot integrates swagger2 to generate API documents
  57. linux操作系统重启后 解决nginx的pid消失问题
  58. Solve the problem of nginx PID disappearing after Linux operating system restart
  59. JAVA版本号含义
  60. The meaning of java version number