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方式。

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

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

示例代码
java 复制代码
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完全实例化前进行部分注入。

示例代码
java 复制代码
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初始化时立即解决依赖。

示例代码
java 复制代码
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。

  1. 二级缓存 :存储部分实例化、但尚未完成初始化的Bean。

  2. 三级缓存:存储可以通过代理对象获取的Bean,用于解决复杂的循环依赖场景。

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

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

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

java 复制代码
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框架,成为更优秀的开发者!


相关推荐
先睡2 小时前
Redis的缓存击穿和缓存雪崩
redis·spring·缓存
Piper蛋窝5 小时前
深入 Go 语言垃圾回收:从原理到内建类型 Slice、Map 的陷阱以及为何需要 strings.Builder
后端·go
Bug退退退1236 小时前
RabbitMQ 高级特性之死信队列
java·分布式·spring·rabbitmq
六毛的毛7 小时前
Springboot开发常见注解一览
java·spring boot·后端
AntBlack8 小时前
拖了五个月 ,不当韭菜体验版算是正式发布了
前端·后端·python
31535669138 小时前
一个简单的脚本,让pdf开启夜间模式
前端·后端
uzong8 小时前
curl案例讲解
后端
一只叫煤球的猫9 小时前
真实事故复盘:Redis分布式锁居然失效了?公司十年老程序员踩的坑
java·redis·后端
大鸡腿同学10 小时前
身弱武修法:玄之又玄,奇妙之门
后端
轻语呢喃11 小时前
JavaScript :字符串模板——优雅编程的基石
前端·javascript·后端