Spring系列 循环依赖

news/2024/10/22 13:27:44/

文章目录

  • 注入方式
  • 循环依赖的场景
  • 单例创建流程
    • getSingleton
    • createBean
      • doCreateBean
      • createBeanInstance
  • 循环依赖分析
    • 为什么都使用构造函数无法解决?
    • 为什么使用@Autowired可以解决?
    • 为什么要添加到 earlySingletonObjects 缓存中?
    • allowCircularReferences 的作用
    • 只用二级缓存能解决循环依赖吗?
    • 二级缓存什么时候放入一级缓存?
    • 循环依赖的暴露顺序
  • 参考资料

注入方式

一般注入方式可以分为自动注入和手动注入
自动注入我分为以下几类

  1. 构造函数参数
  2. setter 方法 + @Autowire,字段 + @Autowire/@Resource

除了自动注入,在 Bean 的生命周期回调中通过 ApplicationContext 进行手动赋值,我将这种方式称为手动注入

循环依赖的场景

下面是不同场景下循环依赖的解决情况的测试结果

依赖情况依赖注入方式循环依赖是否被解决
AB相互依赖均采用setter方法注入
AB相互依赖
allowCircularReferences = false
均采用setter方法注入
AB相互依赖均采用构造器注入
AB相互依赖A中注入B的方式为setter方法,
B中注入A的方式为构造器
AB相互依赖
allowCircularReferences = false
A中注入B的方式为setter方法,
B中注入A的方式为构造器
AB相互依赖B中注入A的方式为setter方法,
A中注入B的方式为构造器

单例创建流程

getSingleton

首先调用 getBean 时会先到单例缓存中去获取一次
在这里插入图片描述

getSingleton(beanName, true)这个方法实际上就是到缓存中尝试去获取Bean,在org.springframework.beans.factory.support.DefaultSingletonBeanRegistry这个类中有三个Map,对应的就是这些缓存

  1. singletonObjects,存储的是所有创建好了的单例Bean
  2. earlySingletonObjects,完成实例化,但是还未进行属性注入及初始化的对象
  3. singletonFactories,提前暴露的一个单例工厂

如下图所示:

在这里插入图片描述

下面我简化了一下 doGetBean 的部分,保留核心流程

java">protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {// 最终要返回的对象Object beanInstance;// Eagerly check singleton cache for manually registered singletons.Object sharedInstance = getSingleton(beanName);if (sharedInstance != null && args == null) {// ... 此处简化了部分代码beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);} else {// ... 此处简化了部分代码if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);} catch (BeansException ex) {// ... 此处简化了部分代码destroySingleton(beanName);throw ex;}});beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}// ... 此处简化了部分代码}return beanInstance;
}

当获取一个 Bean 时会先去缓存中查一次,如果没有则走 getSingleton 带 ObjectFactory 参数的实现

java">Object getSingleton(String beanName, ObjectFactory<?> singletonFactory)

这里传入的 ObjectFactory 是一个 lambda 表达式,就简单调用了 createBean 方法并返回,这个方法就是用来创建Bean的。

同样,简化后的方法内容如下:

java">public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {// ... 此处省略部分代码beforeSingletonCreation(beanName);boolean newSingleton = false;// ... 此处省略部分代码try {// 调用 createBean singletonObject = singletonFactory.getObject();newSingleton = true;} catch (IllegalStateException ex) {// ... 此处省略部分代码} finally {// ... 此处省略部分代码afterSingletonCreation(beanName);}if (newSingleton) {addSingleton(beanName, singletonObject);}}return singletonObject;}
}

调用 createBean 的前后分别调用了 beforeSingletonCreationafterSingletonCreation,这两个方法分别用于标记一个 beanName 是否在创建中以及清除标记

java">protected void beforeSingletonCreation(String beanName) {if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}
}protected void afterSingletonCreation(String beanName) {if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");}
}

最后调用了 addSingleton 方法,此方法的内容就是将 createBean 创建的对象放入 singletonObjects 这个缓存中,并且从 singletonFactories 和 earlySingletonObjects 这两个缓存中移除当前创建的这个 beanName

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

