建议了解以下内容后再阅读本文

Spring IOC容器 和 Spring bean
Spring IOC 容器启动过程拓展点
Spring bean 实例化
Java 动态代理
Spring AOP 实践
Spring AOP XML配置方式原理详解
Spring AOP 注解方式原理详解

关于Spring 循环依赖, 在 Spring bean 实例化一文中讲解了使用三级缓存+暴露早期引用机制 解决循环依赖问题,但其实不是所有的循环依赖都可以被解决,即使三级缓存+暴露早期引用机制,在Spring 启动过程中依然有可能遇到一下循环依赖错误, 该报错信息位于doCreateBean方法中。

1
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'springTransactionService': Bean with name 'springTransactionService' has been injected into other beans [userService] 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(单例bean 才能使用缓存)且不使用构造器注入(构造器注入无法使用早期引用)的前提下

  1. 普通不需要代理的bean 之间 循环依赖一定可以解决
  2. 需要代理的bean 之间产生循环依赖不一定能解决。

1. 再看doCreateBean

doCreateBean 方法中包含了bean 实例化的全过程,在 Spring bean 实例化 一文中,已经说明了实例化的整个流程,并对三层缓存的内容和缓存添加时机都进行了详细介绍,本文将在这些内容之外介绍更多细节,来更好理解循环依赖的过程。

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
AbstractAutowireCapableBeanFactory.java 
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
}

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {

// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
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;
}

// Allow post-processors to modify the merged bean definition.
//检查是否已经对 Bean 定义进行了后处理,如果没有,则调用 applyMergedBeanDefinitionPostProcessors 方法应用后处理器。
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;
}
}

// 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));
//如果允许循环引用,并且当前 Bean 是单例,则调用 addSingletonFactory 方法,提供一个回调以获取早期 Bean 引用。
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 添加第3级缓存, 此处第二个参数又是一个ObjectFactory的匿名类实现
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 (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在getSingleton(beanName, true)生产出来的
// 因为该bean 自己的生产过程中,只会主动添加一级、三级缓存
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 表示init后,Bean实例未被代理,在未代理的情况下,exposedObject 和 bean 是相同的引用

if (exposedObject == bean) {
// 如果存在早期暴露的单例引用,并且exposedObject未被代理,将exposedObject替换为早期暴露的单例引用。
exposedObject = earlySingletonReference;
} // 如果在暴露早期引用的情况下,init阶段还存在代理操作时,要确保依赖当前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。如果成功移除,表示这个Bean只是为类型检查而创建的,不是实际使用的Bean。
// 通过这个循环,Spring会过滤掉那些只为类型检查而创建的Bean,保留那些实际依赖当前Bean的Bean。
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
// 这段逻辑确保在处理循环依赖时,确保依赖的Bean使用的是最终版本的Bean,而不是中间状态的原始Bean。
// 如果存在实际依赖当前Bean的Bean。抛出BeanCurrentlyInCreationException异常
// 因为这意味着有Bean在循环依赖的过程中使用了当前Bean的原始版本,但最终当前Bean被包装(如被AOP代理)。
// 这意味着被依赖的Bean使用的不是最终版本的Bean,这可能导致一些问题。
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.");
}
}
}
}
return exposedObject;
}

1.1 if(earlySingletonExposure)

关于更多的细节,重点来看init 阶段完成后的以下代码
这段逻辑中抛出的异常即是指循环依赖处理失败

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
	// 验证循环引用的处理过程是否正确
if (earlySingletonExposure) {
// 尝试获取早期引用,那么这个早期引用肯定是因为循环依赖,其他bean在getSingleton(beanName, true)生产出来的
// 因为该bean 自己的生产过程中,只会主动添加一级、三级缓存
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 表示init后,Bean实例未被代理,在未代理的情况下,exposedObject 和 bean 是相同的引用

if (exposedObject == bean) {
// 如果存在早期暴露的单例引用,并且exposedObject未被代理,将exposedObject替换为早期暴露的单例引用。
exposedObject = earlySingletonReference;
} // 如果在暴露早期引用的情况下,init阶段还存在代理操作时,要确保依赖当前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。如果成功移除,表示这个Bean只是为类型检查而创建的,不是实际使用的Bean。
// 通过这个循环,Spring会过滤掉那些只为类型检查而创建的Bean,保留那些实际依赖当前Bean的Bean。
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
// 这段逻辑确保在处理循环依赖时,确保依赖的Bean使用的是最终版本的Bean,而不是中间状态的原始Bean。
// 如果存在实际依赖当前Bean的Bean。抛出BeanCurrentlyInCreationException异常
// 因为这意味着有Bean在循环依赖的过程中使用了当前Bean的原始版本,但最终当前Bean被包装(如被AOP代理)。
// 这意味着被依赖的Bean使用的不是最终版本的Bean,这可能导致一些问题。
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.");
}
}
}
}

