Spring Bean怎么解决的循环依赖?

Spring Bean怎么解决循环依赖的问题?

摘要:原创出处「芋道源码」,谢谢!
作为「芋道源码」的忠实读者,本人对此文章进行记录,作为对Spring相关内容的理解和参考的资料。

分析 #doCreateBean(...) 方法的第三个过程:循环依赖处理。其实,循环依赖并不仅仅只是在 #doCreateBean(...) 方法中处理,而是在整个加载 bean 的过程中都有涉及。所以,本文内容并不仅仅只局限于 #doCreateBean(...) 方法,而是从整个 Bean 的加载过程进行分析。

什么是循环依赖?

循环依赖,其实就是循环引用,两个或者两个以上的Bean互相引用对方,形成一个闭环,例如A依赖B,B依赖C,C依赖A。如图:
image.png
循环依赖,其实就是一个死循环的过程,在初始化 A 的时候发现引用了 B,这时就会去初始化 B,然后又发现 B 引用 C,跑去初始化 C,初始化 C 的时候发现引用了 A,则又会去初始化 A,依次循环永不退出,除非有终结条件

Spring的循环依赖场景有两种:

  • 构造器的循环依赖。
  • field属性的循环依赖。

对于构造器的循环依赖,Spring无法解决,只能抛出BeanCurrentlyInCreationException 异常表示循环依赖,所以下面我们分析的都是基于 field 属性的循环依赖
Spring只能解决scope为singleton的循环依赖。

解决循环依赖

1. getSingleton

先从加载bean的最初始方法AbstractBeanFactory的 #doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) 方法开始。

#doGetBean(...) 方法中,首先会根据 beanName 从单例bean缓存中获取, 如果不为空则直接返回

// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);

调用 #getSingleton(String beanName, boolean allowEarlyReference) 方法,从单例缓存中获取。代码如下:

