java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring容器三级缓存

Spring容器三级缓存的使用及说明

作者:找不到、了

这篇文章主要介绍了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、代码解释

流程:

+---------------------+
| 一级缓存 (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、建议

@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按顺序加载

@Component
@DependsOn({"beanB", "beanC"})  // 确保beanB和beanC先加载
public class BeanA {
    // ...
}

@Component
public class BeanB {
    // ...
}

@Component
public class BeanC {
    // ...
}

对于实现了特定接口的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;
    }
}

适用于某些特定场景,如拦截器、AOP切面等的顺序控制

@Component
@Order(1)
public class FirstBean {
    // ...
}

@Component
@Order(2)
public class SecondBean {
    // ...
}
@Component
public class A {
    @Autowire
    private B b;
}

总结

Spring通过三级缓存+提前暴露半成品对象解决循环依赖问题,核心目的是处理AOP代理对象的唯一性。虽然理论上两级缓存可以解决部分场景,但三级缓存是Spring设计上的必要选择。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文