youyichannel

志于道,据于德,依于仁,游于艺!

0%

Spring 彻底解决循环依赖了吗?

首先这个答案是否定的,Spring只是解决了单例模式下属性依赖的循环问题,Spring为了解决单例的循环依赖问题,使用了三级缓存。

什么是循环依赖?

循环依赖也就是循环引用,即两个或者两个以上的 Bean 互相持有对象,最终形成闭环。

graph LR;
    A[ClassA] -->|dependency| B[ClassB]
    B -->|dependency| C[ClassC]
    C -->|dependency| A

Spring 的解决方案:

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

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

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

Spring 解决单例的循环依赖问题,使用到了 三级缓存:

  • 第一级缓存 (singletonObjects):单例对象缓存池,已经实例化并且属性赋值,属于成熟对象
  • 第二级缓存 (earlySingletonObjects):单例对象缓存池,已经实例化但尚未属性赋值,属于半成品对象
  • 第三级缓存 (singletonFactories):单例工厂缓存,用于创建某个对象。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Spring 首先从 singletonObjects(一级缓存)中尝试获取
Object singletonObject = this.singletonObjects.get(beanName);
// 若获取不到 && 对象还在创建中,则尝试从 earlySingletonObjects(二级缓存)中获取
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 若是仍是获取不到而且允许从 singletonFactories 经过getObject获取,
// 则经过singletonFactory.getObject()(三级缓存)获取
singletonObject = singletonFactory.getObject();
// 若是获取到了则将 singletonObject 放入到earlySingletonObjects,
// 也就是将三级缓存提高到二级缓存中

this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
  • isSingletonCurrentlyInCreation():判断当前单例bean是否正在建立中,也就是没有初始化完成。
  • allowEarlyReference :是否允许从singletonFactories中经过getObject拿到对象。

小结

分析 getSingleton() 的整个过程,Spring 首先从一级缓存 singletonObjects 中获取。若是获取不到,而且对象正在建立中,就再从二级缓存 earlySingletonObjects 中获取。若是仍是获取不到且容许 singletonFactories 经过 getObject() 获取,就从三级缓存singletonFactory.getObject()获取,若是获取到了则从三级缓存移动到了二级缓存。

从上面三级缓存的分析,可以看出来,Spring解决循环依赖的诀窍就在于 singletonFactories 三级缓存。这个缓存的类型是 ObjectFactory,定义:

@FunctionalInterface
public interface ObjectFactory<T> {

/**
* Return an instance (possibly shared or independent)
* of the object managed by this factory.
* @return the resulting instance
* @throws BeansException in case of creation errors
*/
T getObject() throws BeansException;
}

bean 建立过程当中,有两处比较重要的匿名内部类实现了该接口。一处是 Spring 利用其建立bean 的时候,另外一处就是:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {

// Instantiate the bean.
// ...
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// ...

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
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");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

// ...
return exposedObject;
}

此处就是解决循环依赖的关键,这段代码发生在 createBeanInstance 以后,也就是说单例对象此时已经被建立出来的。这个对象已经被生产出来了,虽然还不完美(尚未进行初始化的第二步和第三步),可是已经能根据对象引用能定位到堆中的对象,因此 Spring 此时将这个对象提早暴露出来,供后续使用。

Spring 为什么不能解决非单例属性之外的循环依赖

Spring 为什么不能解决构造器的循环依赖?

构造器注入形成的循环依赖:也就是 beanB 需要在 beanA 的构造函数中完成初始化,beanA 也需要在 beanB 的构造函数中完成初始化,根据 Spring Bean 的生命周期,这种情况的结果就是两个 bean 都不能完成初始化,循环依赖难以解决。

Spring 解决循环依赖主要是依赖三级缓存,但是在调用构造方法之前还未将对象对应的 ObjectFactory 放入三级缓存之中,因此后续的依赖调用构造方法的时候并不能从三级缓存中获取到依赖的 Bean,因此不能解决。

Spring 为什么不能解决作用域为 prototype 的 Bean 的循环依赖?

因为 Spring 不会缓存 「prototype」作用域的 bean,而 Spring 中循环依赖的解决正是通过缓存来实现的。

开发中自主解决循环依赖

1)生成代理对象产生的循环依赖

这类循环依赖问题解决方法很多,主要有:

  1. 使用 @Lazy 注解,延迟加载
  2. 使用 @DependsOn 注解,指定加载先后关系
  3. 修改文件名称,改变循环依赖类的加载顺序

2)构造器循环依赖

这类循环依赖问题可以通过使用 @Lazy 注解解决。

public A(@Lazy B b){
this.b = b;
}