I have created a simple demo app with SpringBoot and included the Actuator.
Tasks that are annotated with #Scheduled show up in the Actuator, but those started programmatically do not. Is there a way to get them to show up also?
I have annotated the #EnableScheduling.
My component looks like this:
#Component
public class DemoComponent {
private final TaskScheduler scheduler;
public DemoComponent(TaskScheduler scheduler) {
this.scheduler = scheduler;
}
#PostConstruct
public void init() {
scheduler.scheduleAtFixedRate(() -> System.out.println("Hi"), 1000);
}
#Scheduled(fixedRate = 1000)
public void work() {
System.out.println("Hello");
}
}
and the result from Actuator only shows the annotated task:
{
"cron": [],
"fixedDelay": [],
"fixedRate": [
{
"runnable": {
"target": "com.example.demo.DemoComponent.work"
},
"initialDelay": 0,
"interval": 1000
}
],
"custom": []
}
Looking at the code under https://github.com/spring-projects/spring-boot/pull/9623/commits/94b00a7b0681b050bba03b4c49edf2df2ec65376, I would say that you would need to register your scheduled job with ScheduledTaskRegistrar instead of directly registering it with TaskScheduler. So something like the following should do it:
#Component
public class DemoComponent {
private final ScheduledTaskRegistrar scheduler;
public DemoComponent(ScheduledTaskRegistrar scheduler) {
this.scheduler = scheduler;
}
#PostConstruct
public void init() {
scheduler.addFixedRateTask(() -> System.out.println("Hi"), 1000);
}
#Scheduled(fixedRate = 1000)
public void work() {
System.out.println("Hello");
}
}
Related
Which one executes first with default configs, scheduled or post construct?
#Component
public class x {
#Autowired
private Y y;
#PostConstruct
public void init() {
y.doInit();
}
#Scheduled(fixedDelay = 10000)
public void check() {
y.doCheck();
}
}
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 have a java job configured with #EnableScheduling and I am able to run job with annotation
#Scheduled(cron = "${job1.outgoingCron:0 45 15,18,20 * * MON-FRI}", zone = "America/New_York")
public void Process() {
}
But I don't want to run this job for US holidays what is the best way to update schedule.
Try to set your schedule explicitly, using CronTrigger or custom Trigger, for example:
#SpringBootApplication
#EnableScheduling
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Bean
public TaskScheduler taskScheduler() {
return new ThreadPoolTaskScheduler();
}
}
#Component
#RequiredArgsConstructor // Lombok annotation
public class StartUp implements ApplicationRunner {
#NonNull private final TaskScheduler scheduler;
#Override
public void run(ApplicationArguments args) throws Exception {
// Variant 1
scheduler.schedule(this::myTask, new CronTrigger(/* your schedule */));
// Variant 2
scheduler.schedule(this::myTask, this::myTriger);
}
private void myTask() {
//...
}
private Date myTrigger(TriggerContext triggerContext) {
//...
}
}
I am getting a 200 response from Spring Boot's shutdown endpoint, and I am seeing that the application context shuts down as expected, but then the JVM process itself remains alive forever. Is this the expected behavior of the shutdown endpoint, or is it expected that the process itself would also terminate gracefully?
In http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html, it says that the shutdown endpoint "allows the application to be gracefully shutdown (not enabled by default)".
Thanks Stéphane, I found what was preventing the JVM process from terminating after hitting the /shutdown endpoint. There was a ScheduledExecutor in one of my dependencies that was not being shut down with the application context, and it was preventing the JVM process from shutting down (even after the application context was closed). I wrote a simple example to show how to reproduce the behavior, and another example showing how to resolve it.
This example will NOT terminate the JVM process when you hit /shutdown endpoint:
#SpringBootApplication
public class AppSpringConfiguration {
public static void main(String[] args) {
SpringApplication.run(AppSpringConfiguration.class);
}
#Bean
public ClassWithExecutor ce() {
return new ClassWithExecutor();
}
#PostConstruct
public void startScheduledTask() {
ce().startScheduledTask();
}
#RestController
public static class BusinessLogicController {
#RequestMapping(value = "/hi")
public String businessLogic() {
return "hi";
}
}
public static class ClassWithExecutor {
ScheduledExecutorService es;
ClassWithExecutor() {
this.es = Executors.newSingleThreadScheduledExecutor();
}
public void startScheduledTask() {
es.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
System.out.println("Printing this every minute");
}
}, 0, 3, TimeUnit.SECONDS);
}
}
}
By adding a shutdown hook that shuts down the ScheduledExecutor when the application context is closing, the JVM process now gets terminated after hitting the /shutdown endpoint:
#SpringBootApplication
public class AppSpringConfiguration {
public static void main(String[] args) {
SpringApplication.run(AppSpringConfiguration.class);
}
#Bean
public ClassWithExecutor ce() {
return new ClassWithExecutor();
}
#Bean
ShutdownAction sa() {
return new ShutdownAction(ce());
}
#PostConstruct
public void startScheduledTask() {
ce().startScheduledTask();
}
#RestController
public static class BusinessLogicController {
#RequestMapping(value = "/hi")
public String businessLogic() {
return "hi";
}
}
public static class ShutdownAction implements ApplicationListener<ContextClosedEvent> {
private ClassWithExecutor classWithExecutor;
ShutdownAction(ClassWithExecutor classWithExecutor) {
this.classWithExecutor = classWithExecutor;
}
#Override
public void onApplicationEvent(ContextClosedEvent event) {
classWithExecutor.shutdown();
}
}
public static class ClassWithExecutor {
ScheduledExecutorService es;
ClassWithExecutor() {
this.es = Executors.newSingleThreadScheduledExecutor();
}
public void startScheduledTask() {
es.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
System.out.println("Printing this every minute");
}
}, 0, 3, TimeUnit.SECONDS);
}
public void shutdown() {
es.shutdownNow();
}
}
}
You have something that prevents the JVM to exit besides your Spring Boot application. If you don't and you have a sample projet that demonstrates the problem, then please create an issue and we'll have a look.
Instead of using the shutdown endpoint, you can use the spring-boot-maven-plugin as of 1.3 that has a start and stop goals to be used in typical integration tests scenarios.
If you have a scheduled executor running you should specify destroy method:
#Bean(destroyMethod = "shutdown")