Spring Boot解决循环依赖注入问题
- 代码问题回显
- 启动错误日志
- [解决方案:使用事件驱动或通过 ApplicationContext 手动获取 Bean](#解决方案:使用事件驱动或通过 ApplicationContext 手动获取 Bean)
-
- [1. 事件驱动设计](#1. 事件驱动设计)
- [2. 使用 `ApplicationContext` 手动获取 Bean](#2. 使用
ApplicationContext
手动获取 Bean) - [3. 拆分逻辑](#3. 拆分逻辑)
- 总结
代码问题回显
现有代码1 在InterestService
中依赖MemberInterestService
:
java
@Service
@AllArgsConstructor
public class InterestService {
// 注意此处循环依赖注入
private final MemberInterestService memberInterestService;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
/**
* 调度下一个利息任务
*/
public void scheduleNextInterestTask() {
// 省略其他代码...
}
}
现有代码2 在MemberInterestService
实现类中注入InterestService
:
java
@Service
@AllArgsConstructor
public class MemberInterestServiceImpl extends ServiceImpl<MemberInterestMapper, MemberInterest> implements MemberInterestService {
// 注意此处循环依赖注入
private final InterestService interestService;
@Override
public Boolean updateExpireStatus(MemberInterestExpireStatus body) {
// 省略其他代码...
if (updateById(interest)){
// TODO 此处出现循环依赖注入(直接报错)
interestService.scheduleNextInterestTask();
return true;
}
}
}
启动错误日志
java
Description:
The dependencies of some of the beans in the application context form a cycle:
mobileLoginController (field private com.sinbyte.framework.web.service.SysLoginService
com.sinbyte.web.controller.system.MobileLoginController.loginService)
↓
sysLoginService (field private com.sinbyte.ray.service.MemberUserService
com.sinbyte.framework.web.service.SysLoginService.memberUserService)
┌─────┐
| memberInterestServiceImpl defined in file [D:\Java\IdeaProjects\alliance-server\alliance-ray\target\classes\com\sinbyte\ray\service\impl\MemberInterestServiceImpl.class]
↑ ↓
| interestService defined in file [D:\Java\IdeaProjects\alliance-server\alliance-ray\target\classes\com\sinbyte\ray\delay\InterestService.class]
└─────┘
在场景中, MemberInterestServiceImpl
需要调用 InterestService
的 scheduleNextInterestTask()
方法,但由于这两个服务之间存在循环依赖
,直接注入会导致 Spring 启动时发生循环依赖错误
。
解决方案:使用事件驱动或通过 ApplicationContext 手动获取 Bean
以下是一些可以解决循环依赖问题的方法:
1. 事件驱动设计
可以使用 Spring 的事件机制,将调用 scheduleNextInterestTask
的操作转变为事件驱动。具体做法是,当 MemberInterestServiceImpl
需要调用 scheduleNextInterestTask
时,发布一个自定义事件,InterestService
监听这个事件并执行相应的任务。
首先,定义一个自定义事件类:
java
import org.springframework.context.ApplicationEvent;
/**
* 自定义事件驱动
* @CreateDate: 2024/8/14 15:30
*/
public class InterestTaskEvent extends ApplicationEvent {
public InterestTaskEvent(Object source) {
super(source);
}
}
接着,在 MemberInterestServiceImpl
中发布事件,实现ApplicationEventPublisherAware
并重写setApplicationEventPublisher
方法:
java
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
public class MemberInterestServiceImpl extends ServiceImpl<MemberInterestMapper, MemberInterest> implements MemberInterestService, ApplicationEventPublisherAware {
// 事件发布器
private ApplicationEventPublisher eventPublisher;
@Override
public void setApplicationEventPublisher(@NotNull ApplicationEventPublisher applicationEventPublisher) {
// 注入事件发布器
this.eventPublisher = applicationEventPublisher;
}
@Override
public Boolean updateExpireStatus(MemberInterestExpireStatus body) {
// 省略其他代码...
if (updateById(interest)) {
// 省略其他代码...
eventPublisher.publishEvent(new InterestTaskEvent(this)); // 发布事件
return true;
}
}
}
在 InterestService
中监听这个事件:
java
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
public class InterestService {
// 其他依赖...
@EventListener
public void onInterestTaskEvent(InterestTaskEvent event) {
scheduleNextInterestTask();
}
public void scheduleNextInterestTask() {
// 省略其他代码...
}
}
2. 使用 ApplicationContext
手动获取 Bean
如果你不希望使用事件驱动,还可以通过 Spring 的 ApplicationContext
手动获取 InterestService
Bean,从而避免循环依赖。
在 MemberInterestServiceImpl
中引入 ApplicationContext
并在需要调用时获取 InterestService
:
java
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
@Service
@Slf4j
@AllArgsConstructor
public class MemberInterestServiceImpl extends ServiceImpl<MemberInterestMapper, MemberInterest> implements MemberInterestService, ApplicationContextAware {
private ApplicationContext applicationContext;
private final RedisCache redisCache;
private final PublicService publicService;
private final MemberApplyBusinessService memberApplyBusinessService;
private final MemberTransactionRecordService memberTransactionRecordService;
private final MemberInterestPointService memberInterestPointService;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public Boolean updateExpireStatus(MemberInterestExpireStatus body) {
// 省略其他代码...
if (updateById(interest)) {
// 省略其他代码...
InterestService interestService = applicationContext.getBean(InterestService.class);
interestService.scheduleNextInterestTask(); // 手动获取 Bean 并调用方法
return true;
}
throw new ServiceException("更新失败");
}
}
3. 拆分逻辑
如果可能,考虑将 InterestService
的部分逻辑拆分到一个新的服务中,以减少 InterestService
和 MemberInterestServiceImpl
之间的依赖关系。这可能需要对业务逻辑进行一定的重构,但从长期维护的角度来看,是一种更优雅的解决方案。
总结
通过以上方法,可以有效地解决循环依赖问题并在 MemberInterestServiceImpl
中安全地调用 InterestService
的方法。推荐使用事件驱动的方法,这不仅解决了循环依赖问题,还能让你的代码更具扩展性和松耦合。
生活不能过度的平坦,这样的生活才最有意义!