Spring 循环引用(二)源码分析

Spring 循环引用(二)源码分析

Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html)

Spring 循环引用相关文章:

  1. 《Spring 循环引用(一)一个循环依赖引发的 BUG》:https://www.cnblogs.com/binarylei/p/10325698.html
  2. 《Spring 循环引用(二)源码分析》:https://www.cnblogs.com/binarylei/p/10326046.html

一、Spring 中单例 bean 的管理

Spring 对单例 bean 的管理都是在 DefaultSingletonBeanRegistry 中完成的,这里会涉及到其内部所使用的几个内部属性:

// 1.1 保存最终创建成功的单例 beanName -> beanInstance
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 1.2 中间变量,beanName -> Objectfactory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 1.3 中间变量,bean 还在创建的时候就可以获取,用于检测循环引用
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

这里涉及用于存储 bean 的不同的 Map,可能让人感到崩溃,简单解释如下:

  1. singletonObjects:用于保存 beanName 和创建 bean 实例之间的关系。beanName -> beanInstance
  2. singletonFactories:用于保存 beanName 和对象工厂的引用关系,一旦最终对象被创建(通过 objectFactory.getObject()),此引用信息将删除。beanName -> Objectfactory
  3. earlySingletonObjects:用于保存 beanName 和创建的原始 bean 的引用关系,注意这里是原始 bean,即使用工厂方法或构造方法创建出来的对象,一旦对象最终创建好,此引用信息将删除。 与 singletonObjects 的不同之处在于,此时 bean 还在创建过程中,而且之后还可以进行增强,也就是代理后这两个 bean 就不是同一个了。可以通过 getBean 方法获取到了,其目的是用来检测循环引用。

从上面的解释,可以看出,这 singletonFactories 和 earlySingletonObjects 都是一个临时的辅助状态。在所有的对象创建完毕之后,此两个对象的 size 都为 0。那么再来看下这两个对象如何进行协作:

(1) 方法1:创建单例对象

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 1. 将这个 bean 添加到 singletonsCurrentlyInCreation 集合中,这样就可以判断 bean 是否存在创建
beforeSingletonCreation(beanName);
// 2. 初始化 bean,委托给 ObjectFactory 完成
singletonObject = singletonFactory.getObject();
// 3. 从 singletonsCurrentlyInCreation 移除该 bean
afterSingletonCreation(beanName);
// 4. 创建完成进行注册,这样下次就可以从缓存中直接获取这个对象了
addSingleton(beanName, singletonObject);
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}

(2) 方法2:单例对象创建完成进行注册

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

(3) 方法3:将实例化后的对象暴露到容器中

Spring 在 bean 实例化后就会调用 addSingletonFactory 将这个对象提前暴露到容器中,这们就可以通过 getBean(A) 得到这个对象,即使这个对象仍正在创建。用于解决循环依赖。

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

(4) 方法4:从缓存中获取 bean

这个方法也是专门用于解决循环依赖的问题,当不存在循环依赖时 earlySingletonObjects 总是 null。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}

二、Spring 创建 bean 过程

我们从 BeanFactory#getBean(beanName) 调用说起,看一下这几个方法的调用顺序:

2.1 AbstractBeanFactory#doGetBean

这个方法先从缓存中获取 bean,没有再创建 bean,因此会调用方法 4 和方法 1,我们看一下调用过程。

(1) getSingleton(beanName, true)

doGetBean 首先从缓存中获取数据,Object sharedInstance = getSingleton(beanName),这个方法最终会调用 getSingleton(beanName, true)

(2) getSingleton(beanName, singletonFactory)

如果缓存中没有 bean,则会调用 addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) 来创建一个新 bean,代码如下:

if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

一旦调用 getSingleton(beanName, singletonFactory) 方法,这个方法创建开始时就会标记这个 bean 为正在创建,创建结束后移除对应的标记。直接创建 bean 的过程实际上是委托给了 createBean 方法。继续跟踪这个方法。

2.2 AbstractAutowireCapableBeanFactory#doCreateBean

