Spring——循环依赖&三级缓存【建议收藏】

先抛出两个问题

(1)我们知道Spring是支持一个Bean引入另外的Bean对象的,那么就不可避免出现相互依赖的问题,Spring是如何解决这个问题呢?

(2)此外,Spring注册Bean的方式有很多种,Spring又是可以解决哪些循环依赖又不能解决哪几种循环依赖呢?

本篇介绍Spring循环引用相关的知识点,包括:循环引用/依赖的基本概念、Spring的Bean创建流程、三级缓存解决循环依赖问题、源码调试分析及流程归纳、循环依赖是否一定能被三级缓存解决,此外还给了个特殊的例子并分析三级缓存为什么有时不能解决AOP+循环引用的问题,阅读前建议对SpringBean要有一定背景知识;

1. 什么是循环依赖?

Spring——循环依赖&三级缓存【建议收藏】_第1张图片

Spring——循环依赖&三级缓存【建议收藏】_第2张图片

通俗的讲,循环依赖是指Spring中N个Bean相互依赖从而形成一个闭环的现象;特殊情况下自己依赖自己也是一种闭环(N=1);给几个图理解一下,bean之间的引用关系闭环了,形成了"套娃",也就是循环依赖问题;

1.1 循环依赖的情况

第一种情况:N=1,自己依赖自己的直接依赖;

Spring——循环依赖&三级缓存【建议收藏】_第3张图片

第二种情况:N=2,两个对象之间的直接依赖;这个比较简单,一般用来做demo;

Spring——循环依赖&三级缓存【建议收藏】_第4张图片

第三种情况:N>2,多个对象之间的间接依赖;前面两种情况的循环依赖比较直观,但是第三种间接循环依赖的情况有时候因为业务代码调用层级很深,不容易识别出来;

Spring——循环依赖&三级缓存【建议收藏】_第5张图片

1.2 一个Bean依赖注入其他Bean的场景?

假设此时有A、B两个类需要加载bean,A的bean加载过程中需要依赖注入B的bean,情况可以分为以下几种:

Spring——循环依赖&三级缓存【建议收藏】_第6张图片 一个bean需要依赖注入另一个bean的场景有以上几种;这里先看下分类,对循环依赖的场景有个大致了解,可以先不关注Spring是否能解决每种情况下的循环依赖;

2. Bean的创建流程

这里我们关注的是bean初始化流程中的createBean()流程;先复习一下bean加载过程;

2.1 bean加载流程

可通过下方的源码步骤分析来方便自己debug源码,如果只是为了了解流程,可以直接看2.2 流程示意图;详细加载流程这篇Bean的初始化过程写得很细;源码步骤分析如下

1. 代码入口:org.springframework.context.support.AbstractApplicationContext#refresh,执行到finishBeanFactoryInitialization(beanFactory);

2. 实例化单例Bean:org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization,执行到beanFactory.preInstantiateSingletons();

3. 遍历beanName,执行getBean(beanName)方法:org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons,执行到getBean(beanName);

4. 实际执行doGetBean:org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String),执行doGetBean(name, null, null, false);

5. 获取bean实例:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean;方法流程:

    5.1 String beanName = transformedBeanName(name); 转换beanName:如果name带有"&"前缀,则去掉前缀;尝试在bean的别名映射中根据name找到相应的相应的规范名,如果没有,则就是原始的name

    5.2 Object sharedInstance = getSingleton(beanName); 分别从第1/2/3级缓存中根据beanName查找当前bean,找到则直接返回(此时bean未被标记为'创建中',因此实际上只查1级缓存);否则下一步

    5.3 markBeanAsCreated(beanName),将当前bean标记为'创建中'

    5.4 执行DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory); Singleton()方法传入了一个实现了ObjectFactory接口的匿名对象;方法流程:

        5.4.1 Object singletonObject = this.singletonObjects.get(beanName);,先尝试从1级缓存取实例;

        5.4.2 拿不到则调用入参的匿名类的抽象方法,即执行AbstractAutowireCapableBeanFactory#createBean(beanName, mbd, args)方法;实际是执行AbstractAutowireCapableBeanFactory#doCreateBean(beanName, mbdToUse, args),方法流程:

            5.4.2.1 createBeanInstance()创建一个最原始的bean的实例wrapper,final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null),此时没有进行依赖注入,此刻的bean也称为半成品raw bean,

            5.4.2.2 如果存在MergedBeanDefinitionPostProcessor,则执行applyMergedBeanDefinitionPostProcessors()

            5.4.2.3 如果是earlySingletonExposure,则执行addSingletonFactory(),将半成品bean缓存到3级缓存singletonFactories中,需要注意的是,此时依旧没有发生依赖注入;

            5.4.2.4 populateBean() 这里才是真正发生循环依赖(属性填充)的地方,如果存在依赖,则进行注入;

            5.4.2.5 initializeBean() 这步做一些后置的处理,比如执行invokeAwareMethods()、applyBeanPostProcessorsBeforeInitialization()、invokeInitMethods()、applyBeanPostProcessorsAfterInitialization()等方法;

            5.4.2.6 校验bean和代理替换;如果是earlySingletonExposure,先尝试从1/2级缓存获取记为earlySingletonReference,如果拿的到则将原始bean与当前执行initializeBean() 后获得的exposedObject做一个对比,若相等则将exposedObject置位/替换为earlySingletonReference;这一步其实是检测和代理对象替换,校验失败会抛出BeanCurrentlyInCreationException异常;

        5.4.3 addSingleton(beanName, singletonObject);,将上一步createBean拿到的bean放入1级缓存,然后清除它的2/3级缓存

