读书人

基于Spring的任务调度(二)

发布时间: 2012-10-08 19:54:56 作者: rapoo

基于Spring的任务调度(2)

1.3 Spring对JDK Timer调度的支持

正如之前所看到的,使用JDK Timer和TimerTask类来创建和调度任务是很容易的。但是,我们在前一个例子中使用的方法有一些问题。首先,我们在程序中使用TimerTask实例而不是使用Spring。对于HelloWorldTask,这是可以接受的,因为我们无需配置该任务。但是,许多任务需要一些配置数据,因此我们应该使用Spring来管理它们,使程序易于配置。第二,触发器信息是硬编码到程序中的,这使得对任务被触发的时间上做任何修改都需要修改代码重新编译。最后,调度新任务或移除任务也需要修改程序代码,而在理想情况下我们应该可以在外部对它进行配置。使用Spring的Timer支持类,我们可以将所有的任务和触发器配置以及Timer创建的控制委托给Spring来处理,这样我们就可以在外部定义任务及其触发器。 Spring对Timer的支持的核心是由ScheduledTimerTask和TimerFactoryBean类组成的。ScheduledTimerTask类是对TimerTask的包装器实现,这样你就可以为这个任务定义触发器信息。使用TimerFactoryBean类,你可以让Spring使用配置创建触发器,并为一组指定的ScheduledTimerTask bean自动创建Timer实例。1. 使用ScheduledTimerTask和TimerFactoryBean类 在我们深入讨论新的改善版生日提示程序之前,我们应该首先了解一下ScheduledTimerTask和TimerFactoryBean的工作基础。你需要为每一个待调度的任务配置任务类和一个包含触发器细节的ScheduledTimerTask实例。若你想为同一个任务创建多个触发器,你可以在多个ScheduledTimerTask实例间共享一个TimerTask实例。一旦你配置好了这些组件,只需简单配置一个TimerFactoryBean类并指定ScheduledTimerTask bean的列表。接着Spring创建一个Timer实例,并使用它来调度已被ScheduledTimerTask类定义的所有任务。 这听起来很复杂,但是实际上并不是这样。例6展示了一个简单配置,它调度HelloWorldTask使之第一次运行前有1s延迟而后每3s运行一次。例6 使用TimerFactoryBean配置任务调度

<?xml version="1.0" encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"><bean id="job" value="1000"></property><property name="period" value="3000"></property><property name="timerTask" ref="job"></property></bean><bean id="timerFactory" name="code">package cn.hurraysoft.test;import java.io.IOException;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.context.support.FileSystemXmlApplicationContext;public class Test {public static void main(String[] args) throws IOException {ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");}}

如果运行此应用程序,你将看到消息"Hello World!"在程序开始的1s延迟后,每3s输出一次到标准输出。正如你从示例中看到的,在你的代码外配置任务调度是非常简单的。使用此方法,修改任务调度计划或者移除现存任务增加新的调度任务就简单多了。2. 一个更实用的生日提示程序 在这里,我们使用Spring的Timer支持来创建一个更复杂的生日提醒程序。使用这个例子,我们希望能够调度多个提示任务,每个都有特定的配置来表明是为谁的生日进行提示。我们也希望能无需修改程序代码就可增加和移除提示。 开始之前,我们需要创建一个任务来执行实际的提示。因为我们将使用Spring创建这些任务,可以使用依赖注入的方式提供所有的配置数据。代码清单12-8展示了BirthdayReminderTask类。例8 BirthdayReminderTask类

package cn.hurraysoft.HelloWorldTask;import java.util.TimerTask;public class BirthdayReminderTask extends TimerTask {private String who;@Overridepublic void run() {System.out.println("Don't forget it is"+who+"'s birthday is 7 days");}public String getWho() {return who;}}

