【Spring专题】Spring之Bean的生命周期源码解析——阶段二(三)(属性填充之循环依赖底层原理解析)

目录

  • 前言
    • 阅读准备
    • 阅读建议
  • 前置知识
    • Bean的生命周期
    • 循环依赖的产生
    • Spring里面的3个Map
  • 课程内容
    • 一、【三级缓存】演进推理
      • 1、只有一级缓存的演进推理
        • 1.1 直接将实例化后生成的对象放入到单例池里面
        • 1.2 总结
      • 2、引入二级缓存的演进推理
        • 2.1 引入一个中间Map存实例化后的早期对象(疑似二级缓存)
        • 2.2 解决2.1需要被代理的问题(疑似二级缓存)
      • 3、引入三级缓存的演进推理
        • 3.1 为什么要三级缓存
        • 3.2 解决3.1【粒度偏大】问题
        • 3.3 解决3.2多次调用钩子函数生成的问题
        • 3.4 如何判断是否存在循环依赖(不是那么重要)
        • 3.5 特别声明
      • 4、总结
    • 二、底层源码解析(拓展)
      • 2.1 第一段关键源码
      • 2.2 第二段关键源码
      • 2.3 第三段关键源码
      • 2.3.1 那如何解决上面的问题呢
  • 学习总结

前言

阅读准备

往期回顾:

  • Spring底层核心原理解析【学习难度:★★☆☆☆
  • 手写简易Spring容器过程分析【学习难度:★★☆☆☆
  • Spring之底层架构核心概念解析【学习难度:★★★☆☆,重要程度:★★★★★
  • Bean的生命周期流程图【学习难度:☆☆☆☆☆,重要程度:★★★★★
  • Spring之Bean的生命周期源码解析——阶段一(扫描生成BeanDefinition)【学习难度:★★☆☆☆,重要程度:★★★☆☆
  • Spring之Bean的生命周期源码解析——阶段二(一)(IOC之实例化)【学习难度:★★★★★,重要程度:★★★☆☆
  • Spring之Bean的生命周期源码解析——阶段二(二)(IOC之属性填充/依赖注入)【学习难度:★★★★★,重要程度:★★★★★

阅读建议

  1. 看源码,切记纠结细枝末节,不然很容易陷进去。正常来说,看主要流程就好了
  2. 遇到不懂的,多看看类注释或者方法注释。Spring这种优秀源码,注释真的非常到位
  3. 如果你是idea用户,多用F11的书签功能。
    • Ctrl + F11 选中文件 / 文件夹,使用助记符设定 / 取消书签 (必备)
    • Shift + F11 弹出书签显示层 (必备)
    • Ctrl +1,2,3…9 定位到对应数值的书签位置 (必备)

前置知识

Bean的生命周期

Bean的生命周期指的就是:在Spring中,Bean是如何生成的?Bean的生成步骤如下:(PS:这里不会对Bean的生命周期进行详细的描述,只描述一下大概的过程)

  1. 根据beanDefinition实例化bean
  2. 填充原始对象中的属性(依赖注入)
  3. 初始化前
  4. 初始化
  5. 初始化后
  6. 把生成的bean放入单例池
    把最终生成的代理对象放入单例池(源码中叫做singletonObjects)中,下次getBean时就直接从单例池拿即可

循环依赖的产生

说到循环依赖大家都不陌生,循环依赖的代码,就是如下:

@Component
public class CircularA {

    @Autowired
    CircularB b;
}

@Component
public class CircularB {

    @Autowired
    CircularA a;
}

但是大家有没有想过,循环依赖是如何产生的,然后又是怎么解决的呢?这里,我想给大家推演一下,就像咱是Spring作者一样,思考如何解决循环依赖。

Spring里面的3个Map

