Spring 系列(七)循环依赖问题

1. 什么是循环依赖

循环依赖其实就是循环引用,也就是两个或两个以上的Bean互相持有对方,最终形成闭环。比如A依赖B,B依赖C,C又依赖A。


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

  • 构造器的循环依赖(构造器注入)
  • Field属性的循环依赖(set注入)
    其中,构造器的循环依赖问题无法解决,只能抛出BeanCurrentlyInCreationException异常,在解决属性循环依赖时,Spring采用的是提前暴露对象的方法。

构造器注入代码如下

@Component
public class CircularDependencyA {

    private CircularDependencyB circB;

    @Autowired
    public CircularDependencyA(CircularDependencyB circB) {
        this.circB = circB;
    }
}
@Component
public class CircularDependencyB {

    private CircularDependencyA circA;

    @Autowired
    public CircularDependencyB(CircularDependencyA circA) {
        this.circA = circA;
    }
}

属性注入代码如下

@Component
public class CircularDependencyA {

    private CircularDependencyB circB;

    @Autowired
    public void setCircB(CircularDependencyB circB) {
        this.circB = circB;
    }

    public CircularDependencyB getCircB() {
        return circB;
    }
}

2. 循环依赖处理机制

  • 单例bean构造器参数循环注入(无法解决)
  • prototype原型bean循环依赖(无法解决)
    对于原型bean的初始化无论是通过构造器还是setXxx方法产生循环依赖时,Spring都会直接报错处理。
    AbstractBeanFactory.doGetBean()⽅法:
            if (isPrototypeCurrentlyInCreation(beanName)) {
                throw new BeanCurrentlyInCreationException(beanName);
            }
    protected boolean isPrototypeCurrentlyInCreation(String beanName) {
        Object curVal = this.prototypesCurrentlyInCreation.get();
        return (curVal != null &&
                (curVal.equals(beanName) || (curVal instanceof Set && ((Set) curVal).contains(beanName))));
    }

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

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

总结:Spring不支持原型bean的循环依赖。(举个例子,TestA,TestB两个原型Bean相互依赖,当TestA被实例化,在属性赋值的时候发现对象TestB需要实例化,所以就去实例化TestB,到了TestB属性赋值的时候又发现依赖TestA就开始创建TestA,然而TestA被标记正在创建中,然后就被isPrototypeCurrentlyInCreation这个判断拦截住了抛出异常)

  • 单例Bean通过setXxx或者@Autowired进行循环依赖

Spring的循环依赖的利率一句基于Java的引用传递,当获得对象引用时,对象的属性是可以延后设置的,但是构造器必须在获取引用之前。
Spring通过setXxx或者@Autowired方法解决循环依赖其实是通过提前暴露一个ObjectFactory对象来完成的,简单来说ClassA在调用构造器完成对象初始化之后,在调用ClassA的setClassB方法之前就把ClassA实例化的对象通过ObjectFactory提前暴露给Spring容器中。

  1. Spring容器初始化ClassA通过构造器初始化对象提前暴露给Spring容器。
    AbstractAutowireCapableBeanFactory#doCreateBean
        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");
            }
//将初始化后的对象提前已ObjectFactory对象注入到容器中
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }
  1. ClassA调用setClassB方法,Spring首先从容器中获取ClassB,此时ClassB不存在Spring容器中
  2. Spring容器初始化ClassB,同时也将ClassB提前暴露给Spring容器
  3. ClassB调用setClassA方法,Spring从容器中获取ClassA,因为第一步已经提前暴露了ClassA,因此可以获取到ClassA实例。
    ClassA通过spring容器获取到ClassB,完成对象初始化操作
    5.这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖的问题。

你可能感兴趣的:(Spring 系列(七)循环依赖问题)