方法返回:返回bean,此时bean放入了1级缓存且放入已注册单例registeredSingletons里;

2.2 流程示意图

以上的步骤较为详细,这里给出bean实例化过程中的几个关键步骤的流程图便于理解,也方便用来分析循环依赖;

Spring——循环依赖&三级缓存【建议收藏】_第7张图片

2.3 关键方法及源码

(1)三级缓存

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry	
	
/** Cache of singleton objects: bean name --> bean instance */
// 1级缓存 存放实例bean
private final Map singletonObjects = new ConcurrentHashMap(256);

/** Cache of singleton factories: bean name --> ObjectFactory */
// 3级缓存 存放实现ObjectFactory的lambda 实际上是 "getEarlyBeanReference方法和参数原始半成品bean"
private final Map> singletonFactories = new HashMap>(16);

/** Cache of early singleton objects: bean name --> bean instance */
// 2级缓存 存放半成品bean(通过反射和构造器实例化 未填充属性)
private final Map earlySingletonObjects = new HashMap(16);

(2)从1/2/3级缓存获取单例对象 DefaultSingletonBeanRegistry#getSingleton

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// 先从1级缓存拿
	Object singletonObject = this.singletonObjects.get(beanName);
	// 1级缓存未取到&当前bean被标记为'创建中'
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			// 拿2级缓存
			singletonObject = this.earlySingletonObjects.get(beanName);
			// 2级缓存未取到&方法入参allowEarlyReference为true
			if (singletonObject == null && allowEarlyReference) {
				// 拿3级缓存 通过lambda执行getEarlyBeanReference方法 返回bean实例或代理实例
				ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					singletonObject = singletonFactory.getObject();
					this.earlySingletonObjects.put(beanName, singletonObject);
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

(3)三级缓存lambda中的方法org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference

// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	// 判断是返回原始半成品bean or 代理bean
	if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
				// 如果当前bean的processor中有SmartInstantiationAwareBeanPostProcessor类型 则执行代理创建 返回代理对象
				SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
				// 代理对象通过AbstractAutoProxyCreator以CGlib方式创建 exposedObject被代理对象替换
				exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				if (exposedObject == null) {
					return null;
				}
			}
		}
	}
	return exposedObject;
}

(4)使用代理对象替换原始bean的逻辑

这段代码容易被忽略,但是很关键!

// 4.3.22.RELEASE org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory java:566

if (earlySingletonExposure) {
	// 这里第二入参为false 表示仅从1/2级缓存获取earlySingletonReference
	Object earlySingletonReference = getSingleton(beanName, false);
	// 如果取到了earlySingletonReference
	if (earlySingletonReference != null) {
		// 这一步是关键:如果三级缓存返回了代理对象 而initializeBean未生成代理bean来替换原始bean 
		// -则这里的暴露对象exposedObject会被替换成三级缓存返回的代理对象
		if (exposedObject == bean) {
			exposedObject = earlySingletonReference;
		}
		else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
			String[] dependentBeans = getDependentBeans(beanName);
			Set actualDependentBeans = new LinkedHashSet(dependentBeans.length);
			for (String dependentBean : dependentBeans) {
				if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
					actualDependentBeans.add(dependentBean);
				}
			}
			if (!actualDependentBeans.isEmpty()) {
				// 循环引用下 如果当前beanA所依赖的beanB持有的A属性 与当前A的暴露对象exposedObject不一致
				// -则提示"循环引用下,A被注入beanB,但A最终被代理替换了,意味着beanB持有的不是beanA的最终版本" 抛出异常
				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.");
			}
		}
	}
}

