I have a requirement to run a batch job at a fixed interval and have the ability to change the time of this batch job at runtime. For this I came across @Scheduled
annotation provided under Spring framework. But I'm not sure how I'd change the value of fixedDelay at runtime. I did some googling around but didn't find anything useful.
You can use a Trigger
to dynamically set the next execution time.
See my answer to Scheduling a job with Spring programmatically for details.
NullPointerException
bug noticed in the code. –
Subhead In spring boot, you can use an application property directly!
For example:
@Scheduled(fixedDelayString = "${my.property.fixed.delay.seconds}000")
private void process() {
// your impl here
}
Note that you can also have a default value in case the property isn't defined, eg to have a default of "60" (seconds):
@Scheduled(fixedDelayString = "${my.property.fixed.delay.seconds:60}000")
Other things I discovered:
- the method must be void
- the method must have no parameters
- the method may be
private
I found being able to use private
visibility handy and used it in this way:
@Service
public class MyService {
public void process() {
// do something
}
@Scheduled(fixedDelayString = "${my.poll.fixed.delay.seconds}000")
private void autoProcess() {
process();
}
}
Being private
, the scheduled method can be local to your service and not become part of your Service's API.
Also, this approach allows the process()
method to return a value, which a @Scheduled
method may not. For example, your process()
method can look like:
public ProcessResult process() {
// do something and collect information about what was done
return processResult;
}
to provide some information about what happened during processing.
fixedDelayString
is what I was looking for –
Annabel OutputStream
? –
Thalia at runtime
, and your solution requires a restart? –
Pyrochemical fixedRateString
. –
Modernism You can use a Trigger
to dynamically set the next execution time.
See my answer to Scheduling a job with Spring programmatically for details.
NullPointerException
bug noticed in the code. –
Subhead create interface , something like that:
public abstract class DynamicSchedule{
/**
* Delays scheduler
* @param milliseconds - the time to delay scheduler.
*/
abstract void delay(Long milliseconds);
/**
* Decreases delay period
* @param milliseconds - the time to decrease delay period.
*/
abstract void decreaseDelayInterval(Long milliseconds);
/**
* Increases delay period
* @param milliseconds - the time to increase dela period
*/
abstract void increaseDelayInterval(Long milliseconds);
}
Next, lets implement Trigger interface that is located at org.springframework.scheduling in the spring-context project.
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import java.util.Date;
import java.util.concurrent.ScheduledFuture;
public class CustomDynamicSchedule extends DynamicSchedule implements Trigger {
private TaskScheduler taskScheduler;
private ScheduledFuture<?> schedulerFuture;
/**
* milliseconds
*/
private long delayInterval;
public CustomDynamicSchedule(TaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
@Override
public void increaseDelayInterval(Long delay) {
if (schedulerFuture != null) {
schedulerFuture.cancel(true);
}
this.delayInterval += delay;
schedulerFuture = taskScheduler.schedule(() -> { }, this);
}
@Override
public void decreaseDelayInterval(Long delay) {
if (schedulerFuture != null) {
schedulerFuture.cancel(true);
}
this.delayInterval -= delay;
schedulerFuture = taskScheduler.schedule(() -> { }, this);
}
@Override
public void delay(Long delay) {
if (schedulerFuture != null) {
schedulerFuture.cancel(true);
}
this.delayInterval = delay;
schedulerFuture = taskScheduler.schedule(() -> { }, this);
}
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
Date lastTime = triggerContext.lastActualExecutionTime();
return (lastTime == null) ? new Date() : new Date(lastTime.getTime() + delayInterval);
}
}
now configuration:
@Configuration
public class DynamicSchedulerConfig {
@Bean
public CustomDynamicSchedule getDynamicScheduler() {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.initialize();
return new CustomDynamicSchedule(threadPoolTaskScheduler);
}
}
and usage:
@EnableScheduling
@Component
public class TestSchedulerComponent {
@Autowired
private CustomDynamicSchedule dynamicSchedule;
@Scheduled(fixedDelay = 5000)
public void testMethod() {
dynamicSchedule.delay(1000l);
dynamicSchedule.increaseDelayInterval(9000l);
dynamicSchedule.decreaseDelayInterval(5000l);
}
}
increaseDelayInterval
and decreaseDelayInterval()
implementations are the same. I think you ment to do this.delayInterval -= delay;
in the latter? –
Merissa You can also use Spring Expression Language (SpEL) for this.
@Scheduled(fixedDelayString = "#{@applicationPropertyService.getApplicationProperty()}")
public void getSchedule(){
System.out.println("in scheduled job");
}
@Service
public class ApplicationPropertyService {
public String getApplicationProperty(){
//get your value here
return "5000";
}
}
AFAIK the Spring API won't let you access the internals you need to change the trigger. But you could instead configure manually the beans:
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<property name="jobDetail" ref="jobDetail" />
<property name="startDelay" value="10000" />
<property name="repeatInterval" value="50000" />
</bean>
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="simpleTrigger" />
</list>
</property>
</bean>
Then as documented in SchedulerFactoryBean:
For dynamic registration of jobs at runtime, use a bean reference to this SchedulerFactoryBean to get direct access to the Quartz Scheduler (
org.quartz.Scheduler
). This allows you to create new jobs and triggers, and also to control and monitor the entire Scheduler.
I've dealt with the same problem. We had the requirement to change the cron expression at runtime and reschedule the service. So there should be:
- no recompilation
- no redeployment
- no restart
of the application. I've inspected all the popular solutions but only 2 of them fulfill all the requirements.
- Implement SchedulingConfigurer as ach suggested
- Use a custom solution as described here
The disadvantage of the SchedulingConfigurer approach is that it is pull-based, i.e. the scheduling configuration is pulled every time the service's business logic is executed. This is not a bad thing in general but if the config is changed rarely and the execution interval is short then there will be a lot of unnecessary requests.
The disadvantage of the custom solution is that it requires a bit more coding but it is push-based and reacts to configuration changes so no unnecessary requests/calls are performed.
© 2022 - 2024 — McMap. All rights reserved.