本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2024-11(1)

Spring 是如何解决循环依赖的

发布于2021-05-29 20:53     阅读(813)     评论(0)     点赞(5)     收藏(3)


目录

1.什么是循环依赖?

2.通过手写代码助你理解Spring循环依赖:

为什么需要二级缓存?

为什么需要三级缓存?

为什么Spring不能解决构造器的循环依赖?

为什么多例Bean不能解决循环依赖?

循环依赖可以关闭吗

3 源码:如何解决循环依赖?

3.1 哪三级缓存?

3.2  创建原始 bean 对象

3.3  暴露早期引用

3.4 解析依赖

3.5 获取早期引用

4 如何进行拓展?

4.1 何时进行拓展?(进行bean的实例化时)

4.2 getEarlyBeanReference方法

附带一张循环依赖的总图:


1.什么是循环依赖?

所谓的循环依赖是指,A 依赖 B,B 又依赖 A,它们之间形成了循环依赖。或者是 A 依赖 B,B 依赖 C,C 又依赖 A。它们之间的依赖关系如下:

0

 

2.通过手写代码助你理解Spring循环依赖:

DEMO:

  1. /***
  2. *
  3. * Spring --循环依赖实例DEMO :
  4. * 帮助您更有效的理解Spring循环依赖源码
  5. */
  6. public class MainStart {
  7. private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
  8. /**
  9. * 读取bean定义,当然在spring中肯定是根据配置 动态扫描注册
  10. */
  11. public static void loadBeanDefinitions() {
  12. RootBeanDefinition aBeanDefinition=new RootBeanDefinition(InstanceA.class);
  13. RootBeanDefinition bBeanDefinition=new RootBeanDefinition(InstanceB.class);
  14. beanDefinitionMap.put("instanceA",aBeanDefinition);
  15. beanDefinitionMap.put("instanceB",bBeanDefinition);
  16. }
  17. public static void main(String[] args) throws Exception {
  18. // 加载了BeanDefinition
  19. loadBeanDefinitions();
  20. // 注册Bean的后置处理器
  21. // 循环创建Bean
  22. for (String key : beanDefinitionMap.keySet()){
  23. // 先创建A
  24. getBean(key);
  25. }
  26. InstanceA instanceA = (InstanceA) getBean("instanceA");
  27. instanceA.say();
  28. }
  29. // 一级缓存
  30. public static Map<String,Object> singletonObjects=new ConcurrentHashMap<>();
  31. // 二级缓存: 为了将 成熟Bean和纯净Bean分离,避免读取到不完整得Bean
  32. public static Map<String,Object> earlySingletonObjects=new ConcurrentHashMap<>();
  33. // 三级缓存
  34. public static Map<String,ObjectFactory> singletonFactories=new ConcurrentHashMap<>();
  35. // 循环依赖标识
  36. public static Set<String> singletonsCurrennlyInCreation=new HashSet<>();
  37. // 假设A 使用了Aop @PointCut("execution(* *..InstanceA.*(..))") 要给A创建动态代理
  38. // 获取Bean
  39. public static Object getBean(String beanName) throws Exception {
  40. Object singleton = getSingleton(beanName);
  41. if(singleton!=null){
  42. return singleton;
  43. }
  44. // 正在创建
  45. if(!singletonsCurrennlyInCreation.contains(beanName)){
  46. singletonsCurrennlyInCreation.add(beanName);
  47. }
  48. // createBean
  49. // 实例化
  50. RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
  51. Class<?> beanClass = beanDefinition.getBeanClass();
  52. Object instanceBean = beanClass.newInstance(); // 通过无参构造函数
  53. // 创建动态代理 (耦合 、BeanPostProcessor) Spring还是希望正常的Bean 还是再初始化后创建
  54. // 只在循环依赖的情况下在实例化后创建proxy 判断当前是不是循环依赖
  55. singletonFactories.put(beanName, () -> new JdkProxyBeanPostProcessor().getEarlyBeanReference(earlySingletonObjects.get(beanName),beanName));
  56. // 添加到二级缓存
  57. // earlySingletonObjects.put(beanName,instanceBean);
  58. // 属性赋值
  59. Field[] declaredFields = beanClass.getDeclaredFields();
  60. for (Field declaredField : declaredFields) {
  61. Autowired annotation = declaredField.getAnnotation(Autowired.class);
  62. // 说明属性上面有Autowired
  63. if(annotation!=null){
  64. declaredField.setAccessible(true);
  65. // byname bytype byconstrator
  66. // instanceB
  67. String name = declaredField.getName();
  68. Object fileObject= getBean(name); //拿到B得Bean
  69. declaredField.set(instanceBean,fileObject);
  70. }
  71. }
  72. // 初始化 init-mthod
  73. // 放在这里创建已经完了 B里面的A 不是proxy
  74. // 正常情况下会再 初始化之后创建proxy
  75. // 由于递归完后A 还是原实例,, 所以要从二级缓存中拿到proxy 。
  76. if(earlySingletonObjects.containsKey(beanName)){
  77. instanceBean=earlySingletonObjects.get(beanName);
  78. }
  79. // 添加到一级缓存 A
  80. singletonObjects.put(beanName,instanceBean);
  81. // remove 二级缓存和三级缓存
  82. return instanceBean;
  83. }
  84. public static Object getSingleton(String beanName){
  85. // 先从一级缓存中拿
  86. Object bean = singletonObjects.get(beanName);
  87. // 说明是循环依赖
  88. if(bean==null && singletonsCurrennlyInCreation.contains(beanName)){
  89. bean=earlySingletonObjects.get(beanName);
  90. // 如果二级缓存没有就从三级缓存中拿
  91. if(bean==null) {
  92. // 从三级缓存中拿
  93. ObjectFactory factory = singletonFactories.get(beanName);
  94. if (factory != null) {
  95. bean=factory.getObject(); // 拿到动态代理
  96. earlySingletonObjects.put(beanName, bean);
  97. }
  98. }
  99. }
  100. return bean;
  101. }
  102. }

