核心内容拆解 三级缓存
Spring 三级缓存是 Spring 框架中用于管理 Bean 对象的缓存机制。它由三个不同的缓存区域组成,分别是 singletonObjects、earlySingletonObjects 和 singletonFactories。
- singletonObjects(一级缓存,成品对象):单例对象缓存区域存储已经完全初始化后的单例对象。当第一次使用 getBean () 方法获取 bean 时,Spring 会尝试从这个缓存区获取对象,如果能够找到,则直接返回该对象实例。
- earlySingletonObjects(二级缓存,代理对象,特殊的成品对象):如果一个单例对象需要引用另一个单例对象,但后者尚未被完全初始化,那么容器将创建一个代理对象(Proxy Object),并将其放到 earlySingletonObjects 中。这个代理对象会暴露与实际对象相同的接口,并且能够对其进行一些基本操作,但是它还没有被完全初始化。
- singletonFactories(三级缓存,半成品对象也是工厂对象):单例工厂缓存区域存储创建 bean 实例的 ObjectFactory。在 Bean 依赖关系的创建过程中,如果 A 依赖 B,B 又依赖 A,那么在创建 A 和 B 的过程中就会出现循环依赖的问题。Spring 就是通过提前暴露一个未完成初始化的 Bean 来解决这个问题的。
提示
对于 spring 设计没有完全理解的同学可能很难明白以上的话,还是需要用代码加一说明,以下文章会议代码的方式全程讲清楚
# 代码介绍
首先 Spring 在初始化的时候会先把所有 Bean 加载到 Beandefinition 的缓存中,后续所有对 Bean 的创建都是从 Beandefinition 中获取详细可以看 Spring 源码阅读(一) ,然后再进行 Bean 生命周期的过程,创建好 Bean 的实例放入到 singletonObjects 缓存中,但是代码的第一步都是从获取开始,只有获取不到我才创建
public Object getBean(String name) throws BeansException {
return doGetBean(name, null);
}
public Object getBean(String name, Object... args) throws BeansException {
return doGetBean(name, args);
}
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
return (T) getBean(name);
}
protected <T> T doGetBean(final String name, final Object[] args) {
// 从缓存中获取实例
Object sharedInstance = getSingleton(name);
if (sharedInstance != null) {
// 如果实现了 FactoryBean,则需要调用 FactoryBean#getObject
return (T) getObjectForBeanInstance(sharedInstance, name);
}
// 从BeanDefinition列表中获取对象
BeanDefinition beanDefinition = getBeanDefinition(name);
// Bean实例的创建过程
Object bean = doCreateBean(name, beanDefinition, args);
// 如果实现了 FactoryBean,则需要调用 FactoryBean#getObject
return (T) getObjectForBeanInstance(bean, name);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
可以看到 getSingleton 方法就是获取单例,一旦有则直接返回,但第一次肯定是没有的,详细看下他的获取方式
public Object getSingleton(String beanName) {
// 从一级缓存中获取
Object singletonObject = singletonObjects.get(beanName);
if (null == singletonObject) {
singletonObject = earlySingletonObjects.get(beanName);
// 判断二级缓存中是否有对象,这个对象就是代理对象,因为只有代理对象才会放到三级缓存中
if (null == singletonObject) {
ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 把三级缓存中的代理对象中的真实对象获取出来,放入二级缓存中
earlySingletonObjects.put(beanName, singletonObject);
singletonFactories.remove(beanName);
}
}
}
return singletonObject;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
没有获取到实例则从 beanDefinition 中获取 Bean 的定义信息调用 doCreateBean 创建 Bean 的实例
protected Object doCreateBean(String beanName, BeanDefinition beanDefinition, Object[] args) {
Object bean = null;
try {
// 实例化 Bean
bean = createBeanInstance(beanDefinition, beanName, args);
// 处理循环依赖,将实例化后的Bean对象提前放入缓存中暴露出来
if (beanDefinition.isSingleton()) {
Object finalBean = bean;
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, beanDefinition, finalBean));
}
// 是否需要继续进行后续的属性填充
boolean continueWithPropertyPopulation = applyBeanPostProcessorsAfterInstantiation(beanName, bean);
if (!continueWithPropertyPopulation) {
return bean;
}
// 在设置 Bean 属性之前,允许 BeanPostProcessor 修改属性值(注解属性填充)
applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition);
// 给 Bean 填充属性(xml属性填充)
applyPropertyValues(beanName, bean, beanDefinition);
// 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法
bean = initializeBean(beanName, bean, beanDefinition);
} catch (Exception e) {
throw new BeansException("Instantiation of bean failed", e);
}
// 注册实现了 DisposableBean 接口的 Bean 对象
registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);
// 判断 SCOPE_SINGLETON、SCOPE_PROTOTYPE
Object exposedObject = bean;
if (beanDefinition.isSingleton()) {
// 获取代理对象
exposedObject = getSingleton(beanName);
registerSingleton(beanName, exposedObject);
}
return exposedObject;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
其中 applyPropertyValues 这个方法,对要创建的实例进行属性填充的时候会进入循环依赖的问题,一旦遇到是 BeanReference 类型的,则会调用 getBean 方法去获取实例,该方法又会回到上面的方法。
protected void applyPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) {
try {
// 这里获取到 bean 信息里的属性有哪些,也就是一个对象有哪些属性,并循环赋值到所创建实例的属性中去
PropertyValues propertyValues = beanDefinition.getPropertyValues();
for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {
String name = propertyValue.getName();
Object value = propertyValue.getValue();
if (value instanceof BeanReference) {
// A 依赖 B,获取 B 的实例化
BeanReference beanReference = (BeanReference) value;
value = getBean(beanReference.getBeanName());
}
// 类型转换,不是侧重点可以不看
else {
Class<?> sourceType = value.getClass();
Class<?> targetType = (Class<?>) TypeUtil.getFieldType(bean.getClass(), name);
ConversionService conversionService = getConversionService();
if (conversionService != null) {
if (conversionService.canConvert(sourceType, targetType)) {
value = conversionService.convert(value, targetType);
}
}
}
// 反射设置属性填充
BeanUtil.setFieldValue(bean, name, value);
}
} catch (Exception e) {
throw new BeansException("Error setting property values:" + beanName + " message:" + e);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
当一个实例的创建基本完成,他会把自己添加到一级缓存对象,做为一个成品提供给其他方法使用。如下方法就是添加到一级缓存的过程,其中 getSingleton 在获取三级缓存数据到二级缓存的时候会执行 singletonFactory.getObject (); 这是一个方法,会去执行 getEarlyBeanReference (beanName, beanDefinition, finalBean) 方法,该方法会得到工厂方法里面的一个代理对象。然后再把代理对象存到一级缓存。至此 Bean 的实例化到缓存的过程就结束。
// 判断 SCOPE_SINGLETON、SCOPE_PROTOTYPE
Object exposedObject = bean;
if (beanDefinition.isSingleton()) {
// 把三级缓存对象转换为二级缓存对象
exposedObject = getSingleton(beanName);
// 把二级缓存对象转换为一级缓存对象
registerSingleton(beanName, exposedObject);
}
// getSingleton 的部分代码实现
ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
if (singletonFactory != null) {
// 获取代理对象
singletonObject = singletonFactory.getObject();
// 把三级缓存中的代理对象中的真实对象获取出来,放入二级缓存中
earlySingletonObjects.put(beanName, singletonObject);
singletonFactories.remove(beanName);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 流程介绍
有了完整的概念后,我们可以考虑循环依赖的存在,当 A 依赖 B,B 又依赖 A,那么解决依赖的过程又是如何做的,可以看下图:
- 会先在 getBean 方法里面找 A,通过一二三级缓存中寻找,如果找到则直接返回
- 没找到就去创建 A 并放入 singletonFactories 三级缓存,会对 A 的属性值进行填充,此时 A 的属性依赖了 B,就要调用 getBean 找 B
- 通过在 getBean 的 一二三级缓存查找,如果找到了直接返回
- 没找到就去创建 B 并放入 singletonFactories 三级缓存,会对 B 的属性值进行填充,此时 B 的属性依赖了 A,就要调用 getBean 找 A
- 此时的 A 已经在三级缓存,可以在三级缓存中找到,找到后会生成 A 代理对象 放入 earlySingletonObjects 二级缓存并返回这个代理对象
- B 有了属性 A 的代理对象,此时 B 所有属性填充完毕后,就要把 B 添加到一级缓存,但此时的 B 在三级缓存,会先把三级缓存对象生成代理后放入到 earlySingletonObjects 二级缓存,再由二级缓存把对象放到 singletonObjects 一级缓存对象
- 此时的 B 已经放到一级缓存对象了,并结束了 B 的创建流程,所以会返回到第 2 步,A 就有了 B 的实例,A 的属性填充完毕后,就要把 A 添加到一级缓存,但此时的 A 已经在二级缓存,所以就可以直接放入到 singletonObjects 一级缓存
现在我们知道,按照 Spring 框架的设计,用于解决循环依赖需要用到三个缓存,这三个缓存分别存放了 singletonObjects 成品对象、singletonFactories 半成品对象 (未填充属性值)、earlySingletonObjects 代理对象,分阶段存放对象内容,来解决循环依赖问题。
那么,这里我们需要知道一个核心的原理,就是用于解决循环依赖就必须是三级缓存呢,二级行吗?一级可以不?其实都能解决,只不过 Spring 框架的实现要保证几个事情,如只有一级缓存处理流程没法拆分,复杂度也会增加,同时半成品对象可能会有空指针异常。而将半成品与成品对象分开,处理起来也更加优雅、简单、易扩展。另外 Spring 的两大特性中不仅有 IOC 还有 AOP,也就是基于字节码增强后的方法,该存放到哪,而三级缓存最主要,要解决的循环依赖就是对 AOP 的处理,但如果把 AOP 代理对象的创建提前,那么二级缓存也一样可以解决。但是,这就违背了 Spring 创建对象的原则,Spring 更喜欢把所有的普通 Bean 都初始化完成,在处理代理对象的初始化。
一个单个缓存解决循环依赖的例子
public class ForRelyOn {
static Map<String,Object> singletonObjects = new HashMap<>();
public static void main(String[] args) throws Exception {
System.out.println(getBean(A.class).getB());
System.out.println(getBean(B.class).getA());
}
private static <T> T getBean(Class<T> beanClass) throws Exception {
String beanName = beanClass.getSimpleName().toLowerCase();
if (singletonObjects.containsKey(beanName)) {
return (T) singletonObjects.get(beanName);
}
// 实例化对象入缓存0
Object obj = beanClass.newInstance();
singletonObjects.put(beanName, obj);
// 属性填充补全对象
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Class<?> fieldClass = field.getType();
String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
field.set(obj, singletonObjects.containsKey(fieldBeanName) ? singletonObjects.get(fieldBeanName) : getBean(fieldClass));
field.setAccessible(false);
}
return (T) obj;
}
static class A{
private B b;
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
static class B{
private A a;
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
提示
以上都是我们通过 单例的 set 注入方式来解决循环依赖,在 spring 中有多种多样的注入情况,那会带来什么样的情况呢?
# 不同的循环依赖问题
# set 循环依赖
在多例 set 的循环依赖中,只有多例和多例循环依赖会出现报错,报错信息如下:
Error creating bean with name 'b': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'a': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'b': Requested bean is currently in creation: Is there an unresolvable circular reference?
多例和单例的循环依赖不会有问题,如下是一个单例和多例的循环依赖代码:
@Component
public class A {
@Resource
private B b;
public void getb() {
System.out.println(b);
}
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class B {
@Resource
private A a;
public void geta(){
System.out.println(a);
}
}
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(AdminApplication.class, args);
B b = run.getBean(B.class);
B b1 = run.getBean(B.class);
B b2 = run.getBean(B.class);
A a = run.getBean(A.class);
A a1 = run.getBean(A.class);
A a2 = run.getBean(A.class);
System.out.println(b);
System.out.println(b1);
System.out.println(b2);
a.getb();
a1.getb();
a2.getb();
}
// 结果
// com.wt.admin.controller.B@1e5e2e06
// com.wt.admin.controller.B@26c1f3eb
// com.wt.admin.controller.B@79982bcc
// com.wt.admin.controller.B@16b2d182
// com.wt.admin.controller.B@16b2d182
// com.wt.admin.controller.B@16b2d182
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 构造器 循环依赖
但在 构造器 循环依赖的注入中,因为在构造器注入方式下,需要先创建一个 Bean 对象,然后再将其他 Bean 注入该对象中。但是,如果两个 Bean 都互相依赖,那么就会出现无法创建任何一个 Bean 的情况。因此,Spring 在这种情况下会抛出异常以避免程序出现不可预测的错误。
@Configuration
public class Config {
@Bean
public A a(B b){
return new A(b);
}
@Bean
public B b(A a){
return new B(a);
}
}
// 报错
// The dependencies of some of the beans in the application context form a cycle:
// ┌─────┐
// | a defined in class path resource [com/wt/admin/controller/Config.class]
// ↑ ↓
// | b defined in class path resource [com/wt/admin/controller/Config.class]
// └─────┘
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
解决这种问题,可以通过在方法中添加 @Lazy 注解,只能加到方法里,不能加到 @Bean 的上下位置,否则依然会报循环依赖;这种方式尽可能的被定义为 @Lazy 的 Bean 在第一次被使用的时候在去进行实例化。
构造器也存在多例和单例的问题,如果你是多例依赖循环,会报错,如下
// 多例
@Configuration
public class Config {
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean
public A a(B b){
return new A(b);
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean
public B b(A a){
return new B(a);
}
}
// 报错信息
// Error creating bean with name 'b' defined in class path resource [com/wt/admin/controller/Config.class]: Unsatisfied dependency expressed through method 'b' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a' defined in class path resource [com/wt/admin/controller/Config.class]: Unsatisfied dependency expressed through method 'a' parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'b': Requested bean is currently in creation: Is there an unresolvable circular reference?
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
如果你是多例和单例循环依赖,也会报错,但这里和我们使用注解进行多例和单例的循环依赖测试结果就有所不同了
@Configuration
public class Config {
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean
public A a(B b){
return new A(b);
}
@Bean
public B b(A a){
return new B(a);
}
}
// 报错信息
// The dependencies of some of the beans in the application context form a cycle:
// ┌─────┐
// | b defined in class path resource [com/wt/admin/controller/Config.class]
// ↑ ↓
// | a defined in class path resource [com/wt/admin/controller/Config.class]
// └─────┘
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
那么构造器和 set 注入的方式在多例和单例的结论如下:
- 单例循环依赖:构造器的方式会报错(可以用 @Lazy 解决),set 注入的方式不会报错
- 多例循环依赖:构造器的方式会报错,set 注入的方式会报错,两者都是调用对象时才报
- 单例和多例循环依赖:构造器的方式会报错(可以用 @Lazy 解决),set 注入的方式不会报错
# @DependsOn
@DependsOn 注解可以定义在类和⽅法上,意思是我这个组件要依赖于另⼀个组件,也就是说被依赖的组件会⽐该组件先注册到 IOC 容器中。如下案例,因为两个都要先于,所以造成了循环依赖
@DependsOn("b")
@Component
public class A {
@Resource
private B b;
public void getb() {
System.out.println(b);
}
}
@DependsOn("a")
@Component
public class B {
@Resource
private A a;
public void geta(){
System.out.println(a);
}
}
// 报错
// Error creating bean with name 'b' defined in file [D:\workspace\luckyDraw\java\target\classes\com\wt\admin\controller\B.class]: Circular depends-on relationship between 'b' and 'a'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21