Dynamically Schedule the Same Task with Multiple Cron Expression Using Spring Boot
Learn how to schedule a task with multiple cron expressions with Spring Boot and schedule them using ScheduledTaskRegistrar.
Join the DZone community and get the full member experience.
Join For FreeWe have seen how to schedule a task within Spring or Spring Boot using a cron expression, with fixed delays. In this in this tutorial, we will see how to schedule a task with multiple cron expressions and change the cron expression on the fly without restarting the server.
Creating a cron trigger task with Spring is pretty easy by adding a @Scheduled
annotation to any method and adding @EnableScheduling
in any configuration file. But here we use org.springframework.scheduling.annotation.SchedulingConfigurer
interface . SchedulingConfigurer class has one method,configureTasks(ScheduledTaskRegistrar taskRegistrar)
which will be called at the time of bootstrap and register the tasks.
Here, with the help of ScheduledTaskRegistrar
, we schedule the task to run at particular time intervals or with fixed rate delays.
First, we need to implement the SchedulingConfigurer interface where we need to schedule the cron tasks. Refer to the below code to see how to do that.
xxxxxxxxxx
public class SchedulerConfig implements SchedulingConfigurer {
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
}
}
ScheduledTaskRegistrar has multiple override methods to schedule the task. Here I am going to use the addTriggerTask(Runnable task, Trigger trigger)
which will take Runnable task which we want to schedule and a Trigger at which intervals the task has to be triggered.
ScheduledTaskRegistrar internally implements the InitializingBean so upon creating the bean, it will call the overriden afterProperties
method which will start scheduling the added triggered tasks using ScheduledExecutorService and TaskScheduler. See how to add a trigger task using ScheduledTaskRegistrar below.
x
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
Runnable runnable = () -> System.out.println("Trigger task executed at " + new Date());
Trigger trigger = new Trigger() {
public Date nextExecutionTime(TriggerContext triggerContext) {
CronTrigger crontrigger = new CronTrigger("0 0/1 * * * *");
return crontrigger.nextExecutionTime(triggerContext);
}
};
scheduledTaskRegistrar.addTriggerTask(runnable, trigger);
}
With the above code, the runnable task executes at every one minute.
Now, let's see how to schedule the same task with two or more cron expressions. Here I am taking the list of cron expressions separated with the pipe symbol. In this code, I am running a loop inside the configureTasks to add the trigger for each cron in the list of cron expressions.
xxxxxxxxxx
String cronsExpressions = "0 0/1 * * * * | 0 0/5 * * * * | 0 0/10 * * * *";
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
// Split the cronExpression with pipe and for each expression add the same task.
Stream.of(StringUtils.split(cronsExpressions, "|")).forEache(cronExpression -> {
Runnable runnable = () -> System.out.println("Trigger task executed at " + new Date());
Trigger trigger = new Trigger() {
public Date nextExecutionTime(TriggerContext triggerContext) {
CronTrigger crontrigger = new CronTrigger(cronExpression);
return crontrigger.nextExecutionTime(triggerContext);
}
};
scheduledTaskRegistrar.addTriggerTask(runnable, trigger);
}
}
With the above code, the runnable task excutes every minute or every 5 or 10 minutes all the time.
Now, to change the task to run at every 2, 7, or 9 minutes all the time and the stop the triggers added for evey 1, 5, or 10 minutes but without having to restart my server. Here the configureTasks will be called during the bootstrap only, but how do we do that now by stopping the existing triggers and creating new trigger task with new cron expressions?
First I will maintain a database property where I will have the cron expression separated by the pipe symbol so that I can change the cron expression on the fly without changing the server.
During the every trigger time I will check the cron expression modified by pulling the cron from data base.
If there is a change in the cron expression, it will stop all the scheduled task and rerun the configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar)
concurrently again for each of the new cron expression.
Then call the afterProperties
method of ScheduledTaskRegistrar
to schedule the task with new cron expressions.
Here when we destroy the existing triggered job it will shutdown the ScheduledExecutorService also, so it is our responsibility to create new ExecutorService
and give to ScheduledTaskRegistrar
.
Here is the complete example:
x
public class SchedulerConfig implements SchedulingConfigurer, DisposableBean {
protected String cronExpressions;
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
configDataService // to store the cronexpression in data base so that we can change on the fly when server is running.
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
cronExpressions = configDataService.getPropertyValue(CRON_EXPRESSIONS);
Stream.of(StringUtils.split(cronExpressions, "|")).forEach(cron -> {
Runnable runnableTask = () -> System.out.println("Task executed at ->" + new Date());
Trigger trigger = new Trigger() {
public Date nextExecutionTime(TriggerContext triggerContext) {
String newCronExpression = configDataService.getPropertyValue(CRON_EXPRESSIONS);
if (!StringUtils.equalsAnyIgnoreCase(newCronExpression, cronExpressions)) {
taskRegistrar.setTriggerTasksList(new ArrayList<TriggerTask>());
configureTasks(taskRegistrar); // calling recursively.
taskRegistrar.destroy(); // destroys previously scheduled tasks.
taskRegistrar.setScheduler(executor);
taskRegistrar.afterPropertiesSet(); // this will schedule the task with new cron changes.
return null; // return null when the cron changed so the trigger will stop.
}
CronTrigger crontrigger = new CronTrigger(cron);
return crontrigger.nextExecutionTime(triggerContext);
}
};
taskRegistrar.addTriggerTask(runnable, trigger);
});
}
public void destroy() throws Exception {
if (executor != null) {
executor.shutdownNow();
}
}
}
Always make sure to shut down any executor services that you create.
Please comment here if you see any issues or if you have any suggestions.
Opinions expressed by DZone contributors are their own.
Comments