spring源码系列8——最详细的循环依赖解读

Posted xu_jiang_hua

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring源码系列8——最详细的循环依赖解读相关的知识,希望对你有一定的参考价值。

        前面系列3到系列7总共5篇文章分析了spring容器启动的整个过程,但未对部分重要细节进行深入分析,比如spring循环依赖,因此本节对spring循环依赖进行深入分析。先思考以下四个问题:

A、 spring能解决所有的循环依赖吗?

B、 spring如何解决循环依赖?

C、 一级缓存以及二级缓存能否解决循环依赖?

D、为什么需要三级缓存?

相信看完本文,上面问题豁然开朗。

1、预备知识

1.1、预备知识1——bean生命周期

回顾一下系列7中总结的bean生命周期,如下所示:

可以大致分成两个主要阶段:实例化和初始化,进而可以细分成:实例化前、实例化、实例化后、初始化前、初始化、初始化后。

1.2、预备知识2——依赖注入

依赖注入根据配置的不同,可以分成xml和注解两种,如下:

  • xml
    构造注入、setter注入、静态工厂方法、实例工厂方法

  • 注解
    构造注入、filed注入

    构造注入——通过构造参数注入依赖
    filed注入——@Autowired、@Resource

2、循环依赖

2.1、什么是循环依赖?


如上所示,InstantA依赖InstantB,InstantB依赖InstantC,依次往下传递到最终依赖InstantZ,InstantZ依赖InstantA,形成依赖闭环,即循环依赖。

2.2、构造注入

为了简化分析难度,此处只分析A依赖B,B依赖A的情况,同时现在注解用得更广泛,因此下面以注解作为demo进行分析。

@Component
public class InstantA {

	private InstantB instantB;

	public InstantA(InstantB instantB) {
		this.instantB = instantB;
	}
}

@Component
public class InstantB {

	private InstantA instantA;

	public InstantB(InstantA instantA) {
		this.instantA = instantA;
	}
}

        可以看到InstantA中的属性依赖InstantB,InstantB依赖InstantA,相互依赖,因此形成循环依赖。
        启动spring容器时,报错如下:

从启动结果可以看出spring无法解决构造注入时产生的循环依赖;

2.3、filed注入

@Component
public class InstantA {
	@Autowired
	private InstantB instantB;
}

@Component
public class InstantB {
	@Autowired
	private InstantA instantA;
}

        容器能正常启动并成功注入依赖,表明Spring能成功解决filed注入时产生的循环依赖。

        接下来,深入源码探究为什么不能解决构造注入时产生的循环依赖,而可以解决filed注入时产生的循环依赖?发车!

3、构造注入循环依赖

3.1、源码分析

1. 从容器获取insantA并将beanName加入singletonsCurrentlyInCreation集合

	//正在创建的bean集合
	/** Names of beans that are currently in creation. */
	private final Set<String> singletonsCurrentlyInCreation =
			Collections.newSetFromMap(new ConcurrentHashMap<>(16));

