spring 使用 @Async 循环依赖时报 BeanCurrentlyInCreationException 的问题

先上示例代码
接口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. 抛出一个问题

  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]

  1. 如果把A类方法中 funA()@Async注解去掉,那么项目正常启动。这是因为在spring中,早就解决了循环引用的问题。(注意,springboot2.6之后默认把循环引用的支持给关掉了,需要在yml或者properties中打开开关:spring.main.allow-circular-references = true

那么为什么会发生1中的那种情况呢?spring不是已经解决了循环依赖的问题了吗,而且在1中,springboot 的循环依赖开关已经打开了呀。还是先说解决办法吧。

2. 解决办法

  1. 加一个controller,注入B类,但是A类不要注入到任何的其他类中

  2. 删除controller,去掉A类的 funA()上的@Async,在B类的 funB()上加@Async注解。

o.0 ~~~ 上面两个方法,在实际业务中肯定是不行的。这两种方法只是为了引出原理部分。

  1. 终极解决办法:在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 的顺序问题

  1. spring中加在bean会按某种顺序来实例化bean。先实例化controller,再加在service。而且在实例化controller 的时候如果里面有注入进去的service或者Dao等其他的bean,会把这些servicebean、daobean优先实例化出来。所以本例中,使用了controller先注入B类,那么B类先实例化。A类没有被controller引入,所以排在了B类后面去实例化,现在的实例化顺序就是 C -->B -->A。那么这种启动就不会报错了。
  2. 而如果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类为例

  1. 在实例化A时,会把A中的属性的类都给实例化,也就是在实例化完A还没有初始化A的时候,检测到有B属性又去实例化B
  2. 在实例化完B,初始化B之前,又检测到B中有A属性。所以又转去实例化A类,发生异常就是在第二次谋求实例化A类的时候发生的
  3. 就是 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;
	}
  1. 看130行代码(行数是指上述代码片段的行数,而在实际源码中的行数)抛出的异常。为BeanCurrentlyInCreationException,而且与上述抛异常的提示术语一致,所以上面的错正是这里抛出去的。

  2. 看 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行,也就是无论从 singletonObjectsearlySingletonObjects这两个map中拿到值与否都会返回。由上面的参考资料可知singletonObjectsearlySingletonObjects分别是spring用来解决循环依赖的一、二级缓存。其中singletonObjects一级缓存只有在bean完完全全实例化,包括属性注入完毕等等,也就是这个方法返回之后的之后才会把创建好的bean放入到一级缓存里面。所以在这个地方一级缓存肯定是不会有值的。那么这个方法返回值是否为空主要就看 earlySingletonObjects 二级缓存中是否可以取到值了。