从这我们就知道一个获取单例的一个流程:

  1. 先从单例池中获取一次,如果没有,则进行创建
  2. 创建前,标记当前 beanName 为创建中
  3. 创建 beanName 对应的对象
  4. 创建后,清除标记
  5. 将创建的对象添加到 signletonObjects 缓存中

在这里插入图片描述

createBean

createBean 这个方法是在 AbstractBeanFactory 的子类 AbstractAutowireCapableBeanFactory 中实现的

java">protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {try {// 可以通过 BeanPostProcessors 返回一个代理对象Object bean = resolveBeforeInstantiation(beanName, mbdToUse);if (bean != null) {return bean;}} catch (Throwable ex) {// ... 省略部分代码}try {Object beanInstance = doCreateBean(beanName, mbdToUse, args);// ... 省略部分日志代码return beanInstance;} catch (XxxException... ex) {// ... 省略部分异常处理代码throw ex;}
}

resolveBeforeInstantiation 这部分先不看,这是关于代理对象的逻辑,doCreateBean 是实际创建对象的逻辑。现在我们只需要知道如果 resolveBeforeInstantiation 方法返回了一个对象,那么 createBean 方法根本不会走 doCreateBean ,也就是不回去创建当前这个 beanName 的对象。

doCreateBean

java">protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {// 实例化 BeanBeanWrapper instanceWrapper = null;if (mbd.isSingleton()) {// 先从 factoryBeanInstanceCache 这个缓存中获取instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}if (instanceWrapper == null) {// 创建对象instanceWrapper = createBeanInstance(beanName, mbd, args);}Object bean = instanceWrapper.getWrappedInstance();Class<?> beanType = instanceWrapper.getWrappedClass();if (beanType != NullBean.class) {mbd.resolvedTargetType = beanType;}// 执行 MergedBeanDefinitionPostProcessorsynchronized (mbd.postProcessingLock) {if (!mbd.postProcessed) {applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);// ...mbd.postProcessed = true;}}// 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) {// ... 省略部分日志记录代码addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}// Initialize the bean instance.Object exposedObject = bean;try {populateBean(beanName, mbd, instanceWrapper);exposedObject = initializeBean(beanName, exposedObject, mbd);} catch (Throwable ex) {// ...}if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {if (exposedObject == bean) {exposedObject = earlySingletonReference;}else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {String[] dependentBeans = getDependentBeans(beanName);Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);for (String dependentBean : dependentBeans) {if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {actualDependentBeans.add(dependentBean);}}if (!actualDependentBeans.isEmpty()) {// ... throw}}}}// Register bean as disposable.try {registerDisposableBeanIfNecessary(beanName, bean, mbd);} catch (BeanDefinitionValidationException ex) {// ...}return exposedObject;
}

在这里插入图片描述

addSingletonFactory 的目的就是将 ObjectFactory 添加到 singletonFactories 这个缓存中

java">protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {// ...synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}
}

执行 addSingletonFactory 之前有个前提条件,就是 (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
前面已经说过,createBean 之前,会标记当前 beanName 在创建中,所以 isSingletonCurrentlyInCreation(beanName)); 是为 true 的,
在这里插入图片描述

getEarlyBeanReference 这个方法是调用 SmartInstantiationAwareBeanPostProcessor 这个扩展点的,它是在刚创建好像时进行调用,此时还未执行 populateBean 操作。

java">protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);}}return exposedObject;
}

createBeanInstance

这里要重点了解一下 createBeanInstance 方法,这个方法是创建一个对象的逻辑

