Spirng源码解析之循环依赖
Spring循环依赖
一、java基础中的循环依赖
在java中其实并不存在所谓的循环依赖
A a = new A();
B b = new B();
a.setB(b);
b.setA(a);
这种其实是没有任何问题的,但是在Spring中就可能会存在着问题。
二、循环依赖解决思路
前提条件:单线程条件
V1
bean A 的完整生命周期
1、实例化A
2、填充B属性--->前往单例池中寻找B---->没有则创建B;
3、填充其他属性;
4、初始化前、初始化
5、初始化后
6、放入到单例池
bean B 的完整生命周期
2.1、实例化B
2.2、填充A属性--->前往单例池中寻找A---->没有则创建A;【如果不把这个问题处理好,就会出现递归。继续去创建A,就又会出现循环创建】
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池
解决方式:利用一个Map<beanName,对象>来进行缓存已经创建好了的实例
V2
bean A 的完整生命周期
1、实例化A;
2、填充B属性--->前往单例池中寻找B------>Map<B_name,B>---->没有则创建B;
3、填充其他属性;
4、初始化前、初始化;
5、初始化后;
6、放入到单例池
bean B 的完整生命周期
2.1、实例化B
2.2、填充A属性--->前往单例池中寻找A------>发现A在Map<A_name,A>中,赋值,执行下一步
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池
似乎已经完美的解决了问题。
但是这个时候可能会存在一个问题:在Bean B中,在给A填充属性之后,完成了操作,放入了单例池中。而Bean A在执行初始化之后,如果出现A发生了动态代理,放入了单例池中。那么就不合理了。
因为如果单例池中放入的是代理对象,而Bean B中的属性A是原始对象,不符合单例的概念。
解决方式:将动态代理提前化
V3
bean A 的完整生命周期
1、实例化A--->判断是否需要AOP?如果需要那么执行AOP逻辑----->代理对象--->Map<A_name,A代理对象>
2、填充B属性--->前往单例池中寻找B------>Map<B_name,B>---->没有则创建B
3、填充其他属性;
4、初始化前、初始化
5、初始化后
6、放入到单例池
bean B 的完整生命周期
2.1、实例化B--->判断是否需要AOP?如果需要那么执行AOP逻辑----->代理对象--->Map<B_name,B代理对象>
2.2、填充A属性--->前往单例池中寻找A------>发现A在Map<A_name,A代理对象>中,赋值,执行下一步
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池
那么现在就有几个问题:
1、怎么判断当前Bean是否需要经过AOP?判断逻辑是什么?AOP到底在哪里执行更加合适呢?
2、在出现循环依赖的情况下,要想解决循环依赖,就不得不得考虑到AOP,要考虑到AOP,就不得不去提前进行AOP。
那么重点首先就转移到了如何知道出现了循环依赖?
解决方式:利用一个集合Map来记录一下正在创建的bean,存储bean的名称和相应的对象。
V4
bean A 的完整生命周期
1、实例化A--->记录A正在创建中(集合保存,以set举例)----->
2、填充B属性--->前往单例池中寻找B------>Map<B_name,B>---->没有则创建B
3、填充其他属性;
4、初始化前、初始化
5、初始化后
6、放入到单例池
bean B 的完整生命周期
2.1、实例化B--->记录B正在创建中
2.2、填充A属性--->前往单例池中寻找A------>再从set中找,知道A正在创建中[AB出现循环依赖] ------>①如果需要经过AOP,那么得到代理对象;②如果不需要经过AOP,那么设置原生实例对象
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池
问题:
那么现在问题就转移到了:
1、AOP到底在哪里应该来执行?
2、对于需要和不需要经过AOP的bean如何使用逻辑处理?A出现AOP,需要将代理对象赋值给B;没有出现AOP,出现将原生对象赋值给B
V5
如果此时此刻,又有一个bean C也依赖了A的情况
bean A 的完整生命周期
1、实例化A--->记录A正在创建中(集合保存,以set举例)----->
2、填充B属性--->前往单例池中寻找B------>Map<B_name,B>---->没有则创建B
3、填充C属性--->前往单例池中寻找C------>Map<C_name,C>---->没有则创建C;
4、初始化前、初始化
5、初始化后
6、放入到单例池
bean B 的完整生命周期
2.1、实例化B--->记录B正在创建中
2.2、填充A属性--->前往单例池中寻找A------>再从set中找,知道A正在创建中[AB出现循环依赖] ------>①如果需要经过AOP,那么得到代理对象;②如果不需要经过AOP,那么设置原生实例对象
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池
bean C 的完整生命周期
2.1、实例化C--->记录C正在创建中
2.2、填充A属性--->前往单例池中寻找A------>再从set中找,知道A正在创建中[AC出现循环依赖] ------>①如果需要经过AOP,那么得到代理对象;②如果不需要经过AOP,那么设置原生实例对象
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池
问题:bean B 和 bean C 都依赖了bean A,假如说B在进行属性注入的时候,A经历了一次AOP;在C进行属性注入的时候,A还要进行一次AOP?
解决方式:在B和C属性填充阶段,需要保证BC依赖的是同一个代理对象,不能够重复生成代理对象。因为bean B依赖了bean A ,那么在属性注入阶段,需要将A得到的最终结果存起来,如Map集合;因为bean C也依赖了bean A,那么在属性注入阶段,可以直接从Map中来进行获取得到bean A。
V6
bean A 的完整生命周期
1、实例化A--->记录A正在创建中(集合保存,以set举例)-----> 是否需要经过AOP?①如果需要经过AOP,那么得到代理对象;②如果不需要经过AOP,那么设置原生实例对象
2、填充B属性--->前往单例池中寻找B------>Map<B_name,B>---->没有则创建B
3、填充C属性--->前往单例池中寻找C------>Map<C_name,C>---->没有则创建C;
4、初始化前、初始化
5、初始化后
6、放入到单例池
bean B 的完整生命周期
2.1、实例化B--->记录B正在创建中
2.2、填充A属性--->前往单例池中寻找A------>再从set中找,知道A正在创建中[AB出现循环依赖]------>从Map缓存中查找是否已经创建好了-->没有-->>①如果需要经过AOP,那么得到代理对象;②如果不需要经过AOP,那么设置原生实例对象,最终放入到map中
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池
bean C 的完整生命周期
2.1、实例化C--->记录C正在创建中
2.2、填充A属性--->前往单例池中寻找A------>再从set中找,知道A正在创建中[AB出现循环依赖]------>从Map缓存中查找是否已经创建好了-->没有-->>①如果需要经过AOP,那么得到代理对象;②如果不需要经过AOP,那么设置原生实例对象,最终放入到map中
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池
所以现在,在Map缓存中保存的是没有经历过完整的生命周期的bean;
问题:不管是否需要进行AOP,那么都需要有原始对象。代理对象中的target属性就是原始对象,那么原始对象如何来进行获取呢?
解决方式:1、ThreadLocal;2、通过参数形式;3、利用map
那么这里最好的方式是利用Map
V7
bean A 的完整生命周期
1、实例化A--->记录A正在创建中-----> 是否需要经过AOP?①如果需要经过AOP,那么得到代理对象;②如果不需要经过AOP,那么设置原生实例对象 【map集合保存,key=beanName,value=lambda表达式,对应逻辑是:①如果需要经过AOP,那么得到代理对象;②如果不需要经过AOP,那么设置原生实例对象】----------------->利用BeanPostProcess来进行判断
2、填充B属性--->前往单例池中寻找B------>Map<B_name,B>---->没有则创建B
3、填充C属性--->前往单例池中寻找C------>Map<C_name,C>---->没有则创建C;
4、初始化前、初始化
5、初始化后
6、放入到单例池
bean B 的完整生命周期
2.1、实例化B--->记录B正在创建中 执行lambda表达式得到结果
2.2、填充A属性--->前往单例池中寻找A------>再从set中找,知道A正在创建中[AB出现循环依赖] ------> map集合查询--> 肯定能够在map中找到--->list集合保存
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池
bean C 的完整生命周期
2.1、实例化C--->记录C正在创建中 执行lambda表达式得到结果
2.2、填充A属性--->前往单例池中寻找A------>再从set中找,知道A正在创建中[AC出现循环依赖] ------> map集合查询--> 肯定能够在map中找到--->list集合保存
2.3、填充其他属性;
2.4、初始化前、初始化
2.5、初始化后
2.6、放入到单例池
总结
其实Spring的做法也是如此,那么我们回头来总结一下在上面用的缓存集合中在Spring的具体体现:
一级缓存:singletonObjects:单例池
二级缓存:earlySingletonObjects:不管是原始对象还是代理对象,都是没有经历过完整的生命周期的Bean!作用:保证单例
三级缓存:singletonFactories:保存lambda表达式。作用:打破循环依赖。利用map存了一个,在其他bean在依赖注入的时候能够来进行使用
解决循环依赖的最重要的一个是二级缓存:earlySingletonObjects,里面保存的是没有经历过完整的生命周期的bean。
三、循环依赖源码分析
AService和BService出现循环依赖
@Component
public class AService {
@Autowired
private BService bService;
public void test(){
bService.aaaa();
}
}
@Component
public class BService {
@Autowired
private AService aService;
public void aaaa(){
System.out.println("bservice aaaa........");
}
}
3.1、创建AService过程
直接来到org.Springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法中
1、创建AService实例
2、循环依赖判断阶段
直接来到实例化过程后,进行循环依赖的判断:
当前判断标记为:当前bean为单例、allowCircularReferences(Spring默认支持循环依赖)以及当前bean正在创建中,才会进入到下面的if条件中来。
那么表示当前bean正在创建中是在哪里记录的呢?在上面一步:org.Springframework.beans.factory.support.AbstractBeanFactory#doGetBean
然后进入到getSingleton方法中
在这里将当前正在创建的AService的BeanName添加到集合singletonsCurrentlyInCreation中,记录当前bean正在创建中。
所以当前singletonsCurrentlyInCreation中至少有个beanName为AServcie
当前创建的是AService,所以可以进入到下方的判断中来
3、循环依赖保存lambda表达式阶段
因为正在创建AService,所以单例池中肯定是不包含的。然后将lambda表达式保存到三级缓存中。
看一下lambda表达式的保存,是保存到三级缓存中
4、属性填充阶段
在AService填充BService期间,BService是没有实例化的
在AService填充BService期间,BService是没有实例化的
在AService填充BService期间,BService是没有实例化的
在AService实例属性填充阶段中,调用org.Springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean方法,因为在AService中使用的是@Autowired注解标注BServcie的,那么肯定会走AutowiredAnnotationBeanPostProcessor中的属性填充阶段
然后接着向下走
这里有一个步骤是针对于@Lazy注解的解析处理。表示的是如果属性上同时存在@Lazy注解,那么这里会先赋值一个临时值,然后返回
当前在AService类中有一个BService属性,只有@Autowired注解,没有@Lazy注解,那么看下如何来进行处理的。
继续到下一步骤中
给BService进行赋值的时候,会从容器中来进行查找BService类型的bean,会调用doGetBean方法。
然后进去,查看下具体的方法
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
注意这里的参数:beanName和true
但是因为BService并没有进行实例化过,所以不在单例池中,也不在创建中,所以直接返回null。
那么接下来就开始来进行BService的创建过程。
3.2、创建BService过程
直接来到org.Springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法中
1、创建BService实例
2、循环依赖判断阶段
直接来到实例化过程后,进行循环依赖的判断:
当前判断标记为:当前bean为单例、allowCircularReferences(Spring默认支持循环依赖)以及当前bean正在创建中,才会进入到下面的if条件中来。
那么表示当前bean正在创建中是在哪里记录的呢?在上面一步:org.Springframework.beans.factory.support.AbstractBeanFactory#doGetBean
然后进入到getSingleton方法中
在这里将当前正在创建的AService的BeanName添加到集合singletonsCurrentlyInCreation中,记录当前bean正在创建中。
所以当前singletonsCurrentlyInCreation中至少有两个个beanName为AServcie、BServcie
当前创建的是BService,所以可以进入到下方的判断中来
3、循环依赖保存lambda表达式阶段
因为正在创建BService,所以单例池中肯定是不包含的。然后将lambda表达式保存到三级缓存中。
看一下lambda表达式的保存,是保存到三级缓存中
4、属性填充阶段
在BService实例属性填充阶段中,调用org.Springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean方法,因为在AService中使用的是@Autowired注解标注AServcie的,那么肯定会走AutowiredAnnotationBeanPostProcessor中的属性填充阶段
然后接着向下走
这里有一个步骤是针对于@Lazy注解的解析处理。表示的是如果属性上同时存在@Lazy注解,那么这里会先赋值一个临时值,然后返回
当前在BService类中有一个AService属性,只有@Autowired注解,没有@Lazy注解,那么看下如何来进行处理的。
继续到下一步骤中
给AService进行赋值的时候,会从容器中来进行查找BService类型的bean,会调用doGetBean方法。
然后进去,查看下具体的方法
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
注意这里的参数:beanName和true
那么此时分为了以下几个步骤:
- 1、从单例池中获取,单例池中没有,返回null;
- 2、AService正在创建中且支持循环引用;
- 3、从二级缓存中尝试获取,获取不到,返回null;
- 4、上面都不成立,那么一定在三级缓存中。因为此时的beanName为AService,那么执行AService保存进去的lambda表达式;
- 5、将lambda表达式执行的结果放入到二级缓存中去;
- 6、返回执行lambda表达式之后的结果;
从这里可以看到,如果没有循环引用,那么根本就不会执行到从三级缓存中获取得到lambda表达式来进行执行。
4.1、执行AService的lambda表达式
那么看下保存到三级缓存中的lambda表达式的执行。
找到SmartInstantiationAwareBeanPostProcessor类型的BeanPostProcessor,而当前只有一个AbstractAutoProxyCreator对应的实现类。所以直接看AbstractAutoProxyCreator类中的getEarlyBeanReference方法:
然而当前只有org.Springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference有具体的实现,其他的没有实现。
注意一下这里几个参数:
- bean:表示的是AService根据构造方法创建出来的实例对象;
- beanName:表示的是AService对应的bean的名称,当前beanName为AService
计算缓存key,然后放入到缓存中去。
4.2、为什么要缓存?
在我们分析循环依赖解决思路的过程中,我们提到过一点:已经提前进行了AOP,那么在初始化后的阶段中就不需要再来进行AOP了。
假如说:AService在属性填充阶段中,发现BService没有创建,那么在BService进行属性填充阶段中,发现AService需要进行AOP,那么AService进行AOP。
BService属性注入完成之后,执行初始化前、初始化、初始化后方法(当前BService不需要进行AOP)然后完成BService的生命周期。那么此时就来到了AService属性填充完成之后的阶段,AService也需要来执行初始化前、初始化、初始化后方法,在执行初始化后方法中,是否还需要对AService进行AOP呢?肯定是不需要的,因为在BService对AService进行属性填充的时候,已经进行了AOP。那么这个时候就需要来做一个记录。
不妨来看一下初始化后的方法(这个方法也在这个类中)
首先也是判断,循环依赖中有没有添加过,如果添加过,那么就不再走初始化后的判断是否需要进行AOP的步骤了。没有没有添加过,那么获取得到的肯定是null,那么肯定是不相同的,会进去判断当前bean是否需要经历AOP。
这就是上面添加到缓存中的目的。
4.3、lambda表达式代理逻辑
看一下wrapIfNecessary方法的注释,表示了:即如果它有资格被代理,包装一下给定的bean。
判断一下当前bean(即BService是否需要进行AOP),因为当前没有定义一些Advisor和Advice,所以没有任何代理逻辑。如果存在代理逻辑,那么在advisedBeans(Map<Object, Boolean>)中记录一下false(不需要代理),方便下次来进行查找。
对于当前BService来说,因为没有代理逻辑,所以直接返回原始BService类型的对象。
然后返回,成功进行赋值
5、初始化后方法
BService执行完属性填充阶段之后,然后马上来进行初始化后方法。来到org.Springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.Springframework.beans.factory.support.RootBeanDefinition)方法中来
而当前只有org.Springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization具有对应的代理逻辑
BService中没有保存到earlyProxyReferences中,那么也会进入到代理逻辑中去来进行判断是否需要进行AOP。
因为BService没有保存到这个集合中,返回值为null,而Null != bean,所以进去判断是否需要来进行AOP。
从这里可以看到,如果对于已经在属性赋值阶段中完成AOP的,那么这里返回的将会是原始对象,而不是代理对象。
而对于没有完成AOP的bean,那么执行到这里的时候,对于需要进行AOP的返回的是代理对象。
5.1、初始化后的BService保存到单例池
对于BService来说,earlySingletonReference为null,然后方法最终返回的是exposedObject。
3.3、AService执行初始化后方法
至此BService执行结束。但是因为AService是在属性填充阶段中来进行创建BService,那么BService在创建完成之后,还需要返回到之前的方法中来进行执行AService的初始化后方法。
对于AService来说
那么在初始化后直接返回原始bean对象
AService如何获取得到对象呢?
加入说AService是经过AOP的,那么应该获取得到的是代理对象,而这里获取得到的原始对象。那么就应该在下面来进行获取得到真实的对象
直接从二级缓存中获取得到的对象,然后来进行比较。
如果相等,那么就直接返回;如果不相等,那么就进行判断决定是否抛出异常信息。
对于当前AServcie对应的bean来说,肯定是相等的,那么就直接返回。
因为AService是在循环依赖中,判断添加到三级缓存转移到二级缓存中去的,肯定是经过了代理逻辑的判断,但是实际上是没有对应的代理方式的,所以直接返回的是原始bean对象。原始对象和原始对象相比,肯定是相同的。
四、循环依赖特殊情况
4.1、现象产生
上面也说到两个对象相等的情况,那么什么情况下两个是不相同的呢?
首先在配置类上开启异步任务配置
@ComponentScan(basePackages = "com.guang")
// 开启AOP
@EnableAspectJAutoProxy(exposeProxy=true)
@EnableAsync
public class LiAppCOnfig {}
对应的Service:
@Component
public class AService {
@Autowired
private BService bService;
@Async
public void test(){
bService.aaaa();
}
}
@Component
public class BService {
@Autowired
private AService aService;
public void aaaa(){
System.out.println("bservice aaaa........");
}
}
那么在启动的时候就会来报错!!!
那么需要看下我们的注解@EnableAsync做了什么事情,这个注解配置了一个AsyncAnnotationBeanPostProcessor,而且是一个BeanPostProcessor。
打上断点观察一下,初始化后方法那里现在BeanPostProcessor的执行顺序:
那么就意味着,本来不走org.Springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization中的代理逻辑了,但是因为新加了一个AsyncAnnotationBeanPostProcessor,会进入到这个方法中的代理逻辑中来。
从而导致了AService再次被代理,所以返回的值和原始对象是不相同的。
所以抛出了对应的异常信息。
4.2、解决问题
在AService填充BService属性中,因为造成了BService创建,而BService在填充AService属性中,因为从单例池中获取不到AService对象,而执行了对应的lambda表达式,又执行了AsyncAnnotationBeanPostProcessor,从而导致AService失败。
那么问题就在于,让BService在创建的时候不走后续逻辑,先给BServuce一个临时值,先让AService先完成生命周期流程,那么保存到单例池中的就是最终的结果。那么应该想到了。
@Lazy注解。
如下所示:
@Component
public class AService {
@Autowired
@Lazy
private BService bService;
@Async
public void test(){
bService.aaaa();
}
public BService getbService() {
return bService;
}
}
而在test方法中,使用bService.aaaa()方法时,这个时候,会从容器中查找得到真实的bServcie所对应的对象。
因为AService在进行属性填充时,没有走BService的创建流程;那么在BService在创建中时,要进行属性填充了,发现单例池中是存在AService的,那么就可以直接进行进行属性注入了。