关于Spring Bean之间的循环依赖
最近在做一个项目关于农田项目管理的,碰到了循环依赖问题(之前只在八股中见过),最近在开发GbzntProject项目时,就遇到了这样一个典型的循环依赖错误。本文将详细分析这个问题产生的原因、表现形式以及解决方案。
问题背景
在Spring Boot项目开发过程中,我们经常会遇到循环依赖(Circular Dependency)的问题。最近在开发GbzntProject项目时,就遇到了这样一个典型的循环依赖错误。本文将详细分析这个问题产生的原因、表现形式以及多种解决方案。
一、问题现象
错误信息
```
***************************
APPLICATION FAILED TO START
***************************Description:The dependencies of some of the beans in the application context form a cycle:gbzntFacilityController ↓gbzntFacilityServiceImpl
┌─────┐
| gbzntProjectServiceImpl (field private org.jeecg.gbznt.project.service.GbzntProjectInfoService org.jeecg.gbznt.project.service.impl.GbzntProjectServiceImpl.gbzntProjectInfoService)
↑ ↓
| gbzntProjectInfoServiceImpl (field private org.jeecg.gbznt.project.service.IGbzntProjectService org.jeecg.gbznt.project.service.impl.GbzntProjectInfoServiceImpl.gbzntProjectService)
└─────┘Action:Relying upon circular references is discouraged and they are prohibited by default.
```
错误解读
从错误信息可以看出,存在以下循环依赖链:
1. `gbzntFacilityController` → `gbzntFacilityServiceImpl`
2. `gbzntProjectServiceImpl` → `gbzntProjectInfoServiceImpl` → `gbzntProjectServiceImpl`(形成循环)
二、问题根源分析
2.1 循环依赖的产生
查看相关代码,发现问题的根源在于:
@Service
public class GbzntProjectServiceImpl implements IGbzntProjectService {@Autowiredprivate GbzntProjectInfoService gbzntProjectInfoService; // 依赖ProjectInfoService// ...
}
```
@Service
public class GbzntProjectInfoServiceImpl implements GbzntProjectInfoService {@Autowiredprivate IGbzntProjectService gbzntProjectService; // 又依赖回ProjectService// ...
}
```
这样就形成了:
`ProjectService` → `ProjectInfoService` → `ProjectService`的循环依赖链。
2.2 Spring Bean的创建过程
Spring创建Bean的过程大致如下:
1. 创建`ProjectService`实例
2. 发现需要注入`ProjectInfoService`依赖
3. 创建`ProjectInfoService`实例
4. 发现需要注入`ProjectService`依赖
5. 但`ProjectService`还在创建中,形成循环依赖
6. Spring抛出异常,禁止这种循环引用
三、解决方案
方案:Setter注入 + @Lazy
@Service
public class GbzntProjectServiceImpl implements IGbzntProjectService {private GbzntProjectInfoService gbzntProjectInfoService;@Autowired@Lazypublic void setGbzntProjectInfoService(GbzntProjectInfoService gbzntProjectInfoService) {this.gbzntProjectInfoService = gbzntProjectInfoService;}
}
```
为什么可以
首先介绍一下spring的三级缓存
循环依赖发生在两个或两个以上的bean互相持有对方,形成闭环。
Spring框架允许循环依赖存在,并通过三级缓存解决大部分循环依赖问题:
1.一级缓存:单例池,缓存已完成初始化的bean对象。
2.二级缓存:缓存尚未完成生命周期的早期bean对象。
3.三级缓存:缓存ObjectFactory,用于创建bean对象。
而这个@Lazy注解让Spring创建一个代理对象而不是真实对象,就是存在二级缓存中的代理对象
所以现在的加载逻辑为
创建ServiceA流程:
实例化ServiceA对象
发现setServiceB方法有@Lazy注解
注入一个ServiceB的代理对象(不是真实ServiceB实例)
ServiceA创建完成,放入单例池
创建ServiceB流程:
实例化ServiceB对象
发现setServiceA方法有@Lazy注解
注入一个ServiceA的代理对象(此时ServiceA已创建完成)
ServiceB创建完成,放入单例池
实际调用时:
当第一次调用serviceB的方法时
代理对象会触发真实ServiceB的创建
此时所有依赖都已就绪,不会出现循环依赖
四、总结
循环依赖是Spring Boot项目中常见的问题,其根本原因在于不合理的设计和过度的耦合。通过本文的分析和解决方案,我们可以:
1. 理解循环依赖的产生机制:Spring Bean的创建过程和依赖注入机制
2. 掌握多种解决方:从代码重构到配置调整的各种方法
3. 遵循最佳实践:采用合理的架构设计和编码规范