Using OpenSymphony Quartz Scheduler in Spring

by Christophe Verré

Spring offers a few helper classes to do some scheduling in your application. In Spring 2.0, both the JDK's Timer objects and the OpenSymphony Quartz Scheduler are supported. Quartz is an open source job scheduling system that can be easily used with Spring.

Let's get started

As you may expect, both Spring and Quartz must be installed. Spring can be downloaded with all its dependencies. If you don't mind the size, it is recommended to download the bundle including dependencies. Quartz 1.5.x or above is needed in Spring 2.0. It is assumed that you already know how to include jar files in your classpath. If you don't, there's no better place than JavaRanch to ask. It is also assumed that you already know Spring basics.

About Quartz

Quartz offers five main structures to realize scheduling: The Job interface represents a job. A job does something, only one thing. Its API is very straightforward. Only one execute() method, which will be called by Quartz when a Job has to be executed. Some useful information can be retrieved from the JobExecutionContext that is passed to the execute() method.
public interface Job {
	void execute (JobExecutionContext ctx);
}

Some data can be passed to jobs via the JobDataMap class. If a JobDataMap is registered in a JobDetail, it can be accessed from the Job, via the JobExecutionContext which is passed to the execute() method of the Job interface. The JobDetail class is used to give some information about a particular Job. Jobs are started (or "fired") by triggers, which are represented by the Trigger class. Quartz has some convenient implementation class of Trigger, such as SimpleTrigger and CronTrigger. A SimpleTrigger acts like a basic timer, where you can define a starting time, an ending time, a repeat count and a repeat interval. A CronTrigger uses more advanced settings, using the "cron" Unix utility notation. The settings for a CronTrigger can be very specific, like "fire the job at 10:15am on every last Friday of every month during the years 2008, 2009, 2010". One notion is that jobs and triggers are given names, and may be assigned to a group. A name must be unique within the same group. So you can create a trigger for one group, and all jobs within that group will be executed.

Finally, the SchedulerFactory is used to get a Scheduler instance, which can be used to register jobs and triggers.
Let's illustrate this with a basic example : a simple job printing a welcome message, and a trigger firing the job every ten seconds.
First, the Job, only printing a welcome message:

public class RanchJob implements Job {
	public void execute (JobExecutionContext ctx) throws JobExecutionException {
		System.out.println("[JOB] Welcome at JavaRanch");
	}
}

Then the scheduler, registering a trigger and a job :
public class RanchSchedule {
	public static void main (String[] args) {		
		try {
			SchedulerFactory factory = new org.quartz.impl.StdSchedulerFactory();
	
			Scheduler scheduler = factory.getScheduler();
			scheduler.start();
			
			JobDetail jobDetail = new JobDetail("ranchJob", null, RanchJob.class);
			// Fires every 10 seconds
			Trigger ranchTrigger = TriggerUtils.makeSecondlyTrigger(10); 
			ranchTrigger.setName("ranchTrigger");
			
			scheduler.scheduleJob(jobDetail, ranchTrigger);
		} catch (SchedulerException ex) {
			ex.printStackTrace();
		}
	}
}
After executing RanchSchedule, [JOB] Welcome at JavaRanch is being printed out every 10 seconds.

There are some others interesting things like Priority, TriggerUtils, listeners, but they won't be discussed here.
If you want to know more about Quartz, there's a complete tutorial at Quartz's homepage.

Spring and Quartz

Spring's Quartz API resides in the org.springframework.scheduling.quartz package. There's quite a few classes there, but we'll only see those needed to fire up jobs. The wrapping of Quartz API is obvious. QuartzJobBean implements Job, JobDetailBean extends JobDetail. No need to say which class SimpleTriggerBean and CronTriggerBean extend. MethodInvokingJobDetailFactoryBean is a nifty class to call methods in any objects, instead of Quartz jobs. We'll see that last, but first let's make our previous RanchJob running. (Note that we won't use groups for our jobs and triggers)

Declaring Jobs

The JobDetailBean is used to declare jobs. We set the name of the job class, and if needed, some data the job may use. The job class extends QuartzJobBean. Here is the declaration :
<bean name="ranchJob" class="org.springframework.scheduling.quartz.JobDetailBean">
  <property name="jobClass" value="com.javaranch.journal.spring.quartz.RanchJob" />
  <property name="jobDataAsMap">
    <map>
      <entry key="message" value="Welcome at JavaRanch" />
    </map>
  </property>
