Bug解决篇:Spring Boot 循环依赖问题的解决方案

前言:大家在学习Springboot的时是否会遇到这种问题呢?

Thedependenciesof some of the beans in the application context form a****cycle

bash 复制代码
//详细报错信息
Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  aiCodeGeneratorServiceFactory
↑     ↓
|  chatHistoryServiceImpl
↑     ↓
|  appServiceImpl
↑     ↓
|  aiCodeGeneratorFacade
└─────┘


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.

下面讲解解决循环依赖的几种常见解决方案,可以解决99%的这种问题(尤其是最后一种方案)

方案 推荐度 优点 缺点 适用场景
重构设计 ⭐⭐⭐⭐⭐ 从根本上解决问题,降低耦合,提高可维护性 重构成本高,可能引入新的复杂性 循环依赖逻辑可以被抽象和分离的场景
@Lazy 注解 ⭐⭐⭐⭐ 简单、快速、侵入性低 治标不治本,可能隐藏初始化错误 快速修复,循环依赖的Bean在启动初期不被使用
延迟注入 ⭐⭐⭐ 能解决@Lazy无法处理的复杂依赖 代码侵入性强,破坏依赖注入纯粹性 构造函数初始化复杂,无法使用@Lazy
允许循环依赖 ⭐⭐ 最简单,无需修改代码 掩盖设计问题,未来可能失效,有潜在风险 仅作为临时应急方案

**1.**重构设计,打破循环依赖

从根本上讲,循环依赖是设计问题的症状,而非问题本身。因此,最彻底、最推荐的解决方案就是重构代码,消除这种不合理的依赖关系。这通常意味着需要对服务的职责进行更清晰的划分。

当重构代码的成本过高,推荐使用下面的解决方案

2.使用 @Lazy 注解延迟加载

将其中一个添加**@Lazy注解**,这样有一个延迟注入,从而打破了循环引用的环。

java 复制代码
//字段注入
@Service
public class ServiceA {
    @Autowired
    @Lazy
    private ServiceB serviceB;
}


//构造函数注入
@Service
public class ServiceA {
    private final ServiceB serviceB;

    @Autowired
    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

3. 延迟注入 (@PostConstruct)

使用 @PostConstruct 注解

java 复制代码
@Service
public class ServiceA {
    private ServiceB serviceB;

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        // 在所有Bean实例化完成后,从容器中获取ServiceB
        this.serviceB = applicationContext.getBean(ServiceB.class);
    }
}

实现 InitializingBean 接口

java 复制代码
@Service
public class ServiceA implements InitializingBean {
    private ServiceB serviceB;
    
    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public void afterPropertiesSet() throws Exception {
        // 在属性设置完成后,从容器中获取ServiceB
        this.serviceB = applicationContext.getBean(ServiceB.class);
    }
}

4. 配置文件允许循环依赖

application.properties:

bash 复制代码
spring.main.allow-circular-references=true

application.yml:

bash 复制代码
spring:
  main:
    allow-circular-references: true

最后的问题:大家最后了解决Bean 之间的循环依赖吗?