场景描述:
springboot项目启动的时候报错:
org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'messageService': Bean with name
'messageService' has been injected into other beans [taskService]
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.
乍一看觉得是循环引用的问题,但是之前只知道spring对单例的循环引用是做了处理的,prototype的情况除外。而该项目中的bean全部是默认的singleton,因此排除prototype。
google一下得知是@Async导致的,官方的解决方式是在 @Async所在的类中添加@Lazy注解 ,本文的目的则是在于剖析为什么使用@Async会导致BeanCurrentlyInCreationException,以及为什么可以用@Lazy来解决问题
场景复现:
1、首先项目需要打开spring的异步开关,在application主类上加@EnableAsync
2、创建一个包含了@Async方法的异步类MessageService
:
@Service
public class MessageService {
@Resource
private TaskService taskService;
@Async
public void send(){
taskService.shit();
}
}
3、创建另一个正常类TaskService
,与异步类形成循环引用的关系(注意MessageService
和TaskService
在同一个包内,并且order为默认,因此会先扫描MessageService
再扫描TaskService
):
@Service
public class TaskService {
@Resource
private MessageService messageService;
public void shit(){
System.out.println(); }
}
4、启动springboot项目成功报错
问题出现的原因
在分析原因之前,我们需要提前知道两个重要的点:
- spring的aop代理(包括
@Transactional
事务代理),都是在AbstractAutowireCapableBeanFactory
的populateBean
方法后的initializeBean
当中的applyBeanPostProcessorsAfterInitialization
方法里,通过特定的后置处理器里创建代理对象(一般是AnnotationAwareAspectJAutoProxyCreator
) - 然而1.当中描述的代理过程,是在这个类不涉及到循环引用的情况下才会执行,也就是说满足百分之90的情况,而循环引用的情况会做特殊的处理,即提前创建代理对象。
举个例子:
T类是个包含了@Transactional
方法的类,属于需要被代理的对象,并且通过@Resource
(或者@Autowired
)的方式依赖了A ,A类中也以同样的方式注入了T,并且T类先于A类开始实例化过程,那么简单的实例化流程就是:
- T的
BeanDefinition
被spring拿到后,根据构造器实例化一个T对象(原始对象而非代理对象),并包装成objectFactory
放入singletonFactories
(三级缓存)中
然后执行populateBean
方法开始注入属性的流程,其中会利用CommonAnnotationBeanPostProcessor
(@Resource
用这个后置处理器,@Autowired
用AutowiredAnnotationBeanPostProcessor
)执行T的属性注入步骤,遍历T中所依赖的属性 - 发现T依赖了A,会先到
beanFactory
的一至三级缓存中,通过A的beanName查询A对象,如果没找到,即A还没有被实例化过,那么会将A作为实例化的目标,重复a.步骤:将A实例化后的对象包装成objectFactory
放入singletonFactories
,接着对A执行populateBean
来注入属性 - 遍历A的属性,发现A依赖了T,然后尝试去
beanFactory
中获取T的实例,发现三级缓存中存在T的objectFactory
,因此执行objectFactory.getObject
方法企图获取T的实例。然而这个objectFactory
并非是简单把对象返回出去,而是在当初包装的时候,就将AbstractAutowireCapableBeanFactory
的getEarlyBeanReference
方法写入getObject
当中 - 在
getEarlyBeanReference
方法里,会遍历所有SmartInstantiationAwareBeanPostProcessor
的子类型的后置处理器,执行对应的getEarlyBeanReference
方法,此时会将第1.点提到的代理过程提前,即通过AnnotationAwareAspectJAutoProxyCreator
(SmartInstantiationAwareBeanPostProcessor
的子类)来创建一个代理对象,并放入二级缓存earlySingletonObjects
当中,然后将这个代理对象通过field.set的形式(默认形式)注入到A,至此就完成了普通aop对象的循环引用处理
出现本文标题中循环引用异常的原因分析
包含了@Async
方法的类与@Transactional
的类相似,也会被替换成一个新的代理类,但是与普通aop不同的是,@Async
不会在 getEarlyBeanReference
阶段执行创建代理的逻辑(这么做的原因暂时没仔细分析),而是被延迟到了initializeBean
步骤当中(即1.提到的90%的代理情况),这样一来就会导致TaskService
注入的并不是最终创建完成的MessageService
的代理对象,很明显这样的结果是不合理的,而在代码层面,spring的AbstractAutowireCapableBeanFactory
当中,在initializeBean
和将bean放入一级缓存之间,有这么一段容易被忽视的代码,用于把控最终的循环引用结果正确性:
//是否允许提前暴露,可以理解为是否允许循环引用
if (earlySingletonExposure) {
//遍历一到三级缓存,拿到的bean
Object earlySingletonReference = getSingleton(beanName, false);
//如果缓存中的对象不为空
if (earlySingletonReference != null) {
//exposedObject是执行了initializeBean之后的对象,bean是通过构造器创建的原始对象
//如果两者相等,则将exposedObject设置为缓存中的对象
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
} //如果两者不是同一个对象,并且不允许直接注入原生对象(默认false),且当前beanName有被其他的bean所依赖
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
//则获取所有依赖了该beanName的对象
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
//如果这个对象已经处于一级缓存当中,则添加到actualDependentBeans,即依赖该对象的bean是一个走完了整个流程,不会再有机会回炉重做的bean
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
//最后判断actualDependentBeans是否为空,不为空就抛循环引用的异常
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.");
}
}
}
}
我们结合这段代码来分析@Async
循环引用场景:
- 先看第4行,首先这个时候肯定还没进入一级缓存,而我们知道
@Async
在getEarlyBeanReference
中并没有执行代理,因此第4行获取到的earlySingletonReference
是messageService
的原始对象 - 进入第9行,判断
exposedObject == bean
,由于@Async
的代理过程发生在initializeBean
中, 因此exposedObject
是代理对象,而bean
是通过构造器直接实例化的原始对象,因此肯定不相等 - 进入第12行,
allowRawInjectionDespiteWrapping
默认为false
,而messageService
是被TaskService
所引用的,因此hasDependentBean(beanName)
为true
,会进入14行代码块 - 重点是判断18行的
!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)
,该方法代码为:
protected boolean removeSingletonIfCreatedForTypeCheckOnly(String beanName) {
//如果不是已经完全创建好的bean,就返回true,否则返回false
if (!this.alreadyCreated.contains(beanName)) {
removeSingleton(beanName);
return true;
}
else {
return false;
}
}
这里就要回到场景复现时提到的:
3、注意
MessageService
和TaskService
在同一个包内,并且order为默认,因此会先扫描MessageService
再扫描TaskService
。
- 由于
messageService
先被扫描,因此会在messageService
的populateBean
当中,执行TaskService
的实例化过程,而TaskService
此时并不知道messageService
是一个需要代理的类,因此将一个未代理的messageService
注入之后,心安理得地执行了initializeBean以及后续的初始化操作,然后标记为成功创建并装入一级缓存。 - 也就是说,此时spring判断
TaskService
是一个已经完全实例化并初始化完成的对象。因此removeSingletonIfCreatedForTypeCheckOnly
方法会返回false
,则18行返回的是true,所以TaskService
会被加入到actualDependentBeans
当中,最终抛出BeanCurrentlyInCreationException
异常 - 简单来说,spring认为如果一个bean在
initializeBean
前后不一致,并且一个已经完全初始化的beanA
注入了这个未完全初始化的beanB
,在spring的流程中beanA
就再也没有机会改变注入的依赖了,所以会抛异常。 - 而如果先实例化
TaskService
再实例化MessageService
,就不会有这个问题(不信可以将TaskService
改成ATaskService
试试),因为如果在实例化TaskService
的时候没有发现提前暴露出来的MessageService
,就会专注于创建MessageService
的过程,实例化并初始化完成后才会回到TaskService
并将MessageService
注入
为什么@Lazy
可以解决这个问题
@Lazy
被大多数人理解为:
当使用到的时候才会加载这个类。
这个也算是spring希望我们看到的,但是这个描述实际上不完全准确。举个例子:
@Service
public class TaskService {
@Resource
@Lazy
private MessageService messageService;
public void shit(){
System.out.println();
}
}
- 这里在
messageService
属性上面加了@Lazy
。在实例化TaskService
,并populateBean
的时候,在CommonAnnotationBeanPostProcessor
的getResourceToInject
方法中, spring发现messageService
被@Lazy
注解修饰,便会将其包装成一个代理对象:即创建一个TargetSource
,重写getTarget
方法,返回的是CommonAnnotationBeanPostProcessor
里的getResource(beanName)
方法(方法体中的逻辑,可以理解为从工厂的三层缓存中获取对象)。也就是说,注入给TaskService
的是一个MessageService
的代理对象(这是本文出现的第三种代理场景)。 - 而spring在实例化
MessageService
的时候,不会管他是否是由@Lazy
修饰的,只会将其当做一个普通的bean去创建,成功后就会放入一级缓存(所以严格来讲,不能说是“使用到了再去加载”)。 - 容器启动完成后,
TaskService
在需要使用messageService
的方法时,会执行代理对象的逻辑,获取到TargetSource
,调用getResource
从三层缓存中获取messageService
的真实对象,由于messageService
此时已经被spring完整地创建好了,处于一级缓存singletonObjects
当中,因此拿到之后可以放心使用。
思考
为什么spring要用这样的方式来处理@Lazy
呢?
可能是真的lazy,也是真的聪明吧,这样一来不需要修改整个复杂的bean实例化流程,只需要在注入的时候做一个小小的判断,相比较而言改动很小,影响也很小。