How to Conditionally run the Scheduled job in SpringBoot - spring-boot

I have scheduled job which runs incessantly after certain amount of time.
Now I want to run this scheduled job only if a condition is met. The condition is obtained at run time and it is not dependent on any configuration parameter.
How do I achieve this. I know Spring Boot 4.x provides this interface called Condition. But somehow my code doesnt work.
Here, is my code...
ScheduledTask
#Configuration
public class ScheduleTask {
#Scheduled(fixedRateString = "5000")
#Conditional(SchedulerCondition.class)
public void pollDepots() {
System.out.println("Running");
}
}
Conditional Class
public class SchedulerCondition implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return false;
//Here some condition needs to be implemented which is not dependent on the parameters of this method.
}
}
Await your response.
Have a lovely day.

You could pull your scheduled task in a different class:
public class ScheduledPollDepots {
#Scheduled(fixedRateString = "1000")
public void pollDepots() {
System.out.println("Running");
}
}
Then create a bean based on the conditional:
#Configuration
public class Config {
#Bean
#Conditional(SchedulerCondition.class)
public ScheduledPollDepots pollDepots() {
return new ScheduledPollDepots();
}
}
With your schedule condition as is:
public class SchedulerCondition implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return false;
}
}
it should work properly.

Related

How to schedule a cron job in spring boot without using #Scheduled() annotation

In spring boot, can I schedule a spring job by not using #Scheduled annotation to a method?
I am working with spring job in the spring boot. I want to schedule a job by using cron expression, but without using #Scheduled(cron = " ") annotation to the method.
I know that I can schedule a job inside this method as below.
#Scheduled (cron = "0 10 10 10 * ?")
public void execute() {
/ * some job code * /
}
But I want it to be dynamic so that I can take a cron expression as input from the user and schedule it.
I came up with a working example since I found your question interesting and have been interested in this problem before. It's based entirely on the source code so I have no idea if it comes close to following best practice. Nonetheless, you may be able to tune it to your needs. FYI, you don't necessarily need to create a new ScheduledTaskRegistrar object - I figured that since your objective is a dynamic scheduler, you wouldn't be interested in defining your tasks purely in the overwritten method.
#SpringBootApplication
public class TaskScheduler implements SchedulingConfigurer, CommandLineRunner {
public static void main(String[] args){SpringApplication.run(TaskScheduler.class, args);}
List<CronTask> cronTasks;
#Override
public void run(String... args) throws Exception {
CronTask task = this.createCronTask(new Runnable() {
#Override
public void run() {
System.out.println(LocalDateTime.now());
}
}, "1/10 * * * * *");
ScheduledTaskRegistrar taskRegistrar = new ScheduledTaskRegistrar();
taskRegistrar.addCronTask(task);
configureTasks(taskRegistrar);
Thread.sleep(51);
taskRegistrar.destroy();
taskRegistrar = null;
ScheduledTaskRegistrar taskRegistrar2 = new ScheduledTaskRegistrar();
taskRegistrar2.addCronTask(task);
configureTasks(taskRegistrar2);
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// "Calls scheduleTasks() at bean construction time" - docs
taskRegistrar.afterPropertiesSet();
}
public CronTask createCronTask(Runnable action, String expression) {
return new CronTask(action, new CronTrigger(expression));
}
}
I have experience using cron jobs in Azure and other places. Programming in Java, I have typically used #Scheduled with fixed times just for the sake of simplicity. Hope this is useful to you though.
Here is my working example, If somebody wants to use TaskScheduler without using #Scheduled Annotation
#Configuration Class
#Configuration
public class SchedulerConfig implements SchedulingConfigurer {
final Logger LOGGER = LogManager.getLogger(SchedulerConfig.class);
#Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
LOGGER.debug("Creating Async Task Scheduler");
scheduledTaskRegistrar.setTaskScheduler(taskScheduler());
}
// This is mandatory otherwise it will to be able to find bean of
// taskScheduler. Without this it was giving runtime error says, can not find
// taskScheduler bean.
#Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(20); // Better to read it from property file.
scheduler.setThreadNamePrefix("ThreadScheduler-");
scheduler.initialize();
return scheduler;
}
}
Scheduler Class which is called from Application class.
#Component
public class MyTaskScheduler {
private TaskScheduler taskScheduler;
// Here we are auto-wiring taskScheduler, that's why need to create
// taskScheduler bean in configuration class
#Autowired
public void setScheduler(TaskScheduler scheduler) {
this.taskScheduler = scheduler;
}
public void schedule() {
taskScheduler.scheduleWithFixedDelay(new Runnable(){
#Override
public void run() {
System.out.println("I am running after every 1 second");
}
}, 1000);
}
}
If in any chance #Configuration annotation is not working so put #EnableConfigurationProperties at main running class.
Make sure that you put #EnableScheduling at Application class, so main runnable class will look like
#SpringBootApplication
#EnableScheduling
#EnableConfigurationProperties
public class MainApplication implements CommandLineRunner {
#Autowired
MyTaskScheduler myTaskScheduler;
public static void main(String[] args) {
final Logger logger = LogManager.getLogger(MainApplication.class);
SpringApplication.run(MainApplication.class, args);
logger.info("Application started");
}
#Override
public void run(String... args) throws Exception {
myTaskScheduler.schedule();
}
}
This answer is similar to the two previous ones, but is more compact because it leverages the scheduling registrar already provided in the application context:
#Configuration
#EnableScheduling
public class Schedule implements SchedulingConfigurer {
private final transient WorkflowTriggerService workflowTriggerService;
public Schedule(final WorkflowTriggerService workflowTriggerService) {
this.workflowTriggerService = workflowTriggerService;
}
#Override
public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) {
for (final WorkflowTrigger trigger : workflowTriggerService.getWorkflowTriggersWithSchedules()) {
taskRegistrar.addCronTask(new WorkflowTask(trigger), trigger.getSchedule());
}
}
}
Each WorkflowTrigger returned by the service has its own cron schedule, and therefore allows dynamic registration of scheduled tasks that are not known at compilation time.

