Java Quartz Architecture Example
1. Introduction
In this post, we will take a closer look at the architecture of Quartz, a very popular open source job scheduling library that can be used in Java applications. We will see an architectural diagram and learn all the main and optional components of Quartz by providing code examples.
2. Project Setup
To run the code examples of this post, we will use the following technologies:
- Java 8
- Quartz 2.2.1
- SLF4J 1.7.26
- Logback 1.2.3
- Maven 3.3.3
- Eclipse 4.10.0
3. Quartz Architecture
In a nutshell, the main concept of Quartz is that a scheduler holds a list of jobs, in a persistence store, that are triggered at specific times or repeatedly. You can also register job or trigger listeners to the scheduler which perform some operations before and after the completion of jobs or triggers. The below diagram shows the process of scheduling jobs in a Quartz application.
4. Quartz Main Components
Let’s see in the following sections the main components that are involved in the Quartz scheduling process.
4.1 Scheduler
The org.quartz.Scheduler
is the main interface of a Quartz Scheduler. A Scheduler maintains a registry of JobDetails and Triggers. Once registered, the Scheduler is responsible for executing Jobs when their associated Triggers fire when their scheduled time arrives.
4.2 Scheduler Factory
The org.quartz.SchedulerFactory
is the interface responsible for creating Scheduler instances. Any class that implements this interface must implement the following methods:
Scheduler getScheduler() throws SchedulerException
Scheduler getScheduler(String schedName) throws SchedulerException
Collection<Scheduler> getAllSchedulers() throws SchedulerException
The first two methods return a Scheduler instance with the default or a given name. The third method returns all known Schedulers.
There are two implementations of the SchedulerFactory:
- StdSchedulerFactory –
org.quartz.impl.StdSchedulerFactory
- DirectSchedulerFactory –
org.quartz.impl.DirectSchedulerFactory
The StdSchedulerFactory creates a Scheduler instance based on the contents of a properties file which is by default named quartz.properties and is loaded from the current working directory. On the other hand, the DirectSchedulerFactory is a more simple implementation of SchedulerFactory and it is also a singleton.
Find below an example of how to create a Scheduler instance:
SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler();
From the above code, a new StdSchedulerFactory instance is created which returns a Scheduler instance by calling the getScheduler()
method.
4.3 Job
The org.quartz.Job
is the most crucial interface to be implemented by classes, as it represents a job to be performed. Below we see an example of a class implementing this interface:
public class SimpleJob implements Job { private final Logger log = LoggerFactory.getLogger(SimpleJob.class); public void execute(JobExecutionContext context) throws JobExecutionException { log.info("SimpleJob executed!"); } }
From the above code, we see that the SimpleJob
class implements the execute(JobExecutionContext context)
method of the Job
interface by printing just a single line of code.
4.4 Job Detail
The org.quartz.JobDetail
conveys the detail properties of a given Job instance. Quartz does not store an actual instance of a Job class, but instead allows you to define an instance of one, through the use of a JobDetail. Let’s see how this is done:
JobDetail job = JobBuilder.newJob(SimpleJob.class) .withIdentity("myJob", "myGroup") .build();
In the example above, we define a new job and tie it to the SimpleJob
class we created previously. Note that JobDetails are created using the org.quartz.JobBuilder
class.
4.5 Trigger
The org.quartz.Trigger
is the base interface with properties common to all Triggers. Triggers are the mechanism by which Jobs are scheduled. Many Triggers can point to the same Job, but a single Trigger can only point to one Job. The org.quartz.TriggerBuilder
is used to instantiate Triggers.
There are various implementations of Trigger. The most commonly used ones are:
- SimpleTrigger –
org.quartz.SimpleTrigger
- CronTrigger –
org.quartz.CronTrigger
The SimpleTrigger is used to fire a Job at a given moment in time, and optionally repeated at a specified interval. The CronTrigger is used to fire a Job at given moments in time, defined with Unix cron-like schedule definitions.
The following example shows how to create a Trigger using TriggerBuilder:
Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("myTrigger", "myGroup") .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(3) .repeatForever()) .build();
In the above code, we use the TriggerBuilder helper class to create a Trigger which runs every 3 seconds indefinitely. So far we haven’t tied any Job to the Trigger. This is what the Scheduler does.
4.6 Quartz Scheduler
The org.quartz.core.QuartzScheduler
is the heart of Quartz, an indirect implementation of the Scheduler interface, containing methods to schedule Jobs using Triggers. The above code schedules a Job using the Scheduler instance we created in a previous section:
scheduler.scheduleJob(job, trigger);
From the above code, we see that we don’t pass a Job to the Scheduler, but a JobDetail, in which we tie the Job. We also pass a Trigger which schedules the Job to run at specific times. Finally to start the Scheduler call:
scheduler.start();
And to shutdown the Scheduler:
scheduler.shutdown(boolean waitForJobsToComplete);
The waitForJobsToComplete argument is self-explanatory; it allows the Scheduler to wait the shutdown process until all jobs are completed.
5. Quartz Optional Components
Let’s now see some optional components that we can use for additional functionality in Quartz.
5.1 Job Store
The org.quartz.spi.JobStore
is the interface to be implemented by classes that want to provide a Job and Trigger storage mechanism for the QuartzScheduler‘s use. There are two implementations of the JobStore interface:
- RAMJobStore –
org.quartz.simpl.RAMJobStore
- JobStoreSupport –
org.quartz.impl.jdbcjobstore.JobStoreSupport
The RAMJobStore is the default JobStore which utilizes RAM as its storage device. The ramification of this is that access is extremely fast, but the data is completely volatile – therefore this JobStore should not be used if true persistence between program shutdowns is required. The JobStoreSupport contains base functionality for JDBC-based JobStore implementations.
You can enable the JobStoreSupport using JDBC, via the Quartz properties file:
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.dataSource=quartzDataSource org.quartz.jobStore.tablePrefix=QRTZ_ org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool org.quartz.dataSource.quartzDataSource.driver=com.mysql.jdbc.Driver org.quartz.dataSource.quartzDataSource.URL=jdbc:mysql://localhost:3306/quartz_schema org.quartz.dataSource.quartzDataSource.user=root org.quartz.dataSource.quartzDataSource.password=change_me
From the above, the JobStoreTX class (which extends the JobStoreSupport class) is used as the JobStore. For a more detailed explanation of how to use Quartz with JDBC see here.
5.2 Job Listener
The org.quartz.JobListener
is the interface to be implemented by classes that want to be informed when a JobDetail executes. The job listeners are attached to the scheduler and have methods that are called before and after the execution of jobs. In the following example we create a new JobListener class:
public class MyJobListener implements JobListener { private final Logger log = LoggerFactory.getLogger(MyJobListener.class); public String getName() { return MyJobListener.class.getSimpleName(); } public void jobToBeExecuted(JobExecutionContext context) { log.info("{} is about to be executed", context.getJobDetail().getKey().toString()); } public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { log.info("{} finised execution", context.getJobDetail().getKey().toString()); } public void jobExecutionVetoed(JobExecutionContext context) { log.info("{} was about to be executed but a JobListener vetoed it's execution", context.getJobDetail().getKey().toString()); } }
From the JobListener we create above, the ordering of the methods that would be executed is:
MyJobListener.jobToBeExecuted()
-> MyJob.execute()
-> MyJobListener.jobWasExecuted()
Finally we register the MyJobListener to the Scheduler:
scheduler.getListenerManager().addJobListener(new MyJobListener());
5.3 Trigger Listener
Similarly to JobListener, the org.quartz.TriggerListener
is the interface to be implemented by classes that want to be informed when a Trigger fires. In the following example we create a new TriggerListener class:
public class MyTriggerListener implements TriggerListener { private final Logger log = LoggerFactory.getLogger(MyTriggerListener.class); @Override public String getName() { return MyTriggerListener.class.getSimpleName(); } @Override public void triggerFired(Trigger trigger, JobExecutionContext context) { log.info("{} trigger is fired", getName()); } @Override public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) { log.info("{} was about to be executed but a TriggerListener vetoed it's execution", context.getJobDetail().getKey().toString()); return false; } @Override public void triggerMisfired(Trigger trigger) { log.info("{} trigger was misfired", getName()); } @Override public void triggerComplete(Trigger trigger, JobExecutionContext context, CompletedExecutionInstruction triggerInstructionCode) { log.info("{} trigger is complete", getName()); } }
From the TriggerListener we create above, the ordering of the methods that would be executed is:
MyTriggerListener.triggerFired()
-> MyJob.execute()
-> MyJobListener.
triggerComplete()
Finally we register the MyTriggerListener to the Scheduler:
scheduler.getListenerManager().addTriggerListener(new MyTriggerListener());
6. Java Quartz Architecture – Conclusion
In this post, we examined in more details the architecture of Quartz. We saw how the scheduling of jobs works by providing an architectural diagram. We also took a closer look at the main and optional components of Quartz such as the Scheduler, Job, Trigger etc.
7. Download the Eclipse project
You can download the full source code of the above examples here: Java Quartz Architecture Example