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


相关推荐
晴子呀7 分钟前
Spring底层原理大致脉络
java·后端·spring
andrew_121930 分钟前
腾讯 IEG 游戏前沿技术 二面复盘
后端·sql·面试
这孩子叫逆37 分钟前
35. MyBatis中的缓存失效机制是如何工作的?
java·spring·mybatis
Lill_bin40 分钟前
JVM内部结构解析
jvm·后端·spring cloud·微服务·云原生·ribbon
evanYang_1 小时前
Spring Boot配置文件敏感信息加密
spring boot·后端·oracle
customer082 小时前
【开源免费】基于SpringBoot+Vue.JS网上超市系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
ChinaRainbowSea2 小时前
十八,Spring Boot 整合 MyBatis-Plus 的详细配置
java·数据库·spring boot·spring·mybatis·web
计算机学姐2 小时前
基于python+django+vue的医院预约挂号系统
开发语言·vue.js·后端·python·mysql·django·tornado
我爱李星璇2 小时前
Spring Boot 常用注解
java·spring boot·后端
孟林洁3 小时前
xxl-job、Quartz、power-job、elastic-job对比选型
后端