#RefreshScope stops #Scheduled task

I have a monitoring app wherein I am running a fixedRate task. This is pulling in a config parameter configured with Consul. I want to pull in updated configuration, so I added #RefreshScope. But as soon as I update the config value on Consul, the fixedRate task stops running.
#Service
#RefreshScope
public class MonitorService {
#Autowired
private AppConfig appConfig;
#PostConstruct
public void postConstRun() {
System.out.println(appConfig.getMonitorConfig());
}
#Scheduled(fixedRate = 1000)
public void scheduledMonitorScan() {
System.out.println("MonitorConfig:" + appConfig.getMonitorConfig());
}
}
AppConfig class just has a single String parameter:
#Configuration
#Getter
#Setter
public class AppConfig {
#Value("${monitor-config:default value}")
private String monitorConfig;
}
As soon as I update the value in consul, the scheduled task just stops running (display in sheduledMonitorScan method) stop showing up.
I'm successfully get & override the values from consul config server using RefreshScopeRefreshedEvent
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
#Component
#RefreshScope
public class AlertSchedulerCron implements ApplicationListener<RefreshScopeRefreshedEvent> {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
#Value("${pollingtime}")
private String pollingtime;
/*
* #Value("${interval}") private String interval;
*/
#Scheduled(cron = "${pollingtime}")
//#Scheduled(fixedRateString = "${interval}" )
public void task() {
System.out.println(pollingtime);
System.out.println("Scheduler (cron expression) task with duration : " + sdf.format(new Date()));
}
#Override
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
// TODO Auto-generated method stub
}
}
Here's how we've solved this issue.
/**
* Listener of Spring's lifecycle to revive Scheduler beans, when spring's
* scope is refreshed.
* <p>
* Spring is able to restart beans, when we change their properties. Such a
* beans marked with RefreshScope annotation. To make it work, spring creates
* <b>lazy</b> proxies and push them instead of real object. The issue with
* scope refresh is that right after refresh in order for such a lazy proxy
* to be actually instantiated again someone has to call for any method of it.
* <p>
* It creates a tricky case with Schedulers, because there is no bean, which
* directly call anything on any Scheduler. Scheduler lifecycle is to start
* few threads upon instantiation and schedule tasks. No other bean needs
* anything from them.
* <p>
* To overcome this, we had to create artificial method on Schedulers and call
* them, when there is a scope refresh event. This actually instantiates.
*/
#RequiredArgsConstructor
public class RefreshScopeListener implements ApplicationListener<RefreshScopeRefreshedEvent> {
private final List<RefreshScheduler> refreshSchedulers;
#Override
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
refreshSchedulers.forEach(RefreshScheduler::materializeAfterRefresh);
}
}
So, we've defined an interface, which does nothing in particular, but allows us to call for a refreshed job.
public interface RefreshScheduler {
/**
* Used after refresh context for scheduler bean initialization
*/
default void materializeAfterRefresh() {
}
}
And here is actual job, whose parameter from.properties can be refreshed.
public class AJob implements RefreshScheduler {
#Scheduled(cron = "${from.properties}")
public void aTask() {
// do something useful
}
}
UPDATED:
Of course AJob bean must be marked with #RefreshScope in #Configuration
#Configuration
#EnableScheduling
public class SchedulingConfiguration {
#Bean
#RefreshScope
public AJob aJob() {
return new AJob();
}
}
I have done workaround for this kind of scenario by implementing SchedulingConfigurer interface.
Here I am dynamically updating "scheduler.interval" property from external property file and scheduler is working fine even after actuator refresh as I am not using #RefreshScope anymore.
Hope this might help you in your case also.
public class MySchedulerImpl implements SchedulingConfigurer {
#Autowired
private Environment env;
#Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(10);
}
#Override
public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(this.taskExecutor());
taskRegistrar.addTriggerTask(() -> {
//put your code here that to be scheduled
}, triggerContext -> {
final Calendar nextExecutionTime = new GregorianCalendar();
final Date lastActualExecutionTime = triggerContext.lastActualExecutionTime();
if (lastActualExecutionTime == null) {
nextExecutionTime.setTime(new Date());
} else {
nextExecutionTime.setTime(lastActualExecutionTime);
nextExecutionTime.add(Calendar.MILLISECOND, env.getProperty("scheduler.interval", Integer.class));
}
return nextExecutionTime.getTime();
});
}
}
My solution consists of listening to EnvironmentChangeEvent
#Configuration
public class SchedulingSpringConfig implements ApplicationListener<EnvironmentChangeEvent>, SchedulingConfigurer {
private static final Logger LOGGER = LoggerFactory.getLogger(SchedulingSpringConfig.class);
private final DemoProperties demoProperties;
public SchedulingSpringConfig(DemoProperties demoProperties) {
this.demoProperties = demoProperties;
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
LOGGER.info("Configuring scheduled task with cron expression: {}", demoProperties.getCronExpression());
taskRegistrar.addTriggerTask(triggerTask());
taskRegistrar.setTaskScheduler(taskScheduler());
}
#Bean
public TriggerTask triggerTask() {
return new TriggerTask(this::work, cronTrigger());
}
private void work() {
LOGGER.info("Doing work!");
}
#Bean
#RefreshScope
public CronTrigger cronTrigger() {
return new CronTrigger(demoProperties.getCronExpression());
}
#Bean
public ThreadPoolTaskScheduler taskScheduler() {
return new ThreadPoolTaskScheduler();
}
#Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (event.getKeys().contains("demo.config.cronExpression")) {
ScheduledTasksRefresher scheduledTasksRefresher = new ScheduledTasksRefresher(triggerTask());
scheduledTasksRefresher.afterPropertiesSet();
}
}
}
Then I use the ContextLifecycleScheduledTaskRegistrar to recreate the task.
public class ScheduledTasksRefresher extends ContextLifecycleScheduledTaskRegistrar {
private final TriggerTask triggerTask;
ScheduledTasksRefresher(TriggerTask triggerTask) {
this.triggerTask = triggerTask;
}
#Override
public void afterPropertiesSet() {
super.destroy();
super.addTriggerTask(triggerTask);
super.afterSingletonsInstantiated();
}
}
Properties definition:
#ConfigurationProperties(prefix = "demo.config", ignoreUnknownFields = false)
public class DemoProperties {
private String cronExpression;
public String getCronExpression() {
return cronExpression;
}
public void setCronExpression(String cronExpression) {
this.cronExpression = cronExpression;
}
}
Main definition:
#SpringBootApplication
#EnableConfigurationProperties(DemoProperties.class)
#EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Based on previous answers I added the following interface and used it on #RefreshScope annotated beans:
public interface RefreshScopeScheduled {
#EventListener(RefreshScopeRefreshedEvent.class)
default void onApplicationEvent() { /*do nothing*/ }
}

