开发者

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、缓存介绍

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

                    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对象。

                    如下图所示:

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

                    1.2、联系

                    三级缓存是为了解决循环依赖问题而引入的,当出现循环依赖时,首先会从一级缓存中查找 Bean,如果找不到,会尝试从二级缓存中查找,如果还是找不到,会从三级缓存中获取ObjectFactory并创建 Bean 的早期引用,放入二级缓存中。

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

                    二级缓存中的 Bean 是从三级缓存中创建出来的早期引用,这些 Bean 还没有完成属性注入和初始化操作。

                    一级缓存中的 Bean 是最终可用的 Bean,这些 Bean 已经完成了所有的属性注入和初始化操作。

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

                    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,分别相互依赖。

                    如下图所示:

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

                    1、A对象缓存查询

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

                    依次查询一级、二级、三级查询,由于开始,三种缓存里面分别没有A对象的缓存。

                    2、A对象创建对象

                    通过反射,将a对象放到三级缓存里面。

                    3、A对象属性填充

                    在填充属性的时候,会发现A对象需要依赖B对象,因此重复刚才A对象的操作步骤。

                    如下图所示:

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

                    1、B对象缓存查询

                    先从缓存查询B对象的三级缓存,由于首次查询,b对象的一级、二级、三级缓存均为空。

                    2、B对象创建对象

                    然后,创建B对象,将b对象的引用放到三级缓存里,此时三级缓存里面同时存放了A、B对象的引用。

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

                    3、B对象属性填充

                    在进行B对象填充属性的时候,发现依赖于A。

                    B依赖A的缓存查询

                    然后重复执行缓存查询的操作,此时由于前面A、B三级缓存分别在创建对象的时候,都放在了三级里面。

                    B依赖A的二级缓存

                    因此通过将三级缓存,放入二级缓存里,同时删除三级的a对象。

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

                    4、B对象初始化

                    然后在进行b对象的初始化,此时@postConstruct就在这里执行,完成B对象的初始化。

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

                    5、B对象缓存转移

                    如下图所示:

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

                    将b对象的三级缓存、二级缓存移除掉,同时写入一级缓存里面。

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

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

                    4、A对象初始化

                    5、A对象缓存转移

                    删除A对象的三级缓存、二级缓存、同时写入到1级缓存。

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

                    总结:A、B循环依赖的流程图如下所示:

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

                    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)。

                    0

                    上一篇:

                    下一篇:

                    精彩评论

                    暂无评论...
                    验证码 换一张
                    取 消

                    最新开发

                    开发排行榜