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

 更新时间:2023年04月09日 08:31:18   作者:Jay....  
循环依赖是指在Spring Boot 应用程序中,两个或多个类之间存在彼此依赖的情况,形成一个循环依赖链。这篇文章主要介绍了SpringBoot循环依赖的症状和解决方法

什么是循环依赖?

循环依赖是指在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,避免了循环依赖的问题。

到此这篇关于Spring Boot循环依赖的症状和解决方案的文章就介绍到这了,更多相关解决Spring Boot循环依赖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java实现平滑加权轮询算法之降权和提权详解

    Java实现平滑加权轮询算法之降权和提权详解

    所有负载均衡的场景几乎都会用到这个平滑加权轮询算法,下面这篇文章主要给大家介绍了关于Java实现平滑加权轮询算法之降权和提权的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-04-04
  • Spring Boot和Kotlin的无缝整合与完美交融

    Spring Boot和Kotlin的无缝整合与完美交融

    这篇文章主要给大家介绍了关于Spring Boot和Kotlin的无缝整合与完美交融的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-06-06
  • thymeleaf中前后端数据交互方法汇总

    thymeleaf中前后端数据交互方法汇总

    这篇文章主要介绍了thymeleaf中前后端数据交互小结,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2022-07-07
  • Netty分布式编码器写buffer队列逻辑剖析

    Netty分布式编码器写buffer队列逻辑剖析

    这篇文章主要介绍了Netty分布式编码器写buffer队列逻辑剖析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-03-03
  • 解决Spring Boot 在localhost域奇怪的404问题(Mac book pro)

    解决Spring Boot 在localhost域奇怪的404问题(Mac book pro)

    这篇文章主要介绍了解决Spring Boot 在localhost域奇怪的404问题(Mac book pro),需要的朋友可以参考下
    2017-09-09
  • springboot整合druid及多数据源配置的demo

    springboot整合druid及多数据源配置的demo

    这篇文章主要介绍了springboot整合druid及多数据源配置的demo,本篇主要分两部分 ①springboot整合druid的代码配置,以及druid的监控页面演示;②对实际场景中多数据源的配置使用进行讲解,需要的朋友可以参考下
    2024-01-01
  • 关于mybatis callSettersOnNulls 配置解析

    关于mybatis callSettersOnNulls 配置解析

    这篇文章主要介绍了关于mybatis callSettersOnNulls 配置,非常不错,具有一定的参考借鉴价值 ,需要的朋友可以参考下
    2018-06-06
  • java操作时间方式基础教程demo

    java操作时间方式基础教程demo

    这篇文章主要为大家介绍了java操作时间方式demo基础教程示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • Java如何限制IP访问页面

    Java如何限制IP访问页面

    这篇文章主要介绍了Java如何限制IP访问页面,帮助大家完成需求,实现白名单,感兴趣的朋友可以了解下
    2020-09-09
  • Java架构师的5大基本能力你知道吗

    Java架构师的5大基本能力你知道吗

    这篇文章主要为大家介绍了Java架构师的基本能力,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助<BR>
    2022-01-01

最新评论