Spring Boot循环依赖的症状和解决方案

简介: Spring Boot循环依赖的症状和解决方案

🏆 文章目标:了解Spring Boot 循环依赖的原因,并学习下解决方案

🍀 Spring Boot循环依赖的症状和解决方案

✅ 创作者:Jay…

🎉 个人主页:Jay的个人主页

🍁 展望:若本篇讲解内容帮助到您,请帮忙点个赞,再动动您的小手关注下吧,您的支持是我继续写作的最大动力,谢谢。🙏

什么是循环依赖?

循环依赖是指在Spring Boot 应用程序中,两个或多个类之间存在彼此依赖的情况,形成一个循环依赖链。在这种情况下,当一个类在初始化时需要另一个类的实例,而另一个类又需要第一个类的实例时,就会出现循环依赖问题。这会导致应用程序无法正确地初始化和运行,因为Spring Boot 无法处理这种循环依赖关系。

问题及症状

在2.6.0之前,Spring Boot会自动处理循环依赖的问题。2.6.0及之后的版本会默认检查循环依赖,存在该问题则会报错。

ComponentA类注入ComponentB类,ComponentB类注入ComponentA类,就会发生循环依赖的问题。

ComponentA

import org.springframework.stereotype.Service;
import javax.annotation.Resource;
 
@Service
public class ComponentA {
    @Resource
    private ComponentB componentB;
 
}

ComponentB

import org.springframework.stereotype.Service;
import javax.annotation.Resource;
 
@Service
public class ComponentB {
 
    @Resource
    private ComponentA componentA;
 
}

错误

现在,2.6.0 这个版本已经默认禁止 Bean 之间的循环引用, 则基于上面的代码,会报错:

***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
|  componentA
↑     ↓
|  componentB
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

解决方法

循环依赖是指两个或更多的组件之间存在着互相依赖的关系。在Spring Boot应用程序中,循环依赖通常是由以下几种情况引起的:

  • 构造函数循环依赖:两个或更多的组件在它们的构造函数中互相依赖。
  • 属性循环依赖:两个或更多的组件在它们的属性中互相依赖。
  • 方法循环依赖:两个或更多的组件在它们的方法中互相依赖。

Spring Boot提供了一些解决循环依赖的方法:

  1. 构造函数注入:在构造函数中注入依赖项,而不是在属性中注入。
  2. Setter注入:使用setter方法注入依赖项,而不是在构造函数中注入。
  3. 延迟注入:使用@Lazy注解延迟加载依赖项。
  4. @Autowired注解的required属性:将required属性设置为false,以避免出现循环依赖问题。
  5. @DependsOn注解:使用@DependsOn注解指定依赖项的加载顺序,以避免出现循环依赖问题

构造器注入的案例

假设有以下两个类:

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;
    }
}

通过构造函数注入可以避免循环依赖,改造后的代码如下:

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

这样,在创建 A 实例时,只需要将 B 实例传递给 A 的构造函数即可,不需要再通过 setter 方法将 B 实例注入到 A 中。同理,在创建 B 实例时,只需要将 A 实例传递给 B 的构造函数即可,不需要再通过 setter 方法将 A 实例注入到 B 中。这样可以避免循环依赖。

延迟注入的案例

假设有如下情景:

类A依赖于类B,同时类B也依赖于类A。这样就形成了循环依赖。

为了解决这个问题,可以使用@Lazy注解,将类A或类B中的其中一个延迟加载。

例如,我们可以在类A中使用@Lazy注解,将类A延迟加载,这样在启动应用程序时,Spring容器不会立即加载类A,而是在需要使用类A的时候才会进行加载。这样就避免了循环依赖的问题。

示例代码如下:

@Component
public class A {
    private final B b;
    public A(@Lazy B b) {
        this.b = b;
    }
    //...
}
@Component
public class B {
    private final A a;
    public B(A a) {
        this.a = a;
    }
    //...
}

在类A中,我们使用了@Lazy注解,将类B延迟加载。这样在启动应用程序时,Spring容器不会立即加载类B,而是在需要使用类B的时候才会进行加载。

这样就避免了类A和类B之间的循环依赖问题。

接口隔离的案例

假设有两个类A和B,它们之间存在循环依赖:

public class A {
    private final B b;
    public A(B b) {
        this.b = b;
    }
}
public class B {
    private final A a;
    public B(A a) {
        this.a = a;
    }
}

这时候,如果直接在Spring Boot中注入A和B,就会出现循环依赖的问题。为了解决这个问题,可以使用接口隔离。