在这里,我还是想提前给大家先大概介绍一下,在获取单例bean的时候,Spring源码出现的3个Map是怎样的,用来存储什么的。分别如下:

  • Map singletonObjects:一级缓存。这个就是我们常说的单例池,这里存放的bean,是经历了完整Spring生命周期的,【走完了Spring所设计的生命周期】(这里的经历完整生命周期不是说非得要经历什么实例化前后、初始化前后。简单说,是:Spring认可的,成熟的Bean)
  • Map earlySingletonObjects:二级缓存。直接直译过来,这里存的是【早期单例Bean】。何为早期?就是相对前面的【成熟Bean】,【还没有走完生命周期】的Bean
  • Map> singletonFactories:三级缓存。直译过来是【单例bean的工厂】。其实我还是喜欢用一个之前提到过的专有名词去解释:生产Bean的钩子方法缓存

课程内容

注意:下图的bean生命周期流程图不代表真正的周期,为了方便我只是简单拿了几个处理而已。

一、【三级缓存】演进推理

1、只有一级缓存的演进推理

我们先来看个图,在没有三级缓存之前,只有一个一级缓存的时候,如果A依赖了B,B依赖了A,那么就会造成下面的现象:
【Spring专题】Spring之Bean的生命周期源码解析——阶段二(三)(属性填充之循环依赖底层原理解析)_第1张图片

很显然,在我们刚开创建的过程中,单例池里面是不会有对象B,也不会有对象A的。毕竟它们才走到第二步【注入属性】,它是在最后一步才会把生成好的对象放入单例池中。所以,上图的情况,如果没有外部干预的话,在这两个bean之间就形成了一个闭环,无法解开了。这显然不是我们想要的结果,对吧。那这个问题该如何解决呢?

1.1 直接将实例化后生成的对象放入到单例池里面

这时候一个很正常的想法是,我提前放入到单例池里面不就行了吗,如下所示:
【Spring专题】Spring之Bean的生命周期源码解析——阶段二(三)(属性填充之循环依赖底层原理解析)_第2张图片
这样不就打破了吗?嘿嘿嘿
只能说有点道理,但不多。因为,Spring拿对象其实就是从单例池里面拿,所以这么做,相当于提前把未走完生命周期的半成品对象暴露出去了。这样子,在多线程环境下,如果有人来访问单例池,直接拿到了这个BeanA,然后去调用里面的方法,在没有【属性注入】过的情况下,不就G了吗?是的,这就是并发安全问题!这里只能直接pass这个方案了。
PS:当然,我知道会有人说一级缓存上锁可以解决,啊,是的,可以。但是你有没有想过性能怎么样呢…

1.2 总结

  1. 直接把实例化后,【未走完生命周期的半成品对象】放入到单例池中会有线程安全问题
    (PS:注意这里的结论,后面会考!!! /狗头/狗头)

2、引入二级缓存的演进推理

2.1 引入一个中间Map存实例化后的早期对象(疑似二级缓存)

一个很正常的思考,我新增一个Map,在实例化后即刻存起来不就得了呗。反正都已经实例化了,地址已经固定了,后面再怎么操作都是对这个地址上的对象操作,提前把这个对象暴露出去,完全不影响结果啊。
【Spring专题】Spring之Bean的生命周期源码解析——阶段二(三)(属性填充之循环依赖底层原理解析)_第3张图片
如上图所示,那我新增一个中间缓存Map来存储之前实例化后的对象,总可以吧?嗯,从流程图上来看,这个真的好像是最终答案了。
不过,如果这时候我问你【AOP怎么办】或者准确说,需要的是【代理对象怎么办】,阁下将如何应对呢?很显然啊,这个中间表存放的是原始对象,可是有时候我需要的是代理对象啊。看吧,这样稍微一推敲,又出现问题了。那好,我们继续完善这个方案就是了
(PS:这个问题意味着,我们不得不在这一步考虑提前进行AOP代理。大家要记住这个结论)
(注意:我这里只是举例需要AOP,其实是指,任何需要代理的过程。你想嘛,是不是存在多级代理的情况)
(注意:我这里只是举例需要AOP,其实是指,任何需要代理的过程。你想嘛,是不是存在多级代理的情况)
(注意:我这里只是举例需要AOP,其实是指,任何需要代理的过程。你想嘛,是不是存在多级代理的情况)

