0%

Spring 系列之从 AOP 角度分析循环依赖

引言

在上一篇文章【从循环依赖到三级缓存】中,我们以一个简单的例子介绍了 Spring 是如何解决循环依赖的。文章中也介绍了 Bean 的生命周期发生的时机。而本文更进一步,将 AOP 引入循环依赖,看看 Spring 在解法上有什么不同。

一个简单而不简单的例子

1
2
3
4
5
6
7
8
9
10
@Service
public class A {
@Autowired
private A a;

@Transactional
public void test() {

}
}

本例只有一个主角 A,它有个方法 test(),我们为该方法添加事务注解 @Transactional。而 A 本身依赖自身,这样构成一个循环依赖。只不过我们引入了动态代理的角色,也就是说字段 a 注入的应该是代理对象。至于对不对呢?我们马上揭晓!

再回 doGetBean

有了前一篇文章的基础,我想你应该不再抵触 doGetBean 了。如果还是不想见到它,我劝你再回前一篇文章深造深造。玩笑归玩笑,言归正题,同一个对象会触发多次 doGetBean 方法,第一个是用于初始化当前对象实例,其他的用于属性注入。本例中,由于字段 A 被自己引用,那么势必会触发两次 doGetBean 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// beanName 为本例中的 'a'
String beanName = transformedBeanName(name);
// bean 实例对象
Object beanInstance;

// 首先尝试从缓存中获取,由于是第一次获取,那么三个缓存中必定都为 null
Object sharedInstance = getSingleton(beanName);

...

// 我们省略掉其他暂时触发不到的逻辑,直接进入创建 bean 实例流程(为了好看,去掉额外的注解,毕竟本文中已经存在需要它们了,那么我们尽量使代码简洁)
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
// 为 getSingleton 注册回调方法,即:AbstractAutowireCapableBeanFactory#createBean()
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

我们进入 DefaultSingletonBeanRegistrygetSingleton() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
// 此时缓存中必定为 null
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
...
// 将当前 bean 的名字添加到创建状态集合中
beforeSingletonCreation(beanName);
boolean newSingleton = false;

try {
// 回调 AbstractAutowireCapableBeanFactory#createBean() 方法
singletonObject = singletonFactory.getObject();
newSingleton = true;
...
}
...
finally {
...
// 将 bean 名称从创建状态集合中移除
afterSingletonCreation(beanName);
}
if (newSingleton) {
// 将创建好的 bean 存入一级缓存,同时将二、三级缓存移除
addSingleton(beanName, singletonObject);
}
}
// 返回实例
return singletonObject;
}
}

流程走向 AbstractAutowireCapableBeanFactory#createBean() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
try {
// 这里为代理提供了切入点,那么此时代理会从这里做处理吗?
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}


protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
Object bean = null;
if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
Class<?> targetType = determineTargetType(beanName, mbd);
if (targetType != null) {
// 我们要关注的就是代理相关的处理器的具体行为
bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
if (bean != null) {
// 如果 bean 不为 null,那么就可以应用 bean 初始化后置方法,然后由主流程直接返回
bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
}
}
}
mbd.beforeInstantiationResolved = (bean != null);
}
return bean;
}

与代理相关的 InstantiationAwareBeanPostProcessor 实现是 InfrastructureAdvisorAutoProxyCreator,而 postProcessBeforeInstantiation() 方法则是委托给了父类 AbstractAutoProxyCreator 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
// 生成缓存 key,本例中为 'a'
Object cacheKey = getCacheKey(beanClass, beanName);

// targetSourcedBeans 肯定是不包含当前对象的
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
// 当前增强 bean 里也同样没有它的名字
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
// 该 bean 并非代理相关基础组件实现
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}

// 此时目标源也不存在,所以代理此时并未介入
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
// 将 null 返回调用者
return null;
}

