You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中原型Bean无法触发@Scheduled定时方法问题

解决原型Bean的@Scheduled执行问题

问题核心

Spring 5.x版本中,@Scheduled注解默认仍只会对单例Bean进行持续调度触发。将InsertTransactionDetailsByScheduleAPIInteractor设为原型作用域后,调度器仅会在初始化时创建一次该Bean实例,后续调度不会生成新实例,导致FTP连接无法随任务执行完成正常关闭,违背了使用原型Bean的初衷。

可行解决方案

方案1:单例调度器+动态获取原型Bean

创建单例的调度触发Bean,每次调度时从Spring容器中获取新的原型Bean实例执行任务,执行完成后手动销毁实例并关闭FTP连接。

  1. 改造原原型Bean,移除@Scheduled注解并实现DisposableBean用于资源清理:
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class InsertTransactionDetailsByScheduleAPIInteractor
        implements InsertTransactionDetailsByScheduleAPIInputBoundary, DisposableBean {

    private final InsertTransactionsInputBoundary insertTransactionsInputBoundary;
    private final RetrieveFileFromSecureFileTransferProtocolInputBoundary retrieveFileFromSecureFileTransferProtocolInputBoundary;
    private final ChannelSftp channelSftp;
    private final Session session;

    public InsertTransactionDetailsByScheduleAPIInteractor(
            final InsertTransactionsInputBoundary insertTransactionsInputBoundaryParam,
            @Qualifier(Constants.PROTOTYPE_RETRIEVE_FILE_BEAN_QUALIFIER) final RetrieveFileFromSecureFileTransferProtocolInputBoundary retrieveFileFromSecureFileTransferProtocolInputBoundaryParam,
            ChannelSftp channelSftp, Session session) {
        this.insertTransactionsInputBoundary = insertTransactionsInputBoundaryParam;
        this.retrieveFileFromSecureFileTransferProtocolInputBoundary = retrieveFileFromSecureFileTransferProtocolInputBoundaryParam;
        this.channelSftp = channelSftp;
        this.session = session;
    }

    @Override
    public void insertTransactionsBySchedule() throws Exception {
        this.insertTransactionsInputBoundary.insertTransactions(LocalDate.now(Constants.DEFAULT_ZONE_ID),
                this.retrieveFileFromSecureFileTransferProtocolInputBoundary);
    }

    @Override
    public void destroy() throws Exception {
        // 手动关闭FTP连接资源
        if (channelSftp != null && channelSftp.isConnected()) {
            channelSftp.disconnect();
        }
        if (session != null && session.isConnected()) {
            session.disconnect();
        }
    }
}
  1. 创建单例调度触发类:
@Service
public class ScheduledTaskTrigger {

    private final ApplicationContext applicationContext;

    public ScheduledTaskTrigger(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Scheduled(initialDelay = 5, fixedRate = 1, timeUnit = TimeUnit.SECONDS)
    public void triggerTask() throws Exception {
        // 每次调度获取全新的原型Bean实例
        InsertTransactionDetailsByScheduleAPIInteractor interactor = 
            applicationContext.getBean(InsertTransactionDetailsByScheduleAPIInteractor.class);
        try {
            interactor.insertTransactionsBySchedule();
        } finally {
            // 执行完成后销毁实例,释放FTP连接
            if (interactor instanceof DisposableBean) {
                ((DisposableBean) interactor).destroy();
            }
        }
    }
}

方案2:使用TaskScheduler手动调度

通过TaskScheduler自定义调度逻辑,每次任务触发时创建原型Bean实例,执行完成后清理资源。

@Configuration
public class CustomSchedulerConfig {

    private final ApplicationContext applicationContext;
    private final TaskScheduler taskScheduler;

    public CustomSchedulerConfig(ApplicationContext applicationContext, TaskScheduler taskScheduler) {
        this.applicationContext = applicationContext;
        this.taskScheduler = taskScheduler;
    }

    @PostConstruct
    public void initScheduledTask() {
        // 按指定周期调度任务
        taskScheduler.scheduleAtFixedRate(() -> {
            InsertTransactionDetailsByScheduleAPIInteractor interactor = 
                applicationContext.getBean(InsertTransactionDetailsByScheduleAPIInteractor.class);
            try {
                interactor.insertTransactionsBySchedule();
            } catch (Exception e) {
                // 异常处理逻辑
                e.printStackTrace();
            } finally {
                // 销毁实例释放资源
                if (interactor instanceof DisposableBean) {
                    try {
                        ((DisposableBean) interactor).destroy();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }, 5000, 1000); // initialDelay=5秒,fixedRate=1秒
    }
}

原方案失效原因

Spring的@Scheduled注解处理器在容器初始化阶段,仅会扫描并注册单例Bean的调度方法。原型Bean仅会在调度器初始化时被创建一次,后续调度不会重新生成实例,无法实现“每次调度获取新Bean”的需求。

内容的提问来源于stack exchange,提问作者Philippe Gioseffi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 02:35:17