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

7.1 KiB
Raw Blame History

循环依赖问题修复说明

问题时间: 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初始化错误

错误的写法:

private static final Logger logger = LoggerFactory.getLogger(GroupServiceImpl.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初始化错误

修改前:

private static final Logger logger = LoggerFactory.getLogger(GroupServiceImpl.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

@Service
public class GroupServiceImpl extends ServiceImpl<GroupDao, Group> implements GroupService {
    
    @Autowired
    private GroupMemberService groupMemberService;  // 直接注入
    
    // ...
}

GroupMemberServiceImpl.java

@Service
public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberDao, GroupMember> implements GroupMemberService {
    
    @Autowired
    private GroupService groupService;  // 直接注入
    
    // ...
}

修改后的代码

GroupServiceImpl.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

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 字段注入

修改前(字段注入):

@Autowired
private GroupMemberService groupMemberService;

修改后(构造器注入):

private final GroupMemberService groupMemberService;

@Autowired
public GroupServiceImpl(@Lazy GroupMemberService groupMemberService) {
    this.groupMemberService = groupMemberService;
}

构造器注入的优点:

  1. 依赖关系更明确
  2. 可以使用final关键字保证不可变性
  3. 更容易进行单元测试
  4. Spring官方推荐的注入方式

其他解决循环依赖的方法

方法1使用@Lazy注解推荐

@Autowired
public GroupServiceImpl(@Lazy GroupMemberService groupMemberService) {
    this.groupMemberService = groupMemberService;
}

优点:

  • 简单易用
  • 不改变业务逻辑
  • Spring官方推荐

方法2重构代码消除循环依赖

// 创建一个新的Service将共同的逻辑提取出来
@Service
public class GroupCommonService {
    // 共同的逻辑
}

@Service
public class GroupServiceImpl {
    @Autowired
    private GroupCommonService groupCommonService;
}

@Service
public class GroupMemberServiceImpl {
    @Autowired
    private GroupCommonService groupCommonService;
}

优点:

  • 彻底消除循环依赖
  • 代码结构更清晰

缺点:

  • 需要重构代码
  • 可能增加复杂度

方法3使用Setter注入

private GroupMemberService groupMemberService;

@Autowired
public void setGroupMemberService(@Lazy GroupMemberService groupMemberService) {
    this.groupMemberService = groupMemberService;
}

优点:

  • 可以在Bean创建后修改依赖

缺点:

  • 不能使用final关键字
  • 不如构造器注入清晰

验证结果

编译检查: 无编译错误
循环依赖: 已解决
代码质量: 使用构造器注入,符合最佳实践


最佳实践建议

1. 避免循环依赖

在设计系统时,应该尽量避免循环依赖:

  • 使用分层架构Controller → Service → DAO
  • 遵循单一职责原则
  • 合理划分模块边界

2. 优先使用构造器注入

// ✅ 推荐:构造器注入
private final SomeService someService;

@Autowired
public MyService(SomeService someService) {
    this.someService = someService;
}

// ❌ 不推荐:字段注入
@Autowired
private SomeService someService;

3. 必要时使用@Lazy

当无法避免循环依赖时,使用@Lazy注解:

@Autowired
public MyService(@Lazy SomeService someService) {
    this.someService = someService;
}

4. 定期检查依赖关系

使用工具检查项目中的循环依赖:

  • IDEA的依赖分析工具
  • Maven的依赖插件
  • SonarQube等代码质量工具

总结

通过使用@Lazy注解和构造器注入,成功解决了GroupServiceImplGroupMemberServiceImpl之间的循环依赖问题。

关键改进:

  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