spring 循环依赖处理

解决 bean 之间的循环依赖分为2种:

  • 构造函数注入导致的循环依赖
    发现这种情况, spring无解, 直接抛出 BeanCurrentlyInCreationException 异常
  • setter注入导致的循环依赖
    spring 只解决 singleton bean 下的 setter 注入循环依赖, 其它 scope 下依然会抛出 BeanCurrentlyInCreationException 异常. spring 将正在创建的 singleton bean 提前暴露出来, 供其它 bean 提前引用

1. 为什么 spring 只解决 singleton scope 下的环形依赖

prototype scope 的 bean 是一个 ThreadLocal 变量. 在 AbstractBeanFactory 的 #doGetBean 方法中, 调用了 afterPrototypeCreation(beanName)
prototypesCurrentlyInCreation 是一个 ThreadLocal 集合, bean 在被创建之前加入这个集合, 创建后从这个集合中删除.(如下代码体现了这个过程)

// AbstractBeanFactory.java

private final ThreadLocal prototypesCurrentlyInCreation =
        new NamedThreadLocal<>("Prototype beans currently in creation");

/* 创建后删除 */
protected void afterPrototypeCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();  // 从 threadLocal 中获取
    if (curVal instanceof String) {
        this.prototypesCurrentlyInCreation.remove();     // bean 从 threadLocal 中删除
    }
    else if (curVal instanceof Set) {
        Set beanNameSet = (Set) curVal;  // bean 从 threadLocal 的 set 中删除
        beanNameSet.remove(beanName);
        if (beanNameSet.isEmpty()) {
            this.prototypesCurrentlyInCreation.remove();
        }
    }
}

/* 创建前加入 */
protected void beforePrototypeCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    if (curVal == null) {
        this.prototypesCurrentlyInCreation.set(beanName);
    }
    else if (curVal instanceof String) {
        Set beanNameSet = new HashSet<>(2);
        beanNameSet.add((String) curVal);
        beanNameSet.add(beanName);
        this.prototypesCurrentlyInCreation.set(beanNameSet);
    }
    else {
        Set beanNameSet = (Set) curVal;
        beanNameSet.add(beanName);
    }
}

 
 

AbstractBeanFactory 类的 #doGetBean() 方法中, 首先判断是否是一个 singleton 的 bean, 如果不是, 判断该 bean 是否正在当前线程中创建, 如果是, 抛出异常

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    return (curVal != null &&
            (curVal.equals(beanName) || (curVal instanceof Set && ((Set) curVal).contains(beanName))));
}

2. 为什么只能解决构造器出入的环形依赖

问题的答案同 prototype , 代码在 DefaultSingletonBeanRegistry 类的 #beforeSingletonCreation() 方法 . 创建 bean 之前, 会去 inCreationCheckExclusions 集合判断是否正在创建, 是的话抛出异常

// DefaultSingletonBeanRegistry.java

private final Set inCreationCheckExclusions =
        Collections.newSetFromMap(new ConcurrentHashMap<>(16));
protected void beforeSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
}

3. spring 如何解决 singleton scope 下的 setter 环形依赖

之所以能够解决 setter 环形依赖, 是因为构造器构造对象后, 堆内存中已经分配了内存, 即使再设置属性, 也不会改变对象的地址引用. 为了在创建时获取 singleton 的 bean , spring 使用了三级缓存结构

/** Cache of singleton objects: bean name to bean instance. */
private final Map singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map earlySingletonObjects = new HashMap<>(16);

三级缓存如下:

  • L1 cache: singletonObjects 存储已经实例化好的 bean
  • L2 cache: earlySingletonObjects 提前暴露的 singleton 缓存, 仅通过构造器反射, 还没有注入属性的 bean
  • L3 cache: singletonFactories 对创建 singleton bean 的工厂缓存

创建 bean 的第一步是创建一个同名的 single factory, 这个 factory 也是 bean,

// AbstractAutowireCapableBeanFactory.java

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
    ......
    // () -> getEarlyBeanReference 表示一个匿名的ObjectFactory类, 其 getBean 方法调用了 getEarlyBeanReference() 方法
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    .....
}

// 表示调用 ObjectFactory 工厂的 getBean 时, 会执行 getEarlyBeanReference 方法   
// 该方法会处理一个 SmartInstantiationAwareBeanPostProcessor 类型的 BeanPostProcessor, 获取 earlyReference 的代理对象  
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;
}

