Spring框架(四) 三级缓存与循环依赖
1.循环依赖首先我们需要明白什么是循环依赖 , 打个比方 , 就是说A对象在创建的过程中 , 需要依赖注入B对象 , 但是B对象没有 , 就需要去创建 , 而在创建B对象的过程中又需要注入A对象 , A对象此时还在创建中,所以就构成了一个死循环 , A,B相互依赖 这样的关系被成为循环依赖(当然 , 可能还会有其他的情况),下面我们就来看看Spring是如何让解决循环依赖的2.一二三级缓存三个缓存
感兴趣的话大家可以关注一下公众号 : 猿人刘先生 , 欢迎大家一起学习 , 一起进步 , 一起来交流吧!
1.循环依赖
首先我们需要明白什么是循环依赖 , 打个比方 , 就是说A对象在创建的过程中 , 需要依赖注入B对象 , 但是B对象没有 , 就需要去创建 , 而在创建B对象的过程中又需要注入A对象 , A对象此时还在创建中,所以就构成了一个死循环 , A,B相互依赖 这样的关系被成为循环依赖(当然 , 可能还会有其他的情况),下面我们就来看看Spring是如何让解决循环依赖的
2.一二三级缓存
三个缓存对应着三个不同的Map
一级:singletonObjects
这个缓存也就是单例池 , 它存放的是完整的经过Bean生命周期的Bean
二级:earlySingletonObject
这个缓存存放的一个残疾Bean , 怎么理解呢?就是创建到一半就放进去了
三级:singletonFactories
3.情况一
A , B对象相互依赖
比如现在有两个Service , 分别是AService和BService
AService:
@Component
public class AService {
@Resource
private BService bService;
public void test(){
System.out.println("AService test() -> " + bService);
}
}
BService:
@Component
public class BService {
@Resource
private AService aService;
public void test(){
System.out.println("BService test() -> " + aService);
}
}
如果不考虑Spring的话 , 循环依赖是很正常的事情 , 比如
AService aService = new AService();
BService bService = new BService();
aService.bService = bService;
bService.aService = aService;
但是 , 如果考虑Spring的话这就是一个问题 , 因为如果使用Spring , 那么就会有控制反转, 这个对象就不在是由你自己去创建了 , 而是在Spring内部经过一系列Bean的生命周期 , 就是因为有了Bean的生命周期 , 所以才会出现循环依赖 ,那我们简单回顾一下Bean的生命周期
Bean的生命周期
Bean的生命周期具体指的就是一个Bean对象从创建到销毁的过程 , 其生成步骤如下
1.Spring会通过扫描Class得到BeanDefintion(Bean的定义)
2.根据BeanDefintion去生成对象
3.首先通过Class来进行构造方法的推断(推断构造方法)
4.根据推断出来的构造方法然后反射得到一个对象(暂时叫做原始对象)
5.填充原始对象的属性(属性填充 , 依赖注入)
6.如果原始对象中的某个方法被AOP了 , 那么就需要根据原始对象生成一个代理对象
7.把最终生成的原始对象放入单例池 , 也就是上面的一级缓存(singletonObjects) , 下次getBean从此Map获取就行
这是一个大致的过程 , 对于Spring而言 , Bean的生命周期不止这么7步 , 还包括Aware回调 , 初始化等等 ,可以发现在Spring中构造一个Bean包括了new这个步骤(第四步),生成一个原始对象之后再来进行属性注入 , 那么对于情况1而言他的属性注入的过程大概是这样的:
创建AService得到原始对象 , 然后去给BService属性赋值 , 此时就会根据BService属性的类型或名字在BeanFactory寻找BService所对应的单例Bean , 如果存在 , 直接赋值给BService , 如果不存在则需要生成一个BService对应的Bean , 然后赋值给BService 如下图
通过上图 , 我们可以很清晰的看出来循环依赖出现的原因 , 如何打破这个循环呢?在中间加一层缓存 , 如下图
流程我就不必多做解释了 , 此时从缓存中获取的AService为原始对象 , 还不是最终的Bean , BService的原始对象依赖注入完后 , BService的生命周期结束 , 那么AService的生命周期也结束
因为在整个过程中AService都是单例的 , 所以即使从缓存中拿到的AService的原始对象也没有关系 , 因为在后续的Bean生命周期中 ,AService在堆内存中没有发生变化
所以情况一的循环依赖也就完美的解决了 , 那么又会产生新的问题:
1.一个缓存就可以解决循环依赖, 为什么要三级缓存?
用下面情况二的例子来分析分析
4.情况二
还是AService , BService相互依赖的场景 , 但是多了一层AOP , 就是AService的原始对象赋值给BService时 , 进行了AOP , 那么AService进行AOP之后 , 它的真实对象是代理对象 , 那么BService中AService的值是原始对象的值 ,那么就会产生BService的AService值和AService的实际值不符的问题
AOP就是通过一个BeanPostProcessor来实现的 , 而这个BeanPostProcessor就是
AnnotationAwareAspectJAutoProxyCreator, 它的父类是AbstractAutoProxyCreator(如下图),而在SpringAOP中要么利用JDK动态代理 , 要么利用CGLib的动态代理 , 所以如果给这个类中的某一个方法设置了切面 , 那么它最后都会生成一个代理对象
基本流程看下图
而我们都知道 , AOP是Spring除开IOC的另外一大功能 , 而循环依赖又属于IOC的范围 , 所以如果想要这两者功能共存 , 就必须使用其他的手段:三级缓存 singletonFactories
首先 这个缓存存放的是(beanName:ObjectFactory),在Bean的生命周期中 , 构造完一个原始对象就生成一个ObjectFactory , 然后缓存起来 ,这个ObjectFactory是一个函数式接口 , 所以支持lambda表达式 , () -> getEarlyBeanReference(beanName, mbd, bean);而这个lambda就表示是一个ObjectFactory , 他会去执行这个方法 , 这个方法在SmartInstantiationAwareBeanPostProcessor中
代码的大概意思就是说 ,找到继承了InstantiationAwareBeanPostProcessor这个类的BeanPostProcessor,就表示你需要进行AOP ,然后获取 SmartInstantiationAwareBeanPostProcessor这个 BeanPostProcessor , 然后执行getEarlyBeanReference()方法 , 由于这是一个接口 , 我们直接看实现类是如何实现的
1.首先是执行getCacheKey()获取key的名称 , 如下
他会判断beanName是否为null , 如果不为空 , 那么判断是否是FactoryBean , 如果是就拼接&符号 , 否则直接返回 , 如果beanName为null , 最直接返回
2.然后把cacheKey当作key , bean当作value , 放入earlyProxyReferences(关于earlyProxyReferences下面有解释),然后执行wrapIfNecessary()进行AOP
3.该方法大概意思就是如果你符合AOP的条件 , 那么我就创建一个代理对象返回 , 如果不需要,那么返回原始对象
到这其实可以体会到二级缓存为什么放的是残级Bean,因为此时创建一个代理对象 , 还没有完成Bean的生命周期 , 而三级缓存的ObjectFactory的lambda也可以理解为一段逻辑(大概就是需要AOP的时候, 那么返回一个代理对象 , 如果不需要AOP我还是返回的原始对象),但是不管返回什么对象都是没有完成Bean生命周期的
那么什么时候调用getEarlyBeanReference()这个方法呢?回到循环依赖场景,用一张图就大概可以理清这个思路了
就是创建AService , 然后会产生一个AService的原始对象 , 并且key为beanName , Value为lambda表达式放入三级缓存 , 然后注入BService , 生成BService原始对象 , 此时需要注入AService就要从单例池获取 , 取不到 , 从二级缓存获取 , 取不到 , 然后从三级缓存获取 , 并执行lambda表达式,如果符合AOP的条件 , 那么返回代理对象 , 如果不符合 , 返回原始对象 , 然后赋值给BService的AService , 然后BService完成创建
源码如下:
1.放入三级缓存
2. 从一二三级缓存获取,然后执行表达式
这是getSingletion方法
此时AService正常进行AOP,但是前面已经执行过AOP了 , 所以对于AServicec本身而言就不需要AOP了所以就又产生了一个问题:怎么判断是否执行过AOP了?
会利用earlyProxyReferences,就是上面方法提到的
再AbstractAutoProxyCreator类中的postProcessAfterInitialization()会判断
如果成功移除 , 表示不需要AOP了, 否则就需要AOP
对于AService而言 , 进行了AOP的判断之后 , 以及执行了BeanPostProcessor之后 , 就需要把AService对应的对象放入单例池了 ,但是我们知道 ,如果进行AOP之后那么就要把代理对象放入单例池 , 那么代理对象就可以在二级缓存拿到了 , 然后放入单例池
但也不是所有情况的循环依赖都能解决 , 比如AService和BService都是原型的等
5.总结:
5.1.singletonObjects:单例池 , 一级缓存
缓存的是经历了完整的Bean生命周期的Bean
5.2 earlySingletonObjects:二级缓存
缓存的是未经过完整的Bean生命周期的Bean,如果出现了循环依赖 , 那么就会提前把这个暂时未经过Bean生命周期的Bean放入这个缓存 , 如果这个Bean需要经过AOP , 那么就会把代理对象放入这个缓存 , 就算是经过了AOP , 那么这个代理对象代理的真实对象也是是未完成生命周期的 , 所以我称它为残疾Bean , 这个缓存可以理解为存放的是未经过完成的Bean生命周期的Bean
5.3 singletonFactories:三级缓存
缓存的是ObjectFactory , 也就是一个Lambda表达式 , 这个表示在存入的时候不会执行 , 在get的时候会执行,在每个Bean生成一个原始对象的时候 , 都会基于这个原始对象暴露一个lambda表达式 , 然后放到这个缓存 , 这个lambda可能用到 , 也可能用不到 , 如果当前Bean没有出现循环依赖的情况 , 那么这个表达式就没用 , 按照自己的生命周期正常执行 , 执行完之后把这个对象放入单例池 , 如果出现了循环依赖(当前创建的Bean被其他的Bean依赖了) , 那么就会把这个表达式取出来 , 然后执行得到一个对象 , 并把得到的缓存放入二级缓存 , (如果当前Bean需要AOP , 那么执行完lambda之后得到的就是一个代理对象 ,如果无需AOP , 那就是原始对象)
5.4 earlyProxyReferences
其实还有这个缓存 , 用来记录某个原始对象是否进行了AOP
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)