Spring源码 - Spring IOC 如何解决循环依赖

版本:
springboot :2.2.x
spring 5.x
java 8(springboot 2.x 都是 java 8)
在这里插入图片描述

我们的用例:

// 单例
@Component
public class CircleDependent1 {

	@Resource // 默认按名称注入
	private CircleDependent2 circleDependent2;
}
// 单例
@Component
public class CircleDependent2 {

	@Resource // 默认按名称注入
	private CircleDependent1 circleDependent1;
}

这时候,CircleDependent1、CircleDependent2 是形成循环依赖。
首先我们要明确,我们只探究 单例通过属性注入的循环依赖问题,因为一般我们说 Spring IOC 内部三级缓存循环依赖的知识,也只是针对这种场景进行讨论。

我们提到的三级缓存,指的是在 Bean 单例注册器 DefaultSingletonBeanRegistry 里的几个 Map,我们提前熟知,最好背下来

public class DefaultSingletonBeanRegistry ... {
    // 一级缓存:单例对象(成品)
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    // 二级缓存:早期单例对象
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);  
    // 三级缓存:单例工厂(解决AOP问题)
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}

SpringBoot 的启动链路,直到我们处理示例的问题,链路我贴在这里,具体启动流程可以参考 SpringBoot 的启动流程,这里不讨论

preInstantiateSingletons:897, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:879, AbstractApplicationContext (org.springframework.context.support)
refresh:551, AbstractApplicationContext (org.springframework.context.support)
refresh:141, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:931, SpringApplication (org.springframework.boot)
refreshContext:558, SpringApplication (org.springframework.boot)
run:388, SpringApplication (org.springframework.boot)
run:1415, SpringApplication (org.springframework.boot)
run:1399, SpringApplication (org.springframework.boot)
main:28, LaunchScriptTestApplication (org.springframework.boot.launchscript)

DefaultListableBeanFactory#preInstantiateSingletons 方法里,有以下抽象:

@Override
public void preInstantiateSingletons() throws BeansException {
  List<String> beanNames = new ArrayList<>(this.beanDefinitionNames); // 扫描注册的 Bean 名称集合
  for (String beanName : beanNames) { // 循环所有的 Bean 名称
    // ..
    getBean(beanName); // 通过名称获取 Bean,从这里打断点能很好地观察用户定义bean的生命周期
  }
  // ..
}

这段逻辑,就是将 Spring 扫描的 bean 名称集合,用 AbstractBeanFactory#getBean 进行统一的 Bean 获取(这里用 get,语义上为“获取”),其没有任何返回值,说明这个方法就是获取Bean,而且将其添加到容器中,方法是内部收敛的、闭环的。我们可以有两种猜测:

  1. void getBean(beanName) 会将 beanName 及其整条依赖链路全部创建完毕;
  2. void getBean(beanName) 只会将 beanName 创建,然后链接直接所需依赖的引用;

我们带着问题往下探究
在这里插入图片描述
AbstractBeanFactory#getBean 调用了 AbstractBeanFactory#doGetBean 执行获取操作,这个方法是通用的下层方法,由于可能从缓存中获取,所以不一定“创建”,才叫“获取”。

由于我们探究循环依赖问题,将无关代码移除,后续都只关注循环依赖相关部分。

此方法逻辑简化如下:

protected <T> T doGetBean(
       String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
       throws BeansException {
		// ..
    Object sharedInstance = getSingleton(beanName);  /// 从三级缓存中获取单例
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);  // 处理FactoryBean逻辑
    // Create bean instance.
    if (mbd.isSingleton()) {  // 单例作用域处理
       sharedInstance = getSingleton(beanName, () -> {  /// 通过回调创建单例(核心入口)
          try {
             return createBean(beanName, mbd, args);  /// 实际创建bean实例(模板方法)
          }
          catch (BeansException ex) {
             destroySingleton(beanName);  // 创建失败时清理单例缓存
             throw ex;
          }
       });
		// 由于是讲解循环依赖问题,不会走到原型作用域 prototype 的处理逻辑
    // ..
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
   	return (T) bean;  // 返回最终bean实例
}

步骤拆解如下:

doGetBean
  ├─ getSingleton 检查单例缓存 → 命中则直接返回
  ├─ 按作用域创建 Bean:
       └─ 单例 → getSingleton() (顺便注册 get不到bean就会触发的回调入口 createBean())
  ├─ getObjectForBeanInstance 处理 FactoryBean 的逻辑,获取 sharedInstance 的 bean
  └─ 返回获取到的bean

以上步骤中,获取 Bean,get不到 bean, 就执行回调入口 createBean 去创建 bean,逻辑如下:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
     Object singletonObject = this.singletonObjects.get(beanName); // 从第一级缓存(完全初始化的单例)获取
     if (singletonObject == null) { // 取不到
       	beforeSingletonCreation(beanName); // 标记bean正在创建(解决循环依赖的关键入口)
        try {
           singletonObject = singletonFactory.getObject(); /// 通过 singletonFactory 单例工厂 回调获取对象
           newSingleton = true; // 标记为新创建的实例
        }
        if (newSingleton) { // 如果是新创建的单例
           addSingleton(beanName, singletonObject); // 添加到一级缓存,清除二三级缓存
        }
     }
     return singletonObject; // 返回最终的单例实例
}

