Spring容器三级缓存的使用及说明
作者:找不到、了
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) {
// 先从一级缓存中查找
Object singletonObject = singletonObjects.get(beanName);
if (singletonObject != null) {
return singletonObject;
}
// 再从二级缓存中查找
singletonObject = earlySingletonObjects.get(beanName);
if (singletonObject != null) {
return singletonObject;
}
// 从三级缓存中查找
Supplier<Object> singletonFactory = 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 = beanFactory.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; } // 构造器注入
}原因:构造器注入需要先完成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设计上的必要选择。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