addSingletonFactory()方法如下. 可以发现, 创建 bean 的 single factory 被放到三级缓存

// DefaultSingletonBeanRegistry.java

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

从缓存中获取 bean 的方法在 getSingleton(), 在获取 singleton bean 作为属性注入时,
(1) 首先从 L1 cache 中获取 singleton bean
(2) 如果找不到, 可能是正在创建, 所以去 L2 cache 中查找
(3) 如果找不到, 就去 L3 级的 cache 中找到创建 bean 的工厂, 调用工厂的 #getBean() 获取 singleton bean, 再将创建出的 bean 放到二级缓存中. 这样, 就把 bean 的缓存从三级缓存提升到二级缓存中;

// DefaultSingletonBeanRegistry.java

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;
}

问题

( 1 ) 生么时候, bean 从二级缓存提升到一级缓存中?
在 bean 完成属性注入等一些列操作后, bean 已经构建完毕, 将加入一级缓存, 并从二三级缓存中删除, 相当于缓存提升

( 2 ) 为什么要有第一级缓存singletonObjects
1.先说一级缓存 singletonObjects。实际上,一级依赖已经可以解决循环依赖的问题,假设两个beanA和beanB相互依赖,beanA被实例化后,放入一级缓存,即使没有进行初始化,但是beanA的引用已经创建(栈到堆的引用已经确定),其他依赖beanB已经可以持有beanA的引用,但是这个bean在没有初始化完成前,其内存(堆)里的字段、方法等还不能正常使用,but,这并不影响对象之间引用持有;这个时候beanA依赖的beanB实例化,beanB可以顺利拿到beanA的引用,完成beanB的实例化与初始化,并放入一级缓存,在beanB完成创建后,beanA通过缓存顺利拿到beanB的引用,至此,循环依赖只需一层缓存就能完成。
2.一级缓存的关键点在与:bean实例化与初始化的分离。从JVM的角度,实例化后,对象已经存在,其内的属性都是初始默认值,只有在初始化后才会赋值,以及持有其他对象的引用。通过这个特性,在实例化后,我们就可以将对象的引用放入缓存交给需要引用依赖的其他对象,这个过程就是提前暴露。

( 3 ) 说说第三级缓存singletonFactories
上述我们通过一级缓存已经拿到的对象有什么问题?根本问题就是,我们拿到的是bean的原始引用,如果我们需要的是bean的代理对象怎么办?Spring里充斥了大量的动态代理模式的架构,典型的AOP就是动态代理模式实现的,再比如我们经常使用的配置类注解@Configuration在缺省情况下(full mode),其内的所有@Bean都是处于动态代理模式,除非手动指定proxyBeanMethods = false将配置转成简略模式(lite mode)。所以,Spring在bean实例化后,将原始bean放入第三级缓存singletonFactories中,第三级缓存里实际存入的是ObjectFactory接口签名的回调实现。通过第三级缓存我们可以拿到可能经过包装的对象,解决对象代理封装的问题。

( 4 ) 为什么需要earlySingletonObjects这个二级缓存?并且,如果只有一个缓存的情况下,为什么不直接使用singletonFactories这个缓存,即可实现代理又可以缓存数据。
从软件设计角度考虑,三个缓存代表三种不同的职责,根据单一职责原理,从设计角度就需分离三种职责的缓存,所以形成三级缓存的状态。再次说说三级缓存的划分及其作用。
一级缓存singletonObjects是完整的bean,它可以被外界任意使用,并且不会有歧义。
二级缓存earlySingletonObjects是不完整的bean,没有完成初始化,它与singletonObjects的分离主要是职责的分离以及边界划分,可以试想一个Map缓存里既有完整可使用的bean,也有不完整的,只能持有引用的bean,在复杂度很高的架构中,很容易出现歧义,并带来一些不可预知的错误。
三级缓存singletonFactories,其职责就是包装一个bean,有回调逻辑,所以它的作用非常清晰,并且只能处于第三层。
在实际使用中,要获取一个bean,先从一级缓存一直查找到三级缓存,缓存bean的时候是从三级到一级的顺序保存,并且缓存bean的过程中,三个缓存都是互斥的,只会保持bean在一个缓存中,而且,最终都会在一级缓存中。

cyclicDependency.jpeg

你可能感兴趣的:(spring 循环依赖处理)