I want to run a job based on the cron expression, but it should look for the cron expression from the DB after a rest call.
#SpringBootApplication
#EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Controller
**************
#PostMapping
#RequestMapping(value = "/addProduct")
public ResponseEntity saveProduct(#RequestBody Product product){
prodList.add(product);
startJob();
return new ResponseEntity("Product saved successfully", HttpStatus.OK);
}
#Scheduled(cron = "*/2 * * * * *")
public void startJob() {
System.out.println("printing"+ new Date());
}
Once the REST API will be hit, it should wait for the cron expression, which we can get from the database or from any method, then it should start at the specified time.
It is working like a normal method call.
You can make your controller to implement SchedulingConfigurer which has a callback called configureTasks() which will be invoked when Spring starts up.
In this callback , you can access the ScheduledTaskRegistrar which is a helper bean that can used to programatically schedule a task. So , save this helper bean into the controller 's internal field such that your controller can access it to schedule a new task later .
#RestController
public class MyController implements SchedulingConfigurer{
private ScheduledTaskRegistrar scheduledTaskRegistrar;
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
this.scheduledTaskRegistrar = taskRegistrar;
}
#PostMapping
#RequestMapping(value = "/addProduct")
public ResponseEntity saveProduct(#RequestBody Product product){
//.....
//Load the cron expression from database
String cronExpression = loadCronExpressionFromDatabase();
CronTask cronTask = new CronTask(() -> startJob() ,cronExpression);
scheduledTaskRegistrar.scheduleCronTask(cronTask);
return new ResponseEntity("Product saved successfully", HttpStatus.OK);
}
public void startJob() {
System.out.println("printing"+ new Date());
}
}
Related
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.
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*/ }
}
I am doing a Spring Boot Project
This is the main class
#SpringBootApplication
#ComponentScan(basePackages="blabla.quartz")
#EnableScheduling
public class App
{
public static void main( String[] args )
{
ConfigurableApplicationContext context =SpringApplication.run(App.class, args);
}
}
This is the controller
#RestController
public class Controller {
#Autowired
private SampleTask m_sampletask;
#Autowired TaskScheduler taskScheduler;
ScheduledFuture scheduledFuture;
int jobid=0;
#RequestMapping(value = "start/{job}", method = RequestMethod.GET)
public void start(#PathVariable String job) throws Exception {
m_sampletask.addJob(job);
Trigger trigger = new Trigger(){
#Override
public Date nextExecutionTime(TriggerContext triggerContext) {
org.quartz.CronExpression cronExp=null;
CronSequenceGenerator generator = new CronSequenceGenerator("0 * * ? * *");
Date nextExecutionDate = generator.next(new Date());
System.out.println(nextExecutionDate);
return nextExecutionDate;
}
};
scheduledFuture = taskScheduler.schedule(m_sampletask, trigger);
}
}
This is the ScheduleConfigurer implementation
#Service
public class MyTask implements SchedulingConfigurer{
#Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("somegroup-");
scheduler.setPoolSize(10);
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setAwaitTerminationSeconds(20);
return scheduler;
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
}
}
This is the class which I am calling from controller as scheduled job
#Component
public class SampleTask implements Runnable{
private List<String> jobs=new ArrayList<String>();
private String jobName;
public void addJob(String job){
jobName=job;
}
#Override
public void run() {
System.out.println("Currently running "+jobName);
}
}
How to stop the schedule job by a rest endpoint(Suppose "/stop/{jobname}").. When I have started the job using the "/start/{jobname}" rest endpoint?
You will probably need to use the quartz scheduler (if not already), and add a service with the required methods, then inject that service into your controller.
There's a decent example here: https://github.com/javabypatel/spring-boot-quartz-demo
If you want an in-memory job store (that isn't a database), checkout the RAMJobStore: http://www.quartz-scheduler.org/documentation/quartz-2.x/configuration/ConfigRAMJobStore.html
Stop Example
This is an excerpt from the demo project. Credit goes to Jayesh Patel: https://github.com/javabypatel
/**
* Stop a job
*/
#Override
public boolean stopJob(String jobName) {
System.out.println("JobServiceImpl.stopJob()");
try{
String jobKey = jobName;
String groupKey = "SampleGroup";
Scheduler scheduler = schedulerFactoryBean.getScheduler();
JobKey jkey = new JobKey(jobKey, groupKey);
return scheduler.interrupt(jkey);
} catch (SchedulerException e) {
System.out.println("SchedulerException while stopping job. error message :"+e.getMessage());
e.printStackTrace();
}
return false;
}
I am doing a spring boot project. Here is main method and a controller method
#SpringBootApplication
#ComponentScan(basePackages="nokia.quartz")
#EnableScheduling
public class App
{
public static void main( String[] args )
{
ConfigurableApplicationContext context =SpringApplication.run(App.class, args);
}
}
Controller:
#RestController
public class Controller {
#Autowired
private SampleTask m_sampletask;
#RequestMapping(value = "start", method = RequestMethod.GET)
public void start() throws Exception {
m_sampletask.work();
}
}
And a Sample class
#Component
public class SampleTask {
String cron_expression="0 0/1 * * * ?";
public void work() {
System.out.println("");
}
}
What the problem here is the rest call "/start" should make the SampleTask work() method a schedule job with the given cron expression when I am calling it from from rest endpoint "/start". Also I should be able to configure it at runtime with another rest endpoint as well as stop it..
You can make use combination of #EnableScheduling annotation and #Scheduled(cron= 0 0/1 * * * ?) for creating a scheduler in Spring boot.
Please add #EnableScheduling at starting of App Class and #Scheduled to the method which you want to run using cron.
PFB the useful link.
https://spring.io/guides/gs/scheduling-tasks/
You can try below option
#Autowired
TaskScheduler taskScheduler;
ScheduledFuture<?> scheduledFuture;
#RequestMapping(value = "start", method = RequestMethod.GET)
public void start() throws Exception {
scheduledFuture = taskScheduler.scheduleAtFixedRate(m_sampletask.work(), FIXED_RATE);
}
Runnable Code of Nawaz:
#Component
public class SampleTask implements Runnable {
String cron_expression="0 0/1 * * * ?";
#Override public void run() { System.out.println("Hello"); }
}
I have an #Scheduled task which send data to a client every sec throught a websocket.
My need is to start running my scheduled task only when the client ask for it.
Instead of, my task starts when my server starts. it's not the behavior i want.
currently, I have a bean of my scheduled task which is declared in my SchedulingConfigurer :
#Configuration
#EnableScheduling
public class SchedulingConfigurer implements org.springframework.scheduling.annotation.SchedulingConfigurer {
#Bean
public ThreadPoolTaskScheduler taskScheduler() {
return new ThreadPoolTaskScheduler();
}
#Bean
public ScheduledTask scheduledTask() {
return new ScheduledTask();
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setTaskScheduler(taskScheduler());
}
}
Here is my spring controller code :
#MessageMapping("/hello")
public void greeting() throws Exception {
//How do I start my scheduled task here ?
}
Maybe isn't possible to do that with #Scheduled annotation and i have to use the TaskScheduler interface ?
remove #Scheduled declaration from ScheduledTask class
implements Runnable interface instead of
#Component
//#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ScheduledTask implements Runnable {
private static final Logger log = LoggerFactory.getLogger(ScheduledTask.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
public void doWork() {
printMessage();
// TODO real work
}
private void printMessage() {
log.info("time to work: {}", dateFormat.format(new Date()));
}
#Override
public void run() {
doWork();
}
}
schedule Your task in controller area like this
#Controller
public class ScheduledTaskController {
#Autowired
private TaskScheduler taskScheduler;
#Autowired
private ScheduledTask scheduledTask;
#RequestMapping(value = "/task/run", method = RequestMethod.GET)
public String runTask() {
// start to run task every 5 sec.
taskScheduler.schedule(scheduledTask, new CronTrigger("0/5 * * * * ?"));
// ok, redirect
return "redirect:/task";
}
}
#Schedule is the declarative way, so not the point you're trying to achieve here.
You could create a Bean using one of the TaskScheduler implementations, such as ThreadPoolTaskScheduler and inject that bean in your application.
It has all the necessary methods to dynamically schedule tasks.