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

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

public class StuServiceA {
    private StuServiceB stuServiceB;


    public StuServiceA(StuServiceB stuServiceB) {
        this.stuServiceB = stuServiceB;
    }

    public StuServiceA() {
    }

    public StuServiceB getStuServiceB() {
        return stuServiceB;
    }

    public void setStuServiceB(StuServiceB stuServiceB) {
        this.stuServiceB = stuServiceB;
    }
}
public class StuServiceB {
    private StuServiceA stuServiceA;

    public StuServiceA getStuServiceA() {
        return stuServiceA;
    }

    public void setStuServiceA(StuServiceA stuServiceA) {
        this.stuServiceA = stuServiceA;
    }

    public StuServiceB(StuServiceA stuServiceA) {
        this.stuServiceA = stuServiceA;
    }

    public StuServiceB() {
    }
}

xml配置

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

运行测试结果:

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

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

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

二、Setter方式、单例
xml配置

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

此时需要对bean的声明周期过程要熟悉,https://blog.csdn.net/dhj199181/article/details/108697867

可以看出,先实例化之后,才设置的对象属性
上图分析:
Spring先是用构造实例化Bean对象 (默认无参构造器),此时Spring会将这个实例化结束的对象放到一个Map中,并且Spring提供了获取这个未设置属性的实例化对象引用的方法。

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


三、Setter方式、原型
xml配置

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

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

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

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

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

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

getSingleton方法

    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            Map var4 = this.singletonObjects;
            synchronized(this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }

        return singletonObject;
    }

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


 

猜你喜欢

转载自blog.csdn.net/dhj199181/article/details/109039153
今日推荐