首先,定义一个接口,包含A和B类中需要使用的方法:

public interface Service {
    void doSomething();
}

然后,在A和B类中分别注入Service接口:

public class A {
    private final Service service;
    public A(Service service) {
        this.service = service;
    }
}
public class B {
    private final Service service;
    public B(Service service) {
        this.service = service;
    }
}

最后,在Spring Boot中注入Service实现类:

@Service
public class ServiceImpl implements Service {
    private final A a;
    private final B b;
    public ServiceImpl(A a, B b) {
        this.a = a;
        this.b = b;
    }
    @Override
    public void doSomething() {
        // ...
    }
}

通过这种方式,A和B类不再直接依赖于彼此,而是依赖于同一个接口。同时,Spring Boot也能够正确地注入A、B和ServiceImpl,避免了循环依赖的问题。

关注公众号:熊猫Jay字节之旅,了解更多 AI 技巧 ~


相关文章
|
28天前
|
Dubbo Java 应用服务中间件
Spring Cloud Dubbo:微服务通信的高效解决方案
【10月更文挑战第15天】随着信息技术的发展,微服务架构成为企业应用开发的主流。Spring Cloud Dubbo结合了Dubbo的高性能RPC和Spring Cloud的生态系统,提供高效、稳定的微服务通信解决方案。它支持多种通信协议,具备服务注册与发现、负载均衡及容错机制,简化了服务调用的复杂性,使开发者能更专注于业务逻辑的实现。
52 2
|
3天前
|
缓存 架构师 Java
图解 Spring 循环依赖,一文吃透!
Spring 循环依赖如何解决,是大厂面试高频,本文详细解析,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
图解 Spring 循环依赖,一文吃透!
|
2月前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
188 24
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
1月前
|
缓存 Java Spring
源码解读:Spring如何解决构造器注入的循环依赖?
本文详细探讨了Spring框架中的循环依赖问题,包括构造器注入和字段注入两种情况,并重点分析了构造器注入循环依赖的解决方案。文章通过具体示例展示了循环依赖的错误信息及常见场景,提出了三种解决方法:重构代码、使用字段依赖注入以及使用`@Lazy`注解。其中,`@Lazy`注解通过延迟初始化和动态代理机制有效解决了循环依赖问题。作者建议优先使用`@Lazy`注解,并提供了详细的源码解析和调试截图,帮助读者深入理解其实现机制。
29 1
|
1月前
|
Java 关系型数据库 开发工具
idea创建不了spring2.X版本,无法使用JDK8,最低支持JDK17 , 如何用idea创建spring2.X版本,使用JDK8解决方案
本文提供了解决方案,如何在IDEA中创建Spring 2.X版本的项目并使用JDK8,尽管Spring 2.X已停止维护且IDEA不再直接支持,通过修改pom.xml或使用阿里云的国内源来创建项目。
73 0
idea创建不了spring2.X版本,无法使用JDK8,最低支持JDK17 , 如何用idea创建spring2.X版本,使用JDK8解决方案
|
1月前
|
Java Maven Spring
SpringBoot项目创建失败或无法启动,启动报错时的常见问题及解决方案
文章列举了在IDEA中创建Spring Boot项目时可能遇到的常见问题及其解决方案,如项目不被识别为Maven项目、依赖未找到或报红、JDK版本不一致和POM文件中的Jar包下载失败等问题。
174 0
SpringBoot项目创建失败或无法启动,启动报错时的常见问题及解决方案
|
2月前
|
缓存 Java Spring
手写Spring Ioc 循环依赖底层源码剖析
在Spring框架中,IoC(控制反转)是一个核心特性,它通过依赖注入(DI)实现了对象间的解耦。然而,在实际开发中,循环依赖是一个常见的问题。
40 4
|
2月前
|
Java 开发工具 对象存储
简化配置管理:Spring Cloud Config与Netflix OSS中的动态配置解决方案
简化配置管理:Spring Cloud Config与Netflix OSS中的动态配置解决方案
46 2
|
2月前
|
前端开发 Java Spring
【非降版本解决】高版本Spring boot Swagger 报错解决方案
【非降版本解决】高版本Spring boot Swagger 报错解决方案
|
1月前
|
Java Maven Spring
用Spring导致的无法运行Java文件的问题的解决方案
本文提供了解决在IntelliJ IDEA社区版中使用Spring Initializr插件创建Spring项目后,Java文件无法运行的问题的方法,主要是通过加载Maven项目来解决。
69 0