1. Overview
In this tutorial, we’ll investigate and compare different ways to trigger and stop a scheduled Spring Batch job for any required business cases.
If you need introductions about Spring Batch and Scheduler, please refer to Spring-Batch and Spring-Scheduler articles.
2. Trigger a Scheduled Spring Batch Job
Firstly, we have a class SpringBatchScheduler to configure scheduling and batch job. A method launchJob() will be registered as a scheduled task.
Furtherly, to trigger the scheduled Spring Batch job in the most intuitive way, let’s add a conditional flag to fire the job only when the flag is set to true:
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 ");
}
The variable batchRunCounter will be used in integration tests to verify if the batch job has been stopped.
3. Stop a Scheduled Spring Batch Job
With above conditional flag, we’re able to trigger the scheduled Spring Batch job with the scheduled task alive.
If we don’t need to resume the job, then we can actually stop the scheduled task to save resources.
Let’s take a look at two options in the next two subsections.
3.1. Using Scheduler Post Processor
Since we’re scheduling a method by using @Scheduled annotation, a bean post processor ScheduledAnnotationBeanPostProcessor would’ve been registered first.
We can explicitly call the postProcessBeforeDestruction() to destroy the given scheduled 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());
}
Considering multiple schedulers, it’s better to keep one scheduler in its own class, so we can stop specific scheduler as needed.
3.2. Canceling the Scheduled Future
Another way to stop the scheduler would be manually canceling its Future.
Here’s a custom task scheduler for capturing Future map:
@Bean
public TaskScheduler poolScheduler() {
return new CustomTaskScheduler();
}
private class CustomTaskScheduler
extends ThreadPoolTaskScheduler {
//
@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;
}
}
Then we iterate the Future map and cancel the Future for our batch job scheduler:
public void cancelFutureSchedulerTasks() {
scheduledTasks.forEach((k, v) -> {
if (k instanceof SpringBatchScheduler) {
v.cancel(false);
}
});
}
In the cases with multiple scheduler tasks, then we can maintain the Future map inside of the custom scheduler pool but cancel the corresponding scheduled Future based on scheduler class.
4. Conclusion
In this quick article, we tried three different ways to trigger or stop a scheduled Spring Batch job.
When we need to restart the batch job, using a conditional flag to manage job running would be a flexible solution. Otherwise, we can follow the other two options to stop the scheduler completely.
As usual, all the code samples used in the article are available over on GitHub.