1.2 bean、exposedObject、earlySingletonReference

先来对这3个指向bean 实例的变量进行解释

1
2
3
4
5
6
7
8
9
10
11
12
13
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);

// bean 指create阶段后获取的引用, 这个引用非常原始,肯定不会是代理对象
Object bean = instanceWrapper.getWrappedInstance();

// exposedObject 是doCreateBean方法最终会返回的数据
// 经过init阶段后,如果执行了代理逻辑,则会产生一个新的代理对象赋值给 exposedObject,这时exposedObject和bean就不相等了
Object exposedObject = bean;
exposedObject = initializeBean(beanName, exposedObject, mbd);


// bean的早期引用, 即三级缓存中第二级缓存
Object earlySingletonReference = getSingleton(beanName, false);

1.3 同一个bean实例 的同一个APC不会重复执行

针对AbstractAutoProxyCreator 而言,对一个bean产生对象的有两个时机

  1. 因为循环依赖被其他bean 依赖时,通过AbstractAutoProxyCreator.getEarlyBeanReference获取其早期引用,即代理对象引用
  2. bean 正常实例化流程中, 在init 阶段通过BeanPostProcessor.postProcessAfterInitialization 方法,触发AbstractAutoProxyCreator.postProcessAfterInitialization 方法执行,该方法中包含生成代理对象的逻辑

具体代码如下

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
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport  
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
// 生成了早期引用的bean缓存,
private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);

@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
// 如果 `bean` 为 `null`,则直接返回 `null`。
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}

protected Object getCacheKey(Class<?> beanClass, @Nullable String beanName) {
if (StringUtils.hasLength(beanName)) {
return (FactoryBean.class.isAssignableFrom(beanClass) ?
BeanFactory.FACTORY_BEAN_PREFIX + beanName : beanName);
}
else {
return beanClass;
}
}
}

1.3.1 earlyProxyReferences

之所以说对一个bean 来讲,同一个AbstractAutoProxyCreator实例的代理逻辑不会重复执行,是因为earlyProxyReferences 这个缓存的存在。

earlyProxyReferences 是一个ConcurrentHashMap, key 是beanName(先忽略对FactoryBean的处理,即使加了& 前缀,也是一个字符串,继续理解成beanName就可以了), value是bean 实例对象。

根据getEarlyBeanReferencepostProcessAfterInitialization的逻辑可以看出,如果一个bean 提前获取了早期引用,则会在earlyProxyReferences 中将其记录下来。

⚠️:注意记录的不是早期引用(早期引用可以是原始bean ,也可能是代理对象引用),早期引用是存储在三级缓存中的第二级缓存中的。

那么在bean 实例化init 阶段, 进入AbstractAutoProxyCreator.postProcessAfterInitialization 方法时,通过cacheKey读取value , 判断earlyProxyReferences 中记录的bean和当前入参bean是否一致, 如果一致,就不用重复执行wrapIfNecessary逻辑了。

1.3.2 earlyProxyReferences.remove

判断earlyProxyReferences 中记录的bean和当前入参bean是否一致, 是通过以下代码逻辑完成的,

1
if (this.earlyProxyReferences.remove(cacheKey) != bean)

