Spring解决循环依赖

什么是循环依赖

循环依赖的关键点在循环,为了直观一点,我们看个图

image-20200405224151194

从图中我们看到,要想实例化类A,必须先要实例化类B,但是在实例化B之前也必须先实例化C,可想要实例化C,就要先实例化A,喏,死循环了,三个类相互引用又相互等待。

循环依赖如何产生的

循环依赖的产生可能有多种情况:

  1. A的构造器中依赖了B的实例,B的构造器中依赖了A的实例
  2. A的构造器中依赖了B的实例,B的属性或者setter依赖了A的实例
  3. A的属性或setter依赖了B的实例,B的属性或者setter依赖了A的实例

Spring IOC解决办法

Spring并不是能解决所有的循环依赖情况,比如上方的情况1是无法解决的,并且如果Bean的scope是protype的,也无法解决。

  1. scope=prototype

    此种情况无法解决循环依赖,因为如果获取bean实例的时候,如果类正在创建中,则会抛出异常,详情查看org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean方法

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

    从注释我们就可以看出:如果创建bean实例失败则多半因为存在循环依赖。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * Return whether the specified prototype bean is currently in creation
    * (within the current thread).
    * @param beanName the name of the bean
    */
    protected boolean isPrototypeCurrentlyInCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    return (curVal != null &&
    (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
    }

    prototype类型的Bean在创建之前会进行标记和创建之后进行解标记

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    if (mbd.isPrototype()) {
    // It's a prototype -> create a new instance.
    Object prototypeInstance = null;
    try {
    beforePrototypeCreation(beanName);
    prototypeInstance = createBean(beanName, mbd, args);
    }
    finally {
    afterPrototypeCreation(beanName);
    }
    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
    }
    1
    2
    3
    4
    5
    6
    7
    protected void beforePrototypeCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    if (curVal == null) {
    this.prototypesCurrentlyInCreation.set(beanName);
    }
    else ...
    }
    1
    2
    3
    4
    5
    6
    7
    protected void afterPrototypeCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    if (curVal instanceof String) {
    this.prototypesCurrentlyInCreation.remove();
    }
    else ...
    }
  2. scope=singleton

    Spring通过三级缓存的模式解决非构造器注入引起的循环依赖,详情查看org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean方法

    1
    Object sharedInstance = getSingleton(beanName);

    我们看下getSingleton方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 第一级缓存获取
    Object singletonObject = this.singletonObjects.get(beanName);
    // 第一级未找到缓存且bean处于创建中(例如A定义的构造函数依赖了B对象,得先去创建B对象,或者在populatebean过程中依赖了B对象,得先去创建B对象,此时A处于创建中)
    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;
    }

    在singleton方法中出现了三个属性:singletonObjects,earlySingletonObjects,singletonFactories,这也就是上面说的三级缓存,这个方法的逻辑是先获取一级缓存,若一级缓存中不存在,则获取第二级缓存,二级缓存若也不存在则获取三级缓存,若三级缓存都不存在,则在getBean方法中当成prototype处理。那么这三级缓存中主要存的是什么东西呢?

    • 一级缓存:已经初始化完成的bean对象Cache
    • 二级缓存:提前曝光的bean对象Cache
    • 三级缓存:ObjectFactory工厂bean缓存, 存储实例化后的bean factory

    Spring创建一个Bean的实例大致经过三个步骤:

    1. createBeanInstance:调用Bean的构造器进行实例化,调用构造器并未填充属性
    2. populateBean:填充属性,也就是setter所有的property
    3. initializeBean:初始化Bean,会调用指定的init方法,或者afterPropertiesSet方法,这个时候类属性已经注入完成了

    三级缓存何时设置的

    三级缓存的设置点是什么时候?我们来看下类的创建过程org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    throws BeanCreationException {

    // 1. Instantiate the bean.

    // 2. 创建三级缓存
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
    isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
    if (logger.isTraceEnabled()) {
    logger.trace("Eagerly caching bean '" + beanName +
    "' to allow for resolving potential circular references");
    }
    // 获取Bean的ObjectFactory并放入三级缓存
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
    // 填充属性
    populateBean(beanName, mbd, instanceWrapper);
    // 初始化Bean
    exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
    if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
    throw (BeanCreationException) ex;
    }
    else {
    throw new BeanCreationException(
    mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
    }
    }

    return exposedObject;
    }
    • 可以看到第三级缓存是在createBeanInstance方法之后就被设置,这个时候Bean的对象已经被创建出来了,只不过是还不够完美,只是一个壳,但是在容器中已经可以根据对象引用被认出来了;
    • 第二级缓存是在getSingleton方法之后,若第三级缓存中已经存在,则将对象从三级缓存中转移到二级缓存中;
    • 完全初始化之后将自己放入到一级缓存singletonObjects中