当前位置: 首页 > article >正文

01-Spring中的循环依赖以及它是如何解决的

为介绍跟总结Spring的庞大知识体系,本栏将从一个循环依赖展开,一点点撕开Spring的整个家族。

01-Spring中的循环依赖以及它是如何解决的

一、什么是循环依赖

循环依赖是指在依赖注入过程中,Bean 之间相互依赖,形成一个闭环。常见的场景如下:

@Component
public class A {
    @Autowired
    private B b;
}

@Component
public class B {
    @Autowired
    private A a;
}

在上述代码中,A 依赖于 B,B 又依赖于 A,形成了循环依赖。

Q:这种 @Autowired 方式在什么时候出现循环依赖?
A:使用 @Autowired 进行 setter 注入 或 属性注入 时,如果两个 Bean 相互依赖,就会在 Spring Bean 的初始化阶段 出现循环依赖。
Spring 在实例化 Bean 时,会先创建 Bean 的实例对象,然后再进行依赖注入。如果 BeanA 依赖 BeanB,BeanB 又依赖 BeanA,且没有特殊处理机制,就会导致循环依赖。
不过,Spring 为了解决这种问题,引入了 三级缓存机制,能够在一定条件下解决 Setter 注入和属性注入导致的循环依赖。

Q: 什么方式不会出现循环依赖呢?
A:构造器注入是目前推荐的依赖注入方式。
使用构造器注入时,如果存在循环依赖,Spring 无法通过三级缓存解决,因为 Bean 尚未创建实例,导致 BeanCurrentlyInCreationException 异常。
构造器注入的好处:
依赖关系明确:在类加载时即可检测到循环依赖问题,避免隐藏的异常。
有助于单元测试:通过构造器注入,可以更方便地传递 mock 对象。
符合SOLID原则:特别是依赖倒置原则(Dependency Inversion Principle)。


二、Spring 中的循环依赖场景

Spring 中的循环依赖主要发生在以下三种场景:

  1. 构造器循环依赖
  2. Setter 方法循环依赖
  3. @PostConstruct 或其他初始化方法的循环依赖

1. 构造器循环依赖

构造器循环依赖无法被 Spring 直接解决,因为在对象实例化前需要先完成构造函数,导致无法获取到对象。

2. Setter 方法循环依赖

Spring 使用三级缓存机制解决 Setter 方法循环依赖。


三、Spring 解决循环依赖的核心机制

Spring 通过 DefaultSingletonBeanRegistry 中的三级缓存解决了循环依赖。

三级缓存的结构:

  • singletonObjects:一级缓存,存储完全初始化好的单例 Bean。
  • earlySingletonObjects:二级缓存,存储已经实例化但尚未完成依赖注入的 Bean。
  • singletonFactories:三级缓存,存储 Bean 的工厂,用于提前暴露一个对象的引用,解决循环依赖。

Q: 三级缓存的结构为什么有的用 ConcurrentHashMap,有的用 HashMap?
A:
1、线程安全性要求不同 :一级缓存中的 Bean 是完全初始化完成的,需要支持多线程并发访问,因此使用 ConcurrentHashMap。二级和三级缓存中的 Bean 主要在对象创建阶段使用,生命周期短且通常只会在单线程中访问,因此使用 HashMap 更高效。
2、性能考虑:HashMap 的读写性能通常优于 ConcurrentHashMap,尤其在不涉及多线程访问时。
使用 HashMap 能够减少锁的竞争,提升性能。
3、缓存的阶段性作用:singletonFactories 在 Bean 创建的早期阶段使用,一旦 Bean 初始化完成,相关对象会从二级或三级缓存中移除。earlySingletonObjects 也是临时使用,不需要长时间维护线程安全。
Q:为什么回用到三级缓存,而不是二级缓存?
A: 我认为最本质的原因是因为spring中是有aop之后的代理对象的,这种情况下,要想只有二级缓存是难以完成对象的真正初始化跟实例化的。所以三级缓存也是一个工厂的。

