Spring解决循环依赖问题分析

循环依赖

循环依赖其实就是循环引⽤,也就是两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环。⽐如A
依赖于B,B依赖于C,C⼜依赖于A。

在这里插入图片描述

Spring 中循环依赖场景有:

  • 构造器的循环依赖(构造器注⼊)
  • Field 属性的循环依赖(set注⼊)

其中,构造器的循环依赖问题⽆法解决,只能拋出BeanCurrentlyInCreationException异常,在解决
属性循环依赖时,Spring 采⽤的是提前暴露对象的⽅法。

循环依赖处理机制
  • prototype原型bean循环依赖(⽆法解决)

    Spring对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setter⽅法产⽣循环依
    赖,Spring都会直接报错处理。

    org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
    			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
        // ...
        
    	// Fail if we're already creating this bean instance:
        // We're assumably within a circular reference.
        if (isPrototypeCurrentlyInCreation(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
        // ...
        
        else if (mbd.isPrototype()) {
       // It's a prototype -> create a new instance.
           Object prototypeInstance = null;
           try {
              // 原型bean在创建前先标记为正在创建
              beforePrototypeCreation(beanName);
              prototypeInstance = createBean(beanName, mbd, args);
           }
           finally {
              // 创建完成后将标记清除
              afterPrototypeCreation(beanName);
           }
           bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
        }
    }
    
    protected boolean isPrototypeCurrentlyInCreation(String beanName) {
        Object curVal = this.prototypesCurrentlyInCreation.get();
        return (curVal != null &&
                (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
    }
    
  • 单例bean构造器参数循环依赖(无法解决)

    Spring的循环依赖的理论依据基于Java的引⽤传递,当获得对象的引⽤时,对象的属性是可以延
    后设置的。但是构造器必须是在获取引⽤之前。

  • 单例bean通过setter或者@Autowired进⾏循环依赖

    <!--循环依赖问题-->
    <bean id="lagouBean" class="com.kirito.LagouBean">
        <property name="ItBean" ref="itBean"/>
    </bean>
    <bean id="itBean" class="com.kirito.ItBean">
        <property name="LagouBean" ref="lagouBean"/>
    </bean>
    

    解决setter或者@Autowired循环依赖其实是通过提前暴露⼀个ObjectFactory对象。简单来说 A 在调⽤构造器完成对象初始化之后,在调⽤ A 的 setB ⽅法之前就把 A 实例化的对象通过ObjectFactory提前暴露到Spring容器中。

    具体的实现是通过DefaultSingletonBeanRegistry持有的三级缓存:

    /** Cache of singleton objects: bean name to bean instance. */
    // 一级缓存单例池,包含完整的spring bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    /** Cache of singleton factories: bean name to ObjectFactory. */
    // 三级缓存,当对象被实例化后立马放入三级缓存
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    
    /** Cache of early singleton objects: bean name to bean instance. */
    // 二级缓存早期对象池,当对象从三级缓存中获取到引用对象并赋值后,会升级到二级缓存
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    

    Spring解决循环依赖过程:

    1. A 在创建后,立马将自己放入三级缓存池,提前暴露自己;
    2. A 创建完成后进行属性注入,发现引用了 B,会去缓存中找,没找到就开始创建 B;
    3. B 在创建后同样会将自己放入三级缓存;
    4. B 在进行属性注入时发现引用了 A,就去缓存中找,在三级缓存中找到了 A;
    5. B 升级到二级缓存;(此过程还没有完成依赖注入,可以额外扩展)
    6. B 在完成属性变成完整的Spring Bean后,将自己放入一级缓存;
    7. A 在一级缓存中发现了已经成型的 B,利用 B 继续走创建流程;
    8. A 创建完成后,也将自己放入一级缓存。

    示意图如下:

在这里插入图片描述

循环依赖调用流程

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值