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

Spring 源码解读:解决循环依赖的三种方式


引言

在复杂的应用开发中,循环依赖是一个常见的问题。简单来说,循环依赖是指两个或多个Bean之间互相依赖,导致程序无法正常实例化这些Bean。Spring容器通过依赖注入(DI)来管理Bean的创建与生命周期,并在遇到循环依赖时采取了多种策略进行处理。本篇文章将带你实现三种解决循环依赖的方式,包括构造函数注入、Setter注入和ObjectFactory方式,并对比Spring的循环依赖处理机制,帮助你理解不同的处理方式及其应用场景。

什么是循环依赖

循环依赖(Circular Dependency)是指两个或多个Bean互相依赖,导致它们无法正常实例化。通常情况下,Spring通过依赖注入来管理Bean的生命周期,当遇到循环依赖时,Spring必须采取额外的策略来解决这个问题。

常见的循环依赖类型

  1. 构造函数循环依赖

    • 两个Bean通过构造函数相互依赖,导致它们在实例化时陷入循环。
  2. Setter方法循环依赖

    • 两个Bean通过Setter方法相互依赖,Spring可以通过提前暴露Bean的部分引用来解决这个问题。
  3. ObjectFactory方式

    • 通过ObjectFactory延迟依赖注入,避免在Bean创建时立即解决依赖,允许Bean先部分创建。

手动实现三种循环依赖的解决方式

为了更好地理解循环依赖的处理方式,我们将手动实现三种常见的解决策略:构造函数注入、Setter方法注入和ObjectFactory方式。

实现构造函数注入的循环依赖

构造函数注入的循环依赖比较难解决,因为它要求所有的依赖在实例化时就已经准备好。下面我们通过一个示例展示构造函数循环依赖的问题。

示例代码
public class ServiceA {
    private ServiceB serviceB;

    // 使用构造函数注入ServiceB
    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB; // ServiceA依赖于ServiceB
    }

    public void doSomething() {
        System.out.println("ServiceA is doing something...");
    }
}

public class ServiceB {
    private ServiceA serviceA;

    // 使用构造函数注入ServiceA
    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA; // ServiceB依赖于ServiceA
    }

    public void doSomething() {
        System.out.println("ServiceB is doing something...");
    }
}

public class ConstructorInjectionTest {
    public static void main(String[] args) {
        // 手动实例化构造函数依赖会导致循环依赖问题
        // ServiceA serviceA = new ServiceA(new ServiceB(serviceA)); // 这会导致无限递归,无法解决
    }
}

问题描述

  • 在上面的代码中,ServiceA通过构造函数依赖ServiceB,同时ServiceB也通过构造函数依赖ServiceA。由于构造函数注入要求在实例化时就提供依赖,因此出现了循环依赖问题,导致递归创建的错误。

解决方法:Setter方法注入

Setter方法注入的循环依赖比构造函数注入要容易解决,因为Spring可以提前暴露部分未完成的Bean引用,在Bean完全实例化前进行部分注入。

示例代码
public class ServiceA {
    private ServiceB serviceB;

    // 通过Setter方法注入ServiceB
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB; // ServiceA依赖于ServiceB
    }

    public void doSomething() {
        System.out.println("ServiceA is doing something...");
    }
}

public class ServiceB {
    private ServiceA serviceA;

    // 通过Setter方法注入ServiceA
    public void setServiceA(ServiceA serviceA) {
        this.serviceA = serviceA; // ServiceB依赖于ServiceA
    }

    public void doSomething() {
        System.out.println("ServiceB is doing something...");
    }
}

public class SetterInjectionTest {
    public static void main(String[] args) {
        // 创建实例
        ServiceA serviceA = new ServiceA();
        ServiceB serviceB = new ServiceB();

        // 通过Setter方法解决循环依赖
        serviceA.setServiceB(serviceB); // ServiceA依赖ServiceB,延迟注入
        serviceB.setServiceA(serviceA); // ServiceB依赖ServiceA,延迟注入

        // 测试方法调用
        serviceA.doSomething(); // 输出:ServiceA is doing something...
        serviceB.doSomething(); // 输出:ServiceB is doing something...
    }
}

解决思路

  • 使用Setter方法注入解决循环依赖问题,通过先创建空的Bean对象,再通过Setter方法进行依赖注入。Spring能够在部分Bean完成初始化时将其暴露给其他Bean,从而解决循环依赖问题。

解决方法:ObjectFactory延迟注入

ObjectFactory方式通过延迟注入来解决循环依赖问题。ObjectFactory允许Spring在需要时才创建依赖对象,从而避免在Bean初始化时立即解决依赖。

示例代码
import org.springframework.beans.factory.ObjectFactory;

public class ServiceA {
    private ObjectFactory<ServiceB> serviceBFactory;

    // 使用ObjectFactory进行延迟注入
    public ServiceA(ObjectFactory<ServiceB> serviceBFactory) {
        this.serviceBFactory = serviceBFactory; // 延迟注入ServiceB
    }

    public void doSomething() {
        System.out.println("ServiceA is doing something...");
        serviceBFactory.getObject().doSomething(); // 当需要时才获取ServiceB
    }
}

public class ServiceB {
    private ObjectFactory<ServiceA> serviceAFactory;

    // 使用ObjectFactory进行延迟注入
    public ServiceB(ObjectFactory<ServiceA> serviceAFactory) {
        this.serviceAFactory = serviceAFactory; // 延迟注入ServiceA
    }

    public void doSomething() {
        System.out.println("ServiceB is doing something...");
        serviceAFactory.getObject().doSomething(); // 当需要时才获取ServiceA
    }
}

