文章目录
- 前言
- 一、循环依赖问题
- 二、循环依赖的解决
- 三、整体流程分析
前言
常见的可能存在循环依赖的情况如下:
- 两个bean中互相持有对方作为自己的属性。
类似于:
- 两个bean中互相持有对方作为自己的属性,且在构造时就需要传入:
类似于:
- 在某个bean中注入自身:
其中第二种构造方法的循环依赖一般情况下是无解的,除非加上@Lazy
注解。本篇重点分析第一种循环依赖Spring是如何解决的。
一、循环依赖问题
Spring在创建一个bean时,简单来说会经过实例化bean,属性注入,初始化的操作。当出现第一种循环依赖时,可能会经历以下的过程:
AService
- 去单例池中找有无AService实例,此时没有,执行
doCreateBean
。 createBeanInstance
创建出AService实例。- 执行AService实例的属性填充。
- AService的初始化、初始化前。
- AService的初始化后。
- 放入单例池。
其中在执行AService实例的属性填充
这一步,会根据@AutoWired
的注入点,去寻找BService实例:
- 去单例池中找有无BService实例,此时没有,执行
doCreateBean
。 - createBeanInstance`创建出BService实例。
- 执行BService实例的属性填充。
- BService的初始化、初始化前。
- BService的初始化后。
- 放入单例池。
其中在执行BService实例的属性填充
这一步,会根据@AutoWired
的注入点,发现需要填充AService实例,这就出现了循环依赖的问题。
二、循环依赖的解决
那么Spring是如何解决循环依赖的?主要是通过三级缓存
的机制去实现的:
singletonObjects :一级缓存 earlySingletonObjects 二级缓存 singletonFactories 三级缓存
可以看到,首先在doCreateBean
的方法中,bean实例化后,属性填充之前,先有一段图上的逻辑:
- 判断当前的bean是否是单例的,以及是否支持循环依赖(默认是true),以及
singletonsCurrentlyInCreation
集合中是否包含当前bean。 - 如果满足条件,就把当前的bean的名称,和一段lambda表达式,放入
singletonFactories
集合中。
java"> protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) {//单例池中没有该beanif (!this.singletonObjects.containsKey(beanName)) {//向单例工厂缓存当前bean名称以及对应的lambda表达式this.singletonFactories.put(beanName, singletonFactory);//从早期单例对象的缓存中去除当前的beanthis.earlySingletonObjects.remove(beanName);//向已注册的单例bean集合中添加当前bean名称this.registeredSingletons.add(beanName);}}}
这里的lambda表达式,主要是为了返回一个可以被外部提前访问的 bean 实例。注意:lambda表达式不是在此处执行,而是先放入了
earlySingletonObjects
集合中!
java"> protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {//如果 Spring AOP 代理在这里介入,那么 getEarlyBeanReference() 可能会返回一个动态代理对象,而不是原始 beanexposedObject = bp.getEarlyBeanReference(exposedObject, beanName);}}return exposedObject;}
返回可能经过 AOP 代理的 bean
判断是否需要AOP
并且在执行doGetBean
时,会首先执行getSingleton
方法:
在getSingleton
方法中,运用了双检锁模式,避免在加锁后其它线程已经创建并缓存了该 bean。并且上面提到的lambda表达式,会在此处真正地去执行
java">@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 尝试从单例池中获取当前 bean 的实例,避免加锁操作,提高性能Object singletonObject = this.singletonObjects.get(beanName);// 如果单例池(一级缓存)中没有找到,并且当前 bean 正在创建过程中(循环依赖的处理)if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 尝试从早期单例池(二级缓存)中获取当前 bean(即尚未完全初始化的对象)singletonObject = this.earlySingletonObjects.get(beanName);// 如果在早期单例池中也没有找到,并且允许获取早期引用(通常是为了解决循环依赖)if (singletonObject == null && allowEarlyReference) {// 加锁,避免并发创建同一个 bean 导致不一致的问题synchronized (this.singletonObjects) {// 再次检查,避免在加锁后其它线程已经创建并缓存了该 beansingletonObject = this.singletonObjects.get(beanName);// 如果单例池中依然没有找到该 beanif (singletonObject == null) {// 尝试从早期单例池中获取,如果仍然没有找到singletonObject = this.earlySingletonObjects.get(beanName);// 如果早期单例池中也没有找到,尝试从工厂中获取 bean(即懒加载)if (singletonObject == null) {// 获取 bean 的工厂方法(三级缓存)ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);// 如果工厂方法存在,说明该 bean 尚未初始化,且支持懒加载if (singletonFactory != null) {// 使用工厂创建 bean 并缓存到早期单例池中,防止循环依赖singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject); // 缓存早期对象this.singletonFactories.remove(beanName); // 移除工厂方法,因为 bean 已经创建}}}}}}// 返回获取到的 singletonObject,如果没有找到,则返回 nullreturn singletonObject;
}
这里的isSingletonCurrentlyInCreation
方法,是判断当前的bean名称是否在singletonsCurrentlyInCreation
集合中,那么bean是在什么时候存入该集合的呢?答案是在下方重载的getSingleton
方法中:
无论if条件是否成立,都会把当前的bean名称放入singletonsCurrentlyInCreation集合中
而在执行完createBean(包括实例化,依赖注入,初始化)之后,会执行addSingleton
方法:
会将当前bean从二级、三级缓存中移除,并放入单例池中,表示该bean已经完全创建完成。
java">protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {//将当前初始化完成的bean存入单例池(一级缓存)this.singletonObjects.put(beanName, singletonObject);//从三级缓存中删除当前beanthis.singletonFactories.remove(beanName);//从二级缓存中删除当前beanthis.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}
}
三、整体流程分析
A和B的创建流程 蓝色代表A 绿色代表B
A尝试从单例池中获取
条件不满足,返回null
进入
getSingleton
进入getSingleton
,执行beforeSingletonCreation
,将A放入singletonsCurrentlyInCreation
中,代表A正在被创建。
进入
doCreateBean
的addSingletonFactory
方法:
进入
doCreateBean
的addSingletonFactory
方法,将A放入三级缓存中
执行A的属性填充(A有一个属性为B):
尝试从容器中获取B
这里的条件依旧不满足,返回null:
进入
getSingleton
,执行beforeSingletonCreation
,同样将B放入singletonsCurrentlyInCreation
中,代表B正在被创建。
进入
doCreateBean
的addSingletonFactory
同样将B放入三级缓存
此时的缓存情况:A和B只在二级缓存中
进行B的属性填充**(B中有一个A属性)**
再次尝试从容器中获取A
此时的条件满足:
从三级缓存中取出A的lambda表达式执行:
此时的A的B属性是没有值的
放入二级缓存,并从三级缓存中删除:
此时的缓存情况:
直接返回,不走createBean的逻辑了。
给B的A属性赋值,完成属性填充:
B继续执行初始化
执行B的
getSingleton
的addSingleton
方法,将B放入单例池,并且清除二三级缓存:
回到A的属性填充,填充了B属性
填充完成后,A的B属性有值了,B的A属性也有值了,继续执行A的初始化,完成后将A放入单例池,并且清除二三级缓存:
此时的二三级缓存全部清空:
至此整个流程全部结束。
为什么要加入三级缓存?因为上面的过程只是普通情况,还需要考虑到AOP的情况。如果开启了AOP,那么会在初始化后,**基于切面生成一个代理对象。**而循环依赖
的触发时机是在属性注入时,如果只使用普通的缓存,会导致解决循环依赖时注入的对象是普通对象,而最终的对象是代理对象,产生不一致的情况。
applyBeanPostProcessorsAfterInitialization
是初始化后执行的方法,也是在AOP的场景下生成代理对象的方法:
如果开启了AOP,那么在循环中会执行
AbstractAutoProxyCreator
的postProcessAfterInitialization
方法生成代理,在这一步中会判断当前Bean是否已经在解决了循环依赖的过程中进行了AOP,如果已经进行过了,就不会再次生成代理,保证代理对象的唯一性。