singletonsCurrentlyInCreation记录当前正在创建bean的名字集合。

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
				//创建和销毁冲突
				if (this.singletonsCurrentlyInDestruction) {
			    //省略非关键代码
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
				}
				//回调前:将当前beanName加入singletonsCurrentlyInCreation,如果已在创建过程中则抛出异常
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<>();
				}
				try {
					//执行回调函数
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				catch (IllegalStateException ex) {
                 //省略非关键代码
				}
				catch (BeanCreationException ex) {
				 //省略非关键代码
				}
				finally {
					if (recordSuppressedExceptions) {
						this.suppressedExceptions = null;
					}
					//回调后:从singletonsCurrentlyInCreation移除beanName
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					//从二级、三级缓存移除,并将当前实例加入单例池中
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

beforeSingletonCreation和afterSingletonCreation两个方法分别将beanName从集合中加入或者移除,重点看beforeSingletonCreation

	protected void beforeSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			//当多次加入同一个beanName,则抛出异常   构造注入出现循环依赖时  此处会抛出异常
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

2. 推断instantA的构造函数并从容器获取依赖instantB

因InstantA仅有一个带参的构造函数,因此只能用它,但发现需要InstantB作为构造参数,因此从容器中获取InstantB;

3. 从容器获取instantB将beanName加入singletonsCurrentlyInCreation

此时singletonsCurrentlyInCreation集合中有两个beanName:instantA和instantB。

4.推断实例化InstantB的构造函数并从容器获取依赖instantA

5. 再次从容器获取instantA并将beanName加入singletonsCurrentlyInCreation抛出异常


      当再次将instantA加入singletonsCurrentlyInCreation set集合时,此时集合中已经存在instantA,则抛出BeanCurrentlyInCreationException异常。InstantA和InstantB实例化时,都彼此需要对方作为参数,形成死循环,将以上流程梳理总结如下。

3.2、流程总结


对上面流程简短说明:
A、当实例InstantA前,将beanName instantA加入集合;
B、实例化InstantA时,发现依赖InstantB;
C、实例InstantB前,将beanName instantB加入集合;
D、实例化InstantB时,发现依赖InstantA;
E、从容器获取InstantA,发现没有,则再次执行创建流程,将beanName instantA加入集合,此时集合中已经存在beanName instantA,抛出异常;

因此,spring无法解决构造注入时产生的循环依赖;

4、filed注入循环依赖

4.1、 三级缓存


4.2、源码分析

1. 从容器获取instantA并将beanName instantA 加入singletonsCurrentlyInCreation

2. 推断实例化instantA的构造函数

3.将instanA加入三级缓存

4. populateBean——填充instantA的属性

为InstantA填充属性instanB

5. 从容器获取instantB并将beanName instantB加入singletonsCurrentlyInCreation

6. 推断InstantB的构造函数并生成实例

7. 将instantB加入三级缓存

此时instantA和instantB都在三级缓存中。

8. populateBean—填充instantB的属性

经过解析@Autowired注解,发现instantB依赖InstantA,因此从容器中获取InstantA.。

9. 再次从容器中获取instantA——从三级缓存中获取instantA并加入二级缓存

    此时beanName instantA在singletonsCurrentlyInCreation集合中,因此193行方法isSingletonCurrentlyInCreation返回true,此时继续从二级三级缓存查找。
    此时三级缓存中有instantA的回调函数,执行回调函数得到早期曝光bean并放入二级缓存,同时将回调函数从三级缓存中移除。下面看如何执行三级回调函数?

10. 执行instantA的三级缓存回调函数



生成cacheKey并放入earlyProxyReferences缓存中;下面继续看wrapIfNecessary方法:

此处instantA没aop增强,所以上图363行返回的specificInterceptors为空,因此未创建代理对象,将原有的bean直接返回。

11. 常规aop逻辑

常规创建aop代理对象是通过执行BeanPostProcessor的实现类AspectJAwareAdvisorAutoProxyCreator的postProcessAfterInitialization方法。

12. 填充instantB的属性

将容器中获取的instantA(此时位于二级缓存)填充到instantB的属性中。

13. 从singletonsCurrentlyInCreation移除instantB

移除后集合仅剩instantA。

14. 更新instantB的缓存

当前instantB位于三级缓存中,因此从三级缓存中移除并将生成的bean放到一级缓存单例池中;

15. 继续回到创建instantA的populateBean过程中

将容器获取的instantB填充到instantA实例中。

16. 更新instantA的缓存

当instantA属性填充完后,instantA生成完毕,此时进行缓存更新。

当instantA属性填充完后,再进行初始化。初始化后,instantA生成完毕并进行缓存更新,此时instantA位于二级缓存,将它从二级缓存中移除并加入一级缓存单例池。

4.3、流程总结


结合以上分析,可以看出spring可以解决filed注入产生的循环依赖,主要借助以下四个集合(三级缓存外加正在创建的bean集合):

	//单例池
	/** Cache of singleton objects: bean name to bean instance. */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
	
	//三级缓存——存放刚实例化的bean
	/** Cache of singleton factories: bean name to ObjectFactory. */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    //二级缓存——存放早期曝光bean
	/** Cache of singleton factories: bean name to ObjectFactory. */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);


	//正在创建的bean集合
	/** Names of beans that are currently in creation. */
	private final Set<String> singletonsCurrentlyInCreation =Collections.newSetFromMap(new ConcurrentHashMap<>(16));

5、原型bean的循环依赖

       前面的循环依赖主要是针对单例bean,接下来看看spring能否解决原型bean的循环依赖?还是以注解手动注入为例,Go!

5.1、demo示例

@Component
@Scope("prototype")
public class InstantA {
	
	@Autowired
	private InstantB instantB;
}

@Component
@Scope("prototype")
public class InstantB {
	@Autowired
	private InstantA instantA;
}

5.2、源码分析

protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {
      
		// Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {           //省略非关键代码
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}

		else {
			
             //省略非关键代码
			if (!typeCheckOnly) {
				markBeanAsCreated(beanName);
			}

			try {
				// Create bean instance.
				if (mbd.isSingleton()) {
					  //省略非关键代码
				}

				else if (mbd.isPrototype()) {
					// It's a prototype -> create a new instance.
					Object prototypeInstance = null;
					try {
					    //创建之前将beanName设置到线程中或者加入集合中
						beforePrototypeCreation(beanName);//说明1
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
						afterPrototypeCreation(beanName);
					}
					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}

				else {
		
				}
			}
			catch (BeansException ex) {
				cleanupAfterBeanCreationFailure(beanName);
				throw ex;
			}
		}
public class NamedThreadLocal<T> extends ThreadLocal<T> {

	private final String name;


	/**
	 * Create a new NamedThreadLocal with the given name.
	 * @param name a descriptive name for this ThreadLocal
	 */
	public NamedThreadLocal(String name) {
		Assert.hasText(name, "Name must not be empty");
		this.name = name;
	}

	@Override
	public String toString() {
		return this.name;
	}

}

  • beforePrototypeCreation
	protected void beforePrototypeCreation(String beanName) {
		//如果ThreadLocal的value为空,则直接将beanName当作value
		Object curVal = this.prototypesCurrentlyInCreation.get();
		if (curVal == null) {
			this.prototypesCurrentlyInCreation.set(beanName);
		}
		//如果ThreadLocal的value已经是String,则ThreadLocal的value需要保存多个值,则将新建hashSet作新value
		else if (curVal instanceof String) {
			Set<String> beanNameSet = new HashSet<>(2);
			beanNameSet.add((String) curVal);
			beanNameSet.add(beanName);
			this.prototypesCurrentlyInCreation.set(beanNameSet);
		}
		else {
			如果ThreadLocal的value已经是Set,则直接add
			Set<String> beanNameSet = (Set<String>) curVal;
			beanNameSet.add(beanName);
		}
	}

1. 实例化InstantA之前检查prototypesCurrentlyInCreation是否包含instantA

2. 将instantA加入当前线程的prototypesCurrentlyInCreation

3. 填充InstantA实例,发现依赖InstantB

A、解析InstantA类的注解@Autowired发现依赖InstantB;
B、准备实例化InstantB,实例化之前检查prototypesCurrentlyInCreation是否包含instantB;
C、此时不包含InstantB,将instantB加入prototypesCurrentlyInCreation;
经过以上步骤,prototypesCurrentlyInCreation集合中则有instantA和instantB两个元素,如上图所示;

4. 解析instantB发现需要依赖instantA,则再次从容器获取instantA


获取instantA之前再次检查prototypesCurrentlyInCreation是否包含instantA,此时已经包含instantA,因此抛出异常。

5.3、总结

可以看到spring无法解决原型的filed注入循环依赖,当然构造注入的循环依赖也无法解决。

6、关于三级缓存的思考

6.1、一级缓存能否解决循环依赖?

不行。
因为实例化后(未填充属性)的bean以及完成属性填充实例化的bean都放到一级缓存。如果在实例化后与属性填充之间获取bean,则得到非完整bean,可能属性为空;

6.2、二级缓存能否解决循环依赖?

A、如果没aop,二级缓存能解决;bean生产过程可以分成实例化和初始化两个阶段,实例化后放在二级缓存,初始化完再放到一级缓存。生成bean完成后,后续获取bean只从一级缓存中获取,可以保证bean的完整性;

B、如果有aop,二级无法解决循环依赖,会出现二级缓存中相同的beanName在不同阶段(实例化后和初始化后)不是同一个bean(因此执行aop后会返回代理对象,和之前的bean不是同一个对象),导致混乱;

6.3、三级缓存存在的意义

A、为什么不直接在放入两级缓存之前提前执行aop逻辑?

因为循环依赖的相对较少,没必要针对小部分实例执行一遍(wrapIfNecessary)。反正后续的BeanPostProcessor中的postProcessAfterInitialization有对aop的处理;

B、三级缓存的目的就是让如果有循环依赖,提前执行可能存在的aop操作(如果存在aop),从而放入二级缓存的bean是生成的代理对象,从而保证二级缓存中相同的beanName是同一个bean;

7、总结

还记得开始前的四个问题吗?

  1. spring能解决所有的循环依赖吗?

    答:
    原型bean:spring不能解决原型bean任何注入方式产生的循环依赖;
    单例bean:能解决单例bean在setter、filed注入时产生的循环依赖,不能解决构造注入时产生的循环依赖;

  2. spring如何解决循环依赖?

    答:三级缓存+singletonsCurrentlyInCreation(正在创建的bean集合)

  3. 一级缓存以及二级缓存能否解决循环依赖?
    答:不能;

  4. 为什么需要三级缓存?
    答:相信从三级缓存思考中得到答案;

以上是关于spring源码系列8——最详细的循环依赖解读的主要内容,如果未能解决你的问题,请参考以下文章

Spring源码解读---循环依赖底层源码解析

Spring源码解读---循环依赖底层源码解析

SpringBoot源码解读系列二——依赖配置

0源码基础学习Spring源码系列——Spring如何解决循环依赖

手撕Spring源码,详细理解Spring循环依赖及三级缓存

Spring源码分析系列-循环依赖和三级缓存