代理并未在 resolveBeforeInstantiation() 方法中做任何处理,那么我就继续进入 doCreateBean() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
// 缓存中并不存在,该缓存在 `getSingletonFactoryBeanForTypeCheck()` 中触发
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 根据对象的生成策略完成实例化
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
...
if (earlySingletonExposure) {
...
// 将 bean 添加到三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

...
try {
// 此时就要触发属性填充了,完成字段的自动注入,由于类 A 依赖了自身,那么会触发 A 的 getBean() 方法。另外,方法会触发 InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation 的回调,但是事务的代理并未做任何操作,所以我们就不在展看介绍了
populateBean(beanName, mbd, instanceWrapper);
...
}

方法 populateBean() 会触发 getBean() 创建 A 对象,只不过此时的 A 已经存在三级缓存中了:

1
2
// 当 doGetGean 再次调用 getSingleton() 方法时,就会触发三级缓存的对象获取,然后转移至二级缓存。由于二级缓存就是最终对象未初始化版本,那么代理在该阶段就得生成了,而不是在后续的 initializeBean 中进行
Object sharedInstance = getSingleton(beanName);

三级缓存注入了 getEarlyBeanReference() 回调,所以我们直接进入代理的该方法实现也就是位于 AbstractAutoProxyCreator 类中的 getEarlyBeanReference() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public Object getEarlyBeanReference(Object bean, String beanName) {
// 生成缓存 key,本例中为 'a'
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 为了避免后续重复创建代理对象,将 A 对象保存起来
this.earlyProxyReferences.put(cacheKey, bean);
// 进行代理的封装
return wrapIfNecessary(bean, beanName, cacheKey);
}


protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 与先前一样,条件不成立
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
// 并没有对该实例标记不需要增强
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
// 该条件也不满足,因为 A 并未指定需要跳过并且也并非代理相关实现
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}

// 查看当前对象是否有对应的拦截器,由于 A 对象存在事务的注解,所以这里就会拿到事务的拦截器
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
// 拦截器不为空
if (specificInterceptors != DO_NOT_PROXY) {
// 标志对象 A 为增强 bean
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 创建 A 的代理对象
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
// 标记 A 的代理类
this.proxyTypes.put(cacheKey, proxy.getClass());
// 返回 A 的代理对象
return proxy;
}

this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}

getEarlyBeanReference 方法完成时,代理对象就会被转移至二级缓存,同时将三级缓存移除,getSingleton() 方法完成。产生的代理对象 A 就会被注入到 A 中,然后继续回到最初的构建 A 的流程中,也就是 initializeBean() 方法。