Spring Boot #Conditional annotation gets ignored

I am trying to enable scheduler based on certain properties (Condition) however it ignores my #Conditional annotation irrespective condition outcome. Any suggestions?
Conditional Class
public class SchedulerCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return property#1 || property#2
}
}
Configuration Class
#Configuration
public class Scheduler {
#Conditional(SchedulerCondition.class)
#Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public void processJobs() {
......
}
}

How to get spring scheduled task List with their Trigger info?

In my spring boot app, i am creating scheduled task programatically e.g
#Configuration
#EnableScheduling
public class AppConfig implements SchedulingConfigurer {
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler());
//following loop will get executed according to requirement for now //just looping 0-9
for(int i=0;i<10;i++){
MyRunnable myRunnable=new MyRunnable();
myRunnable.setID(i);
taskRegistrar.addTriggerTask(myRunnable,new CronTrigger("0 0/15 * * * *"));
}
}
#Bean(destroyMethod="shutdown")
public Executor taskScheduler() {
return Executors.newScheduledThreadPool(42);
}
#Bean
public MyTask myTask() {
return new MyTask();
}
}
I need some way to reschedule above task on the fly,can some one help me to complete above work? i would prefer rescheduling task by ID like myRunnable.getID().
Any help would be highly appreciated?
i have tried following in my Controller
#Autowired
private ScheduledTaskRegistrar scheduledTaskRegistrar;
But it is not injecting in controller