3. 三级缓存解决循环依赖的思路

3.1 Spring通过引入三级缓存解决循环依赖问题

Spring解决循环依赖的核心思想在于提前曝光

假设A与B互相引用,Spring按照字典顺序先初始化A;思路如下:

(1)首先实例化A(半成品beanA),并在3级缓存singletonFactories中保存lambda表达式(半成品beanA作为lambda的args);注意:当没有循环依赖和AOP时,这个3级缓存singletonFactories是没有在后续用到的;

(2)接着对半成品beanA执行populate依赖注入;A对象需要注入B对象,发现缓存里还没有B对象,于是开始创建beanB;按照上述所说,将B添加进三级缓存singletonFactories,然后开始对B对象注入A对象;

(3)这时从3级缓存获取A,即通过缓存的lambda表达式来创建A实例对象(返回的可能是lambda的args$3半成品beanA,也可能是代理beanA),并放到2级缓存earlySingletonObjects中,然后将其从3级缓存移除;

(4)此时B对象注入了A对象实例,随后initializeBean初始化,于是获取了成品beanB,并放入1级缓存singletonObjects;当B有AOP时,B对象在初始化initializeBean中进行代理对象的创建,暴露对象被替换为代理对象proxy$B,然后把proxy$B对象放入1级缓存singletonObjects中,然后返回给A对象;即A持有proxy$B;

(5)A注入其他依赖对象后,最后执行初始化,于是获取了成品beanA,并放入1级缓存singletonObjects;

3.2 Spring解决循环依赖的流程示意图

Spring——循环依赖&三级缓存【建议收藏】_第8张图片

上图画的很用心,但是红色部分第二个框应该是getSingletonB,应该是笔误,图转载自:【小明】谈谈你对Spring三级缓存和循环依赖的理解

4. 源码走读及几种循环依赖case分析

4.1 源码走读

为了结合源码分析流程,这里选取"A和B循环依赖且无AOP"的情况,做一个源码的debug,如下(点击看大图);

4.2 case1: A和B循环依赖/无AOP

Spring通过三级缓存解决了循环依赖,TestService1与TestService2互相依赖,流程如下:

Spring——循环依赖&三级缓存【建议收藏】_第9张图片

通过提前暴露半成品bean完成了循环引用时的依赖注入;

细心的朋友可能会发现在这种场景中第2级缓存作用不大,那么问题来了,为什么要用第2级缓存呢?

——试想一下,如果出现以下这种情况,我们要如何处理?TestService1同时依赖TestService2和TestService3两个属性,而TestService2和TestService3都依赖TestService1;假设不用第2级缓存,把TestService1注入到TestService3的流程与上图类似,如图:

Spring——循环依赖&三级缓存【建议收藏】_第10张图片

TestService1注入到TestService3又需要从第3级缓存中获取实例,而第3级缓存里保存的并非真正的实例对象,而是ObjectFactory对象,先后两次通过第3级缓存中获取都是ObjectFactory对象,而通过它创建的实例对象每次可能都不一样(如TestService1的代理对象);

单例bean有多个实例,这样不是有问题?

为了解决这个问题,Spring使用了这个第2级缓存,在对TestService2注入TestService1对象时,TestService1的实例已经从第3级缓存获取并被添加到第2级缓存中了,而在把TestService1注入到TestService3时,只用从第2级缓存中获取该对象即可,这样就不会出现重复创建代理对象的问题;

4.3 case2: A和B循环依赖/有AOP(使用AspectJ方式)

Spring AOP与Spring事务等都是通过代理对象来实现的,而事务的代理对象是由自动代理创建器AbstractAutoProxyCreator来自动完成的,对于存在代理的这种情况,最终放入bean容器的是当前类的代理对象而非原始对象;

流程:实例化A->A放入3级缓存->A注入属性B->实例化B->B放入3级缓存->B注入属性A->从三级缓存拿A返回并且放入2级缓存...,这里当我们执行到对B的populate方法时,给B注入A属性,可以看到从第3级缓存获取A实例的时候,返回的是A的代理对象,即把proxy$A注入了B,并且把proxy$A放入了第2级缓存;