doCreateBean 方法中完成了单例的 bean 有以下几个主要的步骤:

  1. createBeanInstance 实例化 bean 对象,一般是通过反射调用默认的构造器。
  2. populateBean bean 属性注入,在这个步骤会从 Spring 容器中查找对应属性字段的值,解决循环依赖问题。
  3. initializeBean 调用的 bean 定义的初始化方法。

(3) addSingletonFactory(beanName, singletonFactory)

在 createBeanInstance 后 populateBean 前 Spring 会将这个实例化的 bean 提前暴露到容器中,这样 populateBean 属性注入时就可以通过 getBean(A) 查找到。

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

(4) getSingleton(beanName, false)

在 bean 初始化完成还后还需要进行依赖的检查,这时因为提前暴露的这个 bean(即使用工厂方法或构造方法创建出来的对象) initializeBean 还可以进行增强,这样这两个 bean 就不是同一个了。Spring 默认是不允许这种情况发生的。

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

(5) addSingleton(beanName, singletonObject)

在 bean 创建结束后还有一步是在 getSingleton(beanName, singletonFactory) 中完成的,调用 addSingleton(beanName, singletonObject),即注册最终的 bean,同时清空中间的辅助状态。

这样单例 bean 的创建过程就完成了,下面就需要分析循环引用下 singletonFactories、earlySingletonObjects 这两个集合的状态。

三、循环引用下 Bean 状态分析

3.1 正常情况

在正常的情况下,调用顺序如下:以下有无,表示是否持有对指定 Bean 的引用

过程 方法 singletonFactories earlySingletonObjects singletonObjects
缓存中获取 getSingleton(beanName, true)
创建 bean getSingleton(beanName, singletonFactory)
提前暴露到容器中 addSingletonFactory(beanName, singletonFactory)
依赖检查 getSingleton(beanName, false)
注册 addSingleton(beanName, singletonObject)

可以看到正常情况下,单例 bean 暴露的对象只会出现在 singletonFactories 集合中,不可能出现在 earlySingletonObjects 集合中,除非在创建 bean 的过程中又调用了 getSingleton(beanName, true) 方法,也就是此时出现了循环引用。

3.2 循环引用

但是出现循环引用之后呢,就会出现这种情况:

过程 方法 singletonFactories earlySingletonObjects singletonObjects
缓存中获取 A getSingleton(A, true) A无B无 A无B无 A无B无
创建 A getSingleton(A, singletonFactory) A无B无 A无B无 A无B无
暴露 A 到容器中 addSingletonFactory(A, singletonFactory) A有B无 A无B无 A无B无
populateBean(A, mbd, instanceWrapper) A 注入 B 时又依赖了 A,此时由 B 准备解析 A……
缓存中获取 A getSingleton(A, true) A无B有 A有B无 A无B无
注册 B addSingleton(B, singletonObject) A无B无 A有B无 A无B有
populateBean(A, mbd, instanceWrapper) 完成属性注入
A- = initializeBean(beanName, exposedObject, mbd) 在 initializeBean 之后 A 变为 A-
依赖检查 getSingleton(beanName, false) A无B无 A有B无 A无B有
注册 A getSingleton(beanName, false) A无B无 A无B无 A有B有

在上面这个过程中,在对 A 进行验证时,就会从 earlySingletonObjects 中取得一个 A,但是这个 A 和后面的 A- 可能不是同一个对象,这是因为有了 beanPostProcessor 存在,它可以改变 bean 的最终值,比如对原始 bean 进行封装,代理等。在这个过程中,出现了 3 个对象 A, A-, B,而 B 中所持有的 A 对象为原始的 A。如果这里的 A 和 A- 不是同一个对象,即产生了 beanA 有了 beanB 的引用,但 beanB 并没有 beanA 的引用,而是另一个 beanA 的引用。


每天用心记录一点点。内容也许不重要,但习惯很重要!

上一篇:在a标签内添加hover样式的方法:


下一篇:解决VS Code使用code runner开发Python乱码问题