先上示例代码
接口A
package iron.man.lyf.ironmanspring2.service;
/**
* @author liuyanfei
* @description
* @date 2023/2/11 9:24 下午
**/
public interface AInterface {
public void funA();
}
实现类A
package iron.man.lyf.ironmanspring2.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
/**
* @author liuyanfei
* @description
* @date 2023/1/29 11:29 上午
**/
//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class A implements AInterface{
@Autowired
BInterface b;
public A(){
System.out.println("A 被创建了");
}
@Override
@Async
public void funA(){
System.out.println("A:"+Thread.currentThread().getName());
}
}
接口B
package iron.man.lyf.ironmanspring2.service;
/**
* @author liuyanfei
* @description
* @date 2023/2/11 9:25 下午
**/
public interface BInterface {
public void funB();
}
实现类B
package iron.man.lyf.ironmanspring2.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
/**
* @author liuyanfei
* @description
* @date 2023/1/29 11:29 上午
**/
//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class B implements BInterface{
// @Lazy
@Autowired
private AInterface a;
public B(){
System.out.println("B 被创建了");
}
@Override
// @Async
public void funB() {
System.out.println("B:"+Thread.currentThread().getName());
a.funA();
}
}
1. 抛出一个问题
- 如service文件夹中,A类和B类进行了循环引用。其中在A类的
funA()
上加了@Async
的注解,表示其他类调用本方法会开启线程进行异步调用。但是项目这个样子是无法启动的,启动会报错,报错如下:
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.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:730) ~[spring-beans-5.3.24.jar:5.3.24]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:566) ~[spring-beans-5.3.24.jar:5.3.24]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:371) ~[spring-beans-5.3.24.jar:5.3.24]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:244) ~[spring-beans-5.3.24.jar:5.3.24]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:367) ~[spring-beans-5.3.24.jar:5.3.24]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:209) ~[spring-beans-5.3.24.jar:5.3.24]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:968) ~[spring-beans-5.3.24.jar:5.3.24]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:968) ~[spring-context-5.3.24.jar:5.3.24]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:603) ~[spring-context-5.3.24.jar:5.3.24]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.7.jar:2.7.7]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) [spring-boot-2.7.7.jar:2.7.7]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) [spring-boot-2.7.7.jar:2.7.7]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) [spring-boot-2.7.7.jar:2.7.7]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) [spring-boot-2.7.7.jar:2.7.7]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) [spring-boot-2.7.7.jar:2.7.7]
at iron.man.lyf.ironmanspring2.IronManSpring2Application.main(IronManSpring2Application.java:12) [classes/:na]
- 如果把A类方法中
funA()
的@Async
注解去掉,那么项目正常启动。这是因为在spring中,早就解决了循环引用的问题。(注意,springboot2.6之后默认把循环引用的支持给关掉了,需要在yml或者properties中打开开关:spring.main.allow-circular-references = true
)
那么为什么会发生1中的那种情况呢?spring不是已经解决了循环依赖的问题了吗,而且在1中,springboot 的循环依赖开关已经打开了呀。还是先说解决办法吧。
2. 解决办法
-
加一个controller,注入B类,但是A类不要注入到任何的其他类中
-
删除controller,去掉A类的
funA()
上的@Async
,在B类的funB()
上加@Async
注解。
o.0 ~~~ 上面两个方法,在实际业务中肯定是不行的。这两种方法只是为了引出原理部分。
- 终极解决办法:在A中注入B类的时候使用
@Lazy
注解。或者在B类注入A类时使用@Lazy
注解。
好了,如果只是想寻求一个解决办法的话,那么看到这里可以不用接着往下看了。如果想知道为什么会这个样子可以接着往下看
声明:原理部分需要理解 spring 的循环依赖,以及 @Async
的使用以及原理。否则会看的晕晕乎乎不知道所云
关于spring 解决普通bean循环依赖请参照:https://blog.csdn.net/f641385712/article/details/92801300
@Async
的使用以及原理、源码分析请参照:https://blog.csdn.net/f641385712/article/details/89430276
3. 关于 2.1、2.2的疑惑
当看到2.1、2.2 的解决办法的时候会不会感到很神奇。基本啥都没做,就是做了一下引用、换了换注解的位置,为什么问题就解决了呢??其实这两个解决方法都指向了一个点:spring实例化bean 的顺序问题
- spring中加在bean会按某种顺序来实例化bean。先实例化controller,再加在service。而且在实例化controller 的时候如果里面有注入进去的service或者Dao等其他的bean,会把这些servicebean、daobean优先实例化出来。所以本例中,使用了controller先注入B类,那么B类先实例化。A类没有被controller引入,所以排在了B类后面去实例化,现在的实例化顺序就是 C -->B -->A。那么这种启动就不会报错了。
- 而如果service没有controller引用,那么实例化的时候可能是按首字母字母表的顺序来实例化(未验证)。所以在没有加入Controller C类时。A、B两个类的实例化顺序就是 A -->B。此时启动就会报错。但是还是这个顺序,把
@Async
注解转移到 B类的方法中,再启动就不会报错了。
综上所述:可以发现问题的症结:当A、B两个类循环引用时。如果先实例化A,恰好A类的方法上还有@Async
这个注解,那么就会报错。但是如果因为一些额外的因素,导致spring先实例化B,就不会有什么问题。就是 A–>B–>A 会报错,B–>A–>B不会报错
先说结论,问题的根本:之所以会发生上述问题,根本原因是因为A类使用了 @Async
,那么这个类被实例化出来就不是一个单纯的bean了,而是一个被jdk动态代理,或者是被CGLIB代理的bean。所以就会出现一些问题,具体原理如下
4.关于发生异常的时机
上面A、B类为例
- 在实例化A时,会把A中的属性的类都给实例化,也就是在实例化完A还没有初始化A的时候,检测到有B属性又去实例化B
- 在实例化完B,初始化B之前,又检测到B中有A属性。所以又转去实例化A类,发生异常就是在第二次谋求实例化A类的时候发生的
- 就是 A -->B–>A,在第二个A的时候发生了异常
5. 发生这种问题的原理部分
关键代码在这里:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
代码一:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
//封装被创建的Bean对象
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {//--------------创建对象实例。
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 再从Wrapper中把Bean原始对象(非代理~~~) 这个时候这个Bean就有地址值了,就能被引用了~~~
// 注意:此处是原始对象,这点非常的重要
Object bean = instanceWrapper.getWrappedInstance();
//获取实例化对象的类型
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// Allow post-processors to modify the merged bean definition.
//调用PostProcessor后置处理器(不知道这个后置处理器是干啥用的)
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.
//向容器中缓存单例模式的Bean对象,以防循环引用
// earlySingletonExposure 用于表示是否”提前暴露“原始对象的引用,用于解决循环依赖。
// 对于单例Bean,该变量一般为 true 但你也可以通过属性allowCircularReferences = false来关闭循环引用
// isSingletonCurrentlyInCreation(beanName) 表示当前bean必须在创建中才行
//1. 如果 allowCircularReferences 设置为false,
//2. 那么就不能把bean实例放到三级缓存中,放不到里面那么循环依赖的其他的bean就不能拿到三级缓存
//3. 拿不到三级缓存那么就要 调用 getSingleton(beanName, () -> { 去创建bean
//4. 在getSingleton 这个方法里面会把正在创建的bean放到 singletonsCurrentlyInCreation 这个正在创建的bean里面。
//5. 由于是循环依赖 在第一个创建的时候已经把beanName放到里面了,singletonsCurrentlyInCreation是个set集合 第二次肯定是放不进去的,所以就会引出下面的异常
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//这里是一个匿名内部类,为了防止循环引用,尽早持有对象的引用
// 上面讲过调用此方法放进一个ObjectFactory,二级缓存会对应删除的
// getEarlyBeanReference的作用:调用SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()这个方法 否则啥都不做
// 也就是给调用者个机会,自己去实现暴露这个bean的应用的逻辑~~~
// 比如在getEarlyBeanReference()里可以实现AOP的逻辑~~~ 参考自动代理创建器AbstractAutoProxyCreator 实现了这个方法来创建代理对象
// 若不需要执行AOP的逻辑,直接返回Bean
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
//Bean对象的初始化,依赖注入在此触发
//这个exposedObject在初始化完成之后返回作为依赖注入完成后的Bean
Object exposedObject = bean;
try {
//将Bean实例对象封装,并且Bean定义中配置的属性值赋值给实例对象.instanceWrapper 是 BeanWrapper对象
//这里会把@autoware 等注解的对象注入进去。如果所注入的对象还没有被实例化,那么会先把该对象实例化后注入。
//此时候上面说到的getEarlyBeanReference方法就会被执行。这也解释为何我们@Autowired是个代理对象,而不是普通对象的根本原因
populateBean(beanName, mbd, instanceWrapper);
//---------初始化Bean对象。在这里调用了 后置处理器
// 实例化。这里会执行后置处理器BeanPostProcessor的两个方法
// 此处注意:postProcessAfterInitialization()是有可能返回一个代理对象的,这样exposedObject 就不再是原始对象了 特备注意哦~~~
// 比如处理@Aysnc的AsyncAnnotationBeanPostProcessor它就是在这个时间里生成代理对象的(有坑,请小心使用@Aysnc)
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);
}
}
// earlySingletonExposure:如果你的bean允许被早期暴露出去 也就是说可以被循环引用 那这里就会进行检查
if (earlySingletonExposure) {
//=========================================关键代码在这里=========================================
//注意,注意:第二参数为false 表示不会再去三级缓存里查了~~~
// 此时一级缓存肯定还没数据,但是如果 进行了循环依赖,那么二级缓存中肯定会有数据。
// 只要是earlySingletonReference 中能取到值的,那必定是某个被循环依赖的bean第二次执行创建bean了如:
// a和b是循环依赖的:a --> b --> a 现在正是执行的到了 第二个a的阶段了
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 这个意思是如果经过了initializeBean()后,exposedObject还是木有变,那就可以大胆放心的返回了
// initializeBean会调用后置处理器,这个时候可以生成一个代理对象,那这个时候它哥俩就不会相等了 走else去判断吧
if (exposedObject == bean) {
//当前实例化的Bean初始化完成
exposedObject = earlySingletonReference;
}//---------如果能走到下面的 else if 那么说明当前的bean被代理了,可要小心点。如果循环依赖了会给你报错的
// allowRawInjectionDespiteWrapping这个值默认是false。就是允许 在bean被代理的情况下 可以循环依赖
// hasDependentBean:若它有依赖的bean 那就需要继续校验了~~~(若没有依赖的 就放过它~),既然都循环依赖了,肯定会有依赖的bean
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// 拿到它所依赖的Bean们~~~~ 下面会遍历一个一个的去看~~
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
// 一个个检查它所有Bean
// removeSingletonIfCreatedForTypeCheckOnly 这个 在AbstractBeanFactory里面
// 简单的说,它如果判断到该dependentBean并没有在创建中的情况下,那就把它从所有缓存中移除~~~ 并且返回true
// 否则(比如确实在创建中) 那就返回false 进入我们的if里面~ 表示所谓的真正依赖
//(解释:就是真的需要依赖它先实例化,才能实例化自己的依赖)
for (String dependentBean : dependentBeans) {
//对依赖Bean进行类型检查
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
// 若存在真正依赖,那就报错(不要等到内存移除你才报错,那是非常不友好的)
// 这个异常是BeanCurrentlyInCreationException,报错日志也稍微留意一下,方便定位错误~~~~
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.");
}
}
}
}
// Register bean as disposable.
//注册完成依赖注入的Bean
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
-
看130行代码(行数是指上述代码片段的行数,而在实际源码中的行数)抛出的异常。为
BeanCurrentlyInCreationException
,而且与上述抛异常的提示术语一致,所以上面的错正是这里抛出去的。 -
看 102行的
Object earlySingletonReference = getSingleton(beanName, false);
的代码。earlySingletonReference
为空与否直接关乎下面逻辑是否执行。earlySingletonReference 为空下面逻辑不会执行,自然也不会抛异常。
一、关于二级缓存
getSingleton(beanName, false)
的代码位置org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
代码二:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
//先从一级缓存singletonObjects中去获取。(如果获取到就直接return)
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
如果现在创建的这个bean 一级缓存是null,且是正在创建的状态(isSingletonCurrentlyInCreation()),那就再从二级缓存earlySingletonObjects中获取。(如果获取到就直接return)
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
//如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取。
// 就从三级缓存singletonFactory.getObject()获取。(
// 如果获取到了就从singletonFactories中移除,并且放进earlySingletonObjects。其实也就是从三级缓存移动(是剪切、不是复制哦~)到了二级缓存)
//加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决
//向二级缓存添加 数据只有这一个地方
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
//=====================把bean存到二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
因为 allowEarlyReference
传进来的是false,所以这段代码只会执行到第8行,也就是无论从 singletonObjects
、earlySingletonObjects
这两个map中拿到值与否都会返回。由上面的参考资料可知singletonObjects
和earlySingletonObjects
分别是spring用来解决循环依赖的一、二级缓存。其中singletonObjects
一级缓存只有在bean完完全全实例化,包括属性注入完毕等等,也就是这个方法返回之后的之后才会把创建好的bean放入到一级缓存里面。所以在这个地方一级缓存肯定是不会有值的。那么这个方法返回值是否为空主要就看 earlySingletonObjects
二级缓存中是否可以取到值了。
分析下面逻辑请牢记一个实例化顺序:A1–>B–>A2,为了区分第一次实例化A为A1,第二次实例化A为A2,实际是一个A
-
二级缓存
earlySingletonObjects
什么情况下会把bean存到里面。就是上面 23行代码中,把bean put到二级缓存中的。那么就来分析一下什么时候把bean给存到
earlySingletonObjects
中的。 -
请看org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean中的代码
代码三:
protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { //根据指定的名称获取被管理Bean的名称,剥离指定名称中对容器的相关依赖 //如果指定的是别名,将别名转换为规范的Bean名称 String beanName = transformedBeanName(name); Object beanInstance; // Eagerly check singleton cache for manually registered singletons. //-----------------------先从缓存中取是否已经有被创建过的单态类型的Bean,对于单例模式的Bean整个IOC容器中只创建一次,不需要重复创建 //-----------------------先去获取一次,如果不为null,此处就会走缓存了~~ Object sharedInstance = getSingleton(beanName);
重点在 13行。这里就会调用
getSingleton(beanName, true)
。也就是在差不多刚要创建bean 的时候就会调用一下这个函数。为什么要说刚要呢?因为singletonsCurrentlyInCreation
中还没设置值呢。singletonsCurrentlyInCreation
是一个Set集合,这个Set是在bean实例化的时候会把beanName设置进去,实例化完毕了再把beanName从里面删掉。-
在第一次实例化A的时候,也就是A1的过程。在调用
getSingleton(beanName, true)
时,在代码二
中,第4行,去一级缓存中取了一下没取到,在第5行中去 调用isSingletonCurrentlyInCreation(beanName)
从singletonsCurrentlyInCreation
中取值也没取到,所以就直接返回了。下面逻辑不会执行。 -
所有的bean有一个绕不开的逻辑,那就是不论创建哪个bean。都会把实例化出来的bean放到
singletonFactories
三级缓存中。具体可查看上面 《关于spring 解决普通bean循环依赖》的文章。 -
A1–>B–>A2,经过A1后,进入A2,也就是第二次再尝试实例化A ,进入
getSingleton(beanName, true)
情况有所不同。因为经过A1后,singletonsCurrentlyInCreation
中已经把beanName为A 的值set进去了,而且A1过程A在实例化以后也放到了 三级缓存singletonFactories
中。所以代码二
中会一直执行到第 24行。把A 的实例放入到二级缓存earlySingletonObjects
中,同时把A在三级缓存中删除。
这就是bean加入到二级缓存的过程,简单概述就是:在循环依赖的时候A1–>B–>A2,在第二次尝试实例化A 的时候,才会把A 的实例从三级缓存转移到二级缓存,所以说只要有循环依赖,那么肯定会把当前bean搞到二级缓存中
上面论述解决了
代码一
中第103行,过第一个if 的逻辑 -
二、关于bean 的代理
- 只要方法或者类中 有了
@Async
注解,那么在初始化这个bean 的时候就会为这个创建一个代理对象。具体是在代码一
80行中:exposedObject = initializeBean(beanName, exposedObject, mbd);
。如果bean加了Async
、@Transactional
又或者有aop切了这个bean,经过这行代码,返回的exposedObject
就都不是原始的bean了,而是一个被jdk动态代理 或者CGLIB代理的类了。 - 所以在
代码一
106行第二个if中,如果当前bean是被代理的,那么就不会进入到这个if。如果经过initializeBean(beanName, exposedObject, mbd);
返回的还是当初那个少年,就不会再走下面的逻辑了,自然也就不会报错了。 - 这也就解释了 A–>B–>A 会报错,B–>A–>B就不会报错。
- 因为在A类中有
@Async
这种注解,那么bean初始化后exposedObject
就是一个代理对象,但是B中没有@Async
,自然在经过初始化后exposedObject
还是当初那个bean没有被代理过。所以在B–>A–>B的时候,虽然是个循环依赖,但是在实例化B后再实例化A,然后再尝试实例化B(从上面逻辑可知是从三级缓存中拿到的第一次初始化的对象),B2就走到代码一
106行第二个if中了,不会再进入 下面可能会报错的else if了,所以也就没有了抛出异常的机会。 - 同样的 A–>B–>A过程,由于第二次A在初始化后 返回的exposedObject就是一个代理对象,所以
代码一
106行第二个if中铁定是进不去的,进入到下面的else if中,也就有了报错的机会。
- 因为在A类中有
三、关于 alreadyCreated。当这个Bean被创建完成后,会把beanName set进去
由代码一
123行得知,当执行了第三个if,返回true,actualDependentBeans 这个set中有了东西,那就完了,这次铁定报错了。所以重点就看下 removeSingletonIfCreatedForTypeCheckOnly(dependentBean)
这个函数折腾了啥。如果他返回false,那么下面逻辑就会报错,如果返回true,那么相安无事。
代码位置:org.springframework.beans.factory.support.AbstractBeanFactory#removeSingletonIfCreatedForTypeCheckOnly
代码四
protected boolean removeSingletonIfCreatedForTypeCheckOnly(String beanName) {
if (!this.alreadyCreated.contains(beanName)) {
removeSingleton(beanName);
return true;
}
else {
return false;
}
}
-
其实不管他执行 removeSingleton 是干啥的,只关心这个函数返回是true还是false就ok了。从逻辑可知 只要alreadyCreated这个 set中有当前的beanName那么返回false,否则返回true。所以问题就很简单了,alreadyCreated中有当前beanName报错。
-
当前的beanName是什么?
由代码一
114行可知,传到removeSingletonIfCreatedForTypeCheckOnly的beanName就是当前实例化的的那个bean所依赖的所有的BeanName。比如现在是 A1–>B–>A2,能进入到第三个if 的可能已经是A2阶段了。那么A依赖或者在A中注入的是谁呢?从示例代码中可知 肯定是B呀。所以简单点就是看 A1–>B–>A2这种方式之所以会报错,那就看B是什么时候 set到alreadyCreated中的。
-
跟踪代码可知 是在 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 中
代码五
protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { ~~~~~~~~~~~~~~~~~~~~~~~省略部分代码~~~~~~~~~~~~~~~~~~~~ //创建的Bean是否需要进行类型验证,一般不需要 if (!typeCheckOnly) { // 标记beanName a是已经创建过至少一次的~~~ 它会一直存留在缓存里不会被移除(除非抛出了异常) //========================== 向 alreadyCreated 添加 beanName markBeanAsCreated(beanName); } ~~~~~~~~~~~~~~~~~~~~~~~省略部分代码~~~~~~~~~~~~~~~~~~~~ // Create bean instance. //创建单例模式Bean的实例对象 if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { //===========================实例化 bean return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); //获取给定Bean的实例对象 beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } ~~~~~~~~~~~~~~~~~~~~~~~省略部分代码~~~~~~~~~~~~~~~~~~~~ return adaptBeanInstance(name, beanInstance, requiredType); }
由上可知,在正式实例化bean 之前,就已经把beanName给set进去了(bean还没实例化,就设置这个bean已经创建完毕也是很奇怪了,名字起的不恰当)。也就是 A1–>B–>A2的时候,第二步实例化B 的时候,alreadyCreated就有了值了。
其实分析到这里。只要是循环依赖、只要是使用了@Async
注解,只要是 A1–>B–>A2这样的执行顺序,进了第二个if,那么必进第三个if。
6. 回头再看2中解决办法的原理
- 第一个解决办法是 使用controller改变了 A–>B–>A实例化顺序,变成了 B–>A–>B,自然也就不会报错了
- 第二个解决办法 把A 中的
@Async
放到了B中。实际是把代理对象颠了一个个。加在顺序还是A–>B–>A,但是把代理对象从A转移到了B中。从原理上讲 还是A–>B–>A实例化顺序,变成了 B–>A–>B这么个道理。 - 使用
@Lazy
注解 。- 先说把
@Lazy
注解加到A类中,类型为B 的属性上。当遇到A1–>B–>A2实例化的顺序时。由于在A1阶段时,A实例化完毕,但是在加载他的属性时发现 他的属性B是 Lazy的,所以就放过他不加载了,就变成了只有A1的过程,后面的B–>A2,暂时不执行了,也就不存在什么循环依赖的问题了。即使有其他的bean实例化,又注入了B,此时A也早已经实例化完毕,该代理代理,该初始化初始化,实例化出来的那个bean对象早就放到一级缓存中了,二级缓存早就清空了。在实例化B时,加在依赖的A类只需要从代码二
中第4行的 一级缓存中取就可以了,代码一
整个逻辑都不会执行的,也就无从谈起报错了。 - 其次是把
@Lazy
加到 B类中,类型为A 的属性上。当遇到A1–>B–>A2实例化的顺序时,B类实例化时,加在属性A时发现是 Lazy的,所以暂停A2过程 A的实例化,就只剩下 A–>B这么个过程,后面的 –>A被延迟了。等到需要B再需要加在A时,A也已经实例化完毕,该代理代理,该初始化初始化,实例化出来的那个bean对象早就放到一级缓存中了。也就不会再报错了。
- 先说把