Spring IOC循环依赖问题

Spring IOC循环依赖问题

什么是循环依赖

循环依赖其实就是循环引用,也就是两个或者两个以上的Bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A:
在这里插入图片描述

注意:这里不是函数的循环调用,而是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件

Spring 中循环依赖场景有:

  • 构造器的循环依赖(构造器注入)
  • 成员属性的循环依赖(set注入)

其中,构造器的循环依赖问题无法解决,只能抛出 BeanCurrentlyCreationException 异常,在解决属性循环依赖时,spring采用的是提前暴露对象的方法。

循环依赖处理机制

无法解决的循环依赖

  • 多例 prototype 原型 Bean 循环依赖 (无法解决)

对于多例 prototype 原型 Bean 的初始化过程中,不论是构造器参数循环依赖,还是通过setXXX方法产生循环依赖,spring都是直接报错处理。原因很好理解,创建新的A时,发现要注入原型字段B,又创建新的B发现要注入原型字段A…

AbstractBeanFactory#doGetBean 方法

// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
    throw new BeanCurrentlyInCreationException(beanName);
}

在获取bean之前,如果这个原型bean正则被创建则直接抛出异常。原型bean在创建之前会进行标记这个beanName正在被创建,等创建结束之后会 删除标记

AbstractBeanFactory#doGetBean 方法

try {
    // 创建原型bean之前添加标记
    beforePrototypeCreation(beanName);
    // 创建原型bean
    prototypeInstance = createBean(beanName, mbd, args);
}
finally {
    // 创建原型bean之后删除标记
    afterPrototypeCreation(beanName);
}

总结:Spring不支持原型Bean的循环依赖

单例 Bean 构造器参数循环依赖(无法解决)

Spring的循环依赖的解决方式基于Java的引用传递,单例Bean使用构造器参数产生的循环依赖问题,构造器是在获得引用之前的,无法产生实例对象,也无法获得引用对象,所以无法解决。具体原因可以看下去,看看spring是如何解决循环依赖的方法,就容易理解了

Spring循环依赖的解决方法

spring的循环依赖的理论基于Java的引用传递,当获得对象的引用时,对象的属性是可以延后设置的。

spring能解决的循环依赖的场景是 通过setXXX方法或者@Autowired 方法注入依赖对象,其实是通过提前暴露一个ObjectFactory对象来完成的。

首先,Spring 内部维护了三个Map ,也就是我们通常说的三级缓存,但是Spring的官方文档却没有找到三级缓存相关的概念,可能是因为这三个Map的注释都以Cache of 开头吧

DefaultSingletonBeanRegistry 中,声明了这三个Map:

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

	/** Cache of singleton objects: bean name to bean instance. */
	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);
    
    ......
        
}
  • singletonObjects

    它应该是我们最熟悉的了,俗称 “单例池”、“容器”,创建完成的单例Bean就放在这里地方

  • singletonFactories

    映射创建Bean的原始工厂

  • earlySingletonObjects

    映射Bean的早期引用,也就是说在这个Map里的Bean不是完整的,甚至还不能称之为“Bean”,只是一个Instance.

后两个Map其实是“垫脚石”级别的,只是创建Bean的时候,用来借助了一下,创建完成就清掉了。

先看下Spring解决循环依赖的简图

在这里插入图片描述

简单总结下:

  • 如何检测是否有循环依赖?

    Bean在创建的时候给其打个标记,如果递归调用回来发现正在创建中的话—>即可说明循环依赖。

  • Spring是怎么解决的循环依赖?

    Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的field属性是可以延后设置的(但是构造器必须是在获取引用之前)。

    spring bean 的单例对象的初始化主要分为下面三步:

    createBeanInstance 实例化 ==> populateBean 填充属性 ==> InitializeBean 初始化

    从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二步。也就是构造器循环依赖和field循环依赖。

    那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存

    AbstractAutowireCapableBeanFactory#doCreateBean 方法中,这个时候实例化了对象,未设置属性

    if (instanceWrapper == null) {
        // 创建bean实例,仅仅调用构造方法,但是尚未设置属性
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    

    然后加入三级缓存

    if (earlySingletonExposure) {
        if (logger.isTraceEnabled()) {
            logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
        }
        // 加入三级缓存
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    

    加入三级缓存方法:

    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }
    

    这里就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。

    接着注入属性:

    // 初始化bean实例
    Object exposedObject = bean;
    try {
        // bean属性填充
        populateBean(beanName, mbd, instanceWrapper);
        // 调用初始化方法,应用BeanPostProcessor后置处理器
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    

    AbstractAutowireCapableBeanFactory#populateBean然后注入属性发现缺少依赖对象

    if (pvs != null) {
        applyPropertyValues(beanName, mbd, bw, pvs);
    }
    

    AbstractAutowireCapableBeanFactory#applyPropertyValues 获取依赖对象的引用值

    Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
    

    BeanDefinitionValueResolver#resolveReference 方法会执行getBean方法

    resolvedName = String.valueOf(doEvaluate(ref.getBeanName()));
    bean = this.beanFactory.getBean(resolvedName);
    

    这个时候就是正常的创建所依赖的Bean B了,调用 doGetBean ,然后又来到了 AbstractAutowireCapableBeanFactory#doCreateBean 方法

    // 初始化bean实例
    Object exposedObject = bean;
    try {
        // bean属性填充
        populateBean(beanName, mbd, instanceWrapper);
        // 调用初始化方法,应用BeanPostProcessor后置处理器
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    

    AbstractAutowireCapableBeanFactory#applyPropertyValues 这个时候设置Bean B的属性A

    String propertyName = pv.getName();// 类B所依赖的ClassA
    Object originalValue = pv.getValue(); // 类B所依赖的classA
    ......
        
    Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
    

    来到BeanDefinitionValueResolver#resolveReference 获取Bean A

    resolvedName = String.valueOf(doEvaluate(ref.getBeanName()));
    bean = this.beanFactory.getBean(resolvedName);
    

    又走到了 getBean => doGetBean 方法,但是这个时候,已经能获取到ClassA了 AbstractBeanFactory#doGetBean 方法中

    // 尝试从缓存中获取Bean
    Object sharedInstance = getSingleton(beanName);
    
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 尝试从一级缓存中拿
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                // 尝试从二级缓存中拿
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    // 尝试从三级缓存中拿
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        // 把三级缓存放到二级缓存中
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }
    

    由于之前实例化ClassA后,把它放到了三级缓存中了,所以这时候ClassB依赖ClassA的问题已经解决,Bean B成功创建,BeanA自然也就创建成功了。

  • 为什么要把ClassA从三级缓存放到二级缓存中

    if (singletonFactory != null) {
        // 把三级缓存放到二级缓存中
        singletonObject = singletonFactory.getObject();
        this.earlySingletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
    }
    

    这里传入的是一个Lambda表达式

    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    

    这时候就可以做一些扩展操作 如BeanPostProcessor操作

    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
                exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
        return exposedObject;
    }
    

至此,spring循环依赖的问题解决!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值