欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 焦点 > Spring 源码解读:解决循环依赖的三种方式

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

2024/11/8 19:35:56 来源:https://blog.csdn.net/qq_40254606/article/details/142035090  浏览:    关键词: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;// 使用构造函数注入ServiceBpublic ServiceA(ServiceB serviceB) {this.serviceB = serviceB; // ServiceA依赖于ServiceB}public void doSomething() {System.out.println("ServiceA is doing something...");}
}public class ServiceB {private ServiceA serviceA;// 使用构造函数注入ServiceApublic 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方法注入ServiceBpublic 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方法注入ServiceApublic 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框架,成为更优秀的开发者!


版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com