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
- The JobDetail class
- The Trigger abstract class
- The Scheduler interface
- The SchedulerFactory interface
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(); } } }
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 QuartzJobBean abstract class
- The JobDetailBean class
- The SimpleTriggerBean class
- The CronTriggerBean class
- The SchedulerFactoryBean class
- The MethodInvokingJobDetailFactoryBean class
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 :- Keeping all scheduling settings in one place. It makes scheduling easier to maintain. It looks a bit like having one "cron" file for scheduling tasks.
- Coding only jobs. The scheduler, triggers and job details can be configured in the context file.
- Using plain old java objects for jobs, instead of Quartz jobs.