earlyProxyReferences.remove(cacheKey) 的结果有3种情况 null, 与入参bean 相等的值,与入参bean 不相等的值,

  1. 返回 null
    • 解释: 这表明入参bean 没有在其他 bean 的依赖注入过程中提前暴露引用。
    • 处理: 需要对入参bean 进行代理创建,调用 wrapIfNecessary 方法。
  2. 返回与 入参bean 相等的值:
    • 解释: 这表明该入参bean 已经因为其他 bean 的依赖注入提前执行wrapIfNecessary,所以这里不需要重复执行了
    • 处理: 不需要对该 bean 进行代理创建,因此不调用 wrapIfNecessary 方法。
  3. 返回与入参bean 不相等的值:
    • 解释: cacheKey 对应的bean 确实已经提前暴露了引用,但是具有相同cacheKey 的 入参bean不是当初来获取早期引用的那个bean, 所以仍然要对这个入参bean 执行wrapIfNecessary 逻辑
    • 处理: 需要对该 bean 进行代理创建,调用 wrapIfNecessary 方法

前两种情况都比较好理解, 第3种情况需要多加解释,而且第三种也是无法解决的循环依赖。

出现的原因可以分为2种

  1. 能生成代理对象的不只AbstractAutoProxyCreator, 还有AbstractAdvisingBeanPostProcessor
  2. 存在多个AbstractAutoProxyCreator

⚠️:这个缓存的使用逻辑, 和实际开发中我们要删除缓存时思路一致, 要先判断要删除的线程是不是和当前添加缓存的线程一致,一致的话才能删除缓存,否则会造成数据不一致。

2. 有动态代理的循环引用实例

2.1 有动态代理的循环引用示例代码

该示例代码是Springboot 项目,构造一个依赖循环的例子

在如下代码中,userService和springTransactionService通过属性注入互相依赖, 且定义了一个Aspect , 这个Aspect 会为userService和springTransactionService 都生成代理对象

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
@Service
public class SpringTransactionService {
@Autowired
UserService userService;

public void performTransaction() {
// 模拟业务逻辑
System.out.println("performTransaction");
}
}

@Service
public class UserService implements UserI {

@Autowired
SpringTransactionService springTransactionService;
@Override
public void createUser(String username) {
// 模拟业务处理
System.out.println("Creating user: " + username);
}

@Override
public void deleteUser(String username) {
// 模拟业务处理
System.out.println("Deleting user: " + username);
}
}

