spring-quartz-schedule
Scheduling in Spring with Quartz
1. Overview
We’ll begin with a simple goal in mind – to easily configure a new scheduled job.
1.1. Key Components of the Quartz API
Quartz has a modular architecture. It consists of several basic components that can be combined as required. In this tutorial, we’ll focus on the ones that are common to every job: Job, JobDetail, Trigger and Scheduler.
Although we will use Spring to manage the application, each individual component can configured in two ways: the Quartz way or the Spring way (using its convenience classes).
We will cover both as far as possible for the sake of completeness, but either may be adopted. Let’s start building, one component at a time.
Further reading:
A Guide to the Spring Task Scheduler
A quick and practical guide to scheduling in Spring with Task Scheduler
Scheduling in Java EE
A demonstration of how to schedule tasks in Java EE using the @Schedule annotation and the timer service.
Introduction to Drools
Learn how to use Drools as a Business Rule Management System (BRMS).
2. Job and JobDetail
The API provides a Job interface having just one method – execute. It must be implemented by the class that contains the actual work to be done, i.e. the task. When a job’s trigger fires, the scheduler invokes the execute method, passing it a JobExecutionContext object.
The JobExecutionContext provides the job instance with information about its runtime environment, including a handle to the scheduler, a handle to the trigger, and the job’s JobDetail object.
In this quick example – the job delegates the task to a service class: _
_
@Component
public class SampleJob implements Job {
@Autowired
private SampleJobService jobService;
public void execute(JobExecutionContext context) throws JobExecutionException {
jobService.executeSampleJob();
}
}
2.2. JobDetail
2.3. Quartz JobBuilder
@Bean
public JobDetail jobDetail() {
return JobBuilder.newJob().ofType(SampleJob.class)
.storeDurably()
.withIdentity("Qrtz_Job_Detail")
.withDescription("Invoke Sample Job service...")
.build();
}
2.4. Spring JobDetailFactoryBean
Spring’s JobDetailFactoryBean provides bean-style usage for configuring JobDetail instances. It uses the Spring bean name as the job name, if not otherwise specified:
@Bean
public JobDetailFactoryBean jobDetail() {
JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
jobDetailFactory.setJobClass(SampleJob.class);
jobDetailFactory.setDescription("Invoke Sample Job service...");
jobDetailFactory.setDurability(true);
return jobDetailFactory;
}
A new instance of JobDetail is created for every execution of the job. The JobDetail object conveys the detailed properties of the job. Once the execution is completed, references to the instance are dropped.
3. Trigger
A Trigger is the mechanism to schedule a Job, i.e. a Trigger instance “fires” the execution of a job. There’s a clear separation of responsibilities between the Job (notion of task) and Trigger (scheduling mechanism).
In addition to Job, the trigger also needs a type that can be chosen based on the scheduling requirements.
Let’s say, we want to schedule our task to execute once every hour, indefinitely – we can use Quartz’s TriggerBuilder or Spring’s SimpleTriggerFactoryBean to do so.
3.1. Quartz TriggerBuilder
@Bean
public Trigger trigger(JobDetail job) {
return TriggerBuilder.newTrigger().forJob(job)
.withIdentity("Qrtz_Trigger")
.withDescription("Sample trigger")
.withSchedule(simpleSchedule().repeatForever().withIntervalInHours(1))
.build();
}
3.2. Spring SimpleTriggerFactoryBean
SimpleTriggerFactoryBean provides bean-style usage for configuring SimpleTrigger. It uses the Spring bean name as the trigger name and defaults to indefinite repetition, if not otherwise specified:
@Bean
public SimpleTriggerFactoryBean trigger(JobDetail job) {
SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
trigger.setJobDetail(job);
trigger.setRepeatInterval(3600000);
trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
return trigger;
}
4. Configuring the JobStore
JobStore provides the storage mechanism for the Job and Trigger, and is responsible for maintaining all the data relevant to the job scheduler. The API supports both in-memory and persistent stores.
4.1. In-Memory JobStore
For example purposes, we will use the in-memory RAMJobStore which offers blazing-fast performance and simple configuration via quartz.properties:
org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
The obvious drawback of the RAMJobStore is that it is volatile in nature. All the scheduling information is lost between shutdowns. If job definitions and schedules must be kept between shutdowns, the persistent JDBCJobStore must be used instead.
To enable an in-memory JobStore in Spring, we set this property in our application.properties:
spring.quartz.job-store-type=memory
4.2. JDBC JobStore
There are two types of JDBCJobStore: JobStoreTX and JobStoreCMT. They both do the same job of storing scheduling information in a database.
The difference between the two is how they manage the transactions that commit the data. The JobStoreCMT type requires an application transaction to store data, whereas the JobStoreTX type starts and manages its own transactions.
There are several properties to set for a JDBCJobStore. At a minimum, we must specify the type of JDBCJobStore, the data source, and the database driver class. There are driver classes for most databases, but StdJDBCDelegate covers most cases:
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource=quartzDataSource
Setting up a JDBC JobStore in Spring takes a few steps. Firstly, we set the store type in our application.properties:
spring.quartz.job-store-type=jdbc
Next, we need to enable auto-configuration and give Spring the data source needed by the Quartz scheduler. The @QuartzDataSource annotation does the hard work in configuring and initializing the Quartz database for us:
@Configuration
@EnableAutoConfiguration
public class SpringQrtzScheduler {
@Bean
@QuartzDataSource
public DataSource quartzDataSource() {
return DataSourceBuilder.create().build();
}
}
5. Scheduler
A Scheduler can be instantiated with a SchedulerFactory. Once created, Jobs and Triggers can be registered with it. Initially, the Scheduler is in “stand-by” mode, and its start method must be invoked to start the threads that fire the execution of jobs.
5.1. Quartz StdSchedulerFactory
By simply invoking the getScheduler method on the StdSchedulerFactory, we can instantiate the Scheduler, initialize it (with the configured JobStore and ThreadPool), and return a handle to its API:
@Bean
public Scheduler scheduler(Trigger trigger, JobDetail job, SchedulerFactoryBean factory)
throws SchedulerException {
Scheduler scheduler = factory.getScheduler();
scheduler.scheduleJob(job, trigger);
scheduler.start();
return scheduler;
}
5.2. Spring SchedulerFactoryBean
Spring’s SchedulerFactoryBean provides bean-style usage for configuring a Scheduler, manages its life-cycle within the application context, and exposes the Scheduler as a bean for dependency injection:
@Bean
public SchedulerFactoryBean scheduler(Trigger trigger, JobDetail job, DataSource quartzDataSource) {
SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
schedulerFactory.setConfigLocation(new ClassPathResource("quartz.properties"));
schedulerFactory.setJobFactory(springBeanJobFactory());
schedulerFactory.setJobDetails(job);
schedulerFactory.setTriggers(trigger);
schedulerFactory.setDataSource(quartzDataSource);
return schedulerFactory;
}
5.3. Configuring SpringBeanJobFactory
The SpringBeanJobFactory provides support for injecting the scheduler context, job data map, and trigger data entries as properties into the job bean while creating an instance.
However, it lacks support for injecting bean references from the application context. Thanks to the author of this blog post, we can add auto-wiring support to SpringBeanJobFactory like so:
@Bean
public SpringBeanJobFactory springBeanJobFactory() {
AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
6. Conclusion
That’s all. We have just built our first basic scheduler using the Quartz API as well as Spring’s convenience classes.
The key takeaway from this tutorial is that we were able to configure a job with just a few lines of code and without using any XML-based configuration.
The complete source code for the example is available in this github project. It is a Maven project which can be imported and run as-is. The default setting uses Spring’s convenience classes, which can be easily switched to Quartz API with a run-time parameter (refer to the README.md in the repository).