Spring——循环依赖&三级缓存【建议收藏】_第11张图片

A接着执行initializeBean方法,这里执行后,可以看到exposeObject的值任然是原始beanA,下面这段逻辑,取到的是proxy$A,而与相同,因此把赋值给,完成了代理对象的替换,最终被放入第1级缓存,然后从第2级缓存移除;

Spring——循环依赖&三级缓存【建议收藏】_第12张图片

4.4 case3: A和B循环依赖/有AOP(使用@Transactional注解)

使用@Transaction注解在注册了事务管理器下,会启动Spring的事务,其流程与4.3 完全相同;

4.5 case4: A和B循环依赖/有AOP(使用@Async注解)

使用异步代理@Async方式,加在A方法上,预期是与@Transactional注解一样能创建A的代理对象,但是——启动工程,报错了!

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'serviceA': Bean with name 'serviceA' has been injected into other beans [serviceB] 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.
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:583) ~[spring-beans-4.3.22.RELEASE.jar:4.3.22.RELEASE]
...

尝试debug源码,现象是——问题出在从3级缓存获取A的时候,并没有生成A的代理对象!

导致了B注入了A的原始对象,而最终A在initializeBean时生成了proxy$A赋值给exposeObject,校验exposeObject不是原始bean,因此抛出异常;

说明@Transactional和@Async在从3级缓存中判断是否返回代理对象时,执行逻辑不同;查看源码(上文2.3 关键方法及源码(3)三级缓存lambda中的方法),发现从3级缓存beanFactories工厂中获取bean实例时,这个对象工厂返回的是经过一系列BeanPostProcessor处理得到的对象,并且这个BeanPostProcessor必须是SmartInstantiationAwareBeanPostProcessor类型的

对于@Transactional事务注解,当A生成事务代理对象的时候,肯定是依赖负责处理事务代理的BeanPostProcessor,也就是InfrastructureAdvisorAutoProxyCreator,而InfrastructureAdvisorAutoProxyCreator继承的是AbstractAutoProxyCreator,而AbstractAutoProxyCreator又是SmartInstantiationAwareBeanPostProcessor类型的,因此从3级缓存这里拿到了proxy$A;

但是,对于@Async异步注解,异步代理对象并不是通过AbstractAutoProxyCreator这个BeanPostProcessor去创建的,而是通过AsyncAnnotationBeanPostProcessor去创建,而AsyncAnnotationBeanPostProcessor却并不是SmartInstantiationAwareBeanPostProcessor类型的,所以在getEarlyBeanReference方法中是调用不到的,也就是说3级缓存无法返回一个异步代理对象,所以B注入的是一个A的原始对象,而A在initializeBean时生成了异步代理proxy$A,导致后序流程中的校验不通过抛出异常;

总结原因如下图:

Spring——循环依赖&三级缓存【建议收藏】_第13张图片

可见,三级缓存有时候也不能解决AOP+循环依赖的问题

但是这个问题出在A先加载,如果换个顺序就可以启动成功,因为没有用到A的三级缓存;所以这种情况下,调整bean的加载顺序是一种解决方案

5. 循环依赖的场景及是否一定能被三级缓存解决?

5.1 Spring中三个常见的产生循环依赖的场景

在Spring环境中,因为我们的Bean的实例化、初始化都是交给了容器,因此它的循环依赖主要表现为下面三种场景;

(1)构造器注入循环依赖

@Service
public class ServiceA {
    public A(B b) {
    }
}
@Service
public class ServiceB {
    public B(A a) {
    }
}

结论:构造器注入构成的循环依赖,此种循环依赖方式「是无法解决的」,只能抛出BeanCurrentlyInCreationException异常表示循环依赖;

Spring——循环依赖&三级缓存【建议收藏】_第14张图片

根本原因:Spring解决循环依赖依靠的是Bean提前暴露"半成品bean"这个概念,而构造器是直接完成实例化的;从图中的流程看出构造器注入没能添加到三级缓存,也没有使用缓存,所以构造器的循环依赖无法解决;

(2)单例singleton field属性注入(setter方法注入)循环依赖

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
}

@Service
public class ServiceB {
    @Autowired
    private ServiceA serviceA;
}

结论:项目启动成功,Spring能解决;setter方法注入方式因为原理和字段注入方式类似;

