• 这么回答【循环依赖】助力轻松拿下阿里P6


    在这里插入图片描述

    一篇文章彻底搞定Spring循环依赖

    lecture:波哥

    一、什么是循环依赖

    看下图:

    在这里插入图片描述

      上图是循环依赖的三种情况,虽然方式有点不一样,但是循环依赖的本质是一样的,就你的完整创建要依赖与我,我的完整创建也依赖于你。相互依赖从而没法完整创建造成失败。

    二、构造注入

    1. 案例演示

      构造注入就是依赖关系在构造方法中关联。

    @Component
    @Scope(value = "prototype")
    public class BeanA {
    
        private BeanB beanB;
    
        public BeanA(BeanB beanB) {
            this.beanB = beanB;
        }
    }
    
    @Component
    public class BeanB {
        private BeanA beanA;
    
        public BeanB(BeanA beanA) {
            this.beanA = beanA;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

      然后我们可以通过启动容器来查看,在Spring中构造注入的循环依赖在Spring中是没有办法解决的。只是通过抛出异常来处理了。

    @Configuration
    @ComponentScan
    public class Demo1Main {
        public static void main(String[] args) {
            ApplicationContext ac = new AnnotationConfigApplicationContext(Demo1Main.class);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    2.解决方案

      那么在Spring中是如何发现实例的Bean对象有循环依赖的问题,并抛出异常的呢?其实实现逻辑也比较简单,如下图:

    在这里插入图片描述

      实现的过程也很简单,就是在创建对象之前判断该对象在这个临时容器中是否存在,如果不存在就开始创建,如果存在则说明产生了循环依赖。对象创建完成后就把该对象从容器中移除。对应的看看Spring的源码中的处理。

    在这里插入图片描述

    3.单例分析

      先来看单例的处理,进入到getSingleton方法中。我们需要关注的是两个方法beforeSingletonCreation(beanName)afterSingletonCreation(beanName);

    在这里插入图片描述

    在这里插入图片描述

    beforeSingletonCreation方法

    	protected void beforeSingletonCreation(String beanName) {
    		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
    			throw new BeanCurrentlyInCreationException(beanName);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面的代码很清楚的看到,如果已经存在就抛出循环依赖的错误。而且因为是单例,所以容器也没有通过ThreadLocal来处理了。

    对应的afterSingletonCreation方法就是移走了。

    	protected void afterSingletonCreation(String beanName) {
    		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
    			throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.原型分析

      原型Bean思路差不多,但是实现方式区别有点大,我们先来看看beforePrototypeCreation(beanName)afterPrototypeCreation(beanName);这两个方法。

    // 数据存储在了 ThreadLocal 中
    private final ThreadLocal<Object> prototypesCurrentlyInCreation =
    			new NamedThreadLocal<>("Prototype beans currently in creation");
    
    protected void beforePrototypeCreation(String beanName) {
    		Object curVal = this.prototypesCurrentlyInCreation.get();
    		if (curVal == null) {
    			this.prototypesCurrentlyInCreation.set(beanName);
    		}
    		else if (curVal instanceof String) {
    			Set<String> beanNameSet = new HashSet<>(2);
    			beanNameSet.add((String) curVal);
    			beanNameSet.add(beanName);
    			this.prototypesCurrentlyInCreation.set(beanNameSet);
    		}
    		else {
    			Set<String> beanNameSet = (Set<String>) curVal;
    			beanNameSet.add(beanName);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    	protected void afterPrototypeCreation(String beanName) {
    		Object curVal = this.prototypesCurrentlyInCreation.get();
    		if (curVal instanceof String) {
    			this.prototypesCurrentlyInCreation.remove();
    		}
    		else if (curVal instanceof Set) {
    			Set<String> beanNameSet = (Set<String>) curVal;
    			beanNameSet.remove(beanName);
    			if (beanNameSet.isEmpty()) {
    				this.prototypesCurrentlyInCreation.remove();
    			}
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    可以看到这两个方法都只是在ThreadLocal中错了BeanName的写入和移除的操作。并没有做相关的检查,而这个检查其实是在

    在这里插入图片描述

    这样一来Spring中的构造注入的循环依赖针对单例或是原型的处理我们就清楚了!

    三、依赖注入

    1.案例演示

      设值注入就是通过setter方法来完成属性的赋值。

    @Component
    public class UserD {
    
        @Autowired
        private UserC userC;
    }
    
    @Component
    public class UserC {
        @Autowired
        private UserD userD;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    写主方法测试

    @Configuration
    @ComponentScan
    public class Demo2Main {
    
        public static void main(String[] args) {
            ApplicationContext ac = new AnnotationConfigApplicationContext(Demo2Main.class);
            System.out.println(ac.getBean(UserC.class));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我们可以看到在单例的情况下,Spring解决了循环依赖的问题了。
    在这里插入图片描述

    但是在两个对象都是原型的情况下就解决不了了。

    @Component
    @Scope(value = "prototype")
    public class UserC {
        @Autowired
        private UserD userD;
        
    }
    
    @Component
    @Scope(value = "prototype")
    public class UserD {
    
        @Autowired
        private UserC userC;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    启动测试我们可以看到出现了循环依赖的错误提示了

    在这里插入图片描述

    当然,如果我们给其中的一个设置为单例,那么循环依赖问题即可解决。原理,我们分析下就清楚了。

    2.解决方案

      针对依赖注入这种情况下产生的循环依赖问题,我们的解决方案是提前暴露
    在这里插入图片描述

      但是在Spring中对于Bean实例因为AOP的存在,我们可能返回的并不是原始的Bean对象,而是被AOP增强的代理对象,这时在Spring中需要使用到三级缓存来处理。我们具体来看看Spring中的处理方案。

    3.原理分析

      然后我们来具体看看在Spring中对于依赖注入的循环依赖的解决。进入到AbstractAutowireCapableBeanFactorydoCreateBean方法中。
    在这里插入图片描述

      上面看到了具体创建Bean对象的方法createBeanInstance方法中,然后在下面中比较重要的两个的方法

    在这里插入图片描述

      那么对于的提前暴露应该要在populateBean方法之前实现。往前面找看到了earlySingletonExposure变量,标识的就是是否支持提前暴露

    // 判断条件: 1.是否是单例Bean 2.是否支持循环依赖 3.是否是当前正在创建的Bean
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
          isSingletonCurrentlyInCreation(beanName));
    
    • 1
    • 2
    • 3

      如果上面的条件成立就会走下面的代码

    		if (earlySingletonExposure) {
    			// 把 Lambda 表达式存储在了三级缓存中 
    			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    		}
    // 对应的 addSingletonFactory 方法
    	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    		Assert.notNull(singletonFactory, "Singleton factory must not be null");
    		synchronized (this.singletonObjects) {
                // 该单例对象还未创建过
    			if (!this.singletonObjects.containsKey(beanName)) {
                    // 把 上面的 Lambda 表达式 存储在了 三级缓存中
    				this.singletonFactories.put(beanName, singletonFactory);
                    // 对应的二级缓存置空
    				this.earlySingletonObjects.remove(beanName);
                    // 记录该单例对象名称
    				this.registeredSingletons.add(beanName);
    			}
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    然后我们需要在AOP创建对应的代理增强对象之后再看是如果来暴露代理对象的。

    在这里插入图片描述

    也就是我们看的关键是getSingleton方法

    
    
    • 1
    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
       // 从一级缓存中获取对象
       Object singletonObject = this.singletonObjects.get(beanName);
       if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
       	  // 没有获取到创建好的Bean单例,同时当前正在创建Bean对象
       	  // 从 二级缓存中获取 半层品 对象
          singletonObject = this.earlySingletonObjects.get(beanName);
          if (singletonObject == null && allowEarlyReference) {
             // 二级缓存中没有需要获取的Bean 半成品对象
             synchronized (this.singletonObjects) {
                // Consistent creation of early reference within full singleton lock
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    // 一级缓存中没有
                   singletonObject = this.earlySingletonObjects.get(beanName);
                   if (singletonObject == null) {
                       // 二级缓存 没有  就取出之前存储在三级缓存中的 Lambda 表达式
                      ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                      if (singletonFactory != null) {
                          // 根据Lambda表达式 获取到Bean的 代理对象
                         singletonObject = singletonFactory.getObject();
                          // 把Bean的半成品对象存储在二级缓存中
                         this.earlySingletonObjects.put(beanName, singletonObject);
                          // 清除 一级缓存的 对象
                         this.singletonFactories.remove(beanName);
                      }
                   }
                }
             }
          }
       }
        // 返回对应的半成品对象
       return singletonObject;
    }
    
    • 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
    • 32
    • 33
    • 34
    • 35

    然后对应的获取代理对象的逻辑是在singletonFactory.getObject()中。

    	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    		Object exposedObject = bean; // 原始Bean
    		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    			for (BeanPostProcessor bp : getBeanPostProcessors()) {
                    // 通过每个BeanPostProcessor 对Bean对象做后置处理
    				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
    					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                        // 递归获取对应的 代理对象
    					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
    				}
    			}
    		}
            // 返回 对应的增强Bean
    		return exposedObject;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    具体逻辑如下图:

    在这里插入图片描述

    当依赖关系完成后。会进入到前面的getSingleton方法中。

    在这里插入图片描述

    然后还要做对应的二级缓存和一级缓存数据处理

    在这里插入图片描述

    	protected void addSingleton(String beanName, Object singletonObject) {
    		synchronized (this.singletonObjects) {
                // 创建好的单例Bean会存储在 singletonObjects 这个Map 中,也就是一级缓存中
    			this.singletonObjects.put(beanName, singletonObject);
                // 移除三级缓存的Lambda表达式
    			this.singletonFactories.remove(beanName);
                // 移除二级缓存中的Bean的半成品对象,移除提前暴露的对象
    			this.earlySingletonObjects.remove(beanName);
                // 注册 单例
    			this.registeredSingletons.add(beanName);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

      到这儿整个Spring中的循环依赖就给大家介绍完成了~有帮助的话记得关注三连支持哦.

  • 相关阅读:
    什么是代理服务器?它有哪些分类?
    TensorFlow ImportError: initialization failed
    python常见的输出格式总结
    SpringBoot3集成PostgreSQL
    关于ASO优化的分步入门指南2
    增减网络20220101
    【UE】两步实现“从UI中拖出Actor放置到场景中”
    GLSL ES着色器语言 使用矢量和矩阵的相关规范
    SpringCache配置Redis有效解决缓存击穿和缓存雪崩问题
    【LeetCode】一周中的第几天+ 一年中的第几天
  • 原文地址:https://blog.csdn.net/qq_38526573/article/details/127129150