java">protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {// Make sure bean class is actually resolved at this point.Class<?> beanClass = resolveBeanClass(mbd, beanName);// ...Supplier<?> instanceSupplier = mbd.getInstanceSupplier();if (instanceSupplier != null) {return obtainFromSupplier(instanceSupplier, beanName);}if (mbd.getFactoryMethodName() != null) {return instantiateUsingFactoryMethod(beanName, mbd, args);}// Shortcut when re-creating the same bean...boolean resolved = false;boolean autowireNecessary = false;if (args == null) {synchronized (mbd.constructorArgumentLock) {if (mbd.resolvedConstructorOrFactoryMethod != null) {resolved = true;autowireNecessary = mbd.constructorArgumentsResolved;}}}if (resolved) {if (autowireNecessary) {return autowireConstructor(beanName, mbd, null, null);} else {return instantiateBean(beanName, mbd);}}// Candidate constructors for autowiring?Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {return autowireConstructor(beanName, mbd, ctors, args);}// Preferred constructors for default construction?ctors = mbd.getPreferredConstructors();if (ctors != null) {return autowireConstructor(beanName, mbd, ctors, null);}// No special handling: simply use no-arg constructor.return instantiateBean(beanName, mbd);
}

循环依赖分析

为什么都使用构造函数无法解决?

因为创建 A 时, createBeanInstance 里如果使用构造函数进行对象的创建,那么会进行依赖注入,如果发现构造函数中依赖了 B,那么会通过 getBean 方法去获取 B 这个对象,因此会再次走创建单例的逻辑,于是又会碰到 B 的构造方法依赖 A 的情况,发生了循环依赖,因此会抛出下面的异常信息:Error creating bean with name ‘xxx’: Requested bean is currently in creation: Is there an unresolvable circular reference?

这个异常就是在 beforeSingletonCreation 方法抛出的
在这里插入图片描述

整个过程如下:

  1. 第一次标记了 A,还未清除 A,就创建 B
  2. 于是标记 B ,由于B依赖A,所以会再次标记 A 在创建中
  3. 由于 A 已被标记,所以直接抛异常

其实本质原因就是构造函数必须参数要有值才能创建这个对象

为什么使用@Autowired可以解决?

使用 @Autowired 时属性注入其实不是在 populateBean 方法中进行的,而是在 initializeBean 的 BeanPostProcessor 中进行的。
没有构造函数依赖的情况下,会默认通过无参构造创建,先创建这个对象,然后进行相关依赖属性的填充。所以区别就在于使用@Autowire可以创建对象,虽然此时这个对象还未进行属性填充,但是使用构造函数依赖连对象都不能创建。

因为 B 需要自动注入 A,所以在创建B的时候,又会去调用getBean(a),这个时候就又回到之前的流程了

在这里插入图片描述
但是不同的是,因为之前A在实例化后已经将其放入了三级缓存singletonFactories中,所以此时getBean(A)可以直接从缓存中获取

在这里插入图片描述

为什么要添加到 earlySingletonObjects 缓存中?

个人认为是为了性能考虑。举个例子:目前是只有 A 和 B 之间存在循环依赖,但是如果存在多层循环依赖的的话,如下所示。那么执行 doGetBean 方法时直接从而及缓存中就可以获取到,直接就返回了,就没有后续的那么多操作了

java">class A {B b;
}class B {A a;C c;
}class C {A a;B b;
}

allowCircularReferences 的作用

allowCircularReferences,当出现循环依赖时是否自动尝试解决,默认为 true。

在这里插入图片描述

直观点儿的解释就是控制是否将创建的 bean 加入 singletonFactories 缓存

在这里插入图片描述

如果是普通的 spring 项目,可以以下面的方式进行设置

java">@ComponentScan
public class Main {public static void main(String[] args) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();beanFactory.setAllowCircularReferences(false);AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(beanFactory);context.register(Main.class);context.refresh();}
}

如果是springboot环境下则需要配置spring.main.allow-circular-references=true

allowCircularReferences 为 false 的情况下即使都用@Autowire注入还是会报错。例如默认情况下 springboot 项目就会报错,因为 allowCircularReferences 这个配置默认是 false

java">Description:The dependencies of some of the beans in the application context form a cycle:┌─────┐
|  a
↑     ↓
|  b (field org.example.springboot.config.A org.example.springboot.config.B.a)
└─────┘Action:Relying upon circular references is discouraged and they are prohibited by 
default. Update your application to remove the dependency cycle between 
beans. As a last resort, it may be possible to break the cycle automatically 
by setting spring.main.allow-circular-references to true.