public class ObjectFactoryInjectionTest {
    public static void main(String[] args) {
        // 使用ObjectFactory进行延迟注入,解决循环依赖问题
        ObjectFactory<ServiceA> serviceAFactory = () -> new ServiceA(() -> new ServiceB(serviceAFactory));
        ObjectFactory<ServiceB> serviceBFactory = () -> new ServiceB(serviceAFactory);

        // 创建ServiceA和ServiceB的实例
        ServiceA serviceA = serviceAFactory.getObject();
        ServiceB serviceB = serviceBFactory.getObject();

        // 测试方法调用
        serviceA.doSomething(); // 输出:ServiceA is doing something... ServiceB is doing something...
        serviceB.doSomething(); // 输出:ServiceB is doing something... ServiceA is doing something...
    }
}

解决思路

  • ObjectFactory方式通过延迟创建对象的方式解决循环依赖问题。ObjectFactory允许在依赖实际使用时才实例化依赖对象,从而打破了Bean初始化时的相互依赖问题。

类图与流程图

为了更好地理解三种解决方式的工作原理,我们提供了类图和流程图。

类图
ServiceA
-ServiceB serviceB
+doSomething()
ServiceB
-ServiceA serviceA
+doSomething()
ObjectFactory<T>
+T getObject()

解释

  • ServiceAServiceB互相依赖,通过ObjectFactory延迟实例化来避免直接的循环依赖。
流程图
解决循环依赖
依赖ServiceB
ServiceB实例化
依赖ServiceA

解释

  • 使用ObjectFactory延迟注入的方式,ServiceAServiceB可以在需要时获取对方的实例,避免了直接的循环依赖问题。

Spring中的循环依赖处理机制

在Spring中,循环依赖的处理是通过多种策略实现的,主要包括三级缓存机制。Spring容器通过提前暴露未完成的Bean实例、延迟依赖注入等方式解决循环依赖。

Spring的三级缓存机制

Spring容器内部通过三级缓存来处理循环依赖问题:
1

. 一级缓存:存储完全初始化好的单例Bean。
2. 二级缓存:存储部分实例化、但尚未完成初始化的Bean。
3. 三级缓存:存储可以通过代理对象获取的Bean,用于解决复杂的循环依赖场景。

当Spring遇到循环依赖时,能够通过三级缓存中的代理对象提前暴露未完成的Bean,从而解决依赖问题。

源码解析:Spring如何解决循环依赖

Spring在DefaultSingletonBeanRegistry类中,通过addSingletonFactory()方法提前暴露创建中的Bean来解决循环依赖。

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory); // 将未完成的Bean放入三级缓存
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

对比分析:手动实现与Spring的区别

  • Spring的实现

    • Spring采用了三级缓存的机制,通过提前暴露Bean的引用来解决循环依赖。它能够处理复杂的依赖关系和代理对象。
    • 三级缓存是Spring容器处理循环依赖的核心策略,通过将未完成的Bean放入三级缓存,可以提前暴露这些Bean的引用。
  • 手动实现

    • 我们的手动实现展示了三种常见的循环依赖解决方式,虽然能够处理基本的循环依赖问题,但缺乏Spring的高级功能,如三级缓存和生命周期管理。

总结

通过实现构造函数注入、Setter方法注入和ObjectFactory延迟注入三种解决循环依赖的方式,你应该对循环依赖的解决策略有了更深入的理解。在Spring框架中,三级缓存机制是其处理循环依赖的核心策略,它能够灵活地解决复杂的依赖关系。理解这些机制,将帮助你在实际开发中更好地管理Bean的生命周期,并在需要时解决循环依赖问题。


互动与思考

你是否在项目中遇到过循环依赖问题?你更倾向于使用哪种解决策略?欢迎在评论区分享你的经验与见解!


如果你觉得这篇文章对你有帮助,请别忘了:

  • 点赞
  • 收藏 📁
  • 关注 👀

让我们一起深入学习Spring框架,成为更优秀的开发者!



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

相关文章:

  • Python Web 开发的路径管理艺术:FastAPI 项目中的最佳实践与问题解析20241119
  • 微众银行申请专利:不过分丢失泛用能力,提高语音大模型对困难样本学习效率
  • Python世界:力扣题110,平衡二叉树判别,easy
  • 14. 乘法口诀挑战赛
  • python画图|3D errorbars基础教程
  • 【ACM独立出版|高校主办】第四届信号处理与通信技术国际学术会议(SPCT 2024)
  • 自动化立体仓库定义及使用范围
  • 服务器托管是什么意思?优缺点详解
  • ElasticSearch7.8下载、安装教程
  • 游戏工作室搬砖用的多开就是动态代理ip吗
  • vue-router + el-menu
  • 【HarmonyOS】云开发-云数据库(二)
  • 「iOS」折叠cell
  • 【STM32】VOFA+上位机 PID调参
  • 论文翻译:arxiv-2022 Ignore Previous Prompt: Attack Techniques For Language Models
  • Flood Fill算法
  • 如何判断IP地址是否异常?
  • android14多屏幕帧率刷新率统计显示开发及成果展示
  • 如何打造一款成功的游戏
  • OPENAIGC开发者大赛企业组银奖 | Gambit-AI智能合同审核助手
  • 测试开发基础——软件测试中的bug
  • MME-RealWorld:您的多模态大型语言模型能挑战高分辨率的真实世界场景吗?这些场景对人类来说都非常困难!
  • OpenCV结构分析与形状描述符(9)检测轮廓相对于其凸包的凹陷缺陷函数convexityDefects()的使用
  • 【重学 MySQL】二十、运算符的优先级
  • 相亲交友程序系统开发产品分析
  • 小样本目标定位:Few-shot Object Localization