2.2 解决2.1需要被代理的问题(疑似二级缓存)

【Spring专题】Spring之Bean的生命周期源码解析——阶段二(三)(属性填充之循环依赖底层原理解析)_第4张图片

就这样,多加上一步AOP过程不就行了嘛,嘿嘿嘿。不过按照惯例,我已经【嘿嘿嘿】了,所以肯定得问一句:真的行吗?哈,真的行!确实没问题了。那为什么,还要三级缓存呢?

3、引入三级缓存的演进推理

3.1 为什么要三级缓存

讲到这里,我就要开始装逼了。(我甚至怀疑Spring这么写也是在装逼,哈哈,开个玩笑)
【Spring专题】Spring之Bean的生命周期源码解析——阶段二(三)(属性填充之循环依赖底层原理解析)_第5张图片
其实这个网上挺多论调的,我也是总结了百家之长,再结合我课堂上老师说的,总结出了以下结论:

  1. 生命周期被打破这个我认为是最重要的原因,但是也比较难被理解的一点。怎么理解呢?大家还记得我最开始怎么形容Spring的吗?Spring的核心是什么?大家知道AOP的实现是在bean的生命周期中的哪一块吗?
    • 第一个问题:Spring是实现了AOP技术的IOC容器
    • 第二个问题:Spring的核心是IOC跟AOP,但是,所有的基础都来自于IOC
    • 第三个问题:AOP的实现,是在bean生命周期的【初始化后】阶段。因为,AOP技术目前的实现,也是基于Spring提供的众多拓展点里面的某些个而已。比如AOP的实现就使用了:BeanPostProcessor。这里透露出来的意思是什么呢?我认为,它的意思是:在Spring内部,都只是把AOP当作额外拓展而已。就好像是我们基于Spring的拓展点实现了Mybatis,实现了SpringMVC一样的道理。

PS:所以到了这里大家伙知道这个生命周期被打破如何理解了吗?如果我们在实例化后就做判断是否需要做AOP的话,等于,还没【属性注入】,还没做【初始化前】、【初始化】、【初始化后】等等生命周期呢,就要开始做AOP了,直接把AOP过程从【初始化后】移到【属性注入之前】。并且呀,在实现这个AOP的过程中,你还得调用类似如下的方法:

for(BeanPostProcessor bp : this.beanPostProcessorsCache) {
		bp.postProcessAfterInitialization(bean);
}

但是这个代码,其实在后面的【初始化后】也会被调用的。我猜有的朋友会这么说:那我循环遍历那几个指定的、实现了AOP的BeanPostProcessor不就行了吗?嗯,说实在确实行。不过,还是跟上面说的,如果我们站在Spring的角度来看:AOP不过也是我IOC的一个拓展内容而已。这么来看的话,这么实现就侵入有点大了,而且语义上也稍微变了。

  1. 循环依赖出现频率。我想我问问大家,你在实际使用场景中,循环依赖出现的多吗?弟弟我写Java代码4年多,我印象中就几次而已。so,你再回头看看上面的解决方案如何?它每一次实例化生成Bean之后都做了判断!是否有点多余呢?
  2. 代码风格。说这个就很抽象了,这不是很主流的说法,但是又有点道理。这句话怎么理解呢?

其实2、3要结合起来,一起建立在【1】最后的挣扎上,即我还是要在【实例化】完成后就开始判断【是否需要AOP】。首先,你们还记得,为什么要提前判断AOP吗?因为循环依赖的需要嘛。换句话说吗,我们其实是在【存在循环依赖】的情况下,才需要【判断是否AOP】的,对吧?也就是【不存在循环依赖】的时候根本就不需要。所以这里如果直接判断AOP,粒度是不是偏大了呢?