因为 allowCircularReferences 是 false

在这里插入图片描述

allowCircularReferences 是 false 的情况下是根本不会调用 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 这句代码的。也就是

在这里插入图片描述
所以这里是一定要求 singletonObjects 或者 earlySingletonObjects 是有所需的对象的
在这里插入图片描述

只用二级缓存能解决循环依赖吗?

能解决非构造函数循环依赖的根本条件是:A 依赖 B 时,要去创建 B ,此时 A 已经创建。我们需要将 A 或者获取 A 的逻辑保存在一个位置,后面 B 依赖 A 时能够直接获取到。只使用一个缓存也可以实现这个目的。那为什么要设计这 3 个缓存呢?

个人理解:Spring 容器本质就是一个Map,其key为beanName,value为对象。而我们通过BeanFactory#getBean获取的Bean 需要是可以使用的。由于循环依赖的存在,一个对象的依赖是需要在该对象之前先创建好的,所以这个时候不可避免的出现了这种虽然实例化了,但是属性还未填充的Bean,这种半成品Bean也是不可使用的。

如果一个不可使用的 Bean 还未创建好,就通过 getBean 去获取,就可能会出错。并且循环依赖场景如果想要靠 Spring 自身解决循环依赖是一定会存在需要早期暴露的对象的。比如 A 依赖 B,那么创建 B 时不可能拿到已经创建好的 A 对象,这两种对象一定要分开存放。所以这个时候需要两个 Map 来存放,一个 singletonObjects 放已经创建好的,另外一个 earlySingletonObjects 放需要早期暴露的对象。

那么为什么需要 singletonFactories 这个ObjectFactory工厂的缓存呢?

个人认为和 AOP 应该没有关系,AOP 只是利用这几个缓存实现,而非为了 实现 AOP 才使用 singletonFactories 这个缓存。使用 singletonFactories 这个就是为了不在对象未完全创建时进行暴露。

因为创建 Bean 之后紧接着又要调用其他 API ,比如填充属性 populateBean、初始化 initializeBean 等,在这些 API 中可以通过 ApplicationContextAware、BeanFactoryAware 等方式拿到 BeanFactory 并调用 getBean 方法。ObjectFactory 的作用只是将这个对象暴露的时间延迟到属性填充和初始化完毕之后了。直到 ObjectFactory 调用之前, earlySingletonObjects 都是没有值的,而 ObjectFactory 只会调用一次,调用后就直接删除了。

在这里插入图片描述

举个例子,现在 A 与 B、C 分别形成循环依赖。当 A 对象创建好,但是还未填充属性时,这个时候发现 A 依赖 B,于是创建 B。这个时候 A 应该放在哪呢?首先singletonObjects 肯定是不能放的,那么是放 earlySingletonObjects 吗?也不行,因为 getBean 只能从某一个地方取,不管从哪儿取拿到的都是不能用的早期对象。所以这个时候不能直接放对象,而是放一个 ObjectFactory,此时该对象还没暴露出去的,不能通过 API 拿到。真正想要获取的时候再通过 ObjectFactory 进行获取。为什么是从 singletonFactories 中获取放入 earlySingletonObjects 呢?因为现在不只 B 需要 A,C 也需要 A,但是 B 获取 A 时是不知道 C 的存在的,此时 A 仍是早期对象。

二级缓存什么时候放入一级缓存?

放入一级缓存只有一个方式,调用 addSingleton 方法,这个是内部 API
在这里插入图片描述

而调用此方法只有 2 个地方

  1. 一是Object getSingleton(String beanName, ObjectFactory<?> singletonFactory)在依赖注入完成时

在这里插入图片描述

  1. 通过SingletonBeanRegistry#registerSingleton这个public API手动注册单例对象

在这里插入图片描述

依赖注入完成时放入

存在循环依赖的情况下 getBean 是一个递归的过程。递归开始的地方有 2 个,都在 doCreateBean 方法里,一个是根据构造函数创建对象时,

