Scheduling Jobs Using JDK Timer

The most basic scheduling support with Spring is based on the JDK java.util.Timer class. When scheduling using Timer, you are limited to simple interval-based trigger definitions, which makes Timer-based scheduling only suitable for jobs that you need to execute just once at some given future time, or that you need to execute at some fixed frequency.

Trigger Types with Timer

Timer-based scheduling offers you the three types of triggers described in Table 14-1.

Table 14-1: Trigger Types for Timer-Based Scheduling

Trigger Type

Description

One-Off

When you use a one-off trigger, job execution is scheduled for some given point in the future, defined as the number of milliseconds from a given date. After the job executes, it is not rescheduled for further execution. We have found that one-off triggers are great for scheduling jobs that need to be done once, because you might forget to do them yourself. For instance, if a web application has scheduled maintenance coming up in a week, then we can schedule a task to switch on the "In Maintenance" page when the maintenance is due to begin.

Repeating, Fixed-Delay

When you use a fixed-delay trigger, you schedule the first execution of the job just like you did for a one-off trigger, but after that, it is rescheduled to execute after a given interval. When you are using fixed-delay, the interval is relative to the actual execution time of the previous job. This means that the interval between successive executions is always approximately the same, even if this means that executions occur "late" when compared to the original schedule. With this type of trigger, the interval you specify is the actual interval between subsequent executions. Use this approach when the interval between executions must be kept as constant as possible.

Repeating, Fixed-Rate

Fixed-rate triggers function in a similar way to fixed-delay triggers, but the next execution time is always calculated based on the initial scheduled execution time. This means that if a single execution is delayed, subsequent executions are not delayed as a result. With this type of trigger, the interval you specify is not necessarily the actual interval between subsequent executions. Use this approach when the interval between executions is not important, but the actual execution time is.

You may find it quite difficult to visualize the differences between fixed-delay and fixed- rate triggers and, unfortunately, it is very difficult for us to create an example that causes enough of a delay in execution to highlight the differences reliably. That said, here is a simple example that should highlight the differences.

Consider a task that starts executing at 13:00 and has a specified interval for 30 minutes. The task runs fine until 16:30 when the system experiences a heavy load and a particularly nasty garbage collection; these cause the actual execution time to be a minute late—16:31. Now, with fixed-delay scheduling it is the actual interval that is important, that is to say, we want 30 minutes between each actual execution, so the next execution is scheduled for 17:01 rather than 17:00. With fixed-rate scheduling, the interval defines the intended interval—that is to say, we intend the job to execute every 30 minutes based on start time, not on the time of the last job—so the job is scheduled for execution at 17:00.

Both of these trigger types have their uses. In general, you use fixed-rate triggers for realtime–sensitive operations such as those that must execute every hour on the hour. You use fixed-delay triggers for situations where you want the time between each execution to be as regular as possible or when you want to avoid the possibility of two executions happening too close together, as can happen with fixed-rate execution if a particular execution is delayed long enough.

Creating a Simple Job

To create a job to use with the Timer class, you simply extend the TimerTask class and implement the run() method to execute your job's logic. Listing 14-1 shows a simple TimerTask implementation that writes "Hello World" to stdout.

Listing 14-1: Creating a Basic TimerTask

image from book
package com.apress.prospring.ch14.timer;      import java.util.TimerTask;      public class HelloWorldTask extends TimerTask {          public void run() {         System.out.println("Hello World!");             } }
image from book

Here you can see that in the run() method, we simply write the "Hello World" message to stdout. Each time a job is executed, Timer invokes the TimerTask's run() method. The simplest possible trigger we can create for this job is a one-off trigger to start the job in 1 second; Listing 14-2 shows this.

Listing 14-2: Using a One-Off Trigger with the HelloWorldTask

image from book
package com.apress.prospring.ch14.timer;      import java.util.Timer;      public class OneOffScheduling {          public static void main(String[] args) {         Timer t = new Timer();         t.schedule(new HelloWorldTask(), 1000);     } }
image from book

In order to schedule a job using a given trigger when you are using the JDK Timer class, you must first create an instance of the Timer class and then create the trigger using one of the schedule() or scheduleAtFixedRate() methods. In Listing 14-2, we used the schedule() method to schedule an instance of HelloWorldTask to run after a delay of 1,000 milliseconds. If you run this example, after the initial delay of 1 second, you get the following message:

Hello World!

This kind of one-off trigger is fairly useless—how often are you going to need to schedule a one-off task to run an arbitrary period of time after application startup? For this reason, you can also specify an absolute date when you create a one-off trigger. So if we want to create a job to remind us 7 days before an important birthday, we can replace our call to Timer.schedule() with something like this:

Calendar cal = Calendar.getInstance(); cal.set(2005, Calendar.NOVEMBER, 30); t.schedule(new HelloWorldTask(), cal.getTime());

In this example, you can see that we created an instance of Calendar for the date November 30, 2005. Then, using the Calendar instance, we scheduled the HelloWorldTask to run. This is clearly more useful than the first example, because no matter what time the application starts, the job is always scheduled to run at the same time. The only drawback with this approach is that we will not be reminded about the birthday in 2006 or 2007 unless we explicitly add more triggers. By using a repeating trigger, we can get around this.

Both types of repeating trigger, fixed-delay and fixed-rate, are configured in the same way—you specify a starting point, using either a number of milliseconds relative to the call to schedule() or an absolute date, and then you specify an interval in milliseconds to control when subsequent executions occur. Remember that "interval" is interpreted differently depending on whether you are using a fixed-delay or fixed-rate trigger.

We can schedule the HelloWorldTask job to run every 3 seconds with a 1-second delay using the code shown in Listing 14-3.

Listing 14-3: Scheduling a Repeating Task

image from book
package com.apress.prospring.ch14.timer;      import java.util.Timer;      public class FixedDelayScheduling {          public static void main(String[] args) throws Exception{         Timer t = new Timer();         t.schedule(new HelloWorldTask(), 1000, 3000);     } }
image from book

If you run this application you will see the first "Hello World" message displayed after about 1 second, followed by further "Hello World" messages every 3 seconds. To schedule this job using a fixed-rate trigger, simply replace the call to Timer.schedule() with a call to Timer.scheduleAtFixedRate(), as shown in Listing 14-4.

Listing 14-4: Scheduling a Job Using a Fixed-Rate Trigger

image from book
package com.apress.prospring.ch14.timer;      import java.util.Timer;      public class FixedRateScheduling {          public static void main(String[] args) throws Exception {         Timer t = new Timer();         t.scheduleAtFixedRate(new HelloWorldTask(), 1000, 1000);             } }
image from book

As with the one-off trigger, you can start both fixed-delay and fixed-rate triggers using an absolute date. Using this approach, we can create a trigger for our birthday reminder example that runs on a given date and then repeats each year. This is shown in Listing 14-5.

Listing 14-5: Scheduling Birthday Reminders

image from book
package com.apress.prospring.ch14.timer;      import java.util.Calendar; import java.util.Timer;      public class SimpleBirthdayReminderScheduling {          private static final long MILLIS_IN_YEAR = 1000 * 60 * 60 * 24 * 365;          public static void main(String[] args) {         Timer t = new Timer();              Calendar cal = Calendar.getInstance();         cal.set(2005, Calendar.NOVEMBER, 30);         t.schedule(new HelloWorldTask(), cal.getTime());              t.scheduleAtFixedRate(new HelloWorldTask(), cal.getTime(),                 MILLIS_IN_YEAR);     } }
image from book

In this example, you can see that we calculate the number of milliseconds in a year, and then using a Calendar instance, we define a starting point of November 30th and then define the interval to be one year. Now every year on November 30th, provided that this application is running, the "Hello World" message is written to stdout. Clearly this is not a fully functional example, because there is no real notification mechanism, and each time we want to add a new birthday reminder, we need to change the code. In the next section, we create a more robust birthday reminder application using Spring's JDK Timer support classes.

Spring Support for JDK Timer Scheduling

As you saw in the previous section, it is easy to create and schedule jobs using the JDK Timer and TimerTask classes. That said, there are some problems with the approach we took in the previous examples. First, we created the TimerTask instances within the application rather than using Spring. For the HelloWorldTask, this is acceptable because we did not need to configure the job at all, but many jobs require some configuration data, and as a result, we should manage these using Spring to allow for easy configuration. Second, the trigger information is hard- coded into the application, meaning that any changes to the time a job is triggered require a change to the application code along with a recompilation. Finally, scheduling new jobs or removing a job requires changes to the application code when ideally, we should be able to configure this externally. By using Spring's Timer support classes, we can externalize all job and trigger configuration as well as hand over control of Timer creation to Spring, thus allowing jobs and their triggers to be defined externally.

The core of Spring's Timer support comes in the form of the ScheduledTimerTask and TimerFactoryBean classes. The ScheduledTimerTask class acts as a wrapper around your TimerTask implementations and allows you to define trigger information for the job. Using the TimerFactoryBean, you can have Spring automatically create Timer instances for a given list of ScheduledTimerTask beans using the trigger configuration data when creating the trigger.

Using ScheduledTimerTask and TimerFactoryBean

Before we dive in and look at our new and improved birthday reminder application, we should first look at the basics of how ScheduledTimerTask and TimerFactoryBean work. For each scheduled job you want to create, you need to configure an instance of the job class and an instance of ScheduledTimerTask containing the trigger details. You can share the same TimerTask instance across many ScheduledTimerTask instances if you want to create many triggers for the same job. Once you have these components configured, simply configure a TimerFactoryBean and specify the list of ScheduledTimerTask beans. Spring then creates an instance of Timer and schedules all the jobs defined by the ScheduledTimerTask beans using that Timer class.

This might sound complex at first, but in reality it is not. Listing 14-6 shows a simple configuration for scheduling the HelloWorldTask to run every 3 seconds with a delay of 1 second before the first execution.

Listing 14-6: Configuring Job Scheduling Using TimerFactoryBean

image from book
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean  />          <bean             >         <property name="delay">             <value>1000</value>         </property>         <property name="period">             <value>3000</value>         </property>         <property name="timerTask">             <ref local="job"/>         </property>     </bean>          <bean             >         <property name="scheduledTimerTasks">             <list>                 <ref local="timerTask"/>             </list>         </property>     </bean> </beans>
image from book

Here you can see that we have configured a bean, job, of type HelloWorldTask and then using this bean, we have configured a bean of type ScheduledTimerTask, setting the delay to 1,000 milliseconds and the period to 3,000 milliseconds. The final part of the configuration is the timerFactory bean, which is passed a list of beans of type ScheduledTimerTask. In this case, we only have one task to schedule, represented by the timerTask bean. Be aware that when specifying trigger information using ScheduledTimerTask, you can only supply a delay in milliseconds, not an initial date for startup. We show you a way around this when we build the birthday reminder application in the next section.

With all of the scheduling and job definition information contained in the configuration, there is very little for our sample application to do. In fact, all we need to do is load the ApplicationContext, and Spring goes away and creates the Timer class and schedules the HelloWorldTask as per the configuration file. This code is shown in Listing 14-7.

Listing 14-7: The TimerFactoryBeanExample Class

image from book
package com.apress.prospring.ch14.timer;      import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext;      public class TimerFactoryBeanExample {          public static void main(String[] args) throws Exception {         ApplicationContext ctx = new FileSystemXmlApplicationContext(                 "./ch14/src/conf/timer.xml");         System.in.read();     } }
image from book

If you run this application, you will see that the message "Hello World" is written to stdout every 3 seconds after an initial delay of 1 second. As you can see from this example, it is very simple to configure job scheduling external to your application's code. Using this approach, it is much simpler to make changes to a job's schedules or to add new scheduled jobs and remove existing ones.

A More Comprehensive Birthday Reminder Application

In this section, we create a more complex birthday reminder application using Spring's Timer support. With this example, we want to be able to schedule multiple reminder jobs, each with a specific configuration, to identify whose birthday the reminder is for. We also want to be able to add and remove reminders without having to modify the application code.

To get started, we need to create a job to perform the actual reminder. Because we are going to create these jobs using Spring, we can allow all configuration data to be provided using dependency injection. Listing 14-8 shows the BirthdayReminderTask.

Listing 14-8: The BirthdayReminderTask

image from book
package com.apress.prospring.ch14.timer.bday;      import java.util.TimerTask;      public class BirthdayReminder extends TimerTask {          private String who;          public void setWho(String who) {         this.who = who;     }          public void run() {         System.out.println("Don't forget it is " + who                 + "'s birthday is 7 days");     } }
image from book

Notice here that we defined a property on the task, who, that allows us to specify who the birthday reminder is for. In a real birthday reminder application, the reminder would no doubt be sent to e-mail or some similar medium. In the next chapter, where we cover Spring e-mail support, we flesh out this example even more so it sends reminders to e-mail. For now, however,

you'll have to be content with reminder messages written to stdout!

With this task complete, we are almost ready to move on to the configuration stage. However, as we pointed out earlier, you cannot specify the start time of a scheduled job using a date when you are using ScheduledTimerTask. This is problematic for our sample application because we do not want to have to specify reminder dates as a relative offset to the startup time of the application! Thankfully, we can overcome this problem quite easily by extending the ScheduledTimerTask class and overriding the getDelay() method used by TimerFactoryBean

to determine what delay it should assign to a trigger. At the same time, we can also override the getPeriod() method to return the number of milliseconds in a year so that you do not have to add that literal into configuration files! Listing 14-9 shows the code for our custom ScheduledTimerTask, BirthdayScheduledTask.

Listing 14-9: Customizing ScheduledTimerTask

image from book
package com.apress.prospring.ch14.timer.bday;      import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date;      import org.springframework.scheduling.timer.ScheduledTimerTask;      public class BirthdayScheduledTask extends ScheduledTimerTask {          private static final long MILLIS_IN_YEAR = 1000 * 60 * 60 * 24 * 365;          private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");          private Date startDate;          public void setDate(String date) throws ParseException {         startDate = dateFormat.parse(date);     }          public long getDelay() {         Calendar now = Calendar.getInstance();         Calendar then = Calendar.getInstance();         then.setTime(startDate);              return (then.getTimeInMillis() - now.getTimeInMillis());     }          public long getPeriod() {         return MILLIS_IN_YEAR;     } }
image from book

Here you can see that we define a new property for the BirthdayScheduledTask class, date, which allows us to specify the start date as a date rather than a delay period. This property is of type String, because we use an instance of SimpleDateFormat configured with the pattern yyyy-MM-dd to parse dates such as 2005-11-30. You can see that we override the getPeriod() method, which TimerFactoryBean uses when it configures the interval for the trigger, to return the number of milliseconds in a year. Also notice that we override getDelay(), and using the Calendar class, we calculate the number of milliseconds between the current time and the specified start date. This value is then returned as the delay. With this complete, we can now complete the configuration for our sample application, as shown in Listing 14-10.

Listing 14-10: Configuring the Birthday Reminder Application

image from book
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean             >         <property name="date">             <value>2005-11-30</value>         </property>         <property name="fixedRate">             <value>true</value>         </property>         <property name="timerTask">             <bean >                 <property name="who">                     <value>Mum</value>                 </property>             </bean>         </property>     </bean>          <bean             >         <property name="scheduledTimerTasks">             <list>                 <ref local="mum"/>             </list>         </property>     </bean> </beans>
image from book

This code should look familiar to you by now. Notice that we used our BirthdayScheduledTask class in place of the ScheduledTimerTask class and instead of specifying a delay and a period, we have simply specified the date. Also, we rely on the overridden getDelay() and getPeriod() methods to provide the TimerFactoryBean with the delay and period values. In addition, notice that we set the fixedRate property of the BirthdayScheduledTask bean to true. This property is inherited from ScheduledTimerTask; TimerFactoryBean uses it to decide whether or not it should create a fixed-rate or fixed-delay trigger.

Scheduling Arbitrary Jobs

When you are scheduling jobs, you often need to schedule the execution of logic that already exists. If this is the case, you might not want to go to the trouble of creating a TimerTask class just to wrap your logic. Thankfully, you don't have to. Using the MethodInvokingTimerTaskFactoryBean, you can schedule the execution of any method on any given bean or a static method on a specific class; you can even provide method arguments if your logic method requires them.

As an example of this, consider the FooBean shown in Listing 14-11.

Listing 14-11: The FooBean Class

image from book
package com.apress.prospring.ch14.timer;      public class FooBean {          public void someJob(String message) {         System.out.println(message);     } }
image from book

If we want to schedule the someJob() method to run every 3 seconds with a given argument rather than create a TimerTask just to do that, we can simply use the MethodInvokingTimerTaskFactoryBean to create a TimerTask for us. The configuration for this is shown in Listing 14-12.

Listing 14-12: Using MethodInvokingTimerTaskFactoryBean

image from book
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean  />          <bean                  symbol">¿                                               MethodInvokingTimerTaskFactoryBean">         <property name="targetObject">             <ref local="target"/>         </property>         <property name="targetMethod">             <value>someJob</value>         </property>         <property name="arguments">             <value>Hello World!</value>         </property>     </bean>          <bean   >         <property name="delay">             <value>1000</value>         </property>         <property name="period">             <value>3000</value>         </property>         <property name="timerTask">             <ref local="task"/>         </property>     </bean>          <bean                 >         <property name="scheduledTimerTasks">             <list>                 <ref local="timerTask"/>             </list>         </property>     </bean>      </beans> 
image from book

Here you can see that we can replace the definition of our own custom TimerTask bean with a definition using the MethodInvokingTimerTaskFactoryBean. In order to configure MethodInvokingTimerTaskFactoryBean, we specify the target of the invocation as a reference to another bean, the method to execute, and the argument to use when executing. The TimerTask supplied by MethodInvokingTimerTaskFactoryBean is used in the normal way, wrapped in a ScheduledTimerTask, and passed to the TimerFactoryBean.

Listing 14-13 shows a simple driver program to test this out.

Listing 14-13: Testing the MethodInvokingTimerTaskFactoryBean

image from book
package com.apress.prospring.ch14.timer;      import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext;      public class MethodInvokerScheduling {          public static void main(String[] args) throws Exception {         ApplicationContext ctx = new FileSystemXmlApplicationContext(                 "./ch14/src/conf/timerMethodInvoker.xml");         System.in.read();     } }
image from book

Running this example gives you the now familiar timed appearance of "Hello World!" messages on your console. Clearly, using MethodInvokingTimerTaskFactoryBean removes the need to create custom TimerTask implementations that simply wrap the execution of a business method.

Timer Scheduling Summary

JDK Timer-based scheduling provides support for an application's basic scheduling needs using a simple and easy-to-understand architecture. Although the trigger system for JDK Timer is not extremely flexible, it does provide basic schemes that allow you to control simple scheduling. Using Spring's support classes for Timer, you externalize a task scheduling configuration and make it easier to add and remove tasks from the scheduler without having to chance any application code. Using MethodInvokingTimerTaskFactoryBean, you avoid having to create TimerTask implementations that do nothing more than invoke a business method, thus reducing the amount of code you need to write and maintain.

The main drawback of JDK Timer scheduling comes when you need to support complex triggers such as a trigger to execute a job every Monday, Wednesday, and Friday at 15:00. In the next part of this chapter, we look at the Quartz scheduling engine, which provides much more comprehensive support for scheduling and, just like Timer, is fully integrated into Spring.



Pro Spring
Pro Spring
ISBN: 1590594614
EAN: 2147483647
Year: 2006
Pages: 189

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