3.2 解决3.1【粒度偏大】问题

我想,针对【粒度偏大】这个问题,非常聪明的那些同学已经联想到了我前面给大家说的【第三级缓存】的设计了,诶,我把它设计成钩子函数缓存不就行了嘛,就是用Spring的【1级+3级】构成的二级缓存也能解决粒度大的问题啊?(这个理解起来有点难度,大家好好翻翻lambda表达式),如下:
(PS:该map对应的是Spring的第三级缓存)

Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16)

singletonFactories.put(beanName, ()->{determinedAop(bean);});

void determinedAop(Object bean) {
	 Object exposedObject = object;
	 if (object 是否需要被代理) {
		 exposedObject = 代理对象obejct;
	 }
	 return exposedObject;
}

是的,这样看起来有点那个意思了。不过还少了一个判断,那就是判断【是否存在循环依赖】。这个也很好办,判断上面说的这个map有没有值就行了。(PS:我是这么认为的。但是Spring中新增了一个set来存储创建中的bean的名字。这个感兴趣的朋友看我最后面的内容吧)
但是这样的方式,够了吗?不够的。为什么,那如果我存在第三个需要循环依赖的类呢?如下所示:

@Component
public class CircularA {

    @Autowired
    CircularB b;
    
    @Autowired
    CircularC c;
}

@Component
public class CircularB {

    @Autowired
    CircularA a;
}

@Component
public class CircularC {

    @Autowired
    CircularA a;
}

效果如下:
【Spring专题】Spring之Bean的生命周期源码解析——阶段二(三)(属性填充之循环依赖底层原理解析)_第6张图片
通过图片可能也看不出来,我直接提示大家吧。下面这个:
【Spring专题】Spring之Bean的生命周期源码解析——阶段二(三)(属性填充之循环依赖底层原理解析)_第7张图片
【Spring专题】Spring之Bean的生命周期源码解析——阶段二(三)(属性填充之循环依赖底层原理解析)_第8张图片
因为你上面的中间map存的是回调函数,这个函数,如果存在代理的话,你是不是每次都返回一个新的对象???这显然不符合我们的预期啊,单例啊,在B和C中注入的A应该是同一个才对的!怎么办?存起来咯只能。但是能不能直接把它放到单例池呢?不行的,大家知道为什么吗?哈,跟1.1说的原因是一样一样的。

3.3 解决3.2多次调用钩子函数生成的问题

所以,这里就引入了另一个缓存map,用来缓存上面说的bean。这里称之为:早期bean。(PS:该map对应的是Spring的第二级缓存)
至此,三级缓存都已经引入来了。同学们学废了吗
【Spring专题】Spring之Bean的生命周期源码解析——阶段二(三)(属性填充之循环依赖底层原理解析)_第9张图片

3.4 如何判断是否存在循环依赖(不是那么重要)

上面提到了,在判断是否存在循环依赖的时候,虽然我觉得可以使用第三级的map来判断,毕竟这个存在也代表了【创建中】嘛。但是Spring中新增了一个set来存储创建中的bean的名字。为什么会这样子,我觉得有以下几点原因:

  1. 这个第三级缓存map,理论上使用完了就会立马删掉。这样做有啥好处?一定程度上减少了多次调用的风险;
  2. 现在使用的是一个叫做singletonsCurrentlyInCreation的Set来判断的。然后大家伙可以点开来去看看这个判断被引用的地方,会发现,被引用的地方很多,而且跨越的生命周期更宽。或者咱换句话说吧, 很多地方都需要判断bean是否【创建中】,所以新增了一个set来保存。而使用第三级map来判断,只适用于【循环依赖】那个场景。所以,既然已经有一个set了,直接使用它就好了,语义也更清晰。

3.5 特别声明

