建议了解以下内容后再阅读本文
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 才能使用缓存)且不使用构造器注入(构造器注入无法使用早期引用)的前提下
- 普通不需要代理的bean 之间 循环依赖一定可以解决
- 需要代理的bean 之间产生循环依赖不一定能解决。
1. 再看doCreateBean
doCreateBean
方法中包含了bean 实例化的全过程,在 Spring bean 实例化 一文中,已经说明了实例化的整个流程,并对三层缓存的内容和缓存添加时机都进行了详细介绍,本文将在这些内容之外介绍更多细节,来更好理解循环依赖的过程。
1 | AbstractAutowireCapableBeanFactory.java |
1.1 if(earlySingletonExposure)
关于更多的细节,重点来看init 阶段完成后的以下代码
这段逻辑中抛出的异常即是指循环依赖处理失败
1 | // 验证循环引用的处理过程是否正确 |
1.2 bean、exposedObject、earlySingletonReference
先来对这3个指向bean 实例的变量进行解释
1 | BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args); |
1.3 同一个bean实例 的同一个APC不会重复执行
针对AbstractAutoProxyCreator
而言,对一个bean产生对象的有两个时机
- 因为循环依赖被其他bean 依赖时,通过
AbstractAutoProxyCreator.getEarlyBeanReference
获取其早期引用,即代理对象引用 - 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
34public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
// 生成了早期引用的bean缓存,
private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
public Object postProcessAfterInitialization( { 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, { 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 实例对象。
根据getEarlyBeanReference
和postProcessAfterInitialization
的逻辑可以看出,如果一个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 不相等的值,
- 返回
null
:- 解释: 这表明入参bean 没有在其他 bean 的依赖注入过程中提前暴露引用。
- 处理: 需要对入参bean 进行代理创建,调用
wrapIfNecessary
方法。
- 返回与 入参bean 相等的值:
- 解释: 这表明该入参bean 已经因为其他 bean 的依赖注入提前执行
wrapIfNecessary
,所以这里不需要重复执行了 - 处理: 不需要对该 bean 进行代理创建,因此不调用
wrapIfNecessary
方法。
- 解释: 这表明该入参bean 已经因为其他 bean 的依赖注入提前执行
- 返回与入参bean 不相等的值:
- 解释: cacheKey 对应的bean 确实已经提前暴露了引用,但是具有相同cacheKey 的 入参bean不是当初来获取早期引用的那个bean, 所以仍然要对这个入参bean 执行
wrapIfNecessary
逻辑 - 处理: 需要对该 bean 进行代理创建,调用
wrapIfNecessary
方法
- 解释: cacheKey 对应的bean 确实已经提前暴露了引用,但是具有相同cacheKey 的 入参bean不是当初来获取早期引用的那个bean, 所以仍然要对这个入参bean 执行
前两种情况都比较好理解, 第3种情况需要多加解释,而且第三种也是无法解决的循环依赖。
出现的原因可以分为2种
- 能生成代理对象的不只AbstractAutoProxyCreator, 还有AbstractAdvisingBeanPostProcessor
- 存在多个AbstractAutoProxyCreator
⚠️:这个缓存的使用逻辑, 和实际开发中我们要删除缓存时思路一致, 要先判断要删除的线程是不是和当前添加缓存的线程一致,一致的话才能删除缓存,否则会造成数据不一致。
2. 有动态代理的循环引用实例
2.1 有动态代理的循环引用示例代码
该示例代码是Springboot 项目,构造一个依赖循环的例子
在如下代码中,userService和springTransactionService通过属性注入互相依赖, 且定义了一个Aspect , 这个Aspect 会为userService和springTransactionService 都生成代理对象
1 |
|
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 | public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor { |
查看AbstractAdvisingBeanPostProcessor.postProcessAfterInitialization
代码,可以看到也是生成代理对象的diamanté,其逻辑进入getProxy就和之前 讲过代理创建流程 完全一致,就不进入细节去分析了1
2
3
4
5public class ProxyFactory extends ProxyCreatorSupport {
public Object getProxy( { 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 |
|
当使用 @Repository
注解时,Spring 会在后台自动注册一个 PersistenceExceptionTranslationPostProcessor
。
PersistenceExceptionTranslationPostProcessor
继承了AbstractAdvisingBeanPostProcessor
, 因此使用了@Repository
注解的bean 在实例化完成后,最终生成的是其代理对象。
3.1.1 有动态代理的循环引用示例代码
改造下SpringTransactionService , 将@Service 替换成@Repository
, 其他代码保持不变。1
2
3
4
5
6
7
8
9
10
public class SpringTransactionService {
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
,所示PersistenceExceptionTranslationPostProcessor
在 getEarlyBeanReference
阶段是不会发挥作用的
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 |
|
经过AsyncAnnotationBeanPostProcessor.post
处理后,会为原始bean 生成一个代理对象,与暴露的早期引用不一致,最终会报错
3.3 ## BeanPostProcessor的不同过滤条件
在AbstractAdvisingBeanPostProcessor 引发异常的这2个例子中,能够看出,有机会对bean 生成代理的2处逻辑,对要执行的BeanPostProcessor有不同的过滤条件
1 | public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory |
getEarlyBeanReference
中只执行AbstractAutoProxyCreator
的子类,在Springboot 启动的项目中,就是AnnotationAwareAspectJAutoProxyCreator
- 在init 阶段的
applyBeanPostProcessorsAfterInitialization
中,会执行所有BeanPostProcessor
所以在有动态代理的循环依赖+暴露引用的时候,如果其中包含了除AbstractAutoProxyCreator
子类外可生成代理的BeanPostProcessor
, 就会产生一个类的2个实例,违反单例模式报错。
分析到这,可以看出,一个bean 只要暴露了早期引用,那么在init 阶段就绝不能再执行任何生成代理的操作了,因为一旦有代理操作就会再生成一个新的引用,就会有问题。
如果没有暴露早期引用,init 阶段随便几个代理操作都行。
比如把SpringTransactionService 中的userService 去掉, 把前面提到的各种可以生成代理的操作全部叠加,启动都是没有问题的1
2
3
4
5
6
7
8
9
public class SpringTransactionService {
public void performTransaction() {
// 模拟业务逻辑
System.out.println("performTransaction");
}
}
4. 存在多个 AbstractAutoProxyCreator
再来分析另外一种 有多个AbstractAutoProxyCreator
spring默认保证一个容器中只能有一个AbstractAutoProxyCreator 子类存在 ,如过手动添加或者自定义会出现多个APC情况。
这里直接分析源码,假设有2个AbstractAutoProxyCreator 子类存在。
4.1 getEarlyBeanReference
来看下早期引用的获取逻辑
1 | public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory |
由于在调用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 | public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport |
5. 解决方案
- 使用合理的Bean注解
- 使用@Lazy 注解
- 重构你的代码