下面观点完全由看完源码个人观点总结,如有雷同纯属英雄所见略同, 如有误解欢迎讨论、

 

为什么需要二级缓存?

  • 一级缓存和二级缓存相比:

二级缓存只要是为了分离成熟Bean和纯净Bean(未注入属性)的存放, 防止多线程中在Bean还未创建完成时读取到的Bean时不完整的。所以也是为了保证我们getBean是完整最终的Bean,不会出现不完整的情况。

  • 一二三级缓存下二级缓存的意义:

二级缓存为了存储 三级缓存的创建出来的早期Bean, 为了避免三级缓存重复执行。

 

为什么需要三级缓存?

我们都知道Bean的aop动态代理创建时在初始化之后,但是循环依赖的Bean如果使用了AOP。 那无法等到解决完循环依赖再创建动态代理, 因为这个时候已经注入属性。 所以如果循环依赖的Bean使用了aop. 需要提前创建aop。

但是需要思考的是动态代理在哪创建?? 在实例化后直接创建? 但是我们正常的Bean是在初始化创建啊。 所以可以加个判断如果是循环依赖就实例化后调用,没有循环依赖就正常在初始化后调用。

怎么判断当前创建的bean是不是循环依赖? 根据二级缓存判断?有就是循环依赖?

那这个判断怎么加?加载实例化后面行吗? 且看:

  1. 实例化后.省略code....
  2. if(二级缓存有说明是循环依赖?){
  3. 二级缓存=创建动态代理覆盖(判断当前bean是否被二级缓存命中);
  4. }

这样写可以吗? 肯定不行啊, 因为实例化后始终会放入二级缓存中。 所以这样写不管是不是循环依赖都会在实例化后创建动态代理。

创建本身的时候没法判断自己是不是循环依赖,, 只有在B 引用A (不同bean的引用直接)下才能判断是不是循环依赖(比如B引用A,A正在创建,那说明是循环依赖), 所以判断要卸载getSingleton中。

  1. 假如A是proxy:
  2. A创建Bean -->注入属性B-->getBean(B)-->创建B-->注入属性A---->getSingleton("a")之后写如下代码
  3. ==================================================================================================
  4. public object getSingleton(beanName){
  5. 先从一级缓存拿 省略code...
  6. if(二级缓存有说明是循环依赖?){
  7. 二级缓存=调用创建动态代BeanPostProcessor(判断是否使用aop,没有依然返回原实例);
  8. }
  9. }

在这里创建行吗? 行! 所以说二级缓存确实完全可以解决循环依赖的任何情况:包括扩展能力(因为也可以在这里调用BeanPostProcessor, 当然AOP也是基于BeanPostProcessor,虽然也当然可以解决)

那要三级缓存干嘛? 我们只能这样解释: Spring的方法职责都比较单例,一个方法通常只做一件事, getBean就是获取bean 但是调用创建动态代BeanPostProcessor 是属于create的过程中的, 如果在这里明显代码比较耦合,阅读性也不太好。 所以为了解耦、方法职责单一、方便后期维护, 将调用创建动态代BeanPostProcessor 放在createBean中是最合适不过了, 但是我们判断当前是否循环依赖还是要写在getSingleton里面啊,这怎么办:

三级缓存 存一个函数接口, 函数接口实现 创建动态代理调用BeanPostProcessor 。 为了避免重复创建, 调用把返回的动态代理对象或者原实例存储在二级缓存, 三个缓存完美解决解耦、扩展、性能、代码阅读性。

 