</bean>

And the job class :
public class RanchJob extends QuartzJobBean {
	@Override
	protected void executeInternal (JobExecutionContext ctx) throws JobExecutionException {
		String message = (String) ctx.getJobDetail().getJobDataMap().get("message");
		System.out.println("[JOB] " + message);
	}
}

Note how we retrieve the message from the context. This message was set in the bean declaration, using the jobDataAsMap property. Spring actually calls setter methods for each object set in this map. Here is the Spring way to use the message in the job class :
public class RanchJob extends QuartzJobBean {
	private String message;
	
	public void setMessage (String message) {
		this.message = message;	
	}
	
	@Override
	protected void executeInternal (JobExecutionContext ctx) throws JobExecutionException {
		System.out.println("[JOB] " + message);
	}
}

Declaring Triggers

Declaring triggers is as simple as declaring a job. We'll only use a SimpleTriggerBean, which will start immediately, and repeat every ten seconds. You can refer to the CronTriggerBean API to check how to set a cronExpression for your trigger.
<bean id="ranchTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
    <property name="jobDetail" ref="ranchJob" />
    <property name="startDelay" value="0" />
    <property name="repeatInterval" value="10000" />
</bean>

Declaring the Scheduler

Now that we have a job associated to a trigger, we need to register the trigger. We declare Spring's SchedulerFactoryBean the following way :
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="ranchTrigger" />
        </list>
    </property>
</bean>	

Everything has been set up, all we need now is to load the context. The scheduler will be started automatically on initialization. I declared all the above beans in a file called "schedule.xml". I use ClassPathXmlApplicationContext here, so "schedule.xml" must be somewhere in the classpath.
public class RanchQuartz {
	public static void main (String[] args) throws SchedulerException {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("schedule.xml");
	}
}

Executing RanchQuartz will start the scheduler automatically. As expected, The RanchJob will be fired immediately, and repeated every 10 seconds.

Using the MethodInvokingJobDetailFactoryBean

We've seen so far how to fire Quartz jobs, but what if you want to call your own method of your own bean? With Spring, you are not tied to Quartz job instances, you can also use your own pojos to act as jobs. For example, you may wish to use the CronTrigger's advanced settings to fire up your own task.

Declaring the good old bean

Nothing special here. Just a good old bean, declared the good old way.
<bean name="welcomeBean" class="com.javaranch.journal.spring.quartz.RanchBean">
	<property name="message" value="Welcome at JavaRanch" />
</bean>

And the implementation:
public class RanchBean {
	private String message; 
	
	public void setMessage (String message) {
		this.message = message;
	}
	
	public void welcome() {
		System.out.println("[JOBBEAN] " + message);
	}
}

Declaring the MethodInvokingJobDetailFactoryBean

When declaring the MethodInvokingJobDetailFactoryBean, we need to tell Spring which method of which bean to call. Let's also modify our trigger to use this new bean:
<bean id="methodInvokingJob" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
  <property name="targetObject" ref="welcomeBean" />
  <property name="targetMethod" value="welcome" />
</bean>

<bean id="ranchTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
    <property name="jobDetail" ref="methodInvokingJob" />
    <property name="startDelay" value="0" />
    <property name="repeatInterval" value="10000" />
</bean>

Voilà! Executing the RanchQuartz application again will produce the same result, with "[JOBBEAN]" being printed out instead of "[JOB]".

A word of caution

A trigger can fire only one job, but a job may be fired by many triggers. This can bring a concurrency issue. In Quartz, if you don't want the same job to be executed simultaneously, you can make it implement StatefulJob instead of Job. In Spring, if you are using the MethodInvokingJobDetailFactoryBean, you can set its "concurrent" property to false.

Finally

Using Quartz with Spring instead as a standalone application offers the following benefits : As stated at the beginning of this article, Spring also supports JDK's Timer objects. If you want to use a simple timer for a simple task as above, you may consider using a TimerTask and Spring's ScheduledTimerTask and TimerFactoryBean instead. If you decide to use Quartz in Spring, there are many more properties that can be set for the scheduler, jobs and triggers. Refer to Spring's API to discover them all.