Spring源码-IOC部分-Spring是如何解决Bean循环依赖的【6】
实验环境:spring-framework-5.0.2、jdk8、gradle4.3.1
- Spring源码-IOC部分-容器简介【1】
- Spring源码-IOC部分-容器初始化过程【2】
- Spring源码-IOC部分-Xml Bean解析注册过程【3】
- Spring源码-IOC部分-自定义IOC容器及Bean解析注册【4】
- Spring源码-IOC部分-Bean实例化过程【5】
- Spring源码-IOC部分-Spring是如何解决Bean循环依赖的【6】
本文重点将一下Spring是如何解决循环依赖的,其核心逻辑在getSingleton方法中。 在正常情况下,该代码很普通,只是正常的检查下我们要拿的 bean 实例是否存在于缓存中,如果有就返回缓存中的 bean 实例,否则就返回 null,但这段代码对于Spring来说却是解决循环引用的核心代码。
解决循环引用逻辑:使用构造函数创建一个 “不完整” 的 bean 实例(之所以说不完整,是因为此时该 bean 实例还未初始化),并且提前曝光该 bean 实例的 ObjectFactory(提前曝光就是将ObjectFactory 放到 singletonFactories 缓存)。通过 ObjectFactory 我们可以拿到该 bean 实例的引用,如果出现循环引用,我们可以通过缓存中的 ObjectFactory来拿到 bean 实例,从而避免出现循环引用导致的死循环。这边通过缓存中的 ObjectFactory 拿到的 bean 实例虽然拿到的是 “不完整” 的 bean 实例,但是由于是单例,所以后续初始化完成后,该 bean实例的引用地址并不会变,所以最终我们看到的还是完整 bean 实例。
我们先看一下getSingleton方法执行流程
DefaultSingletonBeanRegistry#getSingleton方法
@Override
@Nullable
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
/**
* singletonObjects 缓存:beanName -> 单例 bean 对象。
* earlySingletonObjects 缓存:beanName -> 单例 bean 对象,该缓存存放的是早期单例 bean 对象,可以理解成还未进行属性填充、初始化。
* singletonFactories 缓存:beanName -> ObjectFactory。
* singletonsCurrentlyInCreation 缓存:当前正在创建单例 bean 对象的 beanName 集合。
* singletonObjects、earlySingletonObjects、singletonFactories 在这边构成了一个类似于 “三级缓存” 的概念。
*/
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1.【从一级缓存里面获取】从单例对象缓存中获取beanName对应的单例对象
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 3.加锁进行操作
synchronized (this.singletonObjects) {
// 4.【从二级缓存里面获取】从早期单例对象缓存中获取单例对象
// 之所称成为早期单例对象,是因为earlySingletonObjects里的对象的都是通过提前曝光的ObjectFactory创建出来的,还未进行属性填充等操作
singletonObject = this.earlySingletonObjects.get(beanName);
// 5.如果在早期单例对象缓存中也没有,并且允许创建早期单例对象引用
if (singletonObject == null && allowEarlyReference) {
// 6.【从三级缓存里面获取】从单例工厂缓存中获取beanName的单例工厂
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 7.如果存在单例对象工厂,则通过工厂创建一个单例对象
singletonObject = singletonFactory.getObject();
// 8.将通过单例对象工厂创建的单例对象,放到早期单例对象缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
// 9.移除该beanName对应的单例对象工厂,因为该单例工厂已经创建了一个实例对象,并且放到earlySingletonObjects缓存了,
// 因此,后续获取beanName的单例对象,可以通过earlySingletonObjects缓存拿到,不需要在用到该单例工厂
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
这里的代码还是比较简单的,就是依次从一、二、三级缓存里面获取bean,没有返回null。那么不禁有几个疑问?
1、bean实例是在什么时候放入三级缓存的?
bean实例化的时候,会调用doGetBean方法,方法里面会先调用getSingleton方法从缓存里面获取,获取不到才会调用createBean方法来创建bean。
在创建bean的时候,会先通过反射或cglib实例化一个早期bean对象(未进行属性注入的对象),然后调用addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));将早期bean实例放入了三级缓存中。之后是属性的注入与bean的初始化工作。
AbstractAutowireCapableBeanFactory#doCreateBean方法
/**
* 真正创建bean的方法
*/
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// 封装被创建的bean对象
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// TODO【重要】 这一步创建了bean实例,只是一个早期的对象,还没有填充属性值
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = instanceWrapper.getWrappedInstance();
// 获取实例化对象的类型
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// 调用post-processors后置处理器
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}
// 向容器中缓存单例模式的bean,防止循环引用
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// TODO【重要】 向三级缓存添加bean实例 匿名内部类,防止循环引用,尽早持有对象的引用
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 初始化bean实例,exposedObject在初始化完成之后返回依赖注入完成之后的bean
Object exposedObject = bean;
try {
// TODO 【重要】 bean属性依赖注入,并且将bean定义中配置的属性值赋值给实例对象
populateBean(beanName, mbd, instanceWrapper);
// TODO 【重要】开始初始化bean对象 调用Aware接口方法 -> 调用BeanPostProcessor.before方法 -> 调用init-method方法 -> 调用BeanPostProcessor.after方法
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
if (earlySingletonExposure) {
// 获取指定名称的已注册的单例bean
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 根据名称获取已注册的bean和正在实例化的bean是同一个
if (exposedObject == bean) {
// 当前实例化的bean初始完成
exposedObject = earlySingletonReference;
}
// 当前bean依赖其他bean,且当发送循环引用时,不允许创建新的实例对象
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// 获取当前bean所依赖的其他bean
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
// 对依赖bean进行类型检查
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// 注册完成依赖注入的bean
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
2、bean实例是在什么时候放入二级缓存的?
其实就在DefaultSingletonBeanRegistry#getSingleton方法里面,从三级缓存获取到bean,然后放入二级缓存,再从三级缓存移除。
3、bean实例是在什么时候放入一级缓存的?
在doGetBean方法里,方法里面会先调用getSingleton方法从缓存里面获取,获取不到才会调用createBean方法来创建bean,创建bean的时候会经历实例化、属性注入、初始化等步骤,此时bean已经是个完整的bean对象了,返回出去,调用addSingleton方法存入一级缓存,然后从二级、三级缓存移除。
DefaultSingletonBeanRegistry#getSingleton方法
/**
* 通过singletonFactory创建bean实例,并存入一级缓存singletonObjects
*/
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 如果当前工厂单例正在销毁,则不允许创建,抛出异常
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
// 校验是否已经在创建中,抛出异常
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
// 【在这里创建实例】singletonFactory创建bean实例
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);
}
if (newSingleton) {
// TODO【重要】把bean实例加入缓存
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
4、缓存只一级会有什么问题?
如果只用一级缓存,会把实例化的早期bean放入一级缓存(此时还没有属性注入),此时bean还不可用,但通过getBean方法能直接从一级缓存里面拿,所以肯定是不行的。
5、缓存只用到两级会有什么问题?
如果把早期bean放入三级缓存,bean依赖注入和初始化完成后,不放二级缓存,直接放入一级缓存。如果是普通的bean的话,这种方式是可行的。
但对于实现AOP的代理bean就不可行了,如果我们还是按照这种方式来实现,三级缓存singleFactory.getObject()获取对象的时候,会生成一个新的代理对象。在bean属性注入的过程中,每一次调用都会生成一个新的代理对象。我们要的是单例bean,这种方式肯定是不行的。于是通过二级缓存,从三级缓存生成一个代理对象放入二级缓存,之后都从二级缓存里面取,保证了bean的单例。最后创建完成后再放入一级缓存。
参考资料:
《Spring5核心原理与30个类手写》作者 谭勇德
《Spring源码深度解析》作者 郝佳
《Spring技术内幕》作者 计文柯
Spring源码-IOC部分-Spring是如何解决Bean循环依赖的【6】的更多相关文章
- Spring源码-IOC部分-容器简介【1】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-IOC部分-容器初始化过程【2】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-IOC部分-Xml Bean解析注册过程【3】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-IOC部分-自定义IOC容器及Bean解析注册【4】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-IOC部分-Bean实例化过程【5】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-IOC部分-循环依赖-用实例证明去掉二级缓存会出现什么问题【7】
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...
- Spring源码-AOP部分-Spring是如何对bean实现AOP代理的
实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 历史文章 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] S ...
- Spring源码——IOC控制反转
1.基础知识 Spring有两个核心功能,分别是ioc和aop,其中ioc是控制反转,aop是切面编程. 在ioc中,还有一个名次叫DI,也就是依赖注入.嗯,好像IOC和DI是指同一个,好像又感觉他俩 ...
- Spring源码剖析4:其余方式获取Bean的过程分析
原型Bean加载过程 之前的文章,分析了非懒加载的单例Bean整个加载过程,除了非懒加载的单例Bean之外,Spring中还有一种Bean就是原型(Prototype)的Bean,看一下定义方式: 1 ...
随机推荐
- 【LeetCode】162. Find Peak Element 解题报告(Python)
[LeetCode]162. Find Peak Element 解题报告(Python) 标签(空格分隔): LeetCode 题目地址:https://leetcode.com/problems/ ...
- BZOJ 1857: [Scoi2010]传送带(三分套三分)
Time Limit: 1 Sec Memory Limit: 64 MB Submit: 2549 Solved: 1370 [Submit][Status][Discuss] Descriptio ...
- @Transactional 失效
1.Transactional注解标注方法修饰符为非public时,@Transactional注解将会不起作用. @Transactional void insertTestWrongModi ...
- Centos 切换中文输入法
切换输入法看起来是一个非常简单的操作,但是对于初学者来说,也并非那么简单,开始会发现按Ctrl+space无法切换中文输入法,原因是系统没有安装中文输入法,运行以下命令可以安装中文输入法: yum i ...
- [opencv]Rect集合象限法分类聚合 函数
/** * 矩形拟合 * @param mats * @return */ vector<Rect> PublicCardFrameDetection::RectContainFit(ve ...
- SpringBoot 之 多环境切换
方式一:分开多个配置文件 # /src/main/resources/application.yaml server: port: 8080 spring: profiles: active: dev ...
- 通过 v-once 创建低开销的静态组件
<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <script s ...
- 怎样在idea添加log日志 以及log4j2配置文件解读
网上找了很多篇文章,就数这篇比较全,从下载到配置都有讲到,解决从0开始接触java日志文件添加的各位同学.参考文章:https://www.cnblogs.com/hong-fithing/p/769 ...
- unittest_TestSuite测试套件(2)
在前面一章中演示了unittest如何执行一个简单的测试,但有两个问题: 我们知道测试用例的执行顺序是根据测试用例名称顺序执行的,在不改变用例名称的情况下,我们怎么来控制用例执行的顺序呢? 一个测试文 ...
- spring-aop(二)学习笔记
常用增强处理类型 增强处理类型 特点 before 前置增强处理,在目标方法前织入增强处理 ...