Spring容器三级缓存的使用及说明
目录
- 1、缓存介绍
- 1.1、缓存分类
- 1.一级缓存(singletonObjects)
- 2.二级缓存(earlySingletonObjects)
- 3.三级缓存(singletonFactories)
- 1.2、联系
- 2、循环依赖
- 2.1、循环依赖场景
- 2.2、解决流程图示
- 1、A对象缓存查询
- 2、A对象创建对象
- 3、A对象属性填充
- 2.3、代码示例
- 2.4、代码解释
- 3、三级缓存
- 1、原因
- 4、使用范围
- 5、建议
- 6、扩展
- 总结
Spring容器为了解决循环依赖问题,引入了三级缓存系统。这与Hibernate/MyBATis中的缓存概念不同,是Spring特有的设计。
1、缓存介绍
1.1、缓存分类
1.一级缓存(singletonObjects)
用途:
存放完全初始化好的单例 Bean,这些 Bean 已经完成了所有的属性注入和初始化操作,可以直接使用。
数据结构:
Map<String,Object>,键为 Bean 的名称,值为对应的 Bean 实例。
2.二级缓存(earlySingletonObjects)
用途:
存放早期曝光的 Bean 实例,这些 Bean 已经被创建,但还没有完成属性注入和初始化操作。当需要解决循环依赖时,可以从这个缓存中获取 Bean 的早期引用。
数据结构:
Map<String,Object>,键为 Bean 的名称,值为对应的 Bean 实例。
3.三级缓存(singletonFactories)
用途:
存放ObjectFactory对象,这些对象可以用来创建 Bean 的早期引用。也可以处理AOP代理等特殊情况
数据结构:
Map<String,ObjectFactory<?>>,键为 Bean 的名称,值为对应的ObjectFactory对象。
如下图所示:
1.2、联系
三级缓存是为了解决循环依赖问题而引入的,当出现循环依赖时,首先会从一级缓存中查找 Bean,如果找不到,会尝试从二级缓存中查找,如果还是找不到,会从三级缓存中获取ObjectFactory并创建 Bean 的早期引用,放入二级缓存中。
二级缓存中的 Bean 是从三级缓存中创建出来的早期引用,这些 Bean 还没有完成属性注入和初始化操作。
一级缓存中的 Bean 是最终可用的 Bean,这些 Bean 已经完成了所有的属性注入和初始化操作。
2、循环依赖
关于以下文章介绍的是Spring单例bean的循环依赖解决方案。
2.1、循环依赖场景
@Service public class ServiceA { @Autowired private ServiceB serviceB; } @Service public class ServiceB { @Autowired private ServiceA serviceA; }
2.2、解决流程图示
有对象A和对象B,分别相互依赖。
如下图所示:
1、A对象缓存查询
依次查询一级、二级、三级查询,由于开始,三种缓存里面分别没有A对象的缓存。
2、A对象创建对象
通过反射,将a对象放到三级缓存里面。
3、A对象属性填充
在填充属性的时候,会发现A对象需要依赖B对象,因此重复刚才A对象的操作步骤。
如下图所示:
1、B对象缓存查询
先从缓存查询B对象的三级缓存,由于首次查询,b对象的一级、二级、三级缓存均为空。
2、B对象创建对象
然后,创建B对象,将b对象的引用放到三级缓存里,此时三级缓存里面同时存放了A、B对象的引用。
3、B对象属性填充
在进行B对象填充属性的时候,发现依赖于A。
B依赖A的缓存查询
然后重复执行缓存查询的操作,此时由于前面A、B三级缓存分别在创建对象的时候,都放在了三级里面。
B依赖A的二级缓存
因此通过将三级缓存,放入二级缓存里,同时删除三级的a对象。
4、B对象初始化
然后在进行b对象的初始化,此时@postConstruct就在这里执行,完成B对象的初始化。
5、B对象缓存转移
如下图所示:
将b对象的三级缓存、二级缓存移除掉,同时写入一级缓存里面。
4、A对象初始化
5、A对象缓存转移
删除A对象的三级缓存、二级缓存、同时写入到1级缓存。
总结:A、B循环依赖的流程图如下所示:
2.3、代码示例
下面是一个简单的 Java 代码示例,模拟 Spring 容器的三级缓存机制来解决循环依赖问题:
import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; // 模拟 Bean 定义 class BeanDefinition { private String beanName; private Class<?> beanClass; public BeanDefinition(String beanName, Class<?> beanClass) { this.beanName = beanName; this.beanClass = beanClass; } public String getBeanName() { return beanName; } public Class<?> getBeanClass() { return beanClass; } }
模拟Spring容器
// 模拟 Spring 容器 class BeanFactory { // 一级缓存 private final Map<String, Object> singletonObjects = new HashMap<>(); // 二级缓存 private final Map<String, Object> earlySingletonObjects = new HashMap<>(); // 三级缓存 private final Map<String, Supplier<Object>> singletonFactories = new HashMap<>(); // Bean 定义集合 private final Map<String, BeanDefinition> beanDefinitions = new HashMap<>(); // 注册 Bean 定义 public void registerBeanDefinition(BeanDefinition beanDefinition) { beanDefinitions.put(beanDefinition.getBeanName(), beanDefinition); } // 获取 Bean public Object getBean(String beanName) { //http://www.devze.com 先从一级缓存中查找 Object singletonObject = singletonObjects.get(beanName); if (singletonObject != null) { return singletonObject; } // 再从二级缓存中查找 singletonObject = earlySingletonObjects.get(beanName); if (singletonObject != null) { return singletonObject; } // 从三级缓存中查找 Supplier<Object> singletophpnFactory = singletonFactories.get(beanName); if (singletonFactory != null) { // 创建早期引用 singletonObject = singletonFactory.get(); // 将早期引用放入二级缓存 earlySingletonObjects.put(beanName, singletonObject); // 从三级缓存中移除 singletonFactories.remove(beanName); return singletonObject; } // 创建 Bean BeanDefinition beanDefinition = beanDefinitions.get(beanName); if (beanDefinition != null) { try { // 创建 Bean 实例 Object bean = beanDefinition.getBeanClass().newInstance(); // 将 Bean 工厂放入三级缓存 singletonFactories.put(beanName, () -> bean); // 模拟属性注入,可能会出现循环依赖 // 这里简单处理,不进行实际的属性注入 // ... // 属性注入完成后,将 Bean 放入一级缓存 singletonObjects.put(beanName, bean); // 从二级缓存中移除 earlySingletonObjects.remove(beanName); return bean; } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } } return null; } }
测试类:
// 测试类 public class Main { public static void main(String[] args) { BeanFactory beanFactory = new BeanFactory(); // 注册 Bean 定义 beanFactory.registerBeanDefinition(new BeanDefinition("beanA", BeanA.class)); beanFactory.registerBeanDefinition(new BeanDefinition("beanB", BeanB.class)); // 获取 Bean Object beanA = beanFactopythonry.getBean("beanA"); Object beanB = beanFactory.getBean("beanB"); System.out.println("BeanA: " + beanA); System.out.println("BeanB: " + beanB); } }
BeanA类:
// 示例 Bean A class BeanA { private BeanB beanB; public BeanA() { } public BeanB getBeanB() { return beanB; } public void setBeanB(BeanB beanB) { this.beanB = beanB; } }
BeanB类:
// 示例 Bean B class BeanB { private BeanA beanA; public BeanB() { } public BeanA getBeanA() { return beanA; } public void setBeanA(BeanA beanA) { this.beanA = beanA; } }
2.4、代码解释
- BeanDefinition类用于存储 Bean 的定义信息,包括 Bean 的名称和类。
- BeanFactory类模拟了 Spring 容器的功能,包含了三级缓存和 Bean 定义集合。
- getBean方法用于获取 Bean 实例,首先从一级缓存中查找,如果找不到,再从二级缓存中查找,如果还是找不到,从三级缓存中获取ObjectFactory并创建 Bean 的早期引用,放入二级缓存中。
- Main类用于测试BeanFactory的功能,注册 Bean 定义并获取 Bean 实例。
流程:
+---------------------+ | 一级缓存 (singletonObjects) | | 存放完全初始化的 Bean | +---------------------+ ^ | | +---------------------+ | 二级缓存 (earlySingletonObjects) | | 存放早期曝光的 Bean | +---------------------+ ^ | | +---------------------+ | 三级缓存 (singletonFactories) | | 存放 ObjectFactory 对象 | +---------------------+
这个图展示了 Spring 容器的三级缓存结构,
1. 一级缓存位于最上层,存放完全初始化的 Bean;
2.二级缓存位于中间,存放早期曝光的 Bean;
3.三级缓存位于最下层,存放ObjectFactory对象。
4.当需要解决循环依赖时,会从三级缓存中获取ObjectFactory并创建 Bean 的早期引用,放入二级缓存中,最终将完全初始化的 Bean 放入一级缓存中。
3、三级缓存
1、原因
为什么要使用三级缓存,二级不可以吗?
尽管二级缓存能解决部分循环依赖问题,但 Spring 引入三级缓存主要是为了支持 AOP(面向切面编程)。
若 Bean 需要 AOP 代理(如事务管理),代理对象需要在依赖注入时动态生成。三级缓存中的ObjectFactory可以延迟生成代理对象,确保依赖注入时使用代理后的实例。
具体原因如下:
1、支持 AOP 代理:
在 Spring 中,当一个 Bean 需要进行 AOP 代理时,代理对象和原始 Bean 对象可能是不同的。
如果只使用二级缓存,在早期曝光时放入的是原始 Bean 实例,那么在后续的属性注入过程中,其他 Bean 引用的就是原始 Bean 而非代理对象,这会导致 AOP 失效。
而三级缓存(singletonFactories)存放的是ObjectFactory,可以在需要时通过ObjectFactory的getObject方法来创建代理对象,保证在出现循环依赖时,其他 Bean 引用的是正确的代理对象。
2、延迟创建代理对象:
使用三级编程缓存可以实现延迟创建代理对象。只有在真正出现循环依赖且需要获取早期引用时,才会调用ObjectFactory的getObject方法来创建代理对象,避免了不必要的代理对象创建,提高了性能。
综上所述,虽然二级缓存能解决部分循环依赖问题,但为了支持 AOP 代理和延迟创建代理对象,Spring 引入了三级缓存机制。
4、使用范围
Spring只能解决单例Bean通过Setter/字段注入的循环依赖。
1.构造器注入的循环依赖
@Component public class A { private B b; public A(B b) { this.b = b; } // 构造器注入 } @Component public class B { private A a; public B(A a) { this.a = a; } // 构造器注入 }
原因:构GIUyrhS造器注入需要先完成Bean的实例化,无法提前暴露半成品。
2.多例Bean(@Scope("prototype"))
Spring不缓存多例Bean,因此无法解决循环依赖。
5、建议
- 尽量避免循环依赖:代码结构不合理时容易引发循环依赖,建议通过重构解决。
- 优先使用Setter/字段注入:构造器注入虽然安全,但无法解决循环依赖。
- 利用@Lazy延迟加载:对某个Bean添加
@Lazy
,让Spring延迟注入,打破循环。
@Component public class A { @Lazy // 延迟注入B @Autowired private B b; }
6、扩展
1、多个AOP的顺序怎么定
通过**@Order注解来设置增强类优先级:这个值越小优先级越高**!
@Order(3) public class UserProxy {} @Order(1) public class PersonProxy {}
2、如何让两个Bean按顺序加载
- 1、使用 @DependsOn
@Component @DependsOn({"beanB", "beanC"}) // 确保beanB和beanC先加载 public class BeanA { // ... } @Component public class BeanB { // ... } @Component public class BeanC { // ... }
- 2、实现PriorityOrdered或Ordered接口
对于实现了特定接口的Bean,可以控制它们的初始化顺序:
@Component public class BeanOne implements PriorityOrdered { @Override public int getOrder() { return 1; // 数字越小优先级越高 } } @Component public class BeanTwo implements Ordered { @Override public int getOrder() { return 2; } }
- 3、使用@Order注解
适用于某些特定场景,如拦截器、AOP切面等的顺序控制
@Component @Order(1) public class FirstBean { // ... } @Component @Order(2) public class SecondBean { // ... }
- 4、让后加载的类依赖先加载的类
@Component public class A { @Autowire private B b; }
总结
Spring通过三级缓存+提前暴露半成品对象解决循环依赖问题,核心目的是处理AOP代理对象的唯一性。虽然理论上两级缓存可以解决部分场景,但三级缓存是Spring设计上的必要选择。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。
精彩评论