注意:使用AspectJ或类似@Transactional方式引入的AOP时,Spring也可以解决;但如@Async这种方式引入的AOP旧不一定能解决;

(3)原型prototype field属性注入循环依赖

prototype在平时使用情况较少,但是也并不是不会使用到;

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
}

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class ServiceB {
    @Autowired
    private ServiceA serviceA;
}

结论:需要注意的是本例中「启动时是不会报错的」因为bean加载流程DefaultListableBeanFactory#preInstantiateSingletons中,“非抽象、单例 并且非懒加载的类才能被提前初始bean”,因此原型prototype不会被提前初始化bean,所以程序能够正常启动;

Spring——循环依赖&三级缓存【建议收藏】_第15张图片

如何让原型prototype提前初始化bean呢?

只需要手动getBean()或者在一个单例Bean内@Autowired一下它,这样启动就报错

如何解决?

可能有的小伙伴看到网上有说使用@Lazy注解解决,此处负责任的告诉你这样是解决不了问题的而是「可能会掩盖问题」,@Lazy只是延迟初始化而已,当你真正使用到它(初始化)的时候,依旧会报异常;

5.2 其他特殊的循环依赖场景

(1)@Async

通过上文4.5 A和B循环依赖/有AOP(使用@Async注解)的例子,可知使用@Async异步代理Spring无法处理这种循环依赖,不过可以通过调整A和B的加载顺序来解决,如修改名字调整字典排序;

(2)@DependOn

如A和B循环依赖时,对A和B的类分别打上@DependOn注解,启动时会报错;

Circular depends-on relationship between 'serviceA' and 'serviceB'

这个例子中本来如果A和B都没有加@DependsOn注解是没问题的,反而加了这个注解会出现循环依赖问题,这又是为什么?答案在AbstractBeanFactory类的doGetBean方法的这段代码中:

Spring——循环依赖&三级缓存【建议收藏】_第16张图片

它会检查dependsOn的实例有没有循环依赖,如果有循环依赖则抛异常;​这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环依赖就可以解决问题

6. Spring循环依赖小结

6.1 Spring如何解决循环依赖

  • 1. Spring解决循环依赖的核心思想在于提前曝光;三级缓存中的第2级缓存;

  • 2. Spring解决循环依赖+AOP问题的思路在于"懒加载";三级缓存中的第2/3级缓存;

在不改变原Spring bean加载流程(即在initializeBean这一步判断生成代理)的前提下,在属性注入之前,以一种"懒加载"的思想,提前在第3级缓存存入原始对象及一个判断是否返回代理对象的lambda;实际属性注入时,根据需要判断是否要提前返回代理对象;从而解决AOP+循环依赖时,需要提前(在populate这一步,即initializeBean这一步之前)注入当前对象的代理对象的问题;

6.2 哪三级缓存?

  • 1级缓存:singletonObjects,存放属性已填充的成品bean,要么是原始bean,要么是代理bean,可以被直接使用;
  • 2级缓存:earlySingletonObjects,存放半成品bean,来自3级缓存;用来解决循环依赖,提前暴露早期对象;
  • 3级缓存:singletonFactories,存放lambda表达式,lambda的参数args中存了原始早期bean;3级缓存取完后,会根据是否有AOP来返回原对象或代理对象,然后从3级缓存移除,结果放入2级缓存,因为创建代理的方法只可以执行1次;

6.3 解决循环依赖只用一级缓存行不行?

首先先说结论,实际上一级缓存就可以解决循环依赖无论是不是需要动态代理;但是注意,这里说的是"可以解决",但Spring并没有这么做!

在早期Spring 2.5.1版本只使用了1级缓存解决了循环依赖,在这个时期的版本通过将半成品bean与初始化好的bean都放在singleton中进行保存;但这个时期的版本并没有解决代理对象之间的循环依赖;(这里说的是没有,并不是不能)

我们都知道代理对象的创建在initializeBean方法中,为了让一级缓存就可以解决代理对象的循环依赖,可以将addSingletonFactory直接替换为addSingleton(Proxy Object)在1级缓存中直接添加代理对象;

但实际上并没有这样做,为什么呢?一旦这样做了,那么所有代理对象都会提前创建,不管是否有循环依赖,很明显作者原有意图是希望代理对象在执行初始化方法之后再创建代理对象的,这样做破坏了这个流程;

至此,使用1、3级缓存已经可以很好解决循环依赖的问题了,即1级缓存放成品bean和半成品bean,3级缓存放可生成代理bean的工厂;那为啥还需要2级缓存,我的理解是为了将半成品对象与初始化好的对象分开,因为在1级缓存里面使用类似标记位的方式区分半成品bean和成品bean确实不够"优雅";

参考:spring循环依赖,为什么需要三级缓存? - 知乎

6.4 在三级缓存下,只用其中的两级缓存行不行?

【只保留1/2级缓存singletonObjects和earlySingletonObjects】

当A没有代理时,流程可以这样走:实例化A ->将半成品的A放入earlySingletonObjects中 ->填充A的属性时发现取不到B->实例化B->将半成品的B放入earlySingletonObjects中->从earlySingletonObjects中取出A来填充B的属性->将成品B放入singletonObjects,并从earlySingletonObjects中删除B->将B填充到A的属性中->将成品A放入singletonObjects并删除earlySingletonObjects;一点问题都没有!

这样的流程是线程安全的,但是——如果A上加个切面(AOP),这种做法就没法满足需求了,因为earlySingletonObjects中存放的都是原始对象,而我们要对B注入的其实是A的代理对象,而代理对象在这种情况下要在A执行完属性注入后,在initializeBean过程中生成,而不能在对B注入A的时候就拿到A的代理对象;

【只保留1/3级缓存singletonObjects和singletonFactories】

当A没有代理时,流程是这样的:实例化A ->创建A的对象工厂并放入singletonFactories中 ->填充A的属性时发现取不到B->实例化B->创建B的对象工厂并放入singletonFactories中->从singletonFactories中获取A的对象工厂并获取A填充到B中->将成品B放入singletonObjects,并从singletonFactories中删除B的对象工厂->将B填充到A的属性中->将成品A放入singletonObjects并删除A的对象工厂;一点问题都没有!

但是——如果A上加个切面(AOP),并且A除了B,还有多个循环依赖属性时,这种情况也无法满足需求;从3级缓存取值的逻辑是:拿到ObjectFactory对象后,调用ObjectFactory.getObject()方法最终会调用getEarlyBeanReference()方法;当A还有依赖C时,对C注入A时,还会对A执行一次3级缓存取值,即getEarlyBeanReference()被执行了多次生成了多个代理A,无法保证始终只有1个代理对象proxy$A;

所以,需要借助2级缓存来解决这个问题,将执行了singleFactory.getObject()产生的代理对象proxy$A放到2级缓存中去,然后从3级缓存删除A,当对C注入A时,直接从2级缓存中拿proxy$A,保证始终只有一个代理对象proxy$A;

6.5 哪些循环依赖能解决?哪些些不能解决?

(1)不能解决的情况:

1. 构造器注入循环依赖 ;
2. prototype原型 field属性注入循环依赖;

(2)可能解决的情况:

singleton单例 field属性注入(setter方法注入)循环依赖;

7. 建议

在归纳总结Spring循环依赖的过程中,我找到的资料很多,也看了些视频,但发现几乎没有把相关的问题和细节表述的清晰完整的(如特殊的@Async注解引入的AOP代理),可能让读者对一些关键点的理解上产生误解(如是否真的一定要三级缓存才能解决循环依赖),因此写了这篇文章,里面有场景分类、源码分析、完整debug栈等,希望真的能把问题讲清楚;

总结下来,解决此类问题的关键是要对SpringIOC和DI的整个流程做到心中有数,本文还是花了我一番心思的,个人觉得对Spring 循环依赖&三级缓存这部分的流程描述得还是比较详细的,希望我的总结能够给大家带来帮助。

「另外为了避免循环依赖导致启动问题而又不会解决,有如下建议」

1. 业务代码中尽量不要使用构造器注入,即使它有很多优点;

2. 业务代码中为了简洁,尽量使用field注入而非setter方法注入;

3. 若你注入的同时,立马需要处理一些逻辑(一般见于框架设计中,业务代码中不太可能出现),可以使用setter方法注入辅助完成;

参考:

Spring Bean解决循环依赖为什么是三级缓存?- 腾讯云

谈谈你对Spring三级缓存和循环依赖的理解

Spring 是如何解决循环依赖的? - 知乎

spring循环依赖,为什么需要三级缓存? - 知乎

循环依赖踩坑笔记 - SegmentFault 思否

探索Spring异步代理循环依赖失败的问题

你可能感兴趣的:(Spring框架,编码踩坑,java)