I want to be able to start my job with a REST controller, then when the job is started, it should run on a scheduled basis, until i stop it again with REST.
So this is my Controller:
#RestController
public class LauncherController {
#Autowired
JobLauncher jobLauncher;
#Autowired
Job job;
#RequestMapping("/launch")
public String launch() throws Exception {
...
jobLauncher.run(job, jobParameters);
}
This is some part of the Batch conf:
#Configuration
#EnableBatchProcessing
#EnableScheduling
public class BatchConfiguration {
#Autowired
public JobBuilderFactory jobBuilderFactory;
#Autowired
public StepBuilderFactory stepBuilderFactory;
#Scheduled(cron = "0/5 * * * * ?")
#Bean
public Job job() {
return jobBuilderFactory.get("job")
.incrementer(new RunIdIncrementer())
.flow(step1())
.end()
.build();
}
#Bean
public Step step1() {
return stepBuilderFactory.get("step1")
.<Person, Person> chunk(10)
.reader(reader())
.processor(processor())
.writer(writer())
.build();
}
I have also set the property spring.batch.job.enabled=false, since i do not want the jobs run as soon as the Spring Boot App starts.
Now i can call my Rest api lauch, and the job runs, but only once. Scheduler does not work. And I could not figure it our where exactly i should define my #Scheduled Annotation..
I would approach it in a way, that scheduled job runs always, but it does something only when the flag is set to true:
#Component
class ScheduledJob {
private final AtomicBoolean enabled = new AtomicBoolean(false);
#Scheduled(fixedRate = 1000)
void execute() {
if (enabled.get()) {
// run spring batch here.
}
}
void toggle() {
enabled.set(!enabled.get());
}
}
and a controller:
#RestController
class HelloController {
private final ScheduledJob scheduledJob;
// constructor
#GetMapping("/launch")
void toggle() {
scheduledJob.toggle();
}
}
In first you are defining the job:
#Bean
#Qualifier("fancyScheduledJob")
public Job job() {
return jobBuilderFactory.get("job")
.incrementer(new RunIdIncrementer())
.flow(step1())
.end()
.build();
}
In second you are initiating the execution of this job:
#Autowired
#Qualifier(value = "fancyScheduledJob")
private Job job;
#Autowired
private JobLauncher jobLauncher;
#Scheduled(cron = "0/5 * * * * ?")
public void launch() throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobInstanceAlreadyExistsException, NoSuchJobException {
jobLauncher.run(job, JobParametersBuilder()
.addLong("launchTime", System.currentTimeMillis())
.toJobParameters())
}
Also note that the "launchTime" paramter is introduced: by default spring batch is preventing launching the job with same parameter values.
While your schedule is quite tight - every 5 seconds you should be aware of concurrency. Or if you want to be assured that at each and every moment only 1 instance of the job is executed you can configure custom single threaded job launcher:
#Bean(name = "fancyJobExecutorPool")
public TaskExecutor singleThreadedJobExecutorPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setMaxPoolSize(1);
executor.setQueueCapacity(100500);
executor.setThreadNamePrefix("fancy-job-batch-");
return executor;
}
#Bean(name = "fancyJobLauncher")
public JobLauncher singleThreadedJobLauncher(JobRepository jobRepository)
{
SimpleJobLauncher sjl = new SimpleJobLauncher();
sjl.setJobRepository(jobRepository);
sjl.setTaskExecutor(singleThreadedJobExecutorPool());
return sjl;
}
And use this single threaded job launcher during launch time.
#Autowired
#Qualifier("fancyJobLauncher")
private JobLauncher jobLauncher;
With this your job instances will be executed one by one (but this doesn't limits parallel execution of steps inside of your job).
In this solution you will be able to schedule and unschedule pre defined jobs using http requests. In this example we will create a daily, weekly and an oneTime Job. The application is using Quartz.
<!--Quartz Scheduler -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
First we have to create an AutowiringSpringBeanJobFactory class extends SpringBeanJobFactory.
Subclass of {#link AdaptableJobFactory} that also supports
Spring-style * dependency injection on bean properties. This is
essentially the direct * equivalent of Spring's {#link QuartzJobBean}
in the shape of a Quartz * {#link org.quartz.spi.JobFactory}. * *
Applies scheduler context, job data map and trigger data map
entries * as bean property values. If no matching bean property is
found, the entry * is by default simply ignored. This is analogous to
QuartzJobBean's behavior.
public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
beanFactory = applicationContext.getAutowireCapableBeanFactory();
}
#Override
protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
The second part is to configure the quartz configuration. In this config we need to create a
SchedulerFactoryBean where we set global config and the application context,
JobDetailFactoryBean where we set our job, the jobGroup and the class,
CronTriggerFactoryBean where we set the cron expression.
QuartzConfig.class
#Configuration
public class QuartzConfig {
#Autowired
ApplicationContext context;
#Bean
public SchedulerFactoryBean quartzScheduler(){
SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean();
quartzScheduler.setOverwriteExistingJobs(true);
quartzScheduler.setSchedulerName("job-scheduler");
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(context);
quartzScheduler.setJobFactory(jobFactory);
return quartzScheduler;
}
#Bean
#Scope(value = "prototype")
public JobDetailFactoryBean getJobBean(String jobName, String jobGroup, Class<?> clazz){
JobDetailFactoryBean bean = new JobDetailFactoryBean();
bean.setJobClass(clazz);
bean.setGroup(jobGroup);
bean.setName(jobName);
return bean;
}
#Bean
#Scope(value = "prototype")
public CronTriggerFactoryBean getCronTriggerBean(String cronExpression, String triggerGroup){
CronTriggerFactoryBean bean = new CronTriggerFactoryBean();
bean.setCronExpression(cronExpression);
bean.setGroup(triggerGroup);
return bean;
}
}
So, after the config is done we are now able to create our jobs where the business logic will be placed. For that we have to create a class which implements Job.
#Component
public class DailyJob implements Job{
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("Daily Job runs!");
}
}
The DailyJob class is now ready to get scheduled. We want to schedule this job from outside via a http request. In this example we have a controller where we can send the jobname and the cron expression to schedule the dailyJob.
#Controller
public class JobController {
#Autowired
private Scheduler scheduler;
#Autowired
private ApplicationContext context;;
#ResponseBody
#RequestMapping(value = "/job/create/daily", method = RequestMethod.POST)
public ResponseEntity<JobModel> dailyJob(#RequestBody JobModel jobModel) throws SchedulerException {
JobDetail jobDetail = context.getBean(
JobDetail.class, jobModel.getName(), "MyDailyJob", DailyJob.class);
Trigger cronTrigger = context.getBean(
Trigger.class, jobModel.getCronExpression(), "MyDailyJob");
scheduler.scheduleJob(jobDetail, cronTrigger);
return new ResponseEntity<JobModel>(jobModel, HttpStatus.CREATED);
}
}
What we see here is that we will send a post request with a JobModel as #RequestBody. JobModel is a simple Pojo with two attributes name and cronExpression both Strings.
In this method we have to create the bean instances which we have configured previously in our config class. First create JobDetail with Quartz JobDetail.class, the name of your job, the name of the group and the Class which should be scheduled (in this case DailyJob.class). After that we have to create the Trigger with Quartz Trigger.class, the cronExpression and the group name.
After both beans are created we need to schedule the job now. So we have autowired Quartz Scheduler to schedule the job. After that the job is enabled and ready to do its job.
So let's test the stuff. Start the application and send a post request to /job/create/daily:
{"name":"Job 1", "cronExpression":"0 * * * * ?"}
Here we say that the job should run every minute (just to see that everything works). In your console you should see every minute Daily Job runs!.
And here are some additional things you can do. For example get a list of the scheduled jobs:
#ResponseBody
#RequestMapping("job/list")
public List<String> jobList() throws SchedulerException {
return scheduler.getJobGroupNames();
}
To delete a job you can create endpoints, too. For example:
#ResponseBody
#RequestMapping(value = "job/delete/daily", method = RequestMethod.POST)
public ResponseEntity<Boolean> deleteJob(#RequestBody JobModel jobModel) throws SchedulerException {
JobKey jobKey = new JobKey(jobModel.getName(), "MyDailyJob");
return new ResponseEntity<Boolean>(scheduler.deleteJob(jobKey), HttpStatus.OK);
}
You are free to create many different endpoints to get informations about currently running jobs, how often jobs were running, reschedule jobs and so on. Important is just, that your jobname and the jobgroup( in our case "MyDailyJob") are reusable. Those information are needed to create the jobKey.
P.S.: Just to show the other mappings for the other jobs:
#ResponseBody
#RequestMapping(value = "/job/create/weekly", method = RequestMethod.POST)
public ResponseEntity<JobModel> weeklyJob(#RequestBody JobModel jobModel) throws SchedulerException {
JobDetail jobDetail = context.getBean(JobDetail.class, jobModel.getName(), JobGroup.WEEKLY_GROUP.name(),
WeeklyJob.class);
Trigger cronTrigger = context.getBean(Trigger.class, jobModel.getCronExpression(),
JobGroup.WEEKLY_GROUP.name());
scheduler.scheduleJob(jobDetail, cronTrigger);
return new ResponseEntity<JobModel>(jobModel, HttpStatus.CREATED);
}
#ResponseBody
#RequestMapping(value = "/job/create/oneTime", method = RequestMethod.POST)
public ResponseEntity<JobModel> oneTimeJob(#RequestBody JobModel jobModel) throws SchedulerException {
JobDetail jobDetail = context.getBean(JobDetail.class, jobModel.getName(), JobGroup.ONE_TIME_GROUP.name(),
OneTimeJob.class);
Trigger cronTrigger = context.getBean(Trigger.class, jobModel.getCronExpression(),
JobGroup.ONE_TIME_GROUP.name());
scheduler.scheduleJob(jobDetail, cronTrigger);
return new ResponseEntity<JobModel>(jobModel, HttpStatus.CREATED);
}
The full application is on github
#Scheduled is defined on a method and not on a Bean. So create a new Class which will be a Bean
public class BatchConfiguration {
...
#Bean
public Job job() {
return new Job();
}
new Class:
public class Job {
#Scheduled(cron = "0/5 * * * * ?")
public Job job() {
return jobBuilderFactory.get("job")
.incrementer(new RunIdIncrementer())
.flow(step1())
.end()
.build();
}
I am using quartz in my apache isis project for scheduling. I have a class MyJob which implements org.quartz.Job and it has method execute which is called when scheduler triggers at given time.
My problem is, I have a class DemoService and it has a method showDemo() which I want to call from the execute method.
But when the scheduler runs, it throws Null Pointer Exception at demoService.showDemo().
I have not been able to inject any service in that class. It always gives NPE. How can I inject a service into the MyJob class?
Here is the code:-
public class MyJob implements Job {
#Inject
DemoService demoService;
public MyJob() {
}
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
demoService.showDemo();
}
}
The easiest approach is to put the logic you want to run in a subclass of AbstractIsisSessionTemplate, and then instantiate and execute it from your quartz job.
This technique is used by the Incode Platform's quartz job to run background commands, see here; the quartz module shows this from the quartz perspective (which I think you already have figured out).
HTH
Dan
Try this NullPointerException while deploying Quartz in Spring Boot
You need to use SpringBeanJobFactory to create Job with Spring's autowired beans.
class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
public void setApplicationContext(final ApplicationContext context) {
beanFactory = context.getAutowireCapableBeanFactory();
}
#Override
public Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job); //the magic is done here
return job;
}
}
And then when you do
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
scheduler = schedFact.getScheduler();
AutowiringSpringBeanJobFactory autowiringSpringBeanJobFactory = new AutowiringSpringBeanJobFactory();
autowiringSpringBeanJobFactory.setApplicationContext(applicationContext);
scheduler.setJobFactory(autowiringSpringBeanJobFactory);
I want to be able to run the SAME scheduled job in Spring.
After searching on the Internet, I figured out how to run multiple different jobs at the same time.
I have a #Service annotated class which has only one method, annotated with #Scheduled. I want to have multiple instances of this job running at the same time.
I am not using Quartz or Spring Batch( I have seen a lot of examples with Spring Batch).
The documentation doesn't clearly say if this can be achieved.
Yes, it can be easily achieved, but not with #Scheduled annotation.
How? Let me first explain how Spring works.
Spring from every method annotated with #Scheduled creates a new Runnable object and then schedules it for execution to the TaskScheduler (ThreadPoolTaskScheduler to be precise).
To see the exact code look at ScheduledAnnotationBeanPostProcessor.processScheduled().
So to fulfill your requirement: have multiple instances of the same job, but without using Quartz or Spring Batch we need to abandon #Scheduled annotation and do something a bit different than what the ScheduledAnnotationBeanPostProcessor does by default.
#Configuration
public class SpringConfig {
#Autowired
private TaskScheduler scheduler;
#Autowired
private YourServiceAnnotatedClass service;
#PostConstruct
public void startJobs() {
int numOfJobInstances = 3;
List<ImportantJob> jobs = IntStream.range(0, numOfJobInstances)
.mapToObj(i -> new ImportantJob("job" + i, service))
.collect(Collectors.toList());
jobs.forEach(this::schedule);
}
private void schedule(ImportantJob job) {
scheduler.schedule(job, new CronTrigger("*/5 * * * * *"));
// Above CronTrigger with 5 seconds was used, but feel free to use other variants, e.g.
// scheduler.scheduleAtFixedRate()
// scheduler.scheduleWithFixedDelay()
}
#Bean(destroyMethod = "shutdown")
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(3); // Adjust it to your needs
return taskScheduler;
}
}
class ImportantJob implements Runnable {
private final String name;
private final YourServiceAnnotatedClass service;
public ImportantJob(String name, YourServiceAnnotatedClass service) {
this.name = name;
this.service = service;
}
#Override
public void run() {
service.doSth();
}
}
As you can see, although #Scheduled is useful and simple, it is not very flexible. But with some effort you can gain much more control over scheduled tasks.
I have a code which runs at a regular interval.
Below is code for that
#EnableScheduling
#Component
public class Cer {
#Autowired
private A a;
#Autowired
private CacheManager cacheManager;
#Scheduled(fixedDelayString = "${xvc}")
public void getData() {
getCat();
getB();
return;
}
}
I want to change#Scheduled(fixedDelayString = "${xvc}") this based on a boolean say runOnce if runOnce is true #scheduled should run once only say on code startup.
Any advice how to achieve this.
Thanks in advance.
I would place the functionality that you want to schedule in a component:
#Component
public class ServiceToSchedule {
public void methodThatWillBeScheduled() {
// whatever
return;
}
}
And have two additional components that will be instantiated depending on a Profile. One of them schedules the task, and the other one just executes it once.
#Profile("!scheduled")
#Component
public class OnceExecutor {
#Autowired
private ServiceToSchedule service;
#PostConstruct
public void executeOnce() {
// will just be execute once
service.methodThatWillBeScheduled();
}
}
#Profile("scheduled")
#Component
#EnableScheduling
public class ScheduledExecutor {
#Autowired
private ServiceToSchedule service;
#Scheduled(fixedRate = whateverYouDecide)
public void schedule() {
// will be scheduled
service.methodThatWillBeScheduled();
}
}
Depending on which profile is active, your method will be executed just once (scheduled profile is not active), or will be scheduled (scheduled profile is active).
You set the spring profiles by (for example) starting your service with:
-Dspring.profiles.active=scheduled
I have an #Singleton bean that I use to store state that is to be shared with other beans/threads. The state that is to be shared is maintained in a HashMap. Other beans that require the services of this Singleton simply do an #Inject and invoke the methods.
Recently, we had to introduce Quartz Scheduler since we had some asynchronous jobs to be done. In one quartz job I lookup the Singleton using IntialContext lookup(), and use methods provided by the Singleton. Once this job executes, the other beans that use #Inject (for the Singleton) no longer get information maintained in the HashMap.
#Singleton(mappedName = "ClientSessionMgr")
public class ClientSessionMgr {
private final Map<String, ClientSession> clientSessions = new HashMap<String, ClientSession>();
/* Methods that manage the clientSessions data */
}
One amongst a few beans that Inject the above Singleton.
#Stateless(mappedName = "NwdCredProfRepo")
public class NwdCredProfRepo {
#Inject private ClientSessionMgr csm;
/* Bean Methods that invoke some methods of the singleton */
}
The Quartz Job that does a lookup of the singleton and then uses the methods provided by the same.
public class ClientSyncProbe implements Job {
private ClientSessionMgr csm;
#Override
public void execute(JobExecutionContext jec) throws JobExecutionException {
InitialContent ic = new InitialContext();
csm = (ClientSessionMgr) ic.lookup("java:app/SVWeb/ClientSessionMgr");
/* Some tasks, and finally */
ic.close()
}
}
I am guessing that I am missing a pattern principle. Any help?