Scheduling with Spring


Scheduling is essentially the execution or notification of other software components when a predetermined time is reached. This is important in many enterprise applications. An application might require scheduling in a range of scenarios — for example:

  • To invoke a batch process on data that has accumulated since the last execution. Many financial applications work like this, even when implemented in modern languages.

  • To generate periodic reports about application usage and performance.

  • To execute periodic application maintenance tasks such as archiving or cleaning up temporary data.

The Spring Framework provides support for scheduling. Out-of-the-box integration is included for two scheduling technologies:

  • Timers: Java's built-in scheduling mechanism, expressed in the java.util.Timer class and associated classes.

  • Quartz scheduling: Quartz is an open-source solution provided by OpenSymphony (www.opensymphony.com/quartz).

In this section, we'll briefly summarize these two scheduling solutions before explaining how you can use them with Spring. We do not aim to provide a comprehensive introduction to scheduling, or a manual on use of either of these solutions.

Timers Versus Quartz

J2SE Timers are a simpler approach to scheduling than the more heavyweight Quartz scheduling framework.

However, the Quartz scheduler implementation provides far more functionality than Timers. For example, the following functionality is provided by Quartz implementation, and not supported by J2SE Timers:

  • Persistent jobs: In contrast to a Timer, Quartz provides support for persistent jobs. This enables jobs to maintain state, even between application restarts. This is a very important consideration in many applications.

  • Job management: Quartz allows for more extensive management of scheduled jobs by referencing jobs by their names and group names. This allows the application to trigger and reschedule jobs at will. It even allows the application to change the persistent job details.

  • Cron-like timing support: Through Quartz's CronTrigger, Quartz provides more flexible means, in a cron-like fashion (cron is a scheduled execution facility on Unix and Linux machines), to specify when jobs execute. Timers allow only the specification of the execution time and intervals.

  • Threading model: While Timers are implemented as a single thread, Quartz uses a thread pool for executing jobs. Among others, the size of the thread pool and the thread pool implementation can be specified using the Quartz properties. This allows for more flexible scheduling behavior, as jobs are queued when no free thread is available in the pool.

Quartz also provides a mature event model, allowing listeners on every aspect of the scheduling system (schedulers, triggers, and jobs). It allows for misfire instructions: instructions on what to do in cases where a job was not fired correctly (more on this in the Quartz Trigger section). Quartz also provides plug-in support to developers and an initialization servlet, which starts the Quartz scheduler in a J2EE-compliant manner. Finally, Quartz allows for remote job scheduling through an RMI connector and a Web Service, by means of a plug-in that exposes the scheduler interface through XML-RPC.

Important 

Although JDK Timers are adequate in cases where an application does not require one or more of the features mentioned, use of the Quartz implementation is recommended if non-trivial scheduling requirements seem likely in the future. Even if an application may not initially require persistent jobs, requirements may change to indicate a more sophisticated scheduling system.

As you would expect, Spring supports these two scheduling mechanisms in a consistent way. Thus many of the configuration concepts are the same, whichever you choose.

Timers

Timers offer a simple, lightweight approach to scheduling. Essentially, an instance of java.util.Timer can be used to schedule one or more tasks of type java.util.TimerTask. A Timer is responsible for thread management, allowing tasks to execute independently of other application activity. For more information about Timers, see http://java.sun.com/docs/books/tutorial/essential/threads/timer.html.

The simplest approach to scheduling using Timers in a Spring application is using the Method InvokingTimerTaskFactoryBean to create a task that invokes a certain method on a bean defined in your context. Let's assume a bean exampleBusinessObject with a method doSomething() has been defined:

 <bean     >     <property name="targetObject"><ref bean="exampleBusinessObject"/></property>     <property name="targetMethod"><value>doSomething</value></property> </bean> 

This factory bean creates an instance of java.util.TimerTask that will call the method doSomething() on a bean with the ID of exampleBusinessObject.

We can now ensure that the created task is scheduled to run every 10 minutes through the following bean definition:

 <bean        >     <property name="delay">         <value>60000</value>     </property>     <property name="period">         <value>600000</value>     </property>     <property name="timerTask">         <ref bean="methodInvokingTask"/>     </property> </bean> 

The ScheduledTimerTask class holds delay and periodicity information, as well as a reference to the actual TimerTask. The configuration shown in the preceding example ensures the created method-invoking task is scheduled to run every 10 minutes after an initial delay of 1 minute. Note that the only timing parameters to be configured are the initial delay and the period between repeated task executions. The ScheduledTimer Task also allows the task to be scheduled at a fixed-rate versus fixed-delay, where fixed-rate measures the delay between the start times, while fixed-delay measures the delay between the end time of an execution and the start time of the next.

Now we have declaratively specified what should be executed (the TimerTask) and when (through the ScheduledTimerTask). We cause these scheduled tasks to execute using the TimerFactoryBean, as follows:

<bean  >   <property name="scheduledTimerTasks">     <list>       <ref local="scheduledTask"/>     </list>   </property> </bean> 

A TimerFactoryBean allows us to specify one or more ScheduledTimerTasks. The TimerFactory Bean is responsible for creating and managing an instance of java.util.Timer.

Scheduling using Timers allows the definition of your own custom tasks. Consider the following very simple custom task. In order to schedule this task instead of the method-invoking task, change the reference to the methodInvokingTask to the customTask bean definition.

 package org.springframework.prospring.scheduling;      public class CustomTask extends TimerTask {    public void run() {         // do something    } } 

While the task can be much more complex, the simplest definition looks like the following:

 <bean        /> 

Of course, you have the opportunity to use Dependency Injection and AOP here, as in any Spring bean definition.

Quartz

The main difference for the developer between Timers and Quartz is that while Timers just have a notion of tasks that are scheduled, Quartz has the notion of four entities: a Scheduler, a Trigger, a Job, and a JobDetail.

Let's look at how the preceding example would look using Spring's Quartz integration. Remember the methodInvokingTask in the Timer section. The same job using Quartz would look like this:

 <bean    >     <property name="targetObject"><ref bean="exampleBusinessObject"/></property>     <property name="targetMethod"><value>doSomething</value></property> </bean> 

So far, there is little difference between the two. Quartz does not allow job details to be scheduled directly; first a Trigger needs to be defined. A Trigger is basically the definition of when to fire a job. Spring defines two predefined triggers: SimpleTriggerBean and CronTriggerBean. Let's use SimpleTriggerBean here, leaving discussion of CronTriggerBean until later in this chapter:

 <bean        >     <property name="startDelay">         <value>60000</value>     </property>     <property name="repeatInterval">         <value>50000</value>     </property>     <property name="jobDetail">        <ref bean="methodInvokingJobDetail"/>     </property> </bean> 

Now that the trigger has been defined, the trigger needs to be scheduled. For this, the Spring Framework provides the SchedulerFactoryBean. It initializes a scheduler at startup and handles the scheduler shutdown at application shutdown (more specifically, once the ApplicationContext is closed).

 <bean >     <property name="triggers">         <list>             <ref local="simpleTrigger"/>         </list>     </property> </bean> 

As you can see, the concepts are similar in configuring Timers and Quartz scheduling. The following table should clarify the three configuration elements.

Timer Term

Quartz Term

Notes

Timer task

Scheduled timer task

Job

What needs to be done

There is no corresponding object in the J2SE Timer API: merely arguments to the overloaded schedule() methods on java.util.Timer.

Trigger

When it should be done

TimerFactoryBean

SchedulerFactoryBean

Cause a number of scheduled tasks to be executed

So far, there is very little difference between scheduling using Timers and Quartz. However, the Scheduler FactoryBean provides access to the full power of Quartz. For example, it allows you to specify the data source to use and low-level properties of Quartz such as the details of the thread pool to use. For more options, please see the JavaDoc of the SchedulerFactoryBean.

However, let's dig a bit deeper into Spring's Quartz support, which, like Quartz itself, is more sophisticated than the Timer integration.

Job

Quartz defines the Job interface, which represents a job to be performed. The Spring Framework provides a simple implementation of the interface, the QuartzJobBean. This implementation allows the passed-in data map entries, as well as the entries in the SchedulerContext, to be applied as property values to the job. Consider implementing a job that uses the job data map to store its last execution date.

 package org.springframework.prospring.scheduling;      public class ExampleJob extends QuartzJobBean {     private long lastExecutionDate;          public void setLastExecutionDate(long lastExecutionDate) {         this.lastExecutionDate = lastExecutionDate;     }          protected void executeInternal(JobExecutionContext context)      throws JobExecutionException {         // do something (using the last execution date)              // store the last execution date in the job data map for the next execution         JobDataMap map = context.getJobDetail().getJobDataMap();         map.put("lastExecutionDate", System.currentTimeMillis());      } }   

The JobExecutionContext provides a reference to almost all aspects of the Quartz scheduling system. However, as instantiation of jobs is performed by Quartz, the created job must be defined in a job detail definition.

 <bean  >     <property name="name">         <value>example</value>     </property>     <property name="group">         <value>examples</value>     </property>     <property name="description">         <value>The example job that retains state by storing its last execution time in the job detail map.</value>     </property>     <property name="jobClass">         <value>org.springframework.prospring.scheduling.ExampleJob</value>     </property>          // any other properties that need to be set </bean> 

Note that the example job keeps state between executions, but it does not keep state during system restarts. By default, job details are not persisted between executions. The default Spring implementation of the Quartz scheduling uses an in-memory job store, which subsequently keeps state in memory. Therefore, application restarts (and crashes) result in the loss of state data. Set the data source on the scheduler factory and have your jobs implement the StatefulJob interface. Next to enforcing that the job data map is persisted after each job execution, it also enforces that multiple instances of the job will not be executed concurrently. This means that job execution will be delayed in case a trigger fires during a prior execution. Note that when using the MethodInvokingJobDetailFactoryBean to create jobs, setting the property concurrent to true results in the factory creating jobs that implement the StatefulJob interface.

Important 

Quartz jobs are not persisted by default. You need to implement the StatefulJob interface to have your jobs persisted between executions and between system restarts. You also need to set a data source on the scheduler factory.

Next to the StatefulJob job sub interface, Quartz provides an InterruptableJob. The implementation of an InterruptableJob should allow the job to be interrupted by the scheduler during execution, by means of the Scheduler interrupt(String, String) method.

Note that Quartz provides a number of convenience job implementations, with names that should be self-explanatory; for instance: EJBInvokerJob, JMXInvokerJob, SendMailJob, and NativeJob.

JobDetail

Quartz defines the notion of a JobDetail object, containing all data needed to run a Job. Spring defines a JobDetailBean to provide sensible defaults and make it aware of the application context it is defined in.

In many cases, jobs need to maintain state between executions and even during application restarts (and crashes), for instance last execution times. Quartz uses a JobDataMap to allow the job to store state data between executions. In case of persistent jobs, the data map is serialized into the data store. The JobDetailBean provides a convenience method to add data as a map to the underlying JobDataMap.

Important 

When using a persistent job, whose job details are stored in a data store, do not store references to the ApplicationContext or any Spring-managed bean in the job data. This may result in serialization errors and unexpected behavior.

When storing data in the jobs data map to keep state between executions, store only standard Java types, and even better, only primitive wrappers such as String, Integer, and so on. As the data map is serialized using default Java serialization, deserialization of the stored data fails on changes to the serialized classes if not handled correctly. Storing of non-serializable data will result in runtime exceptions. For storing nonpersistent data that needs to be available during job execution, the scheduler context is preferred. The scheduler context holds context/environment data and is available to jobs during execution, through the scheduler.

In addition to the already discussed properties, the job detail has two more properties: volatility and durability. Volatility specifies whether or not to persist the job itself for re-use between system restarts. Durability specifies whether or not the job should be stored after the job becomes orphaned, and no more triggers refer to it.

Trigger

As mentioned in the beginning of this section, jobs need to be associated with a trigger. The Simple TriggerBean was already used once, so now consider a cron-based trigger to specify the execution using a cron expression.

 <bean        >     <property name="jobDetail">         <ref bean="exampleJobDetail"/>     </property>     <property name="cronExpression">         <!-- run every morning at 4.15 am on every Monday to Friday -->         <value>0 15 4 ? * MON-FRI</value>     </property> </bean> 

The CronTriggerBean is a trigger that defines execution timing as Unix "cron-like" expressions. This allows the definition of a more complex execution schedule, such as the one in the preceding example. In short, a cron expression is a string comprising seven fields, where the last option is optional, separated by a white space. The following table lists all fields and possible values they may have.

Field Name

Allowed Values

Allowed Special Characters

Seconds

0-59

,-*/

Minutes

0-59

,-*/

Hours

0-23

,-*/

Day-of-month

1-31

,-*?/LC

Month

1-12 or JAN-DEC

,-*/

Day-of-week

1-7 or SUN-SAT

,-*?/LC#

Year (optional)

empty, 1970-2099

,-*/

For more information on cron expressions and the use of the special characters, use the extensive documentation in the JavaDoc of the Quartz CronTrigger class.

Both trigger implementations provided by the Spring framework provide the means to specify the mis- fire instruction of the trigger by its name. A misfire instruction indicates an instruction for the trigger on how to handle a misfire, for instance as a result of downtime. Among others, you can choose to ignore, re-execute, or even delete the trigger on a misfire.

Triggers also allow listeners to be added for listening to the state of a trigger. Normally there's no need to listen to the triggers, as the scheduler manages when triggers fire. It might, however, be useful to some applications, for example to veto a job execution or just to get informed of some job being executed.

Scheduler

The scheduler is the main part of the Quartz scheduling system. It maintains a registry of all job details and associated triggers; it manages job creation and execution when the associated triggers fire and it is responsible for persisting job state data.

In the beginning of this section, a simple scheduler was introduced. Now, have a look further at the most important configuration options for the SchedulerFactoryBean:

 <bean >     <property name="name">         <value>Example</value>     </property>     <property name="schedulerContextAsMap">         <map>             <entry key="baseurl"><value>www.springframework.org</value></entry>             <entry key="businessLogic"><ref bean="exampleBusinessLogic"/></entry>         </map>     </property>     <property name="waitForJobsToCompleteOnShutdown">         <value>true</value>     </property>     <property name="dataSource">         <ref bean="exampleDataSource"/>     </property>     <property name="transactionManager">         <ref bean="exampleTransactionManager"/>     </property>     <property name="configLocation">         <value>classpath:quartz.properties</value>     </property>     <property name="triggers">         <list>             <ref local="cronTrigger"/>         </list>     </property> </bean> 

First of all, in situations where multiple schedulers are used, always make sure to specify a name for the scheduler in order to actually have multiple scheduler instances. Next, two entries are set in the scheduler context to be available for all jobs during job execution. Remember that the use of the scheduler context is preferred for storing global data that should not be persisted. The scheduler also allows for a property called waitForJobsToCompleteOnShutdown that instructs the scheduler to wait for all jobs to complete before shutting down, as otherwise jobs can be terminated during execution. This property will be passed right on to the Quartz Scheduler class.

The main configuration items to consider when building an application that requires more complex, persistent jobs are the data source it uses and the location of the Quartz configuration. The first defines where to store the job details data map along with all Quartz related persistent data. The location of the Quartz configuration tells the created scheduler where to obtain its Quartz-specific configuration. A sample configuration file is listed next. More information on Quartz properties can be found in the example_quartz.properties file in the Quartz documentation. Note, however, that properties related to the data store should not be specified as they are configured by Spring.

 # example quartz.properties configuration org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount=5 org.quartz.threadPool.threadPriority=4 

Note that specifying a transaction manager on the scheduler factory is not needed because setting a Spring-managed data source should already handle transactions.

Last, as with triggers, schedulers also allow for listeners to be added. Next to adding listeners for scheduler events, it also allows global job and trigger listeners to listen to events all registered jobs and triggers publish.

Note 

Some users have commented on the heavy use of the BeanNameAware interface in the Quartz integration code. Many Quartz classes require a name to be set and Spring often exposes name properties to configure one. However, if you do not specify a name yourself, Spring will use the bean name (set by the application context if the bean in question is a BeanNameAware bean). Be sure to set the bean name (or ID) if you wire, for example, JobDetailBeans and CronTriggerBeans as inner beans; if you don't, Spring will use the class name of the bean, which might cause unpredictable situations.



Professional Java Development with the Spring Framework
Professional Java Development with the Spring Framework
ISBN: 0764574833
EAN: 2147483647
Year: 2003
Pages: 188

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net