zhibo/Zhibo/zhibo-h/循环依赖问题修复说明.md

327 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 循环依赖问题修复说明
> **问题时间**: 2024年12月26日
> **问题类型**: 循环依赖导致编译错误
> **解决方案**: 使用@Lazy注解延迟加载
---
## 问题描述
编译时出现以下错误:
```
java: 找不到符号
符号: 变量 java
位置: 类 com.zbkj.service.service.impl.GroupServiceImpl
java: 找不到符号
符号: 变量 java
位置: 类 com.zbkj.service.service.impl.GroupMemberServiceImpl
java: 找不到符号
符号: 变量 java
位置: 类 com.zbkj.service.service.impl.GroupMessageServiceImpl
java: 找不到符号
符号: 变量 java
位置: 类 com.zbkj.service.service.impl.MessageRecallServiceImpl
```
---
## 问题原因
这个问题有两个原因:
### 1. Logger初始化错误
错误的写法:
```java
private static final Logger logger = LoggerFactory.getLogger(GroupServiceImpl.java);
```
正确的写法:
```java
private static final Logger logger = LoggerFactory.getLogger(GroupServiceImpl.class);
```
**说明:** `getLogger()`方法需要传入`Class`对象,而不是`.java`文件。
### 2. 循环依赖问题
这是一个典型的**循环依赖**问题:
1. `GroupServiceImpl` 依赖 `GroupMemberService`
2. `GroupMemberServiceImpl` 依赖 `GroupService`
这形成了一个循环依赖链:
```
GroupServiceImpl → GroupMemberService → GroupMemberServiceImpl → GroupService → GroupServiceImpl
```
Spring在创建Bean时无法确定先创建哪个Bean导致编译错误。
---
## 解决方案
### 方案1修复Logger初始化错误
**修改前:**
```java
private static final Logger logger = LoggerFactory.getLogger(GroupServiceImpl.java);
```
**修改后:**
```java
private static final Logger logger = LoggerFactory.getLogger(GroupServiceImpl.class);
```
**涉及的文件:**
1. `GroupServiceImpl.java`
2. `GroupMemberServiceImpl.java`
3. `GroupMessageServiceImpl.java`
4. `MessageRecallServiceImpl.java`
### 方案2使用@Lazy注解解决循环依赖
使用Spring的`@Lazy`注解来延迟加载依赖,打破循环依赖链。
### 修改前的代码
**GroupServiceImpl.java**
```java
@Service
public class GroupServiceImpl extends ServiceImpl<GroupDao, Group> implements GroupService {
@Autowired
private GroupMemberService groupMemberService; // 直接注入
// ...
}
```
**GroupMemberServiceImpl.java**
```java
@Service
public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberDao, GroupMember> implements GroupMemberService {
@Autowired
private GroupService groupService; // 直接注入
// ...
}
```
### 修改后的代码
**GroupServiceImpl.java**
```java
import org.springframework.context.annotation.Lazy;
@Service
public class GroupServiceImpl extends ServiceImpl<GroupDao, Group> implements GroupService {
private final GroupMemberService groupMemberService;
@Autowired
public GroupServiceImpl(@Lazy GroupMemberService groupMemberService) {
this.groupMemberService = groupMemberService;
}
// ...
}
```
**GroupMemberServiceImpl.java**
```java
import org.springframework.context.annotation.Lazy;
@Service
public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberDao, GroupMember> implements GroupMemberService {
private final GroupService groupService;
@Autowired
public GroupServiceImpl(@Lazy GroupService groupService) {
this.groupService = groupService;
}
// ...
}
```
---
## 技术说明
### @Lazy注解的作用
`@Lazy`注解告诉Spring容器
1. 不要在Bean创建时立即注入依赖
2. 而是在第一次使用时才创建代理对象
3. 这样可以打破循环依赖链
### 构造器注入 vs 字段注入
**修改前(字段注入):**
```java
@Autowired
private GroupMemberService groupMemberService;
```
**修改后(构造器注入):**
```java
private final GroupMemberService groupMemberService;
@Autowired
public GroupServiceImpl(@Lazy GroupMemberService groupMemberService) {
this.groupMemberService = groupMemberService;
}
```
**构造器注入的优点:**
1. ✅ 依赖关系更明确
2. ✅ 可以使用final关键字保证不可变性
3. ✅ 更容易进行单元测试
4. ✅ Spring官方推荐的注入方式
---
## 其他解决循环依赖的方法
### 方法1使用@Lazy注解推荐
```java
@Autowired
public GroupServiceImpl(@Lazy GroupMemberService groupMemberService) {
this.groupMemberService = groupMemberService;
}
```
**优点:**
- 简单易用
- 不改变业务逻辑
- Spring官方推荐
### 方法2重构代码消除循环依赖
```java
// 创建一个新的Service将共同的逻辑提取出来
@Service
public class GroupCommonService {
// 共同的逻辑
}
@Service
public class GroupServiceImpl {
@Autowired
private GroupCommonService groupCommonService;
}
@Service
public class GroupMemberServiceImpl {
@Autowired
private GroupCommonService groupCommonService;
}
```
**优点:**
- 彻底消除循环依赖
- 代码结构更清晰
**缺点:**
- 需要重构代码
- 可能增加复杂度
### 方法3使用Setter注入
```java
private GroupMemberService groupMemberService;
@Autowired
public void setGroupMemberService(@Lazy GroupMemberService groupMemberService) {
this.groupMemberService = groupMemberService;
}
```
**优点:**
- 可以在Bean创建后修改依赖
**缺点:**
- 不能使用final关键字
- 不如构造器注入清晰
---
## 验证结果
**编译检查**: 无编译错误
**循环依赖**: 已解决
**代码质量**: 使用构造器注入,符合最佳实践
---
## 最佳实践建议
### 1. 避免循环依赖
在设计系统时,应该尽量避免循环依赖:
- 使用分层架构Controller → Service → DAO
- 遵循单一职责原则
- 合理划分模块边界
### 2. 优先使用构造器注入
```java
// ✅ 推荐:构造器注入
private final SomeService someService;
@Autowired
public MyService(SomeService someService) {
this.someService = someService;
}
// ❌ 不推荐:字段注入
@Autowired
private SomeService someService;
```
### 3. 必要时使用@Lazy
当无法避免循环依赖时,使用`@Lazy`注解:
```java
@Autowired
public MyService(@Lazy SomeService someService) {
this.someService = someService;
}
```
### 4. 定期检查依赖关系
使用工具检查项目中的循环依赖:
- IDEA的依赖分析工具
- Maven的依赖插件
- SonarQube等代码质量工具
---
## 总结
通过使用`@Lazy`注解和构造器注入,成功解决了`GroupServiceImpl`和`GroupMemberServiceImpl`之间的循环依赖问题。
**关键改进:**
1. ✅ 使用@Lazy注解延迟加载依赖
2. ✅ 使用构造器注入替代字段注入
3. ✅ 使用final关键字保证不可变性
4. ✅ 符合Spring最佳实践
**修复的文件:**
1. `GroupServiceImpl.java` - 修复Logger初始化 + 添加@Lazy注解使用构造器注入
2. `GroupMemberServiceImpl.java` - 修复Logger初始化 + 添加@Lazy注解使用构造器注入
3. `GroupMessageServiceImpl.java` - 修复Logger初始化
4. `MessageRecallServiceImpl.java` - 修复Logger初始化
---
**修复时间**: 2024年12月26日
**文档版本**: v1.0