@Aspect
@Component
public class LoggingAspect {
//定义一个匹配userService中所有方法的切点表达式
@Pointcut("execution(* com.example.codingInAction.service.*.*(..))")
public void userServiceAllMethod() {
}

// 在方法执行之前执行的通知
@Before("userServiceAllMethod()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}

// 在方法执行之后执行的通知
@After("userServiceAllMethod()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("After method: " + joinPoint.getSignature().getName());
}

// 定义一个只匹配 UserService.createUser 方法的切点表达式
@Around("execution(* com.example.codingInAction.service.UserService.createUser(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println(joinPoint.getSignature().getName() + " 方法开始执行");

Object result = joinPoint.proceed(); // 执行目标方法

long endTime = System.currentTimeMillis();
System.out.println(joinPoint.getSignature().getName() + " 方法执行时间: " + (endTime - startTime) + "ms");

return result;
}
}

// 示例代码启动入口
@SpringBootApplication
public class CodingInActionApplication {

public static void main(String[] args) {
SpringApplication.run(CodingInActionApplication.class, args);

2.2 springTransactionService 实例化

2.2.1 三级缓存

springTransactionService 经过createBeanInstance阶段后,放入第三级缓存,以便在需要之时提前暴露引用

2.2.2 populate with userService

springTransactionService 在populate阶段发现需要userService, 通过getBean 获取userService 实例。

2.2.2.1 getBean(userService)

此时userService 还没有进行实例化,所以在三级缓存中不存在,要走create 流程进行实例化

2.2.2.2 userService 放入第三级缓存

userService 经过createBeanInstance阶段后,放入第三级缓存,以便在需要之时提前暴露引用

2.2.2.3 userService populate with springTransactionService

userService在populate 阶段发现需要springTransactionService , 再次通过getBean 流程获取springTransactionService, 此时springTransactionService的三级缓存已经存在,可以获取早期引用。

2.2.2.4 获取springTransactionService早期引用

获取springTransactionService 时,依然是getBean作为入口

由于springTransactionService 已经完成createInstance 放入了第三级缓存,此时getSingleton(beanName )方法是可以获取到早期引用的, 获取早期引用的逻辑如下

以singletonFactory.getObject 为入口,进入三级缓存获取早期引用的逻辑AbstractAutoProxyCreator.getEarlyBeanReference

经过AbstractAutoProxyCreator 这个BeanPostProcessor 处理,其earlyProxyReferences 记录了springTransactionService曾来获取过早期引用, 并最终返回了一个CGLIB 动态代理对象

⚠️ wrapIfNecessary 只对需要产生代理的对象生成代理对象,不需要产生代理的对象会直接返回原对象.在这个例子中是springTransactionService 代理对象的引用

getEarlyBeanReference 返回的数据,即singletonFactory.getObject 获取到数据会放到第二级缓存中,同时删除第三级缓存。

2.2.2.5 userService init

现在 userService populate 阶段结束,开始init 阶段,省略其他部分,只看AutoProxyCreator这个BeanPostProcessor.

由于userService 之前没有获取过早期引用, 所以earlyProxyReferences 并不存在userService,this.earlyProxyReferences.remove(cacheKey) 返回null, 需要进入wrapIfNecessary 逻辑,由于userService 符合LoggingAspect 切点表达式,经过wrapIfNecessary 后, 会生成userService的动态代理对象

2.2.2.6 earlySingletonExposure

由于目前为止,userService 并没有触发早期引用的暴露逻辑,即从三级缓存中获取早期引用放到二级缓存中的逻辑,所以一级缓存,二级缓存中均不存在userService, 所以getSingleton(beanName, false)会返回null, 因此userService不需要进行早期引用暴露的特殊处理,直接返回实例化完成的bean 即可(有可能是原始bean, 也有可能是代理对象,在这个例子中是代理对象)

2.2.2.7 一级缓存

实例化完成的userService 会放入一级缓存中,同时删除二、三级缓存,确保一个bean 在同一时刻只会存在于某一级缓存中

2.2.3 springTransactionService init

springTransactionService populate 阶段完成,现在来到init阶段,只看我们关心的 AbstractAutoProxyCreator.postProcessAfterInitialization

由于前面在userService 的实例化过程中,我们已经通过getEarlyBeanReference
获取过springTransactionService的早期引用(且是经过springTransactionService包装过的代理对象引用)

所以earlyProxyReferences中已经记录了springTransactionService相关数据,且和传进来bean数据一致,所以这里的逻辑会直接返回,而不是再走一遍wrapIfNecessary逻辑。

2.2.4 earlySingletonExposure

现在springTransactionService 已经完成了实例化的3个阶段,逻辑来到doCreateBean中最后关于循环依赖和早期引用的处理逻辑

由于在userService 的实例化过程中,springTransactionService已经提前暴露了引用,且是代理对象的引用, 所以getSingleton(beanName, false)会获取到其早期引用。

此时bean 与 exposedObject相等,说明在init阶段没有执行代理逻辑,doCreateBean 最终会返回提前暴露的引用earlySingletonReference

至此springTransactionService 经过和userService 的循环依赖处理,已经成功初始化。userService 也成功初始化,后续在对userService 执行getBean 操作都会直接从一级缓存中获取。

下面来看三级缓存无法解决的循环依赖

3. AbstractAdvisingBeanPostProcessor 引发的异常

在Spring 启动过程中,能为bean 生成代理对象的不止AbstractAutoProxyCreator 一个, AbstractAdvisingBeanPostProcessor也可以为bean 生成一个代理对象

AbstractAdvisingBeanPostProcessor 也是一个BeanPostProcessor , 因此也可以在init 阶段通过postProcessAfterInitialization方法介入bean 的生命周期。

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
public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (this.advisor == null || bean instanceof AopInfrastructureBean) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}

if (bean instanceof Advised) {
Advised advised = (Advised) bean;
if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
// Add our local Advisor to the existing proxy's Advisor chain...
if (this.beforeExistingAdvisors) {
advised.addAdvisor(0, this.advisor);
}
else {
advised.addAdvisor(this.advisor);
}
return bean;
}
}

if (isEligible(bean, beanName)) {
ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
if (!proxyFactory.isProxyTargetClass()) {
evaluateProxyInterfaces(bean.getClass(), proxyFactory);
}
proxyFactory.addAdvisor(this.advisor);
customizeProxyFactory(proxyFactory);
return proxyFactory.getProxy(getProxyClassLoader());
}

// No proxy needed.
return bean;
}
}

查看AbstractAdvisingBeanPostProcessor.postProcessAfterInitialization代码,可以看到也是生成代理对象的diamanté,其逻辑进入getProxy就和之前 讲过代理创建流程 完全一致,就不进入细节去分析了

1
2
3
4
5
public class ProxyFactory extends ProxyCreatorSupport { 
public Object getProxy(@Nullable ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}
}

那实际生产开发中,有两个注解会用到AbstractAdvisingBeanPostProcessor , 如果使用不当,就会引发无法解决的循环依赖,从而导致Spring 容器启动失败

3.1 @Repository注解-PersistenceExceptionTranslationPostProcessor

在Spring中,@Repository 注解主要用于标记数据访问层(DAO)。相比 @Component(通用Bean 注解)、@Service(用于对Service实现类进行标注) 和 @Controller(用于对Controller实现类进行标注),@Repository 注解的一个显著区别在于它的异常处理机制。

Spring为@Repository 提供了一个特殊的处理机制,自动将数据访问异常转换为 Spring 的统一数据访问异常层次结构(如 DataAccessException)。@Service@Controller 没有这种特定的异常处理机制。它们的异常处理通常依赖于全局异常处理器(如 @ControllerAdvice)或手动捕获和处理。

1
2
3
4
5
6
7
8
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(annotation = Component.class)
String value() default "";
}

当使用 @Repository 注解时,Spring 会在后台自动注册一个 PersistenceExceptionTranslationPostProcessor

PersistenceExceptionTranslationPostProcessor 继承了AbstractAdvisingBeanPostProcessor , 因此使用了@Repository注解的bean 在实例化完成后,最终生成的是其代理对象。

3.1.1 有动态代理的循环引用示例代码

改造下SpringTransactionService , 将@Service 替换成@Repository, 其他代码保持不变。

1
2
3
4
5
6
7
8
9
10
@Service
public class SpringTransactionService {
@Autowired
UserService userService;

public void performTransaction() {
// 模拟业务逻辑
System.out.println("performTransaction");
}
}

启动该SpringBoot 项目,将报如下错误
1
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'springTransactionService': Bean with name 'springTransactionService' has been injected into other beans [userService] 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.

直接跟随debug 信息来到springTransactionService 实例化过程 阶段

getEarlyBeanReference

在springTransactionService ->userService -> springTransactionService 循环依赖获取springTransactionService 的早期引用时,

getEarlyBeanReference中,只有 AbstractAutoProxyCreator 这类BeanPostProcessor 才会执行。 AbstractAdvisingBeanPostProcessor不是 AbstractAutoProxyCreator ,所示PersistenceExceptionTranslationPostProcessorgetEarlyBeanReference阶段是不会发挥作用的

postProcessAfterInitialization

在springTransactionService 的init 阶段, 不会过滤 只有AbstractAutoProxyCreator 的子类执行,而是对所有的BeanPostProcessor 都执行其postProcessAfterInitialization 方法。
AnnotationAwareAspectJAutoProxyCreator 里根据if (this.earlyProxyReferences.remove(cacheKey) != bean)判断,不会重复执行wrapIfNecessary, 直接返回入参bean

接着开始执行PersistenceExceptionTranslationPostProcessor

从图中可以看出,此时入参bean 依然是原始bean, 经过@Repository注解-PersistenceExceptionTranslationPostProcessor 处理后,原始入参bean又生成了一个的代理对象返回。

earlySingletonExposure

从图中可以看出, 最终返回的exposedObject 和提前暴露的earlySingletonReference 已经不是同一个实例了,earlySingletonReference 是经过AnnotationAwareAspectJAutoProxyCreator 生成的代对象, exposedObject 是PersistenceExceptionTranslationPostProcessor生成的地理对象,提前暴露的引用和最终生成的引用不一致,且该bean的早期引用已经被userService 依赖了,最终会走到报错逻辑

==所以在实际生产开发中,关于注解的最佳实践一定是按照层级划分使用对应的注解==

3.2 @Async-AsyncAnnotationBeanPostProcessor

@Async 是用于标识异步执行的注解,使用不当的话,会由于同样的原因造成无法解决的循环依赖。
AsyncAnnotationBeanPostProcessor 同样继承了AbstractAdvisingBeanPostProcessor,因此会在init 阶段 为添加了@Async 注解的bean 生成代理对象

3.2.1 有动态代理的循环引用示例代码

改造下SpringTransactionService , 将@Service 替换成@Repository, 其他代码保持不变。启动该SpringBoot 项目,会报同样的错误

1
2
3
4
5
6
7
8
9
10
11
12
13
@EnableAsync  
@Service
public class SpringTransactionService {

@Autowired
UserService userService;

@Async
public void performTransaction() {
// 模拟业务逻辑
System.out.println("performTransaction");
}
}

经过AsyncAnnotationBeanPostProcessor.post处理后,会为原始bean 生成一个代理对象,与暴露的早期引用不一致,最终会报错

3.3 ## BeanPostProcessor的不同过滤条件

在AbstractAdvisingBeanPostProcessor 引发异常的这2个例子中,能够看出,有机会对bean 生成代理的2处逻辑,对要执行的BeanPostProcessor有不同的过滤条件

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 abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory  
implements AutowireCapableBeanFactory {

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}

@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {

Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
Object current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
}
  1. getEarlyBeanReference 中只执行 AbstractAutoProxyCreator的子类,在Springboot 启动的项目中,就是AnnotationAwareAspectJAutoProxyCreator
  2. 在init 阶段的applyBeanPostProcessorsAfterInitialization 中,会执行所有BeanPostProcessor

所以在有动态代理的循环依赖+暴露引用的时候,如果其中包含了除AbstractAutoProxyCreator子类外可生成代理的BeanPostProcessor, 就会产生一个类的2个实例,违反单例模式报错。

分析到这,可以看出,一个bean 只要暴露了早期引用,那么在init 阶段就绝不能再执行任何生成代理的操作了,因为一旦有代理操作就会再生成一个新的引用,就会有问题。

如果没有暴露早期引用,init 阶段随便几个代理操作都行。

比如把SpringTransactionService 中的userService 去掉, 把前面提到的各种可以生成代理的操作全部叠加,启动都是没有问题的

1
2
3
4
5
6
7
8
9
@EnableAsync  
@Repository
public class SpringTransactionService {
@Async
public void performTransaction() {
// 模拟业务逻辑
System.out.println("performTransaction");
}
}

4. 存在多个 AbstractAutoProxyCreator

再来分析另外一种 有多个AbstractAutoProxyCreator

spring默认保证一个容器中只能有一个AbstractAutoProxyCreator 子类存在 ,如过手动添加或者自定义会出现多个APC情况。

这里直接分析源码,假设有2个AbstractAutoProxyCreator 子类存在。

4.1 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
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory  
implements AutowireCapableBeanFactory {
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;
}

}

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
}

由于在调用AbstractAutoProxyCreator.getEarlyBeanReference时, 每次入参bean 其实都是上一个AbstractAutoProxyCreator处理结果。

对于第一层代理来说earlySingletonReferences这个map 中的value 是原始bean
对于第二层代理来说earlySingletonReferences这个map 中的value 是第一层代理生成的代理对象。

⚠️ 每个AbstractAutoProxyCreator实例都有自己的earlySingletonReferences

所以那么第2层代理实际是对第一层代理生成的代理对象又代理了一层,最终生成的早期引用,即放入第二级缓存的数据也是第二层代理对象

4.2 postProcessAfterInitialization

那么当来到init阶段的bean后处理逻辑
当执行第一层代理的postProcessAfterInitialization时,入参bean 时原始bean, 第一层代理earlySingletonReferences 中能找到对应的值,不会重复执行wrapIfNecessary逻辑,直接返回入参原始bean。

继续执行for循环,来到第二层代理,第二层代理earlySingletonReference 存储的是bean 第一层代理的引用, 这里入参bean 还是原始bean, this.earlyProxyReferences.remove(cacheKey) != bean条件成立,会执行wrapIfNecessary逻辑次 会创建一个新的代理对象,经过第二层代理处理后,init 阶段返回的exposedObject已经和提前暴露的早期引用不一致了,会报错。

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 abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport  
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
// 生成了早期引用的bean缓存,
private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
// 如果 `bean` 为 `null`,则直接返回 `null`。
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
}

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
}

5. 解决方案

  1. 使用合理的Bean注解
  2. 使用@Lazy 注解
  3. 重构你的代码

6.参考文章

Spring循环依赖那些事儿(含Spring详细流程图)

一文详解 Spring Bean 循环依赖