分析下面逻辑请牢记一个实例化顺序:A1–>B–>A2,为了区分第一次实例化A为A1,第二次实例化A为A2,实际是一个A

  1. 二级缓存earlySingletonObjects什么情况下会把bean存到里面。

    就是上面 23行代码中,把bean put到二级缓存中的。那么就来分析一下什么时候把bean给存到 earlySingletonObjects中的。

  2. 请看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从里面删掉。

    1. 在第一次实例化A的时候,也就是A1的过程。在调用getSingleton(beanName, true)时,在代码二中,第4行,去一级缓存中取了一下没取到,在第5行中去 调用isSingletonCurrentlyInCreation(beanName)singletonsCurrentlyInCreation中取值也没取到,所以就直接返回了。下面逻辑不会执行。

    2. 所有的bean有一个绕不开的逻辑,那就是不论创建哪个bean。都会把实例化出来的bean放到 singletonFactories三级缓存中。具体可查看上面 《关于spring 解决普通bean循环依赖》的文章。

    3. 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 的代理
  1. 只要方法或者类中 有了@Async注解,那么在初始化这个bean 的时候就会为这个创建一个代理对象。具体是在代码一80行中:exposedObject = initializeBean(beanName, exposedObject, mbd);。如果bean加了 Async@Transactional又或者有aop切了这个bean,经过这行代码,返回的 exposedObject就都不是原始的bean了,而是一个被jdk动态代理 或者CGLIB代理的类了。
  2. 所以在代码一 106行第二个if中,如果当前bean是被代理的,那么就不会进入到这个if。如果经过initializeBean(beanName, exposedObject, mbd);返回的还是当初那个少年,就不会再走下面的逻辑了,自然也就不会报错了。
  3. 这也就解释了 A–>B–>A 会报错,B–>A–>B就不会报错。
    1. 因为在A类中有@Async这种注解,那么bean初始化后exposedObject就是一个代理对象,但是B中没有@Async,自然在经过初始化后exposedObject还是当初那个bean没有被代理过。所以在B–>A–>B的时候,虽然是个循环依赖,但是在实例化B后再实例化A,然后再尝试实例化B(从上面逻辑可知是从三级缓存中拿到的第一次初始化的对象),B2就走到 代码一 106行第二个if中了,不会再进入 下面可能会报错的else if了,所以也就没有了抛出异常的机会。
    2. 同样的 A–>B–>A过程,由于第二次A在初始化后 返回的exposedObject就是一个代理对象,所以代码一 106行第二个if中铁定是进不去的,进入到下面的else if中,也就有了报错的机会。
三、关于 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;
		}
	}

  1. 其实不管他执行 removeSingleton 是干啥的,只关心这个函数返回是true还是false就ok了。从逻辑可知 只要alreadyCreated这个 set中有当前的beanName那么返回false,否则返回true。所以问题就很简单了,alreadyCreated中有当前beanName报错。

  2. 当前的beanName是什么?

代码一 114行可知,传到removeSingletonIfCreatedForTypeCheckOnly的beanName就是当前实例化的的那个bean所依赖的所有的BeanName。比如现在是 A1–>B–>A2,能进入到第三个if 的可能已经是A2阶段了。那么A依赖或者在A中注入的是谁呢?从示例代码中可知 肯定是B呀。所以简单点就是看 A1–>B–>A2这种方式之所以会报错,那就看B是什么时候 set到alreadyCreated中的。

  1. 跟踪代码可知 是在 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中解决办法的原理

  1. 第一个解决办法是 使用controller改变了 A–>B–>A实例化顺序,变成了 B–>A–>B,自然也就不会报错了
  2. 第二个解决办法 把A 中的@Async放到了B中。实际是把代理对象颠了一个个。加在顺序还是A–>B–>A,但是把代理对象从A转移到了B中。从原理上讲 还是A–>B–>A实例化顺序,变成了 B–>A–>B这么个道理。
  3. 使用 @Lazy注解 。
    1. 先说把@Lazy注解加到A类中,类型为B 的属性上。当遇到A1–>B–>A2实例化的顺序时。由于在A1阶段时,A实例化完毕,但是在加载他的属性时发现 他的属性B是 Lazy的,所以就放过他不加载了,就变成了只有A1的过程,后面的B–>A2,暂时不执行了,也就不存在什么循环依赖的问题了。即使有其他的bean实例化,又注入了B,此时A也早已经实例化完毕,该代理代理,该初始化初始化,实例化出来的那个bean对象早就放到一级缓存中了,二级缓存早就清空了。在实例化B时,加在依赖的A类只需要从 代码二中第4行的 一级缓存中取就可以了,代码一整个逻辑都不会执行的,也就无从谈起报错了。
    2. 其次是把@Lazy加到 B类中,类型为A 的属性上。当遇到A1–>B–>A2实例化的顺序时,B类实例化时,加在属性A时发现是 Lazy的,所以暂停A2过程 A的实例化,就只剩下 A–>B这么个过程,后面的 –>A被延迟了。等到需要B再需要加在A时,A也已经实例化完毕,该代理代理,该初始化初始化,实例化出来的那个bean对象早就放到一级缓存中了。也就不会再报错了。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值