Spring 循环依赖

1,471 阅读4分钟

概述

本文将描述, Spring 循环依赖的过程和实现原理。 spring 版本: 5.1.14

Spring 循环依赖

在下文中我将对循环依赖的过程和实现做详细的描述, 以 Spring 的源码过程为主导逐步分析。 里面包含一些属性赋值的前置知识,可以在前面几篇文章中获取相关的信息。

Spring 解决了那些循环依赖场景

Spring 只支持单例非懒加载场景的循环依赖,不能解决构造注入,以及懒加载的循环依赖问题。

Spring 循环依赖定义

顾名思义,循环依赖就是说有 A, B 两个类在 A 中引用 B,并且在 B 中引用 A。这个就是一个简单的循环依赖。
例如: 在A类中通过构造方法注入B,在B类中通过构造方法注入。这种方式互相注入 Spring IOC 容器是无法完成的,回抛出 BeanCurrentlyInCreationException
依赖注入过程中, Bean A 与 Bean B 之间的循环依赖关系,需要其中一个 Bean 在未完成初始化之前被另外一个 Bean 注入。这就是一个类似先有鸡和先有蛋的问题。
一个简单的例子:

// A 依赖 B
@Component
public class A {

	@Autowired
	private B b;
}

// B 依赖 A
@Component
public class B {

	@Autowired
	private A a;
}

Spring 循环依赖处理过程

下面是我自己花的一个草图,前置条件是: A 类,B类互为循环依赖,当 a 先加载 b 后加载,这个时候应该是从 doCreateBean 开始逐步查找。 Spring 循环依赖图解 以上面 A 类 和 B 类的循环依赖过程来说

  1. 首先入口方法 AbstractAutowireCapableBeanFactory#doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) 以此为入口经过了一下的几个流程处理
//1. 创建 Bean 实例可以获取到一个 BeanWrapper 对象
instanceWrapper = createBeanInstance(beanName, mbd, args);
//2. 合并BeanDefinitionPostProcessor
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
//3. 属性填充
populateBean(beanName, mbd, instanceWrapper);
//4. Bean 初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
//5. 注册 Bean 的销毁方法
registerDisposableBeanIfNecessary(beanName, bean, mbd);
  1. 循环依赖的几个步骤, (1). 在属性填充 populateBean 方法调用之前,将 instanceWrapper 放入三级缓存 singletonFactories 中,调用代码:addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 注意,这里三级缓存中存放的是 ObjectFactory 对象工厂,目的是为了解决该对象可能被代理,如果我们直接缓存该对象那么就会存在Bean对象 a 的原始对象和代理对象最终不一致的情况,具体的 AOP 代理部分我会在后续的文章中提到。首先 ()->{} 用法是一个标准的 拉姆达表达式。我们再来看看 addSingletonFactory 方法是如何实现的。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            //三级缓存,缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的。
            this.singletonFactories.put(beanName, singletonFactory);
            //二级缓存,缓存的是早期的bean对象。表示Bean的生命周期还没走完就把这个Bean放入了earlySingletonObjects。
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

(2). 后续会经历,A类 去查找依赖 B类,然后通过 getBean 方法创建 B 类, 然后 B 类又依赖 A 类那么再会通过 getBean 方法来查找 a 类。经过这些流程我们就来到了 。AbstractBeanFactory#doGetBean 方法,然后再通过 getSingleton 来获取Bean, 我们先来看看这个方法的实现是怎么样的:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// 单例池中获取是否存在
	Object singletonObject = this.singletonObjects.get(beanName);
	// 正在创建
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			// 二级缓存 earlySingletonObjects 存放的有可能是经过AOP增强的代理对像
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				// 三级缓存 singletonFactories 用于 Bean 的早期暴露以解决循环依赖
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					singletonObject = singletonFactory.getObject();
					// 放到二级缓存中
					this.earlySingletonObjects.put(beanName, singletonObject);
					// 从三级缓存中移除
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return singletonObject;
}

由于我们 A 类已经在创建中,所以我们拿到的应该是一个 A 类的 ObjectFactory 此时,Bean a 就由3级缓存 singletonFactories 进入二级缓存 earlySingletonObjects, 如果此时多次访问,那么我们可以直接在二级缓存 earlySingletonObjectsget

(3). 那么此时 Bean b 也同样要走这样的流程,最终 b 完成创建之后会来到 addSingleton(beanName, singletonObject); 因为先创建的 a 并且 a 依赖 b 所以在完成 a 创建之前需要先完成 b 的创建。我们再说说 addSingleton 方法, 它主要就是将 bean 从二,三级缓存中移除,然后放入一级缓存 singletonObjects 中。

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 循环依赖总结

  1. 三级缓存 registeredSingletons 对于我看来作用主要是用来:作为一个早期对象的暴露,他可能是一个代理工厂,也可能是一个原始对象,这里具体取决于上下文场景。
  2. 二级缓存 earlySingletonObjects 作为一个不完整的对象来暴露,此时对象没有完成属性的赋值,但是它里面的的对象可以作为自动注入的对象,注入给使用者。
  3. 一级缓存 singletonObjects 存放的一个完整的对象,标志单实例 Bean 已经完成创建。

Spring 源码解析