注意啊,我在3.1、3.2、3.3的做法只是完善2.2的方案,还记得2.2的结论吗?【我们不得不在这一步考虑提前进行AOP】,也就是无可避免的,后面的完善方案也只是尽量减小粒度而已。

4、总结

啊,我是真怕自己没讲清楚。也怕大伙没搞清楚。再给一个推理演进图。
【Spring专题】Spring之Bean的生命周期源码解析——阶段二(三)(属性填充之循环依赖底层原理解析)_第10张图片

二、底层源码解析(拓展)

循环依赖的源码入口,就在AbstractAutowireCapableBeanFactory#doCreateBean(),看这个名字,大家伙多少应该有点印象吧?实例化的时候不也会经过这个方法嘛。其实严格说起来这里的源码没啥好讲的,主要是弄懂它底层原理就好了。我随便给大家贴一下吧,大家注意里面的注释,我会在跟【循环依赖】有关的代码给大家标明一下:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		// 实例化bean
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}
		
		// 合并beanDefinition Bean后置处理器
		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;
			}
		}

		// 【循环依赖】关键源码一
		// 这里就是我们在分析中说的注册钩子方法,判断是否需要【循环依赖】
		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");
			}
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// 【循环依赖】关键源码二
		// 但是这里看不出来,因为AOP就是在下面的initializeBean里面的,需要挖掘一下才能找到
		Object exposedObject = bean;
		try {
			populateBean(beanName, mbd, instanceWrapper);
			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);
			}
		}

		// 【循环依赖】关键源码三
		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.");
					}
				}
			}
		}

		// Register bean as disposable.
		try {
			registerDisposableBeanIfNecessary(beanName, bean, mbd);
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
		}

		return exposedObject;
	}

如上所示,上面有3段比较关键的源码。

2.1 第一段关键源码

这里第一段关键源码如下:

// 【循环依赖】关键源码一
// 这里就是我们在分析中说的注册钩子方法,判断是否需要【循环依赖】
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");
	}
	addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

allowCircularReferences 判断是否【允许循环依赖】,isSingletonCurrentlyInCreation(beanName)判断是否【存在循环依赖】。接着调用addSingletonFactory注册了一个钩子方法getEarlyBeanReference(beanName, mbd, bean)。钩子方法实现如下:

	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;
	}

看,这里出现了一个【智能的能感知实例化的Bean后置处理器SmartInstantiationAwareBeanPostProcessor 】。并且我们调用它的getEarlyBeanReference方法获得了一个【早期bean】对象的引用。诶,这个【早期bean】一出来就很熟悉,二级缓存嘛,没毛病吧?
从这一点不难看出,这个Bean后置处理器跟Spring的AOP实现有非常大的关系了。但我想提前告诉大家,其实AOP的实现最核心的一个类,是这个接口的一个实现类AnnotationAwareAspectJAutoProxyCreator,至于怎么挖掘的,后面介绍AOP的课程再大家。但最终,我们深究getEarlyBeanReference这个方法,最终它的实现源码如下:(PS:当我们调用了如下方法的时候,意味着提前进行AOP判断了

public Object getEarlyBeanReference(Object bean, String beanName) {
	Object cacheKey = getCacheKey(bean.getClass(), beanName);
	this.earlyProxyReferences.put(cacheKey, bean);
	return wrapIfNecessary(bean, beanName, cacheKey);
}

这个源码就很简单了,看名字基本就知道啥意思。最后return wrapIfNecessary无非就是原始对象,或者代理对象嘛,视【是否需要AOP】而定。
那大家伙可能会疑问,第二行this.earlyProxyReferences.put(cacheKey, bean);缓存是干哈子的呢?诶,正常的AOP是在【初始化后】的,你现在提前了,不得记录一下啊?你不记录的化,难道走到【初始化后】的时候再进行一次AOP吗?嘿,就这个道理。

2.2 第二段关键源码

第二段关键源码说是这个:

// 【循环依赖】关键源码二
// 但是这里看不出来,因为AOP就是在下面的initializeBean里面的,需要挖掘一下才能找到
Object exposedObject = bean;
try {
	populateBean(beanName, mbd, instanceWrapper);
	exposedObject = initializeBean(beanName, exposedObject, mbd);
}

但其实是想说initializeBean里面的【初始化后】处理。我估计有经验的同学,或者看过我前面介绍Spring的同学应该知道【初始化后】是哪个方法吧,我就不索引了,直接给调用源码看看:

	@Override
	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			Object current = processor.postProcessAfterInitialization(result, beanName);
			if (current == null) {
				return result;
			}
			result = current;
		}
		return result;
	}

