1. 概述

本文将深入探讨并对比在不同业务场景下,触发和停止定时 Spring Batch 任务的几种常见方式。

如果你对 Spring Batch 或定时任务(Scheduler)还不熟悉,建议先阅读 Spring Batch 入门Spring 定时任务 相关文章。


2. 触发一个定时的 Spring Batch 任务

我们首先定义一个名为 SpringBatchScheduler 的类,用于配置定时任务和批处理作业。其中 launchJob() 方法将被注册为一个定时执行的任务。

最直观的触发方式是:✅ 通过一个条件开关控制任务是否执行。这样可以在不关闭调度器的前提下灵活启停任务。

示例代码如下:

private AtomicBoolean enabled = new AtomicBoolean(true);

private AtomicInteger batchRunCounter = new AtomicInteger(0);

@Autowired
private JobLauncher jobLauncher;

@Autowired
private JobRepository jobRepository;

@Autowired
private PlatformTransactionManager transactionManager;

@Scheduled(fixedRate = 2000)
public void launchJob() throws Exception {
    Date date = new Date();
    logger.debug("scheduler starts at " + date);
    if (enabled.get()) {
       JobExecution jobExecution = jobLauncher.run(job(jobRepository, transactionManager), new JobParametersBuilder().addDate("launchDate", date)
                  .toJobParameters());
       batchRunCounter.incrementAndGet();
       logger.debug("Batch job ends with status as " + jobExecution.getStatus());
    }
    logger.debug("scheduler ends ");
}

📌 说明:

  • enabled 是一个原子布尔值,作为任务执行的“开关”。
  • batchRunCounter 用于记录任务实际执行次数,方便在集成测试中验证任务是否被正确停止。
  • 使用 @Scheduled(fixedRate = 2000) 实现每 2 秒触发一次。

这种方式的优点是:简单粗暴,且支持后续动态恢复,适合需要临时暂停但保留调度器的场景。


3. 停止一个定时的 Spring Batch 任务

上面的方式只是“跳过执行”,调度任务本身仍在运行。如果我们确定不再需要该任务,可以考虑彻底停止调度器,以节省资源。

以下是两种彻底停止调度任务的方式。

3.1. 使用 Scheduler 后置处理器(ScheduledAnnotationBeanPostProcessor)

Spring 在处理 @Scheduled 注解时,会自动注册一个 ScheduledAnnotationBeanPostProcessor,它负责解析注解并创建定时任务。

我们可以通过手动调用其 postProcessBeforeDestruction() 方法,来销毁指定的调度 Bean,从而停止任务。

@Test
public void stopJobSchedulerWhenSchedulerDestroyed() throws Exception {
    ScheduledAnnotationBeanPostProcessor bean = context
      .getBean(ScheduledAnnotationBeanPostProcessor.class);
    SpringBatchScheduler schedulerBean = context
      .getBean(SpringBatchScheduler.class);
    await().untilAsserted(() -> Assert.assertEquals(
      2, 
      schedulerBean.getBatchRunCounter().get()));
    bean.postProcessBeforeDestruction(
      schedulerBean, "SpringBatchScheduler");
    await().atLeast(3, SECONDS);

    Assert.assertEquals(
      2, 
      schedulerBean.getBatchRunCounter().get());
}

⚠️ 注意:

  • 这种方式会直接销毁调度 Bean,不可恢复
  • 推荐将每个调度器独立成一个类,便于精准控制。

✅ 适用场景:

  • 多个调度器共存时,需要单独关闭某一个。
  • 应用运行期间明确知道某个任务不再需要。

3.2. 取消调度任务的 ScheduledFuture

另一种更精细的控制方式是:捕获任务的 ScheduledFuture 对象,并在需要时主动取消。

我们可以自定义一个 TaskScheduler,在调度任务时记录 Future 引用:

@Bean
public TaskScheduler poolScheduler() {
    return new CustomTaskScheduler();
}

private class CustomTaskScheduler 
  extends ThreadPoolTaskScheduler {

    private final Map<Object, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(
      Runnable task, long period) {
        ScheduledFuture<?> future = super
          .scheduleAtFixedRate(task, period);

        ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task;
        scheduledTasks.put(runnable.getTarget(), future);

        return future;
    }
}

然后提供一个方法,用于取消特定调度器的任务:

public void cancelFutureSchedulerTasks() {
    scheduledTasks.forEach((k, v) -> {
        if (k instanceof SpringBatchScheduler) {
            v.cancel(false);
        }
    });
}

📌 要点:

  • scheduledTasks 是一个 ConcurrentHashMap,键为调度目标对象(如 SpringBatchScheduler 实例),值为对应的 ScheduledFuture
  • v.cancel(false) 表示不中断正在运行的任务,但阻止后续执行。

✅ 优势:

  • 精准控制,支持按类、按实例取消。
  • 可扩展性强,适合复杂调度场景。

4. 总结

本文介绍了三种控制定时 Spring Batch 任务的方式:

方式 是否可恢复 适用场景
✅ 条件开关(enabled 标志) 临时暂停,后续可能恢复
✅ 销毁调度 Bean(postProcessBeforeDestruction) 永久关闭,节省资源
✅ 取消 ScheduledFuture 否(但更可控) 需要精细控制取消行为

📌 建议:

  • 如果只是想“暂停”任务,用 条件开关 最简单。
  • 如果确认任务不再需要,推荐 取消 Future销毁 Bean,避免资源浪费。

所有示例代码已上传至 GitHub:https://github.com/example/spring-batch-scheduling-demo(原仓库为 eugenp/tutorials,此处为模拟地址)


原始标题:How to Trigger and Stop a Scheduled Spring Batch Job