getSingleton 方法的逻辑简介:看看有没有(singletonObjects 一级缓存中存在),如果有则返回,没有则通过单例工厂创建,然后移动到一级缓存。

我们回到我们的例子,我们先处理的是 1对象(为了直观,之后将 CircleDependent1 简化描述为 1对象) 的逻辑不存在于三级缓存,所以通过 singletonFactory.getObject() 回调到匿名函数(回到前文可看到该匿名函数),调用 createBean 去创建 bean(此时 create ,而不是 get,注意这个差别,意味着这时候才是真正的创建逻辑,毕竟从缓存中没有拿到现有的只能去创建,命名十分合理)。

那么 createBean 的“创建” bean 的过程又是怎么样的呢?

我们的 AbstractBeanFactory 抽象工厂的实现是 AbstractAutowireCapableBeanFactory#createBean

@Override // 实例化
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
    throws BeanCreationException {
	// ..
  /// 正式创建 Bean 实例(包含 实例化 -> 属性注入 -> 初始化完整流程)
  Object beanInstance = doCreateBean(beanName, mbdToUse, args);
  return beanInstance; // 返回 bean 实例
}

调用了 doCreateBean 真正执行创建逻辑

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
		// .. 
		/// 创建 Bean 实例
		instanceWrapper = createBeanInstance(beanName, mbd, args); /// 核心 实例化,使用策略模式:构造器注入/工厂方法/简单实例化
		// 获取原始 bean 实例
		Object bean = instanceWrapper.getWrappedInstance(); 
		/// 提前暴露单例引用(解决循环依赖核心)
		// Eagerly cache singletons to be able to resolve circular references
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); /// 5. 将单例工厂添加到三级缓存(singletonFactories)提前暴露对象,解决循环依赖
		}
		/// Bean 初始化
		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			/// 以下是 spring 循环注入的关键,处理@Autowired/@Value/@Resource等注解,通过BeanPostProcessor进行属性注入,触发依赖bean的实例化(可能形成循环依赖)
			populateBean(beanName, mbd, instanceWrapper); 
			/// 属性注入阶段 三级缓存机制在这里面
			exposedObject = initializeBean(beanName, exposedObject, mbd); // 初始化阶段 (AOP代理在此阶段完成) 执行aware接口中的方法,初始化方法,完成AOP代理
		}
		return exposedObject; // 返回最终处理后的Bean(可能是代理对象)
	}

总体流程是 doGetBean 先 createBeanInstance 实例化 Bean,然后提前将 getEarlyBeanReference 这个方法包装成匿名函数(ObjectFactory 实现),addSingletonFactory 加到第三级缓存(可处理 AOP 代理对象注入问题),接下来再调用 populateBean 进行属性注入,再 initializeBean 初始化。

这四个步骤:

  1. createBeanInstance 实例化 Bean,通过调用链路 createBeanInstance -> instantiateBean -> SimpleInstantiationStrategy#instantiate -> BeanUtils#instantiateClass -> java.lang.reflect.Constructor#newInstance,最终就是构造器反射new出Instance(实例)
  2. addSingletonFactory ,添加单例工厂,如果是单例、允许循环引用、正在创建中,那么将单例工厂 ObjectFactory 的引用加入三级缓存,提前暴露引用,后续可以通过工厂函数对 getEarlyBeanReference 的调用而拿到引用(听说是屏蔽 AOP 代理细节,具体先不探究)
  3. populateBean,意思是 注入bean,是这几个步骤的核心,会进入一个很长的注入属性的逻辑链路里,反复迭代递归,方法栈会很“满”。
  4. initializeBean,意思是 初始化bean,完成 invokeAwareMethods 方法对 Aware 的执行、applyBeanPostProcessorsBeforeInitialization 完成初始化前的Bean后置处理器执行、 invokeInitMethods 初始化方法执行、applyBeanPostProcessorsAfterInitialization 完成初始化后的Bean后置处理器执行。(听说还有完成AOP代理,具体先不探究)

目前为止的流程示意图

getBean()  // 获取 Bean(不一定创建)
      └─> doGetBean() // 执行 获取 Bean
          └─> createBean()  // 创建 bean,如果 doGetBean 不在一级缓存中时回调执行
              ├─> createBeanInstance() // 创建bean实例,构造器反射创建对象
              ├─> addSingletonFactory()  // 添加单例工厂到三级缓存,暴露早期引用
              ├─> populateBean()       // 注入 bean
              └─> initializeBean()     // 初始化 bean