相关类解析:

  1. DefaultSingletonBeanRegistry
    • 管理 Spring 中的三级缓存,提供 getSingleton() 方法来获取单例 Bean。
  2. AbstractAutowireCapableBeanFactory
    • 负责创建和初始化 Bean,同时在创建阶段处理循环依赖。
  3. BeanFactory
    • Spring 的顶层接口,提供获取 Bean 的方法。

源码解析:

AbstractAutowireCapableBeanFactory#doCreateBean 方法中,Spring 采用以下步骤解决循环依赖:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
        throws BeanCreationException {
    // 第一步:创建 Bean 实例
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
    final Object bean = instanceWrapper.getWrappedInstance();

    // 第二步:将创建的 Bean 放入三级缓存,提前暴露引用
    if (mbd.isSingleton()) {
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // 第三步:填充属性
    populateBean(beanName, mbd, instanceWrapper);

    // 第四步:初始化 Bean
    Object exposedObject = initializeBean(beanName, bean, mbd);

    return exposedObject;
}

关键方法解析:

  1. addSingletonFactory()

    • 将一个工厂对象加入到 singletonFactories 中,用于创建早期对象引用。
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        synchronized (this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
            }
        }
    }
    
  2. getEarlyBeanReference()

    • 如果需要,Spring 会通过工厂获取一个代理对象,供后续使用。
    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        return bean;
    }
    
  3. getSingleton()

    • 获取 Bean 时,Spring 会依次从一级缓存、二级缓存和三级缓存中查找。
    public Object getSingleton(String beanName) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }
    

四、Spring 为何需要三级缓存

  1. 防止重复创建对象:二级缓存 earlySingletonObjects 确保同一 Bean 不会被重复实例化。
  2. 解决循环依赖:通过提前暴露 Bean 的引用,即使对象未完全初始化,也可以被其他 Bean 使用。
  3. 代理对象支持:三级缓存 singletonFactories 提供了动态代理的支持,例如 AOP 代理。

五、总结

  1. Spring 使用 三级缓存 解决了 Setter 方法的循环依赖。
  2. 构造器循环依赖 无法通过缓存机制解决,需要通过重构代码或者使用 @Lazy 解决。
  3. Spring 的 getEarlyBeanReference() 方法为 AOP 提供了机会,确保代理对象可以参与循环依赖。

六、最佳实践

  • 尽量避免复杂的循环依赖。
  • 使用 @Lazy 注解打破循环依赖。
  • 使用接口和抽象类解耦。

本文基于博主的实际工作经验,与大家共同探讨 Spring 中的循环依赖问题。如有疑问或更深入的见解,欢迎在评论区交流,共同学习进步。

原文地址:https://blog.csdn.net/qq_17589751/article/details/146373155
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/594802.html

相关文章:

  • 健康医疗:动态代理 IP 保障医疗数据安全,提升远程医疗服务质量!
  • LeetCode算法题(Go语言实现)_03
  • 详解string类+迭代器
  • 【记录】使用 Docker 搭建 MongoDB 分布
  • 嵌入式软件开发--面试总结
  • 工具层handle_response
  • Facebook和心理健康:社交媒体对我们情绪的影响
  • 如何在ubuntu上安装pig数据库
  • 快速了解以太坊多种代币标准
  • 前端进化:从焦虑到机遇的范式转型之路
  • navicat 创建Oracle连接报错:ora28040 没有匹配的验证协议
  • TCP/UDP传输过程
  • AI辅助的逆向分析
  • MutableList 和 ArrayList 区别
  • 画出ConcurrentHashMap 1.8的put流程图,记住CAS和synchronized的结合
  • 【Oracle资源损坏类故障】:详细了解坏块
  • 视觉Transformer架构的前沿优化技术与高效部署
  • 微服务》》Kubernetes (K8S) 集群配置网络》》Calico
  • Java 中 LinkedHashMap 的底层数据结构及相关分析
  • 甘特图dhtmlx-gantt 一行多任务