initializeBean() 方法中,虽然提供了各种后置处理器用于修改 bean 对象,但是此时就不能产生新的代理对象了,因为当循环依赖存在时,该阶段就已经完成注入了,但是如果此刻你把 A 替换了,导致两者不一样。那么接下来的检测就会抛出异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 肯定是允许早期访问的,否则就无法解决循环依赖了
if (earlySingletonExposure) {
// 此时会从一级、二级缓存中获取对象引用,而当前的 A 由于注入已经被转移到了二级缓存中(二级缓存放的就是从三级缓存生成的代理对象)
Object earlySingletonReference = getSingleton(beanName, false);
// 二级缓存存在说明肯定发生了循环依赖,当然也可能是一级缓存的对象,所以后续还要判断是否有其他的 bean 依赖于当前对象
if (earlySingletonReference != null) {
// exposedObject 在存在循环依赖的情况下是不能被 initializeBean() 中的回调方法替换的
if (exposedObject == bean) {
// 如果两者相等,那么就将缓存中的对象赋给 exposedObject(也就是本例中 A 的代理对象)
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 " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}

方法 getSingleton(beanName, false) 拿到的对象可能是一级缓存的也可能是二级缓存的。二级缓存已经介绍过了,那么什么时候会拿到一级缓存的呢?其实 DefaultSingletonBeanRegistry 对外提供了 registerSingleton(String beanName, Object singletonObject) 方法,而该方法就是将实例对象存入一级缓存,同时删除二级、三级缓存。

所以,如果想通过将自己的对象替换掉原有的 bean 对象甚至是代理对象,那么只需要通过调该方法就可以了,当然,该方法已经被 BeanFactory 子类重写了,所以只需要注入 BeanFactory,然后调用对应的方法即可将实例添加到一级缓存。

💡 Tips

虽然可以通过 registerSingleton() 方法手动注入 bean 实例,但是一定要保证当前的实例还没有创建完成,换句话说该实例还未出现在一级缓存里,否则调用失败,提示该实例已经存在了。另外,当该实例添加到一级缓存后,该实例原有的创建流程会继续,然后在 getSingleton(String beanName, ObjectFactory<?> singletonFactory) 方法的最后,该对象会再次添加到一级缓存里,也就是说手动创建的对象会被添加两次。之所以说 registerSingleton() 方法可以替换掉生成的代理对象,是因为代理对象处于二级缓存中,而 getSingleton() 方法会优先从一级缓存中获取,从而达到替换代理对象的目的。

未发生循环依赖时,代理何时产生?

为了解决循环依赖,Spring 将对象放入到三级缓存进行提前暴露,如果有代理产生,那么也是在三级缓存获取对象阶段发生。

但是,如果不存在循环依赖时,也就不会从三级缓存拿对象,那么代理就无法应用了。那么针对于这种情况,想想代理的发生是在哪个阶段呢?

我们换个例子

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class A {
@Transactional
public void test() {
}
}

@Component
public class B {
@Autowired
private A a;
}

例子依旧简单,这也是常用的一种注入方式,A 是包含事务的服务,B 依赖了 A。但是此时并没有循环依赖发生。

此时由于 A 没有任何依赖,那么 Spring 可以明正严顺的将 A 构造完毕,然后在注入给 B。那么也就是说在第一次调用 doGetBean 时,就要完成 A 代理对象的创建。A 代理的创建要满足什么要求呢?起码得是 A 对象自己初始化完毕(在循环依赖中,虽然创建的 A 是早期的,但是由于是相同引用,只要引用所指的对象初始化完就行了)。说到这里我想你应该知道在哪个阶段发生了。

对,就是 initializeBean() 阶段!在这个阶段,会进行各种 BeanPostProcessor 实例的回调,而代理的创建就在 AbstractAutoProxyCreatorpostProcessAfterInitialization() 方法里。

1
2
3
4
5
6
7
8
9
10
11
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 此时代理缓存并不存在当前 bean 的记录,因为 getEarlyBeanReference() 方法未调用
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 进行代理包装,这里方法就比较熟悉了
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}

由于此时 A 只存在于三级缓存中,所以以下的条件就不成立了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if (earlySingletonExposure) {
// 一级、二级缓存不存在
Object earlySingletonReference = getSingleton(beanName, false);
// 条件返回 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 " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}

虽然 bean 对象与 exposedObject 对象不一致了,但是也没有关系,因为并不会从缓存里得到任何该对象的实例。如果通过 registerSingleton() 方法添加到一级缓存里,也不会起到任何作用了,因为由于 exposedObjectbean 本身就不一致了,再创建一个新的实例取代 exposedObject,此时系统里不就有三种 A 的实例了。


总结

对象的创建最早出现在三级缓存,三级缓存是每个实例的必经之路。

如果发生循环依赖,代理的创建就要在三级缓存解析拿到对象时完成,然后将代理对象添加到二级缓存。

如果未发生循环依赖,那么对象会直接进入到一级缓存,所以代理的创建就要在 initializeBean() 阶段发生,为了避免重复创建代理(通过三级缓存 getEarlyBeanReference 回调创建),当第一次创建好代理后,将该对象存入缓存。

对象返回时,将该对象添加到一级缓存,同时将二级、三级缓存删除。