Update Cron expression runtime in SpringBoot #Scheduled - spring-boot

#Configuration
#EnableScheduling
public class CustomScheduler {
#Autowired
private IAppUpdateService appUpdateService;
#Scheduled(cron = "#{#appUpdateService.findCroExpById()}")
public void job1() {
System.out.println(new Date());
}
}
Above snippet dosent work properly as expected is there any way to achieve this or any other approach is available!!!
I want to achieve like to retrieve cron expression from db at runtime.
User may change cron timing through UI,thats why i want to fetch it through service.

You can create a bean to get the values from database and use the bean in the corn. Detailed example is in the link :
http://mbcoder.com/dynamic-task-scheduling-with-spring/

Related

Unable to set the cron job schedule in application properties. SpringBoot ;scheduling of cron job at runtime using Scheduler

I am trying to schedule my rest service(with GET method) using
#Scheduled(cron = xyzzy.getTimeSchedule())
The schedule details are expected to be obtained from application properties from cloud during the application start up. however I get "The value for Annotation attribute Scheduled.cron must be a constant expression" compile time error. Please suggest. Also what might be the underlying issue like with the Spring annotation and the properties being available during the time application start up. Please guide or direct me to understand.TIA.
Using #Scheduled annotation you cannot provide a method for cron-expression which comes from the cloud. As java annotation needs constant-expression, which is a variable whose value cannot change once it has been assigned. For that, you need to use final keyword.
public static final String TIME_SCHEDULE = "0 0/30 8-10 * * *";
Then use that constant expression in your scheduler method,
#Scheduled(cron = TIME_SCHEDULE)
In your case, you should go for #TaskScheduler (from doc)
Task scheduler interface that abstracts the scheduling of Runnables based on different kinds of triggers.
This interface is separate from SchedulingTaskExecutor since it usually represents for a different kind of backend, i.e. a thread pool with different characteristics and capabilities. Implementations may implement both interfaces if they can handle both kinds of execution characteristics.
Replacing #Scheduled annotation with #TaskScheduler
First, autowire TaskScheduler and make sure that you annotated your main class with #EnableScheduling annotation to provide bean for TaskScheduler.
#Autowired
private TaskScheduler scheduler;
Now you need to schedule providing Runnable and CronTrigger arguments. It schedules the given Runnable, invoking it whenever the trigger indicates a next execution time.
That means you need to wrap your logic(currently this is the code from your #Schduled method body) into Runnable instance. And your xyzzy.getTimeSchedule() should be provided to CronTrigger constructor.
Runnable runnableTask = () -> {
//call REST API here
};
scheduler.schedule(runnableTask, new CronTrigger(xyzzy.getTimeSchedule());
Now you get rid of "The value for Annotation attribute Scheduled.cron must be a constant expression"
Finally this is what worked for me.
I have my property stored as key : value pair over the cloud.like so..
xyz.Schedule = */5 * * * * ;
public Class testController {
#Autowired
private Type type;
#Scheduled(cron = "${type.getSchedule()}")
#GetMapping(path = "/", produces = "application/json")
public void getmethod() { blah blah } }
I ran my application successfully and was being able to populate the property configured over cloud at the start of application, and was able to get response for my api as well.
Things I tried:
As suggested by Shekhar Rai in this discussion chain, declaring it as final variable, but was unable to access it in my method.
tried wrapping the method as a runnable task, couldn't do so.
arrived at: #Scheduled.cron always expects a constant parameter(like a string), but get() is dynamic , therefore wrapped it as a constant parameter.
#ManagedConfiguration
private ConfigClass configClass;
#bean
public String getSchedulerValue() {
return configClass.getSchedule();}
#Scheduled(cron="#{getSchedulerValue}")

Evaluate property from properties file in Spring's #EventListener(condition = "...")

I would like to make the execution of an event handler dependent on whether or not a property is set to true in a properties file.
#EventListener(ContextRefreshedEvent.class, condition = "${service.enabled}")
public void onStartup() { }
However, this does not seem to work. I am getting the following error on startup:
org.springframework.expression.spel.SpelParseException: EL1043E:(pos 1): Unexpected token. Expected 'identifier' but was 'lcurly({)'
Is it possible to use a property from a properties file as a condition here?
The issue is condition argument is expecting a SPEL.
This works try it out.
In your bean where you have this #EventListener, add these lines
public boolean isServiceEnabled() {
return serviceEnabled;
}
#Value("${service.enabled}")
public boolean serviceEnabled;
change your declaration of evnt listener like this
#EventListener(classes = ContextRefreshedEvent.class, condition = "#yourbeanname.isServiceEnabled()")
public void onStartup() { }
change yourbeanname with the correct bean name .
I had the same annoying experience (with Spring Boot 2.4.2 on Java11).
In my case I had the boolean property in a #ConfigurationProperties class anyways in the same java file and still struggled a bit. First the #ConfigurationProperties need to be annotated as #Component to actually be a valid Bean and can be used in SpEL.
And I had to use the same long attributeName for the ConfigurationProperties in the Service itself and the EventListener Annotation for the Bean resolution to work fine. I needed some the ConfigurationProperties values also in another place of the Service, that's why they needed to be (Constructor) Autowired as well...
So this worked for me:
#ConfigurationProperties("my.custom.path")
#Component //Important to make this a proper Spring Bean
#Data //Lombok magic for getters/setters etc.
class MyCustomConfigurationProperties {
boolean refreshAfterStartup = true;
}
#Service
#RequiredArgsConstructor //Lombok for the constructor
#EnableConfigurationProperties(MyCustomConfigurationProperties.class)
#EnableScheduling
public class MyCustomService {
private final MyCustomConfigurationProperties myCustomConfigurationProperties;
#EventListener(value = ApplicationReadyEvent.class, condition = "#myCustomConfigurationProperties.refreshAfterStartup")
public void refresh() {
//the actual code I want to execute on startup conditionally
}
}

Reset state before each Spring scheduled (#Scheduled) run

I have a Spring Boot Batch application that needs to run daily. It reads a daily file, does some processing on its data, and writes the processed data to a database. Along the way, the application holds some state such as the file to be read (stored in the FlatFileItemReader and JobParameters), the current date and time of the run, some file data for comparison between read items, etc.
One option for scheduling is to use Spring's #Scheduled such as:
#Scheduled(cron = "${schedule}")
public void runJob() throws Exception {
jobRunner.runJob(); //runs the batch job by calling jobLauncher.run(job, jobParameters);
}
The problem here is that the state is maintained between runs. So, I have to update the file to be read, the current date and time of the run, clear the cached file data, etc.
Another option is to run the application via a unix cron job. This will obviously meet the need to clear state between runs but I prefer to tie the job scheduling to the application instead of the OS (and prefer it to OS agnostic). Can the application state be reset between #Scheduled runs?
You could always move the code that performs your task (and more importantly, keeps your state) into a prototype-scoped bean. Then you can retrieve a fresh instance of that bean from the application context every time your scheduled method is run.
Example
I created a GitHub repository which contains a working example of what I'm talking about, but the gist of it is in these two classes:
ScheduledTask.java
Notice the #Scope annotation. It specifies that this component should not be a singleton. The randomNumber field represents the state that we want to reset with every invocation. "Reset" in this case means that a new random number is generated, just to show that it does change.
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
class ScheduledTask {
private double randomNumber = Math.random();
void execute() {
System.out.printf(
"Executing task from %s. Random number is %f%n",
this,
randomNumber
);
}
}
TaskScheduler.java
By autowiring in ApplicationContext, you can use it inside the scheduledTask method to retrieve a new instance of ScheduledTask.
#Component
public class TaskScheduler {
#Autowired
private ApplicationContext applicationContext;
#Scheduled(cron = "0/5 * * * * *")
public void scheduleTask() {
ScheduledTask task = applicationContext.getBean(ScheduledTask.class);
task.execute();
}
}
Output
When running the code, here's an example of what it looks like:
Executing task from com.thomaskasene.example.schedule.reset.ScheduledTask#329c8d3d. Random number is 0.007027
Executing task from com.thomaskasene.example.schedule.reset.ScheduledTask#3c5b751e. Random number is 0.145520
Executing task from com.thomaskasene.example.schedule.reset.ScheduledTask#3864e64d. Random number is 0.268644
Thomas' approach seems to be a reasonable solution, that's why I upvoted it. What is missing is how this can be applied in the case of a spring batch job. Therefore I adapted his example little bit:
#Component
public class JobCreatorComponent {
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Job createJob() {
// use the jobBuilderFactory to create your job as usual
return jobBuilderFactory.get() ...
}
}
your component with the launch method
#Component
public class ScheduledLauncher {
#Autowired
private ... jobRunner;
#Autwired
private JobCreatorComponent creator;
#Scheduled(cron = "${schedule}")
public void runJob() throws Exception {
// it would probably make sense to check the applicationContext and
// remove any existing job
creator.createJob(); // this should create a complete new instance of
// the Job
jobRunner.runJob(); //runs the batch job by calling jobLauncher.run(job, jobParameters);
}
I haven't tried out the code, but this is the approach I would try.
When constructing the job, it is important to ensure that all reader, processors and writers used in this job are complete new instances as well. This means, if they are not instantiated as pure java objects (not as spring beans) or as spring beans with scope "step" you must ensure that always a new instance is used.
Edited:
How to handle SingeltonBeans
Sometimes singleton beans cannot be prevented, in these cases there must be a way to "reset" them.
An simple approach would be to define an interface "ResetableBean" with a reset method that is implemented by such beans. Autowired can then be used to collect a list of all such beans.
#Component
public class ScheduledLauncher {
#Autowired
private List<ResetableBean> resetables;
...
#Scheduled(cron = "${schedule}")
public void runJob() throws Exception {
// reset all the singletons
resetables.forEach(bean -> bean.reset());
...

How to program the spring MVC cron without annotation

package com.test.cron;
#Service
public class CronJob {
protected static final Logger logger = Logger.getLogger(CronJob.class);
#Scheduled(cron="0 0 23 * * *")
public void demoServiceMethod()
{
logger.debug("Cron job started.");
}
}
Cron excution time will be often changed.
I have to exchange '#Scheduled' annotation to java code.
You can externalize this cron value into a properties file.
#Scheduled(cron="${schedularTime}")
and in your properties file ( example: application-dev.properties)
schedularTime=0 0/2 * * * ?
Old thread, but there is no real answer to the question.
Here is a way to schedule jobs without any annotations.
The example is in Kotlin, but works the same way in java.
#Component
class CliRunner(
private val scheduler: ThreadPoolTaskScheduler,
private val scheduledService: ScheduledService
) : CommandLineRunner {
override fun run(vararg args: String) {
scheduler.scheduleAtFixedRate(scheduledService::run, 10_000)
}
}
Inject ThreadPoolTaskScheduler and whatever you want to schedule, and call any of the scheduling methods on the scheduler.
My example schedules the run method to be executed every 10 seconds.
You could use a database for the configuration of schedule, Quartz had a capability to save its job meta data in a datasource.
Somebody had implemented something like this here.
https://github.com/davidkiss/spring-boot-quartz-demo

Spring Quatrz Dynamic Annotation Based Java Config

Sorry i had to ask it over here as i searched a lot tried many things but failed to achieve the result.
So what i am trying to do is i have a service which give me the list of the Jobs along with the interval at which they need to run so , what i want to do is loop over the list of the jobs and schedule them using Spring Quartz and i want to do them using the java config and not XML based .
This application will be a web application which will be running on a server, a maven project.
I found result and the are mainly using the Custom Annotation . Is there any sample or example which i can try or use.?
The problem is we are very new to all this and none of us have a idea how to proceed with this so any help is very appreciated.
Thanks in advance,
Vishesh
Have your #Confugation class implement SchedulingConfigurer. This allows you to schedule tasks programmatically. For example
#Configuration
#EnableScheduling
public class TaskConfiguration implements SchedulingConfigurer{
#Autowired
private TaskDao taskDao; // implement TaskDao to read tasks from DB
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar){
List<Task> tasks = taskDao.findAllTasks();
for(Task task : tasks){
Runnable taskJob = createTaskJob(task); // create task by reflection
taskRegistrar.addCronTask(taskJob , task.getCronExpression());
}
}
}
public class Task implements Serializable{
private String cronExpression;
private String jobClass;
//getters and setters
}
#ekem chitsiga workaround will also do. Following is another alternate to run your cron jobs based on the cron epressions you set.
#Configuration
#EnableScheduling
public class Scheduler {
private static final Logger logger = LoggerFactory
.getLogger(Scheduler.class);
#Autowired
private JobRepository jobRepository;
/*
* Cron expression ="0 0/1 * 1/1 * ?" for every minute
*/
#Scheduled(cron = "0 0/1 * 1/1 * ?")
public void sendNotification() {
logger.info("Initializing Cron job scheduler....");
//Do you scheduler specific work here
}
}

Resources