Spring Boot解决循环注入问题

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 的方法。推荐使用事件驱动的方法,这不仅解决了循环依赖问题,还能让你的代码更具扩展性和松耦合。

生活不能过度的平坦,这样的生活才最有意义!

相关推荐
Pniubi5 小时前
HashMap的扩容和树化边界
java·开发语言
Mikko75 小时前
用 Java 复刻了高中时在塞班上玩的「熔岩矿车」:纯 Swing、零依赖、没有一张图片,一个 jar 双击就能玩
java·游戏开发·swing
知了学历孙老师5 小时前
小自考的校考和统考,出题方和难度差在哪
java·大数据·前端
Java爱好狂.6 小时前
Java初学者如何一周速通SpringBoot!
java·spring boot·后端·spring·高并发·架构师·java面试
秋饼6 小时前
LLM API密钥动态轮换实战:Spring Vault + Spring AI + 多Provider路由
java·ai·技术分享·后端开发
福兮说8 小时前
JSON 格式正确,数据就可靠吗?我用一份自编配置检查类型与空值
java·前端·json
紧跟先前的步伐9 小时前
Go AI Agent 实战 03|Token、上下文窗口与模型调用成本
java·人工智能·golang
const_qiu10 小时前
第四章 00:开篇 —— 从「会聊天」到「记得住、会干活、能协作」
java·开发语言·人工智能·python
Wang's Blog10 小时前
Java 项目实战: 交友项目 - 个人资料管理的更新接口实现与坑
java·开发语言·交友
Promising_GEO10 小时前
GEE实战02期:从卫星波段开始:利用 Sentinel-2 自己计算 NDVI
java·sentinel·遥感·空间分析