调用处是这样的,但是处理AOP的那个Bean后置处理器源码如下,中间我就不索引了,这里只是给大家看看有个印象:

	@Override
	public 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;
	}

看,这里就是利用了earlyProxyReferences判断一下是否已经提前进行过AOP了。但这里有一个很重要的细节,下面讲第三段关键源码的时候要用到!
即:this.earlyProxyReferences.remove(cacheKey) == bean的时候,表示我有提前进行过AOP,此时是直接返回原始对象,而不是代理对象!(正常逻辑,没有提前AOP的时候,这里返回的是代理对象)。记住这个结论,下面会考

2.3 第三段关键源码

如下:(注意:进入这段代码有个关键前提,即:earlySingletonExposure==true,这表示,之前存在过循环依赖!

// 【循环依赖】关键源码三
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.");
			}
		}
	}
}

说实在,讲这个挺麻烦的,而且这个知识有点冷门,也很难描述,估计大家不常遇到。不过,我们可以通过最后抛出的异常信息大概知道,这里要处理的逻辑是什么,翻译如下:

异常直译:名称为“beanName”的Bean已经在其早期版本中作为循环引用的一部分被注入到其他Bean [xxxx] 中,但最终还是被包装(包装的意思是:代理)。这意味着上述其他bean使用的不是最终版本的bean。
。。。。。。。。。。。。。。。。。。。。
讲人话:名为beanName的Bean早在循环依赖中已经被【AOP代理】包装了一次,但是后期又在其他处理中被再次【代理包装】了。这就导致,先前在【循环依赖】中被注入的代理bean,不是最新的代理bean。如果遇到了这个情况,只能报错了

看,通过上面的翻译就很清晰的知道咋回事了。事儿就是这么个事儿,问题就是这个问题。

但其实Spring源码还有个毛病。由于2.2结论的存在,可能会导致如下图黑色判断框里面说的情况,接着就报错了:【Spring专题】Spring之Bean的生命周期源码解析——阶段二(三)(属性填充之循环依赖底层原理解析)_第11张图片
过程是因为,实例化A的过程中,由于A与B产生了循环依赖,并且,A的AOP代理对象是在B的过程中,用钩子方法对象生成的,所以,在创建A的过程中,A是无法感知到自己被生成了AOP代理的!所以在后期,走到了【初始化后】的流程的时候,又由于2.2结论的原因,用原有对象,被Async代理了!可怕!这显然不是我们想要的!

2.3.1 那如何解决上面的问题呢

其实思路很直接,既然这个问题是循环依赖产生的,那就打破循环依赖吧!hold on hold on,你该不会以为我叫你改变类结构吧?不是的。那大家思考一下,有什么办法在不改变类结构的情况下打破循环依赖呢?
说到这里我们回想一下,你还记得循环依赖产生的原因是什么吗?不就是因为属性,需要【串行注入】吗?通俗解释就是,因为我在创建bean的过程中,就需要注入属性嘛。那有办法,在创建bean的时候先不注入属性吗?还真有,@Lazy注解。

学习总结

  1. 学习循环依赖产生的原因,以及三级缓存结构的设计原理

你可能感兴趣的:(tuling学院学习笔记,spring,java,后端)