Call a Scheduled Async bean method in a running application - spring-boot

I've got an Async method which is scheduled to run once a day:
#Component
public class MyClass {
//myCronProperty set to "0 25 20 * * *" in application.properties
#Scheduled(cron="{myCronProperty}")
#Async
#Override
public void doDailyTask() {
//Do work here
}
}
Is there a way of triggering doDailyTask() for testing purposes when the application is already running, perhaps by doing something clever with Groovy and reflection?
I figure I can always tweak the cron property to 1 minute in the future in my application.properties file, and then restart the application - but just wondered if there was a smarter method?

You should be able to simply inject the component into another class, for example a #RestController, and invoke the doDailyTask() method on it there.

Related

Spring #Async with Delay

I am trying to define an Async method using Spring that needs to be called at the end of a synchronous function, like below:
void syncFunction() {
...
asyncFuntion();
}
#Async
void asyncFunction() {
...
}
I need to add a delay of 10 seconds before the execution of asyncFunction begins and would like to avoid adding Thread.sleep(). I've also explored #Scheduled annotation, but it doesn't seem to work for a one-time scheduled task. What is the best way to achieve this using Spring? Thanks in advance.

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}")

#EventListener(ApplicationReadyEvent.class) starts only one method?

I'm trying to run a few methods after the Spring Boot project starts. I'm using #EventListener(ApplicationReadyEvent.class) annotation above the methods I want ran after the project launches. But it's only starting for one method at a time. I want multiple methods started at once. Is that the expected behavior for #EventListener(ApplicationReadyEvent.class)?
Its OK to place several (more than one) methods annotated with #EventListener all of them will be executed:
#Configuration
public class SampleConfiguration {
#Bean
public SampleBean sampleBean() {return new SampleBean();}
#EventListener
public void onApplicationReadyEvent(ApplicationReadyEvent event) {
System.out.println("Hello");
}
#EventListener
public void onApplicationReadyEvent2(ApplicationReadyEvent event) {
System.out.println("How are you");
}
}
This will print both "Hello" and "How are you" upon the succesfull start of the Application Context.
Now, its true that spring doesn't invoke them concurrently, it resolves all the listeners and calls them sequentially.
If you need a parallel execution you can create one listener that will be an "entry point" for the logical tasks that must be run in parallel and use Threads / Thread Pool Executors to run the code of your choice in parallel
Did you try adding #Async also above the method?
This listener is invoked synchronously. You can make it asynchronous by simply adding #Async annotation.
You can have the event listeners execute asynchronously by adding the following bean to your #Configuration class.
#Bean(name = "applicationEventMulticaster")
public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster =
new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
return eventMulticaster;
}
If you've defined a custom TaskExecutor then you should replace new SimpleAsyncTaskExecutor() with yourCustomTaskExecutorBeanMethod()
I had run into a similar issue where ApplicationReadyEvent was annoted for 3 function but in debugging we found it to fire for only 1 always. We added #Order and kept that function with the highest index(i.e. lowest priority), as in our case it was running for an indefinite while loop, and found that all the 3 functions were invoked seamlessly.

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());
...

Spring MVC 3 Time scheduled task starting at a specific time

I want to schedule a method call in Spring MVC to run after every two hours. This I can easily do via Spring 3.0 Time Scheduler. However, I want to kick the execution off only at a specific time of the day. That is, the method should only be invoked every 2 hours starting at a particular time.
For example - I want the method to run every 2 hours starting 6 AM.
The TimeScheduler interface has a scheduleAtFixedRate method which is overloaded to use startTime Date argument. I am not really sure how to use this.
Any idea how this can be achieved ?
You could take a look at the TaskScheduler interface. It provides a method scheduleAtFixedRate(Runnable task, Date startTime, long period) which returns a ScheduledFuture. You can use this with some simple Spring configuration:
<task:scheduler id="scheduler" pool-size="10"/>
This will create an instance of ThreadPoolTaskScheduler which implements TaskScheduler. Wire this bad boy into the class to call your specific method:
public class MyClass {
#Autowired
private TaskScheduler scheduler;
public void init() {
scheduler.scheduleAtFixedRate(new Runnable() {
public void run() {
myMethod();
}
}, new Date(), 1000 * 60 * 60 * 2); //This will start now and run every two hours
}
public void myMethod() {
// the method you want to invoke
}
}
I would take a look at Spring's support for Task Execution and Scheduling. Specifically check out the #Scheduled annotation (with which you can specify a schedule based on a cron expression):
#Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// something that should execute on weekdays only
}

Resources