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

Spring 之循环依赖

        Spring 框架是一个流行的Java应用程序框架,它提供了许多强大的功能,如依赖注入和面向切面编程。然而在使用 Spring 框架时,我们可能会遇到循环依赖的问题。

        这种情况发生在两个或多个 Bean 之间相互依赖的情况下,其中一个 Bean 依赖于另一个 Bean,而另一个 Bean 又依赖于第一个 Bean。在这种情况下,Spring 框架需要解决循环依赖的问题,否则应用程序可能会出现死锁或其他错误。

        本文将探讨 Spring 框架是如何解决循环依赖的问题,以及它是如何工作的。我们将分析 Spring 框架的源代码,并提供一些示例来说明 Spring 框架如何解决循环依赖的问题。

解决循环依赖的原理

        在 Spring 框架中,当两个或多个 Bean 之间相互依赖时, Spring 框架会创建一个代理对象,该代理对象负责管理这些 Bean 之间的依赖关系。这个代理对象被称为“early proxy”或“exposed proxy”。

        当一个 Bean 需要访问另一个 Bean 时, Spring 框架会通过代理对象来获取该 Bean。这个代理对象负责保证 Bean 的实例化顺序,确保每个 Bean 都只被实例化一次,并且在所有依赖关系被满足之前,不会暴露任何未实例化的 Bean 。

Spring 框架解决循环依赖的过程如下

  1. 当 Spring 框架启动时,它会扫描应用程序中的所有 Bean,并将它们注册到一个 Bean Factory中。
  2. 当一个 Bean 被实例化时Spring 框架会检查它是否有任何依赖关系。
  3. 如果 Bean 有依赖关系,则 Spring 框架会检查这些依赖关系是否已经被创建。
  4. 如果依赖关系已经被创建,则 Spring 框架会将依赖关系注入到 Bean 中,并返回该 Bean 的实例。
  5. 如果依赖关系还没有被创建,则 Spring 框架会创建一个代理对象(通过 getEarlyBeanReference 方法),并将该代理对象暴露给该 Bean。
  6. 当依赖关系被创建时,Spring 框架会使用代理对象来获取依赖关系,并将依赖关系注入到 Bean 中。
  7. 当所有依赖关系都被满足时,Spring 框架会返回该 Bean 的实例。

源码解析

为了更好地理解 Spring 框架如何解决循环依赖的问题,我们将分析 Spring 框架的源代码。下面是一个简单的示例,演示了 Spring 框架如何解决循环依赖的问题。

public class A {
    private B b;
    public A() {}
    public void setB(B b) {
        this.b = b;
    }
}

public class B {
    private A a;
    public B() {}
    public void setA(A a) {
        this.a = a;
    }
}

@Configuration
public class AppConfig {
    @Bean
    public A a() {
        return new A();
    }

    @Bean
    public B b() {
        return new B();
    }
}

在这个示例中,类A依赖于类B,而类B又依赖于类A。因此,这个示例展示了一个循环依赖的情况。

当应用程序启动时,Spring 框架会扫描所有的 Bean ,并将它们注册到一个BeanFactory中。

当 BeanFactory创建 Bean 时,它会检查 Bean 是否有任何依赖关系。

在这个示例中,当 BeanFactory 创建 A 和 B 时,它会检查它们之间的依赖关系。由于 A 依赖于 B,而 B 又依赖于 A,因此存在循环依赖的问题。

为了解决这个问题,Spring 框架会创建一个代理对象,该代理对象负责管理A和B之间的依赖关系。在这个示例中,当 BeanFactory 创建 A 时,它会创建一个代理对象,并将该代理对象暴露给A。当BeanFactory创建B时,它会创建一个代理对象,并将该代理对象暴露给 B。这样,A和B就可以通过代理对象来访问彼此,而不会出现循环依赖的问题。

下面是 Spring 框架解决循环依赖的源码示例:

首先 Spring 框架会从缓存中获取需要的 bean:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

上面代码中,框架分别从 singletonObjects、earlySingletonObjects、singletonFactories 中获取需要的实例,如果获取不到,就就行创建,在创建的过程中如果发现需要处理循环依赖,就会调用下面方法获取代理对象:

private Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                if (exposedObject == null) {
                    return exposedObject;
                }
            }
        }
    }
    return exposedObject;
}

        在这个示例中,getEarlyBeanReference() 方法是 Spring 框架用来获取代理对象的方法。该方法首先检查 Bean 是否为合成的 Bean ,然后检查该 Bean 是否有任何实例化后的 Bean 后处理器。如果 Bean 有实例化后的 Bean 后处理器,则 Spring 框架会使用这些 Bean 后处理器来获取代理对象。

spring.png

总结

        在本文中,我们探讨了 Spring 框架是如何解决循环依赖的问题的。我们分析了 Spring 框架的源代码,并提供了一些示例来说明 Spring 框架如何解决循环依赖的问题。总之, Spring 框架通过创建代理对象来解决循环依赖的问题,该代理对象负责管理 Bean 之间的依赖关系,并确保每个 Bean 都只被实例化一次。


http://www.kler.cn/a/8127.html

相关文章:

  • 内网穿透ubuntu20 docker coplar
  • 我用Cursor+DeepSeek做了个飞书文档一键同步插件,免费使用!
  • Java 中压缩图片并应用 EXIF 旋转信息
  • js事件机制详解
  • 前端开放性技术面试—面试题
  • 新能源汽车锂离子电池各参数的时间序列关系
  • 【计算机网络-网络层】路由选择协议
  • Redission分布式锁
  • vue3脚手架Vite
  • ChatGPT常用prompts汇总
  • springboot感受优化06
  • 使用for循环对ArrayList在遍历时删除存在的问题
  • Linux如何在Ubuntu系统服务器上安装 Jenkins?【详细教程】
  • 初识设计模式 - 策略模式
  • 基于SpringBoot+Vue的家政平台
  • 命名空间和程序集
  • Mysql 的B+树索引 和HASH索引
  • 160. 相交链表 ——【Leetcode每日一题】
  • CSS基础知识,必须掌握!!!
  • Android:启动流程
  • 城乡供水一体化管控平台-农村供水监管平台-乡村振兴
  • C++实现多线程
  • 聚类问题的算法总结
  • Nuxt中使用vuex管理组件信息通讯
  • 图像分割技术及经典实例分割网络Mask R-CNN(含基于Keras Python源码定义)
  • 一文看懂多模态大型语言模型GPT-4