一、Spring容器的三级缓存说明
三级缓存产生的原因:
- 解决循环依赖可能导致的死循环问题
- 循环依赖下的AOP代理问题
Spring解决循环依赖的核心就是提前暴露对象,而提前暴露对象就是放置于第二级缓存中。缓存的底层都是Map,至于它们属于第几层是由Spring回去数据顺序及其作用来表现的。
名称 | 作用 |
singletonObjects | 一级缓存:存放完整的Bean |
earlySingletonObjects | 二级缓存:存放提前暴露的Bean,Bean不是完整的,为完成属性注入和执行初始化方法 |
singletonFactories | 三级缓存:存放的是Bean工厂,主要生产Bean,存放到二级缓存中 |
在DefaultSingletonBeanRegistry类中:
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
二、Spring创建\管理Bean的基本流程(重点)
获取bean时通过bean工厂先从单例池中获取,如果没有则创建并添加至单例池,最终返回需要的bean对象
getBean --》 实例化 --》 填充属性 --》 初始化 --》放入单例池
首先需要了解是Spring它创建Bean的流程,我把它的大致调用栈绘图如下:
对Bean的创建最为核心三个方法解释如下:
- createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
- populateBean:填充属性,这一步主要是对bean的依赖属性进行注入(@Autowired)
- initializeBean:初始化,回到一些形如initMethod、InitializingBean等方法
1、getBean:先去单例池中寻找 A对象 是否存在,存在则直接返回,不存在则进入后续创建流程
2、实例化:通过反射实例化该对象 A对象
3、填充属性:为 A对象 填充属性(其中一个属性为 B对象 :需要 从单例池中获取 B对象 进行填充,这里等待第4步完成再能填充)
4、由于填充的其中一个属性是需要引用另一个对象,则开始对 B对象 进行相同的创建流程
getBean --》 实例化 --》 填充属性 --》 初始化 --》放入单例池
5、初始化:将完成的对象放入单例池中,再给返回
6、放入单例池将完成的 A对象 放入单例池
实例化:是从无到有在创造一个对象(把一个不存在的东西实例出来带到现实),只是单纯的把对象 new 一下就行了;例:new Student();在构造一个实例的时候需要在内存中开辟空间
初始化:是在对象实例化的基础上,并且对 对象中的值进行赋一下初始值(这样的好处是避免当你没有赋值的时候,可以用这个初始的值来代替,友好的帮助你的功能)
三、循环引用的场景及解决方案
1、循环引用的死循环场景
从对单例Bean
的初始化流程可以看出,循环依赖主要发生在第三步(populateBean),也就是field属性注入的处理。 如果继续遵循一个单例池的原则,会出现下列死循环引用问题。
两对象相互引用一直在填充属性中进行循环(死循环)
因为引用间没有任何一个可以进入下一阶段 “初始化” 并 “放入单例池”
所以单例池中需要的对象永远找不到(getBean)
2、二级缓存解决死循环方案(无法解决AOP代理问题)
增加一个【半成品池】结解决循环依赖创建问题:
创建时:对象一旦实例化完成【createBeanInstance("a")】,会先将对象放入半成品池中
对象引用时:会先找单例池,如果不存在则会去半成品池中寻找
【重点】代码调用栈大致流程:
1、当 A对象 执行【第2行】实例化完成【createBeanInstance("a")】后,将实例化的 A对象 放入半成品池中
2、【第3行】当为 A对象 填充属性(其中一个属性为 B对象 :需要 从 单例池 或 半成品池 获取 B对象 进行填充)
3、此时 单例池 和 半成品池 中都没有 B对象 因而执行B的创建流程:
(1)执行到【第7行】为 B对象 填充属性时需要 A对象
(2)首先判断 单例池中是否存在【没有】,再判断 半成品池中是否存在【找到了】
(3)将 半成品池中的 A对象 填充进 B的属性中
(4)B对象 属性填充完成 --》初始化完成 --》放入单例池
4、A对象 填充 B属性存在【getBean:单例池中找到了】
5、A对象 属性填充完成 --》初始化完成 --》放入单例池
6、将半成品池中的A对象 清除
3、三级缓存解决死循环+AOP代理问题
我们通常情况下调用的是代理对象,而非对象本身(回忆一下事务处理的场景)
增加一个【工厂池】结解决循环依赖创建+AOP代理问题:
所谓的三级缓存就是在 单例池、半成品池 的基础上,增加了一个工厂池,里面存储的都是每个对象绑定的ObjectFactory(),此时的 单例池、半成品池 中 存储的 已经 不是对象本身,而是代理对象
ObjectFactory()作用特点:
- 为了调用提前处理【getEarlyBeanReference】来执行创建动态代理【createProxy】
- 对象 实例化完成【createBeanInstance】,就会在工厂池中创建一个factory()
- factory()不一定会被执行,只有该对象在创建过程中,又被其他对象所引用才会被调用,从而执行createProxy
AOP处理器实现了Bean处理器接口
AOP处理器创建代理对象会通过两个入口来调用创建动态代理(createProxy):
- postProcessAfterInitialization:【后置处理】在对象 初始化完成 后调用后置处理来创建代理对象
- getEarlyBeanReference:【提前处理】在对象 实例化完成 后在工厂池中创建一个工厂,这个工厂会通过调用提前引用来创建代理对象
【重点】代码调用栈大致流程:
1、当 A对象 执行【第2行】实例化完成【createBeanInstance("a")】后,会在 工厂池 中放入一个【factory(a)】
2、【第3行】当为 A对象 填充属性(其中一个属性为 B对象 :需要 从 单例池 或 半成品池 获取 B对象 进行填充)
3、此时 单例池 和 半成品池 中都没有 B代理对象 因而执行B的创建流程(同样会在工厂池中放入factory(b)):
(1)执行到【第7行】为 B对象 填充属性时需要 A代理对象
(2)首先判断 单例池中是否存在【没有】,再判断 半成品池中是否存在【没有】
(3)从而进入 工厂池 中执行调用 【factory(a)】--》执行调用 提前引用【getEarlyBeanReference】--》执行调用 创建动态代理【createProxy】
(4)将创建的 A代理对象 放入 半成品池 ,可以被获取到并进行填充
(5)B对象 属性填充完成 --》初始化完成 --》执行调用 后置处理【postProcessAfterInitialization】--》执行调用 创建动态代理【createProxy】
(6)将创建的 B代理对象 放入 单例池 ,可以被获取到并进行填充
4、A对象 填充 B属性存在【getBean:单例池中找到了B代理对象】
5、A对象 属性填充完成 --》初始化完成 --》执行调用 后置处理【postProcessAfterInitialization】
6、此时 半成品池 中 已经存在 了 A代理对象 所以不需要再执行 createProxy,会将 A代理对象 从 半成品池中 移入 单例池
7、一旦对象进入单例池,则意味着创建流程已经全部完成,此时会将工厂池的factory()进行清理
问:为什么不在创建实例化后直接调用提前引用,而是要通过工厂方法调用?
答:所谓的提前引用指的是在创建的过程当中被引用。A在创建过程中,如果有人要去引用这个A,那么才会执行这个提前引用流程,否则这个factory(a)是不会被执行的,重点取决于是否被引用决定。