为什么Spring不能解决构造器的循环依赖?

从流程图应该不难看出来,在Bean调用构造器实例化之前,一二三级缓存并没有Bean的任何相关信息,在实例化之后才放入三级缓存中,因此当getBean的时候缓存并没有命中,这样就抛出了循环依赖的异常了。

 

为什么多例Bean不能解决循环依赖?

我们自己手写了解决循环依赖的代码,可以看到,核心是利用一个map,来解决这个问题的,这个map就相当于缓存。

为什么可以这么做,因为我们的bean是单例的,而且是字段注入(setter注入)的,单例意味着只需要创建一次对象,后面就可以从缓存中取出来,字段注入,意味着我们无需调用构造方法进行注入。

  • 如果是原型bean,那么就意味着每次都要去创建对象,无法利用缓存;
  • 如果是构造方法注入,那么就意味着需要调用构造方法注入,也无法利用缓存。

 

循环依赖可以关闭吗

可以,Spring提供了这个功能,我们需要这么写:

  1. public class Main {
  2. public static void main(String[] args) {
  3. AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
  4. applicationContext.setAllowCircularReferences(false);
  5. applicationContext.register(AppConfig.class);
  6. applicationContext.refresh();
  7. }
  8. }

 

3 源码:如何解决循环依赖?

3.1 哪三级缓存?

  1. DefaultSingletonBeanRegistry类的三个成员变量命名如下:
  2. /** 一级缓存 这个就是我们大名鼎鼎的单例缓存池 用于保存我们所有的单实例bean */
  3. private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  4. /** 三级缓存 该map用户缓存 key为 beanName value 为ObjectFactory(包装为早期对象) */
  5. private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  6. /** 二级缓存 ,用户缓存我们的key为beanName value是我们的早期对象(对象属性还没有来得及进行赋值) */
  7. private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

以 BeanA 和 BeanB 两个类相互依赖为例

3.2  创建原始 bean 对象

也就是所说的纯洁态Bean

  1. instanceWrapper = createBeanInstance(beanName, mbd, args);
  2. final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);

假设 beanA 先被创建,创建后的原始对象为BeanA@1234,上面代码中的 bean 变量指向就是这个对象。

3.3  暴露早期引用

该方法用于把早期对象包装成一个ObjectFactory 暴露到三级缓存中 用于将解决循环依赖...

  1. protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
  2. ...
  3. //加入到三级缓存中,,,,,暴露早期对象用于解决循环依赖
  4. this.singletonFactories.put(beanName, singletonFactory);
  5. ...
  6. }

beanA 指向的原始对象创建好后,就开始把指向原始对象的引用通过 ObjectFactory 暴露出去。getEarlyBeanReference 方法的第三个参数 bean 指向的正是 createBeanInstance 方法创建出原始 bean 对象 BeanA@1234。

3.4 解析依赖

populateBean(beanName, mbd, instanceWrapper);

还没有进行属性装配,自动注入的属性都是null

0

初始化好的Bean

0

populateBean 用于向 beanA 这个原始对象中填充属性,当它检测到 beanA 依赖于 beanB 时,会首先去实例化 beanB。

beanB 在此方法处也会解析自己的依赖,当它检测到 beanA 这个依赖,于是调用 BeanFactroy.getBean("beanA") 这个方法,从容器中获取 beanA。

3.5 获取早期引用

  1. protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  2. /**
  3. * 第一步:我们尝试去一级缓存(单例缓存池中去获取对象,一般情况从该map中获取的对象是直接可以使用的)
  4. * IOC容器初始化加载单实例bean的时候第一次进来的时候 该map中一般返回空
  5. */
  6. Object singletonObject = this.singletonObjects.get(beanName);
  7. /**
  8. * 若在第一级缓存中没有获取到对象,并且singletonsCurrentlyInCreation这个list包含该beanName
  9. * IOC容器初始化加载单实例bean的时候第一次进来的时候 该list中一般返回空,但是循环依赖的时候可以满足该条件
  10. */
  11. if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  12. synchronized (this.singletonObjects) {
  13. /**
  14. * 尝试去二级缓存中获取对象(二级缓存中的对象是一个早期对象)
  15. * 何为早期对象:就是bean刚刚调用了构造方法,还来不及给bean的属性进行赋值的对象(纯净态)
  16. * 就是早期对象
  17. */
  18. singletonObject = this.earlySingletonObjects.get(beanName);
  19. /**
  20. * 二级缓存中也没有获取到对象,allowEarlyReference为true(参数是有上一个方法传递进来的true)
  21. */
  22. if (singletonObject == null && allowEarlyReference) {
  23. /**
  24. * 直接从三级缓存中获取 ObjectFactory对象 这个对接就是用来解决循环依赖的关键所在
  25. * 在ioc后期的过程中,当bean调用了构造方法的时候,把早期对象包裹成一个ObjectFactory
  26. * 暴露到三级缓存中
  27. */
  28. ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
  29. //从三级缓存中获取到对象不为空
  30. if (singletonFactory != null) {
  31. /**
  32. * 在这里通过暴露的ObjectFactory 包装对象中,通过调用他的getObject()来获取我们的早期对象
  33. * 在这个环节中会调用到 getEarlyBeanReference()来进行后置处理
  34. */
  35. singletonObject = singletonFactory.getObject();
  36. //把早期对象放置在二级缓存,
  37. this.earlySingletonObjects.put(beanName, singletonObject);
  38. //ObjectFactory 包装对象从三级缓存中删除掉
  39. this.singletonFactories.remove(beanName);
  40. }
  41. }
  42. }
  43. }
  44. return singletonObject;
  45. }

接着上面的步骤讲:

  1. populateBean 调用 BeanFactroy.getBean("beanA") 以获取 beanB 的依赖。
  2. getBean("beanB") 会先调用 getSingleton("beanA"),尝试从缓存中获取 beanA。此时由于 beanA 还没完全实例化好
  3. 于是 this.singletonObjects.get("beanA") 返回 null。
  4. 接着 this.earlySingletonObjects.get("beanA") 也返回空,因为 beanA 早期引用还没放入到这个缓存中。
  5. 最后调用 singletonFactory.getObject() 返回 singletonObject,此时 singletonObject != null。singletonObject 指向 BeanA@1234,也就是 createBeanInstance 创建的原始对象。此时 beanB 获取到了这个原始对象的引用,beanB 就能顺利完成实例化。beanB 完成实例化后,beanA 就能获取到 beanB 所指向的实例,beanA 随之也完成了实例化工作。由于 beanB.beanA 和 beanA 指向的是同一个对象 BeanA@1234,所以 beanB 中的 beanA 此时也处于可用状态了。

以上的过程对应下面的流程图:

https://www.processon.com/view/link/5f1fb2cf1e08533a628a7b4c

0

 

4 如何进行拓展?

bean可以通过实现SmartInstantiationAwareBeanPostProcessor接口(一般这个接口供spring内部使用)的getEarlyBeanReference方法进行拓展

4.1 何时进行拓展?(进行bean的实例化时)

  1. protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)throws BeanCreationException {
  2. //省略其他代码,只保留了关键代码
  3. //...
  4. // Eagerly cache singletons to be able to resolve circular references
  5. // even when triggered by lifecycle interfaces like BeanFactoryAware.
  6. boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
  7. isSingletonCurrentlyInCreation(beanName));
  8. if (earlySingletonExposure) {
  9. if (logger.isDebugEnabled()) {
  10. logger.debug("Eagerly caching bean '" + beanName +
  11. "' to allow for resolving potential circular references");
  12. }
  13. //将刚实例化好的bean添加到一级缓存中
  14. addSingletonFactory(beanName, new ObjectFactory
  15. @Override
  16. public Object getObject()throws BeansException {
  17. //执行拓展的后置处理器
  18. return getEarlyBeanReference(beanName, mbd, bean);
  19. }
  20. });
  21. }
  22. }

4.2 getEarlyBeanReference方法

  1. protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
  2. Object exposedObject = bean;
  3. //判读我们容器中是否有InstantiationAwareBeanPostProcessors类型的后置处理器
  4. if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
  5. //获取我们所有的后置处理器
  6. for (BeanPostProcessor bp : getBeanPostProcessors()) {
  7. //判断我们的后置处理器是不是实现了SmartInstantiationAwareBeanPostProcessor接口
  8. if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
  9. //进行强制转换
  10. SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
  11. //挨个调用SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference
  12. exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
  13. }
  14. }
  15. }
  16. return exposedObject;
  17. }

扩展示例:

  1. @Component
  2. public class TulingBPP implements SmartInstantiationAwareBeanPostProcessor {
  3. public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
  4. if(beanName.equals("instanceA") || beanName.equals("instanceB")) {
  5. JdkDynimcProxy jdkDynimcProxy = new JdkDynimcProxy(bean);
  6. return jdkDynimcProxy.getProxy();
  7. }
  8. return bean;
  9. }
  10. }

 

附带一张循环依赖的总图:

原文链接:https://blog.csdn.net/Xx__WangQi/article/details/117307087



所属网站分类: 技术文章 > 博客

作者:我睡觉爱打呼噜

链接:http://www.javaheidong.com/blog/article/207396/4cde2da8175a9c798ee0/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

5 0
收藏该文
已收藏

评论内容:(最多支持255个字符)