国庆结束了,不知道各位是以一种怎样的状态迎接接下来的魔鬼工作周。反正本人是在家呆了七天,跟着网友的镜头也去了不少地方。有爬山,但 10 分钟只走了一米的;有因为车没电了,在众人艳羡的目光中享受了平时花钱才能走的应急车道;还有的参加了大大小小、各式各样的婚礼,明白了什么叫钱到用时方恨少的哀伤。不管怎么说,过去的那七天就是两个字,痛快!
扯了这么多,该回到正题了。读者可以先看下下面的代码,复制之后运行下,看下是什么效果,会不会出乎你的意料,笔者使用的 springboot 版本为:2.4.8:
@Component
public class TestAsync {
@Autowired
private TestAsync testAsync;
@Async
public void fun(){
}
}
运行之后发现 springboot 启动居然失败了,控制台打印了让人眼花缭乱的错误:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testAsync': Bean with name 'testAsync' has been injected into other beans [testAsync] 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.at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:631) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:657) ~[spring-beans-5.3.8.jar:5.3.8]
错误信息很多,但能帮我们解决问题的可能就那么几个:
Bean with name 'testAsync' has been injected into other beans [testAsync] 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复制代码
上面的这段错误描述或许能让我们发现其中的端倪,testAsync 这个 bean 被注入到了其他 bean 中,但 testAsync 是被包装过的,意味着其他 bean 用的并不是 testAsync 的最终版本。那么造成这个的原因是啥?为啥一个平时已经养成了习惯的用法不灵了?
不要束缚自己,这会让你陷入怪圈
根据提示,可以定位到报错的位置就在 AbstractAutowireCapableBeanFactory 这个类中。下面的代码来自 doCreateBean 方法,笔者删掉了部分无用的代码:
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
}
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, 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 放入到缓存中,用于后续的循环依赖;然后调用 populateBean 和 initializeBean 方法为 bean 进行属性赋值和初始化。正是由于 exposedObject == bean 这个判断为 false,因此流程走到了 else if 中抛出了异常。
好像每个人都走过这样一段路,云淡风轻的
文章到这还没完,还有一段代码:
@Servicepublic class TestService {
@Autowired
private TestService testService;
@Transactional
public void fun(){
}
}
上面的代码和开头的那段差不多,只不过一个是被 @Async 注解修饰,一个是被 @Transactional 注解修饰,但 TestService 却能够成功完成注入。关键就在于用来处理 @Transactional 注解的 AbstractAutoProxyCreator 缓存了原始 bean。
spring 为 bean 中属性赋值和初始化前,有这样一段代码:
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
上面短短的几行代码就是 spring 处理循环依赖的关键,spring 将尚未初始化完全的对象放入到 singletonFactories 缓存中:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
当出现循环依赖时,spring 会从 singletonFactories 缓存中取出对应的 ObjectFactory 并调用 getEarlyBeanReference 方法:
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;
}
AbstractAutoProxyCreator 实现了 SmartInstantiationAwareBeanPostProcessor 接口:
@Overridepublic Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
AbstractAutoProxyCreator 为 bean 创建代理前会将原始的 bean 缓存下来。
@Overridepublic 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;
}
在 AbstractAutoProxyCreator 实现的 postProcessAfterInitialization 方法中,会判断当前传入的 bean 还是不是之前放入的 bean,如果是的话会直接返回当前传入的 bean。因此下面的 if 条件就为 true,也就不会走到 else if 中抛出异常。
if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 这里的 if 判断为真
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.");
}
}
}
}
如果有读者比较懵逼,可以在自己的电脑上 debug 跟踪下就明白了。断点就打在上面提到的方法上即可。