likes
comments
collection
share

一文搞懂 Spring 的三级缓存模型

作者站长头像
站长
· 阅读数 20

为什么需要三级缓存?

众所周知,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的初始化阶段的详细步骤

  1. Bean实例化:首先,Spring创建Bean的实例。
  2. 依赖注入:在Bean的实例化之后,Spring容器会将定义的依赖注入到Bean中。这包括对字段、设置器方法(setter方法)或构造器参数的注入。
  3. BeanNameAware、BeanClassLoaderAware、BeanFactoryAware等Aware接口的方法执行:如果Bean实现了Spring的Aware接口,这一阶段将会调用相应的方法,使Bean能够访问容器的某些资源,如Bean的ID、加载它的类装载器、所属的BeanFactory等。
  4. BeanPostProcessors的前置处理:之后,Spring容器将调用已注册的BeanPostProcessor实现的postProcessBeforeInitialization方法,进行前置处理。这一步允许进一步修改新创建的Bean实例。
  5. @PostConstruct注解方法的执行:接下来,如果Bean中有用@PostConstruct注解标注的方法,这些方法将被调用。通常,@PostConstruct注释用于执行所有初始化工作或者启动后的设置。
  6. InitializingBean的afterPropertiesSet方法的执行:如果Bean实现了InitializingBean接口,则调用其afterPropertiesSet方法。此方法允许Bean知道所有必要的属性都已经设置完毕。
  7. 自定义的init方法的执行:如果在Bean定义中指定了一个init方法,那么该方法将被调用。这可以通过@Bean注解的initMethod属性或者在XML配置中使用init-method属性来指定。
  8. BeanPostProcessors的后置处理:最后,Spring容器将调用已注册的BeanPostProcessor实现的postProcessAfterInitialization方法,进行后置处理。这一步允许进一步修改Bean实例,一些AOP代理就是在这个时候添加到对应的Bean上的。
转载自:https://juejin.cn/post/7383201957747687474
评论
请登录