likes
comments
collection
share

Spring循环依赖探究

作者站长头像
站长
· 阅读数 51

1. 前言

Spring在较新版本中已经默认不允许bean之间发生「循环依赖」了,如果检测到循环依赖,容器启动时将会报错,此时可以通过配置来允许循环依赖。

spring.main.allow-circular-references=true

什么是循环依赖? 循环依赖也叫循环引用,简单点说,就是bean之间的依赖关系形成了一个循环,例如beanA依赖beanB,beanB又依赖beanA。 Spring循环依赖探究

@Component
public class A {

    @Autowired
    B b;
}

@Component
public class A {

    @Autowired
    B b;
}

如上代码所示,Spring本身是支持循环依赖的,Spring加载bean时,首先会实例化A,然后对A做初始化,其中就包含属性填充,填充属性时发现A依赖B,于是Spring又会从容器中去加载B,创建B时发现B又依赖了A,循环依赖就此产生,Spring是如何打破这个循环的呢?

2. 依赖注入的方式

Spring完成依赖注入的方式有三种:

  1. 属性注入
  2. Setter方法注入
  3. 构造函数注入

对于构造函数注入的方式,循环依赖是无解的,Spring也无法支持。属性注入和Setter方法注入原理上是一样的,本文通过属性注入的方式来分析Spring是如何支持循环依赖的。

循环依赖仅限于单例bean,对于原型bean,循环依赖也是不允许的。

3. bean的多级缓存

Spring支持循环依赖的核心是bean的三级缓存。 Spring会在启动的时候,加载容器内所有非lazy的单例bean,方法是DefaultListableBeanFactory#preInstantiateSingletons(),该方法会把容器内所有非lazy的单例beanName拿出来,然后依次调用getBean()方法获取bean。 getBean()时,首先会调用getSingleton()方法优先从缓存中获取bean,只要缓存中存在bean,就可以直接获取了,避免二次创建。 Spring循环依赖探究 核心就在getSingleton()方法中,Spring会在各级缓存中查找bean。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 检查一级缓存,是否存在bean
    Object singletonObject = this.singletonObjects.get(beanName);
    // bean是否创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            /**
             * 二级缓存查找
             * 二级缓存意义不大?没有二级缓存也能解决循环依赖,作用是啥?
             * 三级缓存的ObjectFactory#getObject()方法是有代价的,会触发后置处理器获取早期引用
             * @see SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
             * 频繁触发势必带来性能问题,引入二级缓存,一旦getObject()触发就移入二级缓存
             */
            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;
}

该方法做了几件事:

  1. 一级缓存中是否存在bean?存在直接返回bean。
  2. 一级缓存没有,且bean正在创建中,则去二级缓存查找。
  3. 二级缓存没有,且允许循环依赖,则去三级缓存查找。
  4. 三级缓存并没有直接存储bean,而是存储bean的工厂方法对象,通过调用工厂方法来获得bean,一旦通过三级缓存得到了bean,则将其从三级缓存移除并加入到二级缓存中。

所谓的多级缓存,其实就是三个Map容器。

/**
* 一级缓存,完整的bean容器
* 经历了完整生命周期的bean
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/**
* 二级缓存,提前暴露的bean,只实例化了,还未初始化
*/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/**
 * 三级缓存,不直接缓存bean
 * 而是提前暴露的bean的工厂方法,调用方法来产生bean
 * 对于需要AOP增强的bean,此时就需要生成带来对象了
 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

Spring解决循环依赖的思路是:当beanA还没有完全创建完毕时,就提前暴露这个半成品beanA到三级缓存,接着在创建beanB时,直接引用半成品beanA,由此来打破循环。

Spring创建A和B两个bean时,流程是这样的:

  1. getA时,一级缓存没有,且不在创建中,于是去createA。
  2. createA之前,先把A加入到singletonsCurrentlyInCreation,表示创建中。
  3. A实例化完毕,还未初始化前,先放入三级缓存singletonFactories。
  4. A填充属性时,发现需要B,去getB。
  5. getB时,一级缓存没有,且不在创建中,于是去createB。
  6. B填充属性时,发现需要A,去getA。
  7. getA发现三级缓存有,将其移入二级缓存,返回A。
  8. B创建完毕,A创建完毕。

4. 二级缓存的意义

仔细看看getSingleton()方法,会发现二级缓存好像很多余,没有二级缓存丝毫不影响循环依赖。二级缓存的作用好像就是承接了bean从三级缓存往二级缓存里迁移而已。真的是这样吗?

二级缓存还是有存在的意义的,因为只要Spring允许循环依赖,就会在创建bean的时候提前暴露到三级缓存,但是循环依赖并不一定会产生,也就是说,暴露到三级缓存里的bean其实未必会被提前引用。只要循环依赖没有产生,bean没有被提前引用,就不用触发Spring的扩展点去生成提前暴露的bean对象了。因为获取提前引用的bean对象是有代价的,bean需要被Spring的扩展点处理,如果bean需要被AOP增强,此时会提前创建代理对象。有了二级缓存,就可以保证只有在真的发生了循环依赖的情况下,才去真正的提前暴露bean对象。

Spring会在bean还没有完全创建完毕时,提前将半成品bean暴露到三级缓存中,代码在AbstractAutowireCapableBeanFactory#doCreateBean()Spring循环依赖探究 当Spring调用ObjectFactory#getObject()获取这个提前暴露的bean,其实会触发AbstractAutowireCapableBeanFactory#getEarlyBeanReference()

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

获取提前暴露的bean引用,这个方法的调用是有代价的,它会触发容器内所有的SmartInstantiationAwareBeanPostProcessor扩展点,调用getEarlyBeanReference()方法来获取bean。如果这个bean需要被AOP增强,此时就会基于bean创建代理对象了。 也就是说,三级缓存和二级缓存其实是互斥的,只要调用了三级缓存里的ObjectFactory#getObject()方法拿到了bean,三级缓存就没用了,可以直接往二级缓存里放了。

转载自:https://juejin.cn/post/7288499615749898255
评论
请登录