Spring MVC how to get progress of running async task

I would like to start an asynchronous task from within controller like in following code sniplet from Spring docs.
import org.springframework.core.task.TaskExecutor;
public class TaskExecutorExample {
private class MessagePrinterTask implements Runnable {
private int cn;
public MessagePrinterTask() {
}
public void run() {
//dummy code
for (int i = 0; i < 10; i++) {
cn = i;
}
}
}
private TaskExecutor taskExecutor;
public TaskExecutorExample(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
public void printMessages() {
taskExecutor.execute(new MessagePrinterTask());
}
}
afterwards in annother request (in the case that task is running) I need to check the progress of the task. Basicaly get the value of cn.
What would be the best aproach in Spring MVC a how to avoid syncronisation issues.
Thanks
Pepa Procházka
Have you looked at the #Async annotation in the Spring reference doc?
First, create a bean for your asynchronous task:
#Service
public class AsyncServiceBean implements ServiceBean {
private AtomicInteger cn;
#Async
public void doSomething() {
// triggers the async task, which updates the cn status accordingly
}
public Integer getCn() {
return cn.get();
}
}
Next, call it from the controller:
#Controller
public class YourController {
private final ServiceBean bean;
#Autowired
YourController(ServiceBean bean) {
this.bean = bean;
}
#RequestMapping(value = "/trigger")
void triggerAsyncJob() {
bean.doSomething();
}
#RequestMapping(value = "/status")
#ResponseBody
Map<String, Integer> fetchStatus() {
return Collections.singletonMap("cn", bean.getCn());
}
}
Remember to configure an executor accordingly, e.g.
<task:annotation-driven executor="myExecutor"/>
<task:executor id="myExecutor" pool-size="5"/>
One solution could be: in your async thread, write to a DB, and have your checking code check the DB table for progress. You get the additional benefit of persisting performance data for later evaluation.
Also, just use the #Async annotation to kick off the asynchronous thread - makes life easier and is a Spring Way To Do It.
Check this github source, it gives pretty simple way of catching status of the background job using #Async of Spring mvc.
https://github.com/frenos/spring-mvc-async-progress/tree/master/src/main/java/de/codepotion/examples/asyncExample
Ignoring synchronization issues you could do something like this:
private class MessagePrinterTask implements Runnable {
private int cn;
public int getCN() {
return cn;
}
...
}
public class TaskExecutorExample {
MessagePrinterTask printerTask;
public void printMessages() {
printerTask = new MessagePrinterTask();
taskExecutor.execute(printerTask);
}
...
}

Resources