在这里插入图片描述

另外一个是对象创建好之后进行初始化的时候

在这里插入图片描述
在 createBean 方法里,如果允许暴露早期对象,会自动调用 getSingleton ,此时 allowEarlyReference 为 false,因为此时 earlySingletonObjects 这个缓存里已经有了(退出递归的结果就是报错或者完成依赖注入被放入 earlySingletonObjects 缓存)
在这里插入图片描述

这个调用的目的就是将 earlySingletonObjects 缓存再放入 singletonObjects 缓存

java">Object earlySingletonReference = getSingleton(beanName, false);

如下图所示

在这里插入图片描述

循环依赖的暴露顺序

A 依赖 B ,B 先暴露,最后才是 A,因为是个递归过程

参考资料

  1. https://developer.aliyun.com/article/766880

http://www.ppmy.cn/news/1538375.html

相关文章

AIGC的底层技术:生成对抗网络(GAN)、变分自编码器(VAE)、预训练模型(如GPT、BERT等)

引言 随着人工智能生成内容(AIGC)技术的快速发展,我们看到它在文本、图像、音频和视频生成等领域的广泛应用。AIGC的核心在于底层技术的支持,本文将深入探讨AIGC的底层技术,包括生成对抗网络(GAN)、变分自编码器(VAE)、预训练模型(如GPT、BERT等),以及相关的深度学…

Spring源码学习(拓展篇):SpringMVC中的异常处理

目录 前言SimpleMappingExceptionResolver通过接口 HandlerExceptionResolver 实现通过 ExpceptionHander ControllerAdvice 注解实现&#xff08;推荐&#xff09; 前言 SpringMVC的异常处理主要有以下三种方式&#xff1a; 使用SpringMVC自带的异常处理类&#xff1a;Simpl…

git的提取和拉取有啥区别

在Git中&#xff0c;“提取”&#xff08;fetch&#xff09;和“拉取”&#xff08;pull&#xff09;是两个不同的概念&#xff0c;它们分别对应不同的操作行为&#xff1a; 提取&#xff08;Fetch&#xff09; git fetch 命令主要用于从远程仓库下载最新的提交信息到本地仓库…

《机器学习与数据挖掘综合实践》实训课程教学解决方案

一、引言 随着信息技术的飞速发展&#xff0c;人工智能已成为推动社会进步的重要力量。作为人工智能的核心技术之一&#xff0c;机器学习与数据挖掘在各行各业的应用日益广泛。本方案旨在通过系统的理论教学、丰富的实践案例和先进的实训平台&#xff0c;帮助学生掌握机器学习…

【优选算法】(第三十五篇)

目录 验证栈序列&#xff08;medium&#xff09; 题目解析 讲解算法原理 编写代码 N叉树的层序遍历&#xff08;medium&#xff09; 题目解析 讲解算法原理 编写代码 验证栈序列&#xff08;medium&#xff09; 题目解析 1.题目链接&#xff1a;. - 力扣&#xff08;L…

vue3学习:数字时钟遇到的两个问题

在前端开发学习中&#xff0c;用JavaScript脚本写个数字时钟是很常见的案例&#xff0c;也没什么难度。今天有时间&#xff0c;于是就用Vue的方式来实现这个功能。原本以为是件非常容易的事&#xff0c;没想到却卡在两个问题上&#xff0c;一个问题通过别人的博文已经找到答案&…

vue3中的computed属性

模板界面&#xff1a; <template><div class"person"><h2>姓&#xff1a; <input type"text" v-model"person.firstName" /></h2><h2>名&#xff1a; <input type"text" v-model"person…

Maven打包运行,引入三方jar及打包,不导入本地库的方法

Maven打包运行&#xff0c;引入三方jar及打包&#xff0c;不导入本地库的方法 maven、打包、springboot、jar、本地、引入背景 业务系统要对接某硬件&#xff0c;需要用到其三方jar&#xff0c;maven官方仓库没有这个&#xff0c;我也没有maven&#xff0c;又不想mvn install…