Spring循环依赖产生与解决

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

Spring循环依赖产生与解决

码畜c   2022-12-20 我要评论

循环依赖产生情景

探讨如何解决循环依赖之前,更应该思考清楚什么情况下会发生这种问题?

1、模拟Prototype Bean的循环依赖

static class BeanA {
    // 1. 属性循环依赖
    BeanB beanB = new BeanB();
    // 2. 构造器循环依赖
    public BeanA(BeanB beanB) {
        this.beanB = beanB;
    }
}
static class BeanB {
    // 1. 属性循环依赖
    BeanA beanA = new BeanA();
    // 2. 构造器循环依赖
    public BeanB(BeanA beanA) {
        this.beanA = beanA;
    }
}
public static void main(String[] args) {
    // 1. 属性循环依赖
    BeanA beanA = new BeanA(); // StackOverflowError
    // 2. 构造器循环依赖
    new BeanA(new BeanB(new BeanA(new BeanB( /*....*/ ))));
}

Prototype bean,构造器注入Bean时,强调注入的是成品Bean,无法解决循环依赖问题。

Prototype bean,属性上注入Bean时,由于原型模式是单个bean可以被多次创建的,一旦发生循环依赖,就会产生上面这种不断在创建新的BeanA与BeanB导致的栈溢出。源码中的解决方式:记录并检测,如下:

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 {
            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))));
}
protected void beforePrototypeCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    if (curVal == null) {
        this.prototypesCurrentlyInCreation.set(beanName);
    }
    else if (curVal instanceof String) {
        Set<String> beanNameSet = new HashSet<>(2);
        beanNameSet.add((String) curVal);
        beanNameSet.add(beanName);
        this.prototypesCurrentlyInCreation.set(beanNameSet);
    }
    else {
        Set<String> beanNameSet = (Set<String>) curVal;
        beanNameSet.add(beanName);
    }
}

获取Bean前先判断是否当前Bean是否为Prototype并且在创建中。如果不是,且当前Bean为Prototype,那么会记录当前Bean正在创建中(通过beforePrototypeCreation方法)。

以模拟循环依赖代码为例:

  • 创建BeanA
  • populateBean(BeanA)
  • 创建BeanB
  • populateBean(BeanB)
  • 创建BeanA,isPrototypeCurrentlyInCreation发现BeanA正常创建中,抛出BeanCurrentlyInCreationException

2、模拟Singleton Bean的循环依赖

static class BeanA {
    BeanB beanB;
    public BeanA(BeanB beanB) {
        this.beanB = beanB;
    }
}
static class BeanB {
    BeanA beanA;
    public BeanB(BeanA beanA) {
        this.beanA = beanA;
    }
}
public static void main(String[] args) {
    // 1. 属性循环依赖
    BeanA beanA = new BeanA();
    BeanB beanB = new BeanB();
    beanA.beanB = beanB;
    beanB.beanA = beanA;
    // 2. 构造器循环依赖
    new BeanA(new BeanB(new BeanA(new BeanB( /*....*/ ))));
}

Singleton bean,构造器注入Bean时,强调注入的是成品Bean,无法解决循环依赖问题。

Singleton bean,属性上注入Bean时,允许注入提前暴露的半成品的Bean,即没有填充属性&初始化的Bean,只要引用关系能关联上,属性填充与初始化随着程序的执行自然就会处理完成,可以解决循环依赖。

为什么构造器中不能先传递一个半成品Bean,然后赋值给成员Bean属性呢?而一定要传递一个成品Bean?很好理解,spring并不能确定用户在构造器中的操作仅为赋值给Bean属性。且很多时候,我们需要通过bean的一些注入的Bean属性来执行一些业务操作的。如果为空,那么就可能会发生空指针异常。而且这样也不是特别合理。若用户选择属性注入的方式,用户不会涉及这些操作,那么自然就不需要考虑这些问题。

3、总结成一句话来说:spring仅能解决单例Bean下发生在属性上注入Bean产生的循环依赖。

Spring如何解决循环依赖

通过三级缓存解决循环依赖,分别是:

1、singletonObjects: 一级缓存,存储成品Bean

2、earlySingletonObjects: 二级缓存,存储半成品Bean

3、singletonFactories: 三级缓存,存储生成半成品Bean的ObjectFactory的lambda

还是以BeanA注入BeanB,BeanB注入BeanA为例,描述一下大致的执行流程:

  • 检查BeanA的缓存信息
  • 反射实例化BeanA
  • 注册暴露 BeanA的lambda(生成放入二级缓存中的半成品BeanA)到三级缓存:addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))
  • 填充属性BeanB:populateBean
  • 检查BeanB的缓存信息
  • 注册暴露 BeanB的lambda(生成放入二级缓存中的半成品BeanB)到三级缓存
  • 反射实例化BeanB
  • 填充属性BeanA:populateBean
  • 检查BeanA的缓存信息,从三级缓存中获取到了生成半成品BeanA的lambda,执行获取半成品BeanA并放入二级缓存,并在三级缓存中移除lambda
  • 将生成的BeanA的二级缓存对象赋值到BeanB的属性上
  • 将BeanB放入一级缓存,并在二、三级缓存中清理关于BeanB的记录
  • 初始化BeanB
  • 返回到第四步,将成品BeanB赋值到BeanA的属性上
  • 将BeanA放入一级缓存,并在二、三级缓存中清理关于BeanA的记录
  • 初始化BeanA

思考一下,是否能通过二级缓存来解决循环依赖?

首先spring对于三级缓存的应用,就是在生成需要提前暴露的半成品Bean,要么返回的是通过反射实例化后的对象,要么是被一组SmartInstantiationAwareBeanPostProcessor处理后的对象(比如生成代理Bean),然后在放到二级缓存中。那么我们在放入二级缓存时,不通过三级缓存获取这个过程,直接在方法中复现这个过程,在放入二级缓存中,效果想必也是相同的。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们