Spring循环依赖问题分析和解决

野性酷女 2021-09-24 19:34 459阅读 0赞

项目路径:https://gitee.com/wuhan1/spring-parent.git 下的spring-10
循环依赖出现的三种情况
1、构造器参数循环依赖
2、setter方式单例,singleton
3、setter方式原型模式,prototype
一、构造参数依赖,新建StuServiceA和StuServiceB,之间相互引用

  1. public class StuServiceA {
  2. private StuServiceB stuServiceB;
  3. public StuServiceA(StuServiceB stuServiceB) {
  4. this.stuServiceB = stuServiceB;
  5. }
  6. public StuServiceA() {
  7. }
  8. public StuServiceB getStuServiceB() {
  9. return stuServiceB;
  10. }
  11. public void setStuServiceB(StuServiceB stuServiceB) {
  12. this.stuServiceB = stuServiceB;
  13. }
  14. }
  15. public class StuServiceB {
  16. private StuServiceA stuServiceA;
  17. public StuServiceA getStuServiceA() {
  18. return stuServiceA;
  19. }
  20. public void setStuServiceA(StuServiceA stuServiceA) {
  21. this.stuServiceA = stuServiceA;
  22. }
  23. public StuServiceB(StuServiceA stuServiceA) {
  24. this.stuServiceA = stuServiceA;
  25. }
  26. public StuServiceB() {
  27. }
  28. }

xml配置

  1. <bean id="stuServiceA" class="com.xqc.cycle.bean.StuServiceA" scope="singleton" >
  2. <constructor-arg index="0" ref="stuServiceB" ></constructor-arg>
  3. </bean>
  4. <bean id="stuServiceB" class="com.xqc.cycle.bean.StuServiceB" scope="singleton" >
  5. <constructor-arg index="0" ref="stuServiceA" ></constructor-arg>
  6. </bean>

运行测试结果:
20200921143812179.png

请求的bean当前正在创建中:是否存在无法解析的循环引用?

Spring容器会将每一个正在创建的Bean标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中。如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。

上述源码分析:
Spring容器先创建单例stuServiceA,stuServiceA依赖stuServiceB,然后将A放在“当前创建Bean池”中,此时创建stuServiceB, stuServiceB依赖stuServiceA, 但是,此时stuServiceA已经在池中,所以会报错,因为在池中的Bean都是未初始化完的,所以会依赖错误 ,(初始化完的Bean会从池中移除)

二、Setter方式、单例
xml配置

  1. <!--Setter方式单例-->
  2. <bean id="stuServiceA" class="com.xqc.cycle.bean.StuServiceA" scope="singleton" >
  3. <property name="stuServiceB" ref="stuServiceB" ></property>
  4. </bean>
  5. <bean id="stuServiceB" class="com.xqc.cycle.bean.StuServiceB" scope="singleton" >
  6. <property name="stuServiceA" ref="stuServiceA" ></property>
  7. </bean>

此时需要对bean的声明周期过程要熟悉,https://blog.csdn.net/dhj199181/article/details/108697867
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2RoajE5OTE4MQ_size_16_color_FFFFFF_t_70
可以看出,先实例化之后,才设置的对象属性
上图分析:
Spring先是用构造实例化Bean对象 (默认无参构造器),此时Spring会将这个实例化结束的对象放到一个Map中,并且Spring提供了获取这个未设置属性的实例化对象引用的方法。

当Spring实例化了stuServiceA、stuServiceB后,紧接着会去设置对象的属性,此时stuServiceA依赖stuServiceB,就会去Map中取出存在里面的单例StudentB对象,以此类推,不会出来循环的问题。

三、Setter方式、原型
xml配置

  1. <!--Setter方式原型-->
  2. <bean id="stuServiceA" class="com.xqc.cycle.bean.StuServiceA" scope="prototype" >
  3. <property name="stuServiceB" ref="stuServiceB" ></property>
  4. </bean>
  5. <bean id="stuServiceB" class="com.xqc.cycle.bean.StuServiceB" scope="prototype" >
  6. <property name="stuServiceA" ref="stuServiceA" ></property>
  7. </bean>

运行结果,报循环依赖的问题

20200921145622981.png

scope=”prototype” 意思是 每次请求都会创建一个实例对象。
两者的区别是:有状态的bean都使用Prototype作用域,无状态的一般都使用singleton单例作用域。
对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。

所以上述三种情况可以看出只有Setter单例模式的可以成功

Spring解决单例模式循环依赖原理(三级缓存)
Spring循环依赖的理论依据其实是Java基于引用传递,当我们获取到对象的引用时,对象的field或者或属性是可以延后设置的。
三级缓存所在的类名:DefaultSingletonBeanRegistry

  1. //单例对象的cache
  2. private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
  3. //单例对象工厂的cache
  4. private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
  5. //提前曝光的单例对象的cache
  6. private final Map<String, Object> earlySingletonObjects = new HashMap(16);

getSingleton方法

  1. @Nullable
  2. protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  3. Object singletonObject = this.singletonObjects.get(beanName);
  4. if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
  5. Map var4 = this.singletonObjects;
  6. synchronized(this.singletonObjects) {
  7. singletonObject = this.earlySingletonObjects.get(beanName);
  8. if (singletonObject == null && allowEarlyReference) {
  9. ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
  10. if (singletonFactory != null) {
  11. singletonObject = singletonFactory.getObject();
  12. this.earlySingletonObjects.put(beanName, singletonObject);
  13. this.singletonFactories.remove(beanName);
  14. }
  15. }
  16. }
  17. }
  18. return singletonObject;
  19. }

可以看出当获取bean时会先从singletonObjects对象中获取,如果没有则判断提前曝光的单例对象中earlySingletonObjects 时候有(正在创建的bean池中),如果也没有则从单例对象工厂singletonFactories中获取。如果能获取到则移除对应的singletonFactory,将singletonObject放入到earlySingletonObjects,其实就是将三级缓存提升到二级缓存中!

发表评论

表情:
评论列表 (有 0 条评论,459人围观)

还没有评论,来说两句吧...

相关阅读

    相关 spring循环依赖分析

    1:写在前面 这里的循环依赖指的是基于引用的依赖,而非[depend-on][]或者是其他。当A引用B,B引用C,C又引用A就构成了循环引用,也就是我们平时所说的循环依赖