// DefaultSingletonBeanRegistry.java

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    
    
    // 从单例缓冲中加载 bean
    Object singletonObject = this.singletonObjects.get(beanName);
    // 缓存中的 bean 为空,且当前 bean 正在创建
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    
    
        // 加锁
        synchronized (this.singletonObjects) {
    
    
            // 从 earlySingletonObjects 获取
            singletonObject = this.earlySingletonObjects.get(beanName);
            // earlySingletonObjects 中没有,且允许提前创建
            if (singletonObject == null && allowEarlyReference) {
    
    
                // 从 singletonFactories 中获取对应的 ObjectFactory
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
    
    
                    // 获得 bean
                    singletonObject = singletonFactory.getObject();
                    // 添加 bean 到 earlySingletonObjects 中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 从 singletonFactories 中移除对应的 ObjectFactory
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

这个方法主要是从三个缓存中获取,分别是:singletonObjectsearlySingletonObjectssingletonFactories。三者定义如下:

// DefaultSingletonBeanRegistry.java
        
/**
 * Cache of singleton objects: bean name to bean instance.
 *
 * 一级缓存,存放的是单例 bean 的映射。
 *
 * 注意,这里的 bean 是已经创建完成的。
 *
 * 对应关系为 bean name --> bean instance
 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
/**
 * Cache of early singleton objects: bean name to bean instance.
 *
 * 二级缓存,存放的是早期半成品(未初始化完)的 bean,对应关系也是 bean name --> bean instance。
 *
 * 它与 {@link #singletonObjects} 区别在于, 它自己存放的 bean 不一定是完整。
 *
 * 这个 Map 也是【循环依赖】的关键所在。
 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    
/**
 * Cache of singleton factories: bean name to ObjectFactory.
 *
 * 三级缓存,存放的是 ObjectFactory,可以理解为创建早期半成品(未初始化完)的 bean 的 factory ,最终添加到二级缓存 {@link #earlySingletonObjects} 中
 *
 * 对应关系是 bean name --> ObjectFactory
 *
 * 这个 Map 也是【循环依赖】的关键所在。
 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16)
  • singletonObjects:单例对象的Cache
  • earlySingletonObjects: 提前曝光 的单例对象的Cache(也是通过ObjectFactory对象创建的bean的代理对象)。
  • singletonFactories:单例对象工厂的Cache( 提前曝光 的ObjectFactory对象)。

这三个,我们普遍称为 三级缓存

  • 第一级为 singletonObjects
  • 第二级为 earlySingletonObjects
  • 第三级为 singletonFactories

我们已经通过 #getSingleton(String beanName, boolean allowEarlyReference) 方法,看到他们是如何配合的。详细分析该方法之前,提下其中的 #isSingletonCurrentlyInCreation(String beanName) 方法和 allowEarlyReference 变量:

  • #isSingletonCurrentlyInCreation(String beanName) 方法:判断当前 singleton bean 是否处于创建中。bean 处于创建中,也就是说 bean 在初始化但是没有完成初始化,有一个这样的过程其实和 Spring 解决 bean 循环依赖的理念相辅相成。因为 Spring 解决 singleton bean 的核心就在于提前曝光 bean
  • allowEarlyReference 变量:从字面意思上面理解就是允许提前拿到引用。其实真正的意思是,是否允许从 singletonFactories 缓存中通过 #getObject() 方法,拿到对象。为什么会有这样一个字段呢?原因就在于 singletonFactories 才是 Spring 解决 singleton bean 的诀窍所在。

#getSingleton(String beanName, boolean allowEarlyReference) 方法,整个过程如下:

  • 首先,从一级缓存 singletonObjects 获取。
  • 如果没有,并且当前指定的 beanName 正在创建,就再从二级缓存 earlySingletonObjects 中获取。
  • 如果,还是没有获取到并且允许 singletonFactories 通过#getObject() 获取,则从三级缓存 singletonFactories 获取。如果获取到,则通过其 #getObject() 方法,获取对象,并将其加入到二级缓存 earlySingletonObjects 中,并从三级缓存 singletonFactories 删除。代码如下:
// DefaultSingletonBeanRegistry.java

singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
  • 三级缓存从这里升级到二级缓存了。
  • 所以,二级缓存存在的意义,就是缓存三级缓存中的ObjectFactory的 #getObject() 方法的执行结果,提早曝光的 单例 Bean对象。
2. addSingletonFactory

上面说的都是从缓存中获取数据,那么这些数据都是从什么时候放到缓存里面的呢? #doCreateBean() #addSingletonFactory() 这个方法用来添加bean到三级缓存中

// AbstractAutowireCapableBeanFactory.java

boolean earlySingletonExposure = (mbd.isSingleton() // 单例模式
        && this.allowCircularReferences // 运行循环依赖
        && isSingletonCurrentlyInCreation(beanName)); // 当前单例 bean 是否正在被创建
if (earlySingletonExposure) {
    
    
    if (logger.isTraceEnabled()) {
    
    
        logger.trace("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
    }
    // 提前将创建的 bean 实例加入到 singletonFactories 中
    // <X> 这里是为了后期避免循环依赖
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

// DefaultSingletonBeanRegistry.java

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    
    
	Assert.notNull(singletonFactory, "Singleton factory must not be null");
	synchronized (this.singletonObjects) {
    
    
		if (!this.singletonObjects.containsKey(beanName)) {
    
    
			this.singletonFactories.put(beanName, singletonFactory);
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}
}
  • 从这段代码我们可以看出,singletonFactories 这个三级缓存才是解决 Spring Bean 循环依赖的诀窍所在。同时这段代码发生在 #createBeanInstance(...) 方法之后,也就是说这个 bean 其实已经被创建出来了,但是它还不是很完美(没有进行属性填充和初始化),但是对于其他依赖它的对象而言已经足够了(可以根据对象引用定位到堆中对象),能够被认出来了。所以 Spring 在这个时候,选择将该对象提前曝光出来让大家认识认识。

另外,<X> 处的 #getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) 方法也非常重要,这里会创建早期初始化 Bean 可能存在的 AOP 代理等等。代码如下:

// AbstractAutowireCapableBeanFactory.java

/**
 * 对创建的早期半成品(未初始化)的 Bean 处理引用
 *
 * 例如说,AOP 就是在这里动态织入,创建其代理 Bean 返回
 *
 * Obtain a reference for early access to the specified bean,
 * typically for the purpose of resolving a circular reference.
 * @param beanName the name of the bean (for error handling purposes)
 * @param mbd the merged bean definition for the bean
 * @param bean the raw bean instance
 * @return the object to expose as bean reference
 */
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;
}
  • 这也是为什么 Spring 需要额外增加 singletonFactories 三级缓存的原因,解决 Spring 循环依赖情况下的 Bean 存在动态代理等情况,不然循环注入到别人的 Bean 就是原始的,而不是经过动态代理的!
3. addSingleton

我们发现三级缓存 singletonFactories 和 二级缓存 earlySingletonObjects 中的值都有出处了,那一级缓存在哪里设置的呢?在类 DefaultSingletonBeanRegistry 中,可以发现这个 #addSingleton(String beanName, Object singletonObject) 方法,代码如下:

// DefaultSingletonBeanRegistry.java

protected void addSingleton(String beanName, Object singletonObject) {
    
    
	synchronized (this.singletonObjects) {
    
    
		this.singletonObjects.put(beanName, singletonObject);
		this.singletonFactories.remove(beanName);
		this.earlySingletonObjects.remove(beanName);
		this.registeredSingletons.add(beanName);
	}
}

总结:

循环依赖的解决方案:

  • Spring 在创建 bean 的时候并不是等它完全完成,而是在创建过程中将创建中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 缓存中)。
  • 这样,一旦下一个 bean 创建的时候需要依赖 bean ,则直接使用 ObjectFactory 的 #getObject() 方法来获取了。

猜你喜欢

转载自blog.csdn.net/weixin_43743650/article/details/113864332