注意,我们在任务上定义了一个属性who,我们可以指定我们正在提示的是谁的生日。在真实的生日提示程序中,提示无疑应该使用e-mail或其他相似媒介通知。但是现在,则不得不将具有提示信息的内容发送到标准输出。 完成此任务后,我们就可以进入配置阶段了。但是,就像我们先前指出的那样,你使用ScheduledTimerTask时不可以通过日期来指定一个调度任务的起始时间。这对我们的应用程序是个问题,因为我们不可以把程序启动后的一个相对时间延迟作为触发器的起始时间。幸运的是,我们可以扩展ScheduledTimerTask类并重写getDelay()方法,TimerFactoryBean使用getDelay()方法判断应该分配多少延迟时间给触发器,这种做法很容易克服前面的问题。同时,我们也可以重写getPeriod()方法来返回代表一年时间的毫秒数,这样你就无需将该参数(毫秒数)增加到配置文件中。下面展示了我们定义的ScheduledTimerTask和BirthdayScheduledTask类。例9 自定义BirthdayScheduledTask类

package cn.hurraysoft.HelloWorldTask;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=100*60*60*24*365;private DateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd");private Date startDate;@Overridepublic long getDelay() {Calendar now=Calendar.getInstance();Calendar then=Calendar.getInstance();then.setTime(startDate);return (then.getTimeInMillis()-now.getTimeInMillis());}@Overridepublic long getPeriod() {return MILLIS_IN_YEAR;}public void setDate(String date) throws ParseException {this.startDate = dateFormat.parse(date);}}
? 在例子中,你可以看到我们为BirthdayScheduledTask定义了一个新属性date,它可以让我们指定任务的起始时间而不是一个延迟时间段。此属性是String类型,因为我们使用一个SimpleDateFormat的实例,根据格式yyyy-MM-dd来解析属性值为日期格式,如2008-11-30。你也看到我们重写了getPeriod()方法,TimerFactoryBean类使用它来配置触发器运行的时间间隔,此方法返回代表一年时间的毫秒数。也需注意我们对getDelay()方法的重写,使用Calendar类来计算当前时间和给定起始时间之间的毫秒数。接着该值作为程序开始时的延迟被返回。现在我们已经完成了示例程序的配置,如例10:例10 生日提示程序的配置
<?xml version="1.0" encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"><bean id="job" value="mum"></property></bean><bean id="timerTask" value="2010-03-17"></property><property name="fixedRate" value="true"></property><property name="timerTask" ref="job"></property></bean><bean id="timerFactory" name="code">package cn.hurraysoft.HelloWorldTask;public class FooBean {public void someJob(String message){System.out.println(message);}}
如果我们希望调度someJob()方法,为它提供一个既定参数并在每3s运行一次,而不是创建一个TimerTask来完成此任务,我们可以只使用MethodInvokingTimerTaskFactoryBean创建一个TimerTask。它的配置文件见例子12。例12 使用MethodInvokingTimerTaskFactoryBean类
<?xml version="1.0" encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"><bean id="target" ref="target"></property><property name="targetMethod" value="someJob"></property><property name="arguments" value="Hello World!"></property></bean><bean id="timerTask" value="1000"></property><property name="period" value="3000"></property><property name="timerTask" ref="job"></property></bean><bean id="timerFactory" name="code">package cn.hurraysoft.test;import java.io.IOException;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.context.support.FileSystemXmlApplicationContext;public class Test {public static void main(String[] args) throws IOException {ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext_2.xml");}}
?

运行这个示例将输出熟悉的结果,"Hello World!"消息即时出现在你的控制台。使用Method- InvokingTimerTaskFactoryBean消除了对创建自定义的TimerTask实现的需求,而后者只是包装了一个业务方法的执行。
基于JDK Timer的调度,使用一个简单易懂的框架提供了对一个程序基本调度需求的支持。尽管JDK Timer的触发器系统有点死板,但是它提供了基本的设计来让你完成简单的任务调度。使用Spring为Timer提供支持类,你可以在外部对任务进行配置,可以更容易地实现任务的添加和移除,而无需修改任何代码。你使用MethodInvokingTimerTaskFactoryBean可以避免创建除了调用一个业务方法以外什么都不做的TimerTask实现,这样也减少了你需要编写和维护的代码量。当我们需要支持复杂的触发器时,例如每个周一、周三和周五下午3点执行一个任务,JDK Timer调度的主要缺陷就显露出来了。在下文,我们开始介绍Quartz引擎,它给任务调度提供了更全面的支持,并且和Timer任务调度一样与Spring完全集成。

读书人网 >软件架构设计

热点推荐