其他都很好理解,只有 populateBean 我们还不知道如何对 bean 进行 populate(注入)

在这里插入图片描述

populateBean 方法的简化逻辑如下:

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
    // .. 
		// 遍历所有的 BeanPostProcessor Bean 后置处理器
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof InstantiationAwareBeanPostProcessor) {
				PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);/// 后处理器处理属性值(重要:AutowiredAnnotationBeanPostProcessor/CommonAnnotationBeanPostProcessor负责在此注入依赖)
			}
			applyPropertyValues(beanName, mbd, bw, pvs);// 属性赋值的最终执行点
		}
}

核心步骤:

  1. getBeanPostProcessors 遍历所有的 BeanPostProcessor,其中,重点关注执行 InstantiationAwareBeanPostProcessor#postProcessProperties 接口,实现依赖注入,如果是 @Resource 会走到 CommonAnnotationBeanPostProcessor#postProcessProperties 实现 ,如果是 @Autowired 会走到 AutowiredAnnotationBeanPostProcessor#postProcessProperties 实现。其实底层都差不多,都是同样的元素注入流程。
  2. applyPropertyValues 进行属性最终赋值

以上步骤的核心流程,postProcessProperties 流程都差不多,由于我们用例是 @Resource,所以走到 CommonAnnotationBeanPostProcessor#postProcessProperties,持续往下看

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
    try {
       metadata.inject(bean, beanName, pvs); /// 注入
    }
    catch (Throwable ex) {
       throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
    }
    return pvs;
}
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
  	// .. 
     for (InjectedElement element : elementsToIterate) { // 遍历所有注入元素(字段或方法)
        element.inject(target, beanName, pvs); /// 执行单个元素的注入操作(多态调用,实际可能是字段或方法注入)
     }
}

在这里插入图片描述

elementsToIterate 是需要迭代注入的元素,这里 element 只有 2对象,跟我们的用例是一致的

protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
       throws Throwable {
  	 // ..只展示字段注入逻辑
     Field field = (Field) this.member;
     ReflectionUtils.makeAccessible(field);
     field.set(target, getResourceToInject(target, requestingBeanName));
}

getResourceToInject 字面含义为 获取资源去注入,那我们继续链路往下

CommonAnnotationBeanPostProcessor.ResourceElement#getResourceToInject 
└─> CommonAnnotationBeanPostProcessor#getResource 
		└─> CommonAnnotationBeanPostProcessor#autowireResource
				└─> AbstractAutowireCapableBeanFactory#resolveBeanByName
						└─> AbstractBeanFactory#getBean
								└─> AbstractBeanFactory#doGetBean

在这里插入图片描述

2对象的创建,最终走到了 doGetBean 的逻辑,这个逻辑如果仔细看,你会觉得似曾相识。

目前为止的流程示意图

getBean()  // 获取 Bean(不一定创建)
      └─> doGetBean() // 执行 获取 Bean
          └─> createBean()  // 创建 bean,如果 doGetBean 不在一级缓存中时回调执行
              ├─> createBeanInstance() // 创建bean实例,构造器反射创建对象
              ├─> addSingletonFactory()  // 添加单例工厂到三级缓存,暴露早期引用
              ├─> populateBean()       // 注入 bean
              		└─> getBean ()    // 依赖的 bean 通过其构建
              					└─> doGetBean()  // 执行 获取 Bean
              └─> initializeBean()     // 初始化 bean

其实又回到了原来的 AbstractBeanFactory#getBean -> AbstractBeanFactory#doGetBean 的逻辑,也就是说:

通过populateBean注入依赖又回到了我们的 doGetBean 去获取 Bean 的流程,也就是说是递归调用。我们知道,递归调用就会有递归调用的算法思路。Spring IOC 获取 Bean 递归三要素如下:(不是很准确的描述,仅供参考)

  • 函数:getBean(beanName)
  • 边界:inject 时,elementsToIterate 为空
  • 递推公式:getBean(beanName) = doGetBean(beanName) = populateBean(createBeanInstance(beanName))

这时候,我们就可以看出 addSingletonFactory 这个步骤的前瞻性了,假如没有提前暴露 1对象的引用到第三级缓存中,2对象创建时 populateBean 为了注入 1对象, getBean 不到就又得去 createBean 创建1对象,递归就进入了死循环。而提前暴露早期工厂到缓存中,就可以提前返回,注入时就可以直接使用。


其实到这里,已经将循环依赖解决的方法讲完了,但是其实还有很多地方是有疑问的:

  • 用两级缓存,一个暴露引用,一个成品仓库,其实就可以解决循环依赖,为什么Spring要用三级缓存?
  • 为什么暴露的早期引用是工厂匿名函数,而不是普通的引用呢?
  • 三级缓存分别扮演了什么角色?
  • 完整的数据链路是什么样的呢?

后续再一一解答,这里先挖个坑

微信公众号关注 「源码启示录」
持续推送全网最易懂最优质最原创的源码解析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值