spring三级缓存详情 为何采用三级缓存?
循环依赖相信大家都知道 比如对象A自动注入B 对象B自动注入A 那么如果没有缓存 spring创建A时 检测到A依赖B 但是容器此时还没有B 就会转而创建B 而容器B又依赖A 但是此时容器还没有A 又创建A去了 从而无限递归 造成错误。
对于上述情况
spring将创建对象分为大概三个步骤
1 实例化 在堆中new一个对象 但此时还没有给对象属性赋值
2 给对象赋值
3 类的增加等操作 也就是容器中调用BeanPostProcessor 的实现类的方法(AOP 其实就是这一步实现的 但这一步不止实现AOP )AOP的处理类
AnnotationAwareAspectJAutoProxyCreator 的注解处理类 类关系处理如下图
最终都是利用了spring BeanPostProcessor后置处理器机制 在这个阶段 我们的组件已经被new 且属性也赋值结束 这个时候会把容器中所有的实现了 BeanPostProcessor的类的方法都给调用
public interface BeanPostProcessor { @Nullable default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Nullable default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }
在方法中 得到原始类 我们可以根据原始类 从而代理这个类 返回我们的代理类 这也是实现AOP的最基本原理了
4 初始化 (如果这个类实现了 InitializingBean 则会调用此方法完成类的初始化 )
5 返回最终的类(也即是存放到一级缓存中 这里面还是有门道的)
好了 闲话不说了 回到正题 spring把创建对象分为了五小步
对于循环依赖 如果我们可以在创建对象时 的第一步时就将创建的未完全初始化的对象放在一个缓存中 比如说创建A对象的时候
在new后 就将其放入一个Map 里面 在对A对象赋值的时候 检测到B需要创建 从而去创建B B检测到需要对象A时 就从这个缓存中去拿 这样就不会无限递归 出现错误 解决循环依赖 一级缓存就足以处理这个问题 但是这样有个大问题?是什么呢?想一想?
对 就是类的增加 比如我们的A类被代理了 那我们开始存的是A类原始类 那么B的依赖也就被赋值为A的原始类了 不是被增强的A类了 所以一级缓存是不能解决这个问题的 所以 spring引入了三级缓存 下面详细叙说思路:
注 spring的三级依赖在源码中 存在于:
DefaultSingletonBeanRegistry 类中以下三个变量:
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256); //一级缓存 存放的是 完整的单例对象 属性完整 且被增强后的对象(若被增强了(包含代理)) private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16); //二级缓存 存放的是 属性未被赋值 但是已经被代理的对象(若被代理) private final Map<String, Object> earlySingletonObjects = new HashMap(16);
//三级缓存 存放的是 一个代码块 这个代码块 的功能就是提前完成Aop (若被代理 如果没有被代理则返回原始对象) 并返回产生的对象
对于代码块 对于熟悉java lumda 函数式编程式的同学来说 就知道这个代码块只会在用到的时候才会执行 不用到的时候是不会执行的
spring的思路是如下:
在创建A对象的时候 在三级缓存存放了能产生提前产生A对象AOP的代理对象的 代码块 这一步 然后返回A的原始对象
然后 A原始对象开始属性赋值 检测到B对象不存在 这一步 spring会从一级缓存依次到三级缓存找 没有找到则说明这个依赖没被创建 所以 转而去创建B对象
对于B对象来说第一步与A的类似 在三级缓存存放了能产生提前产生B对象AOP的代理对象的 代码块 这一步 会返回B的原始对象
然后开始对B属性赋值 B对象有个依赖A 然后就会从从一级缓存依次到三级缓存找 很明显 三级缓存存在有A的能产生A的提前AOP的代码块 就会执行这个代码块 返回一个对象 (这个对象会根据我们自己配置的情况返回原始对象还是被代理的对象)然后将这个属性赋值给对象B (请注意这一步会把生成的对象放到二级缓存中 为什么后续再说)
执行B的第三步
B的AOP操作是否已经被提前执行了 所以这一步其实会有一个判定条件 判定是否已经提前被代理了 如果没有才会执行AOP的增强操作
执行B的第四步
略
执行B的第五步
这一步会判断返回的是B的原始类还是B的代理类
B对象创建结束 回到A的创建过程中 也就是第二步 属性赋值
执行A的第三步
对于这一步 A的AOP操作已经被提前执行了 所以这一步其实会有一个判定条件 判定是否已经提前被代理了 如果没有才会执行AOP的增强操作 ( 很明显A被提前Aop了所以 A不会执行了这一步的AOP增强操作了)
执行A的第四步
略
执行A的第五步
这一步会判断返回的是A的原始类还是A的代理类 很明显 A 被提前代理了 返回的应该是代理类
其实用一级缓存加三级缓存就可以解决循环依赖的问题了 但是为什么要采用二级缓存了 ?试想一下 加入对象A循环依赖B C 也就是A与B A与C都存在循环依赖 在实例化A的时候会提前实例化B和C 在示例化B的时候 已经提前利用三级缓存生成了A的代理类 但是由于A的属性赋值这个阶段还没结束 一级缓存还没有A 所以在实例化C的时候 又要从三级缓存中拿代码块生成A的代理类 这这样 对我们的程序是没问题的 因为代理类依托的原始类一直都是A 所以程序功能是没影响的 但是这样存在性能的浪费 如果我们把代码快生成的A的代理类 存放到二级缓存中 那么C需要的时候就可以直接获取到这个类了 从而不会又执行代码块生成一个了
自己写的乞丐版 spring 按照上述思路 解决循环依赖:gitHub地址
https://github.com/pupansheng/pps-spring.git
效果图: