引子
@Configuration
@EnableAsync
public class Test {
@Bean
public A a(){ return new A();}
@Bean
public B b(){ return new B();}
public class A{
@Resource
private B b;
public void a(){
}
}
public class B{
@Resource
private A a;
public void b(){
}
}
}
对于上方代码,你觉得运行是否会报错?
答案是不会,虽然A和B之间循环引用,但Spring早就想到这一点,并进行处理了。
接着我们在a()
和b()
方法上加上注解@Async
,并且运行
@Configuration
@EnableAsync
public class Test {
@Bean
public A a(){ return new A();}
@Bean
public B b(){ return new B();}
public class A{
@Resource
private B b;
@Async
public void a(){
}
}
public class B{
@Resource
private A a;
@Async
public void b(){
}
}
}
错误信息如下:
Error creating bean with name 'a': Bean with name 'a' has been injected into other beans [b] 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.
意思是创建a时,其他依赖a的bean获取到的a和最终加入缓存的不一样
原因分析:
bean构建步骤
首先看一下Spring怎么创建Bean的,我们这里讨论的是单例模式:
缓存说明
bean创建过程中的缓存:
singletonObjects
:构建bean并初始化完成后加入的,也称一级缓存earlySingletonObjects
:处理依赖过程中被其他bean所引用后加入,也称二级缓存singletonFactories
:构建完成后加入,也称三级缓存
同一bean只会在一个缓存中出现
没有循环引用bean的构建
循环引用bean的构建
上图完美解释了为什么A,B互相引用,却不会导致循环的原因。为什么只是加入了@Aysnc
注解却报错,我们需要知道一些其他的东西
源码分析
上面代码就是bean创建的核心部分,从红框部分讲解
- 创建或获取bean的对象
- 将对象加入三级缓存,值得注意的是,加入的是一个lambda表达式,调用了
getEarlyBeanReference()
方法,这里我们之后会说明 - 填充依赖bean,如果依赖bean不存在,会再次进入
doCreateBean()
。若存在,则会从缓存中获取;要注意的是,如果是从三级缓存中获取,则会加入到二级缓存 - 初始化bean,执行
BeanPostProcessor
的postProcessBeforeInitialization/postProcessAfterInitialization
,实现如InitializingBean
的afterPropertiesSet
或自定义init
方法,或AOP的实现,并返回初始化后的bean - 判断是否循环引用,是的话判断其他引用的bean和当前的bean是否相同,否则删除所有依赖于当前bean一级缓存中的bean(前提是这个bean只用来进行类型检查),不能删除则报错。
很明显,之前我们遇到的问题,就是在4、5步出现的。因为第4步初始化时返回一个和之前不同的A对象,并且A被B所引用了,进入了二级缓存。又因为B不是用来类型检查,也就是说它的引用是有效的,最终报错
接下来我们就要看看第4步为什么会返回一个和之前不相同的对象,这其中主要类是BeanPostProcessor
BeanPostProcessor - bean初始化前后处理器
BeanPostProcessor 有两个方法:
postProcessBeforeInitialization(Object bean, String beanName)
:bean初始化之前调用,并返回处理后的beanpostProcessAfterInitialization(Object bean, String beanName)
:bean初始化之后调用,并返回处理后的bean
第4步exposedObject = initializeBean(beanName, exposedObject, mbd);
进入:
- 执行所有BeanPostProcessor的
postProcessBeforeInitialization
方法 - 执行bean的初始化方法
- 执行所有BeanPostProcessor的
postProcessAfterInitialization
方法
回到注解@Async
,为什么它能生效,就是因为Spring注册了一个AsyncAnnotationBeanPostProcessor
类的bean,它检测带有@Async
方法的对象,代理并返回一个新对象
总结问题
因为A在创建过程中被B引用,并且A在初始化之后发生改变,导致B引用了一个不正确的对象,结果自然出现错误。
下面我们会提出怎么解决这个问题,并且提出一些不同的原因导致的这种错误
解决方案:
- 手动获取依赖bean
- 实现自定义
BeanPostProcessor
- 向Spring提供一个
AsyncAnnotationAdvisor
1.手动获取依赖bean
既然是因为循环引用导致bean的提前暴露,那我们大可以让Spring走完全部的流程,我们自己手动注入依赖。
@Configuration
@EnableAsync
public class Test {
@Bean
public A a(){ return new A();}
@Bean
public B b(){ return new B();}
/**
* 继承ApplicationContextAware,获取ApplicationContext。
*/
public class A implements ApplicationContextAware {
private ApplicationContext context;
private B b;
/**
* b为空则使用上下文获取此bean
* @return
*/
public B getB() {
if (b == null){
b = context.getBean("b",B.class);
}
return b;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
@Async
public void a(){
}
}
public class B implements ApplicationContextAware {
private ApplicationContext context;
private A a;
public A getA() {
if (a == null){
a = context.getBean("a",A.class);
}
return a;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
@Async
public void b(){
}
}
}
如上,使用上下文手动获取Bean。一定要在Spring的bean构建和初始化流程结束后才可调用getA()
或getB()
,否则在构建或初始化过程中又触发依赖bean的构建,那样就等同于使用@Resource
了
2.实现自定义BeanPostProcessor
还记得第2步加入三级缓存的lambda表达式里的getEarlyBeanReference
方法吗,我们看看它的源码
一个简单的循环,大致内容是调用实现了SmartInstantiationAwareBeanPostProcessor
接口的getEarlyBeanReference
方法,我们接着看getEarlyBeanReference
方法到底是做什么的
在三级缓存被调用时,调用此方法,能返回postProcessBeforeInitialization
/ postProcessAfterInitialization
处理后的对象。之后在调用postProcessBeforeInitialization
/ postProcessAfterInitialization
时会直接返回原始bean。
按照这个逻辑,使用@Async
不应该会报错,这是因为实现@Async
功能的AsyncAnnotationBeanPostProcessor
类并没有实现getEarlyBeanReference
方法,所以我们大可以自己实现一个@Aysnc
处理类,并且加入到Spring中。关键代码Spring已经帮我们写好,我们只需要实现SmartInstantiationAwareBeanPostProcessor
接口的getEarlyBeanReference
方法,使之在被循环引用时能返回初始化之后的对象。
/**
* 自定义异步方法代理处理,实现{@link #getEarlyBeanReference}
* 使循环引用时能获得正确的对象
*/
public class MyAsyncAnnotationBeanPostProcessor extends AsyncAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor {
private Set worked = new HashSet();
/**
* 假设在bean还未创建完成时被其他bean引用,在缓存中获取就会触发此方法
* 返回{@link #postProcessBeforeInitialization} / {@link #postProcessAfterInitialization}所返回的
* 之后再调用{@link #postProcessBeforeInitialization} / {@link #postProcessAfterInitialization}不做处理
* @param bean 原始bean
* @param beanName bean名称
* @return 处理后的结果
* @throws BeansException
*/
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
Object wraBean = super.postProcessAfterInitialization(bean, beanName);
worked.add(bean);
return wraBean;
}
/**
* 判断是否处理过,已经处理直接返回原始bean,反正则进行处理
* @param bean 原始bean
* @param beanName bean名称
* @return 处理后的结果
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (worked.contains(bean))return bean;
Object wraBean = super.postProcessAfterInitialization(bean, beanName);
worked.remove(bean);
return wraBean;
}
}
修改后的Test.java
@Configuration
//@EnableAsync
public class Test {
@Bean
public A a(){ return new A();}
@Bean
public B b(){ return new B();}
@Bean
public AsyncAnnotationBeanPostProcessor asyncAnnotationBeanPostProcessor(){
return new MyAsyncAnnotationBeanPostProcessor();
}
public class A {
@Resource
private B b;
@Async
public void a(){
}
}
public class B {
@Resource
private A a;
@Async
public void b(){
}
}
}
注意:@EnableAsync
会提供一个AsyncAnnotationBeanPostProcessor
bean,这与我们向Spring提供的所冲突,所以必须注释它
3.向Spring提供一个AsyncAnnotationAdvisor
除了@Aysnc
会进行代理,Spring还有许多功能使用到代理。要知道@Transactional
是我们非常常用的注解,它也会导致Spring代理bean,并且服务之间相互依赖非常常见,为什么不会发生错误呢?
这归功于SmartInstantiationAwareBeanPostProcessor
,Spring AOP 的主要实现类是AbstractAutoProxyCreator
,这是一个抽象类,它主要功能是获取切面或织入点,对对象代理返回代理对象,它的几个实现类如下:
DefaultAdvisorAutoProxyCreator
:默认的,根据切面代理,不支持AspectJ注解BeanNameAutoProxyCreator
:根据bean名称代理,非常不常用,并且没有切面InfrastructureAdvisorAutoProxyCreator
:只对框架内的切面进行代理,比如说实现@Transactional
的切面。从源码来说,BeanDefinition的role定义为2,这个切面就会被处理AspectJAwareAdvisorAutoProxyCreator
:引入了AspectJ的理念,和DefaultAdvisorAutoProxyCreator
没有太大区别。对切面进行排序,比方说Before排序在After之前AnnotationAwareAspectJAutoProxyCreator
:最完全的处理器,不仅能处理Spring定义的切面,也能处理AspectJ内的注解
以上类在你向Spring注入切面bean后,创建bean时就会触发,并自动代理bean。它们其中并没有AsyncAnnotationBeanPostProcessor
,这是因为AsyncAnnotationBeanPostProcessor
只进行@Async
的处理,并且@Async
这个功能定义的切面不会注册到Spring中。而上方的所有类会从Spring中获取所有切面进行分析处理。
看看下方的代码:
@Configuration
//@EnableAsync
public class Test {
@Bean
public A a(){ return new A();}
@Bean
public B b(){ return new B();}
@Bean
public AsyncAnnotationBeanPostProcessor asyncAnnotationBeanPostProcessor(){
return new MyAsyncAnnotationBeanPostProcessor();
}
public class A {
@Resource
private B b;
@Async
@Transactional
public void a(){
}
}
public class B {
@Resource
private A a;
@Async
public void b(){
}
}
}
报错信息:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'main': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Bean with name 'a' has been injected into other beans [b] 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不一样导致的。至于为什么,查看下方流程图
在getEarlyBeanReference
执行时,被多个处理器进行了处理。之后在初始化时执行,给的是一个原始对象,但是在第一个代理的处理器之后,他们所存储的是前一个代理的处理器返回内容,所以postProcessAfterInitialization
还会对其进行处理,最终返回一个与原始对象不同的对象
AsyncAnnotationBeanPostProcessor
可以看做成只处理@Async
的切面,而AbstractAutoProxyCreator
则是处理所有的切面,所以我们只需要定义一个处理@Async
的切面。那我们可以将AsyncAnnotationBeanPostProcessor
中处理的切面交给AbstractAutoProxyCreator
。
代码如下:
@Configuration
//@EnableAsync
public class Test {
@Bean
public A a(){ return new A();}
@Bean
public B b(){ return new B();}
// @Bean
// public AsyncAnnotationBeanPostProcessor asyncAnnotationBeanPostProcessor(){
// return new MyAsyncAnnotationBeanPostProcessor();
// }
/**
* 注册一个AsyncAnnotationAdvisor
* AsyncAnnotationAdvisor是Spring用来处理@Async的切面,不需要我们自己定义
* @return
*/
@Bean
public AsyncAnnotationAdvisor asyncAnnotationAdvisor(){
AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor();
return advisor;
}
public class A {
@Resource
private B b;
@Async
@Transactional
public void a(){
System.out.println(1);
}
}
public class B {
@Resource
private A a;
@Async
public void b(){
}
}
}
框架注册的代理处理器冲突问题
这个bug是我踩的坑。下面看看这个示例。
项目引入如下依赖
pom.xml
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
application.yml
spring:
datasource:
druid:
aop-patterns: com.*
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.3.12:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
Test.java
@Configuration
public class Test {
@Bean
public A a(){ return new A();}
@Bean
public B b(){ return new B();}
public class A {
@Resource
private B b;
@Transactional
public void a(){
System.out.println(1);
}
}
public class B {
@Resource
private A a;
@Transactional
public void b(){
}
}
}
上述代码同样会和之前报一样的错误。
如何找出问题所在
出现这个错误,基本可以断定是注册了多个代理的BeanPostPocessor,并且产生了循环引用,直接进入org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference
中并添加一个断点启动。
启动并观察this.beanPostProcessorCache.smartInstantiationAware
,看看里面是否有多个代理BeanPostPocessor
明显看到AnnotationAwareAspectJAutoProxyCreator
和DefaultAdvisorAutoProxyCreator
,说明引入的maven依赖中注册了多个。
我使用idea,复制一下名称,ctrl+shift+f搜索一下试试
找到和依赖jar相关的类,进入查看
如何解决
Druid注册一个DefaultAdvisorAutoProxyCreator
,这是不应该的。这个时候我们除了注释yml中的aop-patterns或看看项目作者有没有发现这一问题。查看druid的github,发现再1.1.12版本就已经修复这个问题,更新版本,解决问题。
项目中可能产生此类错误的不是我所写的示例,但都可以归到这一类目。如果升级版本不可以解决问题,可以看看我是如何解决@Async的问题,举一反三。
AnnotationAwareAspectJAutoProxyCreator
则是Spring boot自动构建的,看看源码:
下方是上方代码的流程图
控制开关是spring.aop.auto
,根据是否引入aspectjweaver-1.9.7.jar来提供两个BeanPostProcessor,AnnotationAwareAspectJAutoProxyCreator
和InfrastructureAdvisorAutoProxyCreator
。
我推荐引入aspectjweaver-1.9.7.jar来向Spring提供AnnotationAwareAspectJAutoProxyCreator
,因为InfrastructureAdvisorAutoProxyCreator
只对框架内的切面有效。