一文搞懂 Spring 的三级缓存模型
为什么需要三级缓存?
众所周知,Spring框架为了解决循环依赖的问题,引入了一个三级缓存的机制。这个机制主要涉及到singleton类型的bean的创建过程。当两个或多个bean相互依赖时,仅靠一级缓存无法解决bean的创建和依赖注入问题,因此Spring通过引入三级缓存机制,确保即使在循环依赖的情况下也能正确处理bean的创建和初始化流程。三级缓存的引入增加了Spring容器解决循环依赖问题的能力,同时也保持了bean实例化和初始化的正确性和顺序。
什么是bean的实例化和初始化?
实例化是指创建对象的过程。在这一阶段,Spring通过调用类的构造方法来创建对象。
初始化是指在实例化之后,Spring对bean进行配置和处理的过程。在这个阶段,Spring将按照配置对该对象的属性进行注入,并且如果bean实现了某些接口(如
InitializingBean
)、定义了初始化方法(通过@PostConstruct
注解或init-method
属性指定),或者需要做一些自定义的初始化操作,这些都将在初始化阶段完成。
三级缓存中存储的内容
一级缓存(Singleton Objects)
- 存储的内容:已经完全初始化好的bean实例。
- 存入阶段:当一个bean完全初始化完成(包括实例化、属性注入、初始化方法执行等)后,它会被存入一级缓存。这里的bean已经准备好被使用了。
二级缓存(Early Singleton Objects)
- 存储的内容:提前曝光的bean引用,这些bean已经进行了实例化,但还没有完全初始化(比如还未进行属性注入)。
- 存入阶段:如果在bean的初始化过程中存在依赖其它bean的情况,为了解决这种依赖(尤其是循环依赖)的问题,Spring容器会把一个尚未完全初始化的原始bean实例放入二级缓存中,以此作为一个早期的引用。
三级缓存(Singleton Factories)
- 存储的内容:对于每一个正被创建的bean,存储的是一个ObjectFactory对象或一个FunctionalInterface,它可以生成提前曝光的bean引用(实质上是对bean的早期引用的一个包装)。
- 存入阶段:在bean的实例化阶段后、初始化阶段前,Spring容器将为尚未完成初始化的bean创建一个ObjectFactory,并将此ObjectFactory放入三级缓存中。当存在对这个尚未完成初始化的bean的依赖时,Spring容器通过调用三级缓存中的ObjectFactory来创建一个提前曝光的bean引用,并将其存入二级缓存,如果完成初始化后没有发现循环依赖,这个bean最终会被放置到一级缓存中,并从二、三级缓存中删除。
存入二级缓存的时机
- Bean A的提前引用首次被需要:在处理循环依赖时,如果发现某个Bean需要被提前引用(为了注入到另一个依赖它的Bean中),这时ObjectFactory会被用来创建Bean的提前引用,且这个提前引用会被存入二级缓存。
- 减少对三级缓存的直接访问:将提前引用存入二级缓存,可以减少后续对三级缓存中ObjectFactory的访问次数,因为直接从二级缓存获取Bean的引用更为快捷。
为什么单单二级缓存不够
理论上来说,如果bean在实例化后就存入二级缓存,初始化后再存入一级缓存,是否就只需要二级缓存就能解决循环依赖问题?
由于实际情况和需求往往更为复杂,Spring采用三级缓存机制是为了提供更灵活和更全面的解决方案。
三级缓存机制的必要性
三级缓存机制扩展了对不同种类循环依赖问题的解决能力,具体来说:
1. 提供更灵活的暴露机制
三级缓存中的ObjectFactory
允许Spring在Bean实例化后但在初始化之前阶段时创建一个早期引用。这种机制提供了额外的灵活性,因为它允许AOP
等代理机制在Bean的生命周期的更早阶段被引入,从而确保代理同样可以参与到依赖注入过程中。
2. 避免代理问题
如果一个Bean需要被代理,而代理需要在Bean完全初始化完成前完成,对于这种情况,三级缓存中的ObjectFactory
可以在适当的时刻创建完整的代理对象,并提前暴露它。这解决了在二级缓存中仅仅存储简单引用所无法解决的代理问题。
如果没有三级缓存,直接将Bean放入二级缓存可能带来一些操作上的限制和问题:
- 代理创建时机问题:在某些框架使用下,如
AOP
,Bean代理的创建可能会引起复杂性,仅靠提前暴露原生Bean实例而不考虑代理,将可能导致依赖注入的失效或是不完整。 - 复杂依赖管理:在复杂的依赖关系树中,简化的二级缓存机制较难精确控制容器何时可以提前暴露某些Bean引用,从而增加了出现意外结果的风险。
Bean的初始化阶段的详细步骤
- Bean实例化:首先,Spring创建Bean的实例。
- 依赖注入:在Bean的实例化之后,Spring容器会将定义的依赖注入到Bean中。这包括对字段、设置器方法(setter方法)或构造器参数的注入。
- BeanNameAware、BeanClassLoaderAware、BeanFactoryAware等Aware接口的方法执行:如果Bean实现了Spring的
Aware
接口,这一阶段将会调用相应的方法,使Bean能够访问容器的某些资源,如Bean的ID、加载它的类装载器、所属的BeanFactory等。 - BeanPostProcessors的前置处理:之后,Spring容器将调用已注册的
BeanPostProcessor
实现的postProcessBeforeInitialization
方法,进行前置处理。这一步允许进一步修改新创建的Bean实例。 - @PostConstruct注解方法的执行:接下来,如果Bean中有用
@PostConstruct
注解标注的方法,这些方法将被调用。通常,@PostConstruct注释用于执行所有初始化工作或者启动后的设置。 - InitializingBean的afterPropertiesSet方法的执行:如果Bean实现了
InitializingBean
接口,则调用其afterPropertiesSet
方法。此方法允许Bean知道所有必要的属性都已经设置完毕。 - 自定义的init方法的执行:如果在Bean定义中指定了一个init方法,那么该方法将被调用。这可以通过
@Bean
注解的initMethod
属性或者在XML配置中使用init-method
属性来指定。 - BeanPostProcessors的后置处理:最后,Spring容器将调用已注册的
BeanPostProcessor
实现的postProcessAfterInitialization
方法,进行后置处理。这一步允许进一步修改Bean实例,一些AOP代理就是在这个时候添加到对应的Bean上的。
转载自:https://juejin.cn/post/7383201957747687474