I need to create task on the fly in my app. How can I do that? I can get scheduler with #autowired annotation, but scheduler takes Runnable objects. I need to give Spring objects, so that my tasks can use #autowired annotation too.
#Autowired private TaskScheduler taskScheduler;
You just need to wrap your target object in a Runnable, and submit that:
private Target target; // this is a Spring bean of some kind
#Autowired private TaskScheduler taskScheduler;
public void scheduleSomething() {
Runnable task = new Runnable() {
public void run() {
target.doTheWork();
}
};
taskScheduler.scheduleWithFixedDelay(task, delay);
}
Related
I created a simple spring boot application with scheduled (#Scheduled) task. In that scheduled task, I would like to call async function with #Async, but I can see it still runs on the scheduling thread without switch to another thread. I also tried to customise executor, but no luck. Here are some codes.
I also already enable async in main class
public class scheduledService {
#Scheduled(fixedRateString = "${config.scheduleInterval}")
public void pollDataWithFixSchedule() {
AsyncService service = new AsyncService();
service.asyncCall();
service.asyncCall();
service.asyncCall();
asyncCall();
}
}
public class AsyncService {
#Async()
public void asyncCall(){
System.out.printly("Current thread -- {}",Thread.currentThread().getName()))
Thread.sleep(10000);
}
}
#Bean(name = "MyThreadPoolExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(7);
executor.setMaxPoolSize(42);
executor.setQueueCapacity(11);
executor.setThreadNamePrefix("MyThreadPoolExecutor-");
executor.initialize();
return executor;
}
#SpringBootApplication
#EnableScheduling
#EnableAsync
public class ScheduledApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(ScheduledApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.run(args);
}
}
according to Baeldung:
#Async has two limitations:
it must be applied to public methods only
self-invocation – calling the async method from within the same class – won't work
The reasons are simple – the method needs to be public so that it can be proxied. And self-invocation doesn't work because it bypasses the proxy and calls the underlying method directly.
so you can put your async method in a service and use it from there
you need to autowire AsyncService, do not create new object like
AsyncService service = new AsyncService();
Also, annotate your scheduledService class with #Service or #Component
#Service
public class scheduledService {
#Autowired
private AsyncService service ;
#Scheduled(fixedRateString = "${config.scheduleInterval}")
public void pollDataWithFixSchedule() {
service.asyncCall();
service.asyncCall();
service.asyncCall();
}
}
不要在同一个类中调用异步方法
Do not call asynchronous methods in the same class.
将异步任务单独放到一个类 并且在这个类上加上#Component
Put asynchronous tasks into a single class and add # component to this class
Use #EnableAsync on the top of class where you are creating async bean, not on the ScheduledApplication.
Does Spring Boot have something that allows to run a task several minutes later after a call?
Update:
Found that Spring Boot has ThreadPoolTaskScheduler. That allows to implement one time task execution with a time delay. See the answer below
First, we need to define a Bean
#Bean
public ThreadPoolTaskScheduler getScheduler(){
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
return scheduler;
}
The task that needs to be executed
public class WakeUpCallTask implements Runnable {
private String parameter1;
private String parameter2;
public WakeUpCallTask(String parameter1, String parameter2) {
super();
this.parameter1= parameter1;
this.parameter2= parameter2;
}
#Override
public void run() {
// Code that should be executed
}
}
Endpoint example - scheduling a task in 10000ms
#Autowired
ThreadPoolTaskScheduler scheduler;
#PostMapping(value = "/wake-me-up", consumes = "application/json")
#ResponseStatus(HttpStatus.OK)
public void scheduleCall(#RequestBody WakeMeUpRequest request) {
scheduler.schedule(
new WakeUpCallTask(request.getParameter1(), request.getParameter2()),
new Date(System.currentTimeMillis() + 10000));
}
I have used scheduled annotation to schedule the task reading cronexpression from db. But when we change cron expression using UI , it needs to restart the app. How to inject TaskScheduler and ScheduledFuture or any other approach to reschedule without restarting when user update from UI which will call this method updateTestJobSchedule . Any example as per below code will be much helpful.
Currently i have : -
#Configuration
public class BasicConfig {
#Autowired
private TestJobSchedulerRepository testJobSchedulerRepository;
#Bean
public String getCronExpressionFromDb(){
return testJobSchedulerRepository.findByIsActive(1).getCronExpression();
}
}
#RestController
#EnableScheduling
#RequestMapping("/api")
public class TestJobController {
#Scheduled(cron="#{getCronExpressionFromDb}")
public void doTestJob(){
// does the job
}
// This update cron expression when request comes from UI
#RequestMapping(value = "/update-testjob-schedule_application",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> updateTestJobSchedule(#RequestBody
TestJobScheduler testJobScheduler) throws URISyntaxException {
if(testJobScheduler.getIsActive() == 0){
testJobScheduler.setIsActive(1);
} else{
testJobScheduler.setIsActive(0);
}
testJobSchedulerRepository.save(testJobScheduler);
return new ResponseEntity<>("{\"status\":\"success\"}", HttpStatus.OK);
}
}
Your best bet is to use the TaskScheduler object. The taskScheduler bean is automatically created when you have #EnableScheduling on a class annotated with #Configuration. This object can be used to schedule job on the fly. Here a quick exemple of what you can do with it :
#Configuration
#EnableScheduling
public class CronJobConfiguration implements CommandLineRunner {
#Autowired
private TestJobSchedulerRepository testJobSchedulerRepository;
#Autowired
private TaskScheduler taskScheduler;
#Override
public void run(String... strings) {
String cronExpression = testJobSchedulerRepository.findByIsActive(1).getCronExpression();
Trigger trigger = new CronTrigger(cronExpression);
ScheduledFuture scheduledFuture = taskScheduler
.schedule(() -> {/* the job you want to run*/}, trigger);
/* Keep the scheduledFuture to be able to cancel the job later Ex : */
scheduledFuture.cancel(true);
}
}
You'll probably need to keep the ScheduledFuture object returned by the schedule method to be able to cancel the job at some point. Also you'll need to have a service that use the TaskScheduler so that you can schedule job when the updateTestJobSchedule is called.
I have a method that will be rarely called. This method collect garbage in db. I don't want to make user to wait for server response so i decided to call this method from new thread from my service layer. i'm using Spring.
Service class:
#Service
#Transactional
public class UploadService {
#Resource(name = "UploadDAO")
private UploadDao uploadDao;
Method that i don't want to wait for
public void collectBlobGarbage(){
Thread th = new Thread(new Runnable() {
#Override
public void run() {
uploadDao.collectBlobGarbage();
}
});
th.start();
}
Is it a good way to do like this?
If you have Spring on your classpath you might as well use #Async
#Async
public CompletableFuture<Void> collectBlobGarbage() throws InterruptedException {
CompletableFuture.completeFuture(uploadDao.collectBlobGarbage());
}
On your main class you need to use #EnableAsync like:
#SpringBootApplication
#EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
And you need an executor bean:
#Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("Stackoverflow-");
executor.initialize();
return executor;
}
I think the provided solution can potentially cause a lot of threads on you server.
As an alternative, you can consider using Executors.newSingleThreadExecutor in such a way that you will get an executor service that is limited only to one thread—so you will never create more than one thread—and that is what you need.
Also as you are using Spring, consider configuring a SingleThreadExecutor instantiation as a separate bean—in such a way that you will be able to change the implementation of ExecutorService in the future.
In an application, since I converted it from a classical Spring webapp (deployed in a system Tomcat) to a Spring Boot (V1.2.1) application I face the problem that the Quartz-based scheduled jobs are not working anymore.
I schedule these Quartz jobs like this:
// My own Schedule object which holds data about what to schedule when
Schedule schedule = scheduleService.get(id of the schedule);
String scheduleId = schedule.getId();
JobKey jobKey = new JobKey(scheduleId);
TriggerKey triggerKey = new TriggerKey(scheduleId);
JobDataMap jobData = new JobDataMap();
jobData.put("scheduleId", scheduleId);
JobBuilder jobBuilder = JobBuilder.newJob(ScheduledActionRunner.class)
.withIdentity(jobKey)
.withDescription(schedule.getName())
.usingJobData(jobData);
JobDetail job = jobBuilder.build();
TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger()
.forJob(jobKey)
.withIdentity(triggerKey)
.withDescription(schedule.getName());
triggerBuilder = triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(schedule.toCronExpression()));
Trigger trigger = triggerBuilder.build();
org.quartz.Scheduler scheduler = schedulerFactoryBean.getScheduler();
scheduler.scheduleJob(job, trigger);
ScheduledActionRunner:
#Component
public class ScheduledActionRunner extends QuartzJobBean {
#Autowired
private ScheduleService scheduleService;
public ScheduledActionRunner() {
}
#Override
public void executeInternal(final JobExecutionContext context) throws JobExecutionException {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
final JobDataMap jobDataMap = context.getMergedJobDataMap();
final String scheduleId = jobDataMap.getString("scheduleId");
final Schedule schedule = scheduleService.get(scheduleId);
// here it goes BANG since scheduleService is null
}
}
ScheduleService is a classical Spring service which fetches data from Hibernate.
As I said above, this worked fine until I moved to Spring Boot.
When I implemented this code with the classical Spring application, SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); did the trick to take care of autowiring the service.
What is needed to make this work again in the Spring Boot environment ?
Edit:
At the end I chose to move away from using Quartz in favour of Spring's ThreadPoolTaskScheduler.The code was much simplified and it works as expected.
The SpringBeanAutowiringSupport uses the web application context, which is not available in your case. If you need a spring managed beans in the quartz you should use the quartz support provided by spring. This will give you full access to all the managed beans.
For more info see the quartz section at spring docs at http://docs.spring.io/spring/docs/current/spring-framework-reference/html/scheduling.html. Also see following example of usage quartz with spring managed beans. Example is based on your code.
So you can change the first code snippet (where the quartz initialization is done) with follwoing spring alternatives.
Create job detail factory
#Component
public class ScheduledActionRunnerJobDetailFactory extends JobDetailFactoryBean {
#Autowired
private ScheduleService scheduleService;
#Override
public void afterPropertiesSet() {
setJobClass(ScheduledActionRunner.class);
Map<String, Object> data = new HashMap<String, Object>();
data.put("scheduleService", scheduleService);
setJobDataAsMap(data);
super.afterPropertiesSet();
}
}
Create the trigger factory
#Component
public class ActionCronTriggerFactoryBean extends CronTriggerFactoryBean {
#Autowired
private ScheduledActionRunnerJobDetailFactory jobDetailFactory;
#Value("${cron.pattern}")
private String pattern;
#Override
public void afterPropertiesSet() throws ParseException {
setCronExpression(pattern);
setJobDetail(jobDetailFactory.getObject());
super.afterPropertiesSet();
}
}
And finally create the SchedulerFactory
#Component
public class ActionSchedulerFactoryBean extends SchedulerFactoryBean {
#Autowired
private ScheduledActionRunnerJobDetailFactory jobDetailFactory;
#Autowired
private ActionCronTriggerFactoryBean triggerFactory;
#Override
public void afterPropertiesSet() throws Exception {
setJobDetails(jobDetailFactory.getObject());
setTriggers(triggerFactory.getObject());
super.afterPropertiesSet();
}
}
My answer not fully matches to you question, but Spring expose you another ability - to start cron-expression based scheduler on any service.
Using Spring.Boot you can configure your application to use scheduler by simple placing
#EnableScheduling
public class Application{
....
After that just place following annotation on public(!) method of #Service
#Service
public class MyService{
...
#Scheduled(cron = "0 * * * * MON-FRI")
public void myScheduledMethod(){
....
}