基于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"?>
<beans
	xmlns="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" class="cn.hurraysoft.HelloWorldTask.HelloWorldTask"/>
	<bean id="timerTask" class="org.springframework.scheduling.timer.ScheduledTimerTask">
		<property name="delay" value="1000"></property>
		<property name="period" value="3000"></property>
		<property name="timerTask" ref="job"></property>
	</bean>
	<bean id="timerFactory" class="org.springframework.scheduling.timer.TimerFactoryBean">
		<property name="scheduledTimerTasks">
			<list>
				<ref local="timerTask"/>
			</list>
		</property>
	</bean>
</beans>

    这里你可以看到我们已经配置了一个bean,这是一个HelloWorldTask类型的任务,接着我们使用这个bean配置了一个ScheduledTimerTask类型的bean,设置启动延迟为1 000 ms,后续延迟为3 000 ms。配置的最后部分是timerFactory bean,它接受一个ScheduledTimerTask类型的bean列表。在此种情况下,我们只有一个任务要调度,由timerTask bean表示。在使用ScheduledTimerTask指定触发器信息时,你可以只提供毫秒数作为参数,这表示在程序启动以后有一段时间的延迟而不使用原有的启动时间。我们将在文创建生日提示程序时介绍这种方法。 当所有的调度计划和任务定义信息都包括在配置文件中,我们的示例程序几乎不需要做什么。实际上,我们所需要做的就是加载ApplicationContext,Spring会自动创建Timer类并依据配置文件调度HelloWorldTask。

例7 测试类

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;
	@Override
	public 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;
	@Override
	public long getDelay() {
		Calendar now=Calendar.getInstance();
		Calendar then=Calendar.getInstance();
		then.setTime(startDate);
		return (then.getTimeInMillis()-now.getTimeInMillis());
	}
	@Override
	public 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"?>
<beans
	xmlns="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" class="cn.hurraysoft.HelloWorldTask.BirthdayReminderTask">
		<property name="who" value="mum"></property>
	</bean>
	<bean id="timerTask" class="cn.hurraysoft.HelloWorldTask.BirthdayScheduledTask">
		<property name="date" value="2010-03-17"></property>
		<property name="fixedRate" value="true"></property>
		<property name="timerTask" ref="job"></property>
	</bean>
	<bean id="timerFactory" class="org.springframework.scheduling.timer.TimerFactoryBean">
		<property name="scheduledTimerTasks">
			<list>
				<ref local="timerTask"/>
			</list>
		</property>
	</bean>
</beans>
上述代码对你来说应该相当熟悉。注意,我们使用我们自己的BirthdayScheduledTask类替换了BirthdayTimerTask类,只指定一个日期而没有指定延迟和时间间隔。我们用重写的getDelay()和getPeriod()方法来为TimerFactoryBean类提供延迟和时间间隔值。除此之外,注意我们设置BirthdayScheduledTask bean的fixedRate属性为true。该属性从ScheduledTimerTask继承,TimerFactory- Bean使用它来判断是创建一个定时触发器还是创建固定延迟触发器。

 

3. 调度任意任务

当你进行任务调度时,常常需要调度现有逻辑的执行。在这种情况下,你可能不希望麻烦地创建一个仅用于包装逻辑的TimerTask类。幸运的是,你没有必要这样做。你能够使用MethodInvokingTimer- TaskFactoryBean类调度任意给定的bean的任意方法或指定类的静态方法。如果你的逻辑需要,你甚至可以为方法提供参数。例11中的FooBean类是此种情况的示例。 例11 FooBean类

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"?>
<beans
	xmlns="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" class="cn.hurraysoft.HelloWorldTask.FooBean"></bean>
	<bean id="job" class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean">
		<property name="targetObject" ref="target"></property>
		<property name="targetMethod" value="someJob"></property>
		<property name="arguments" value="Hello World!"></property>
	</bean>
	<bean id="timerTask" class="org.springframework.scheduling.timer.ScheduledTimerTask">
		<property name="delay" value="1000"></property>
		<property name="period" value="3000"></property>
		<property name="timerTask" ref="job"></property>
	</bean>
	<bean id="timerFactory" class="org.springframework.scheduling.timer.TimerFactoryBean">
		<property name="scheduledTimerTasks">
			<list>
				<ref local="timerTask"/>
			</list>
		</property>
	</bean>
</beans>

  我们使用一个MethodInvokingTimerTaskFactoryBean类的定义替换了我们自定义的TimerTask bean的定义。为了配置MethodInvokingTimerTaskFactoryBean,我们设置需要调用的目标为对另外一个bean的引用,以及执行的方法和执行时使用的参数。MethodInvokingTimerTaskFactoryBean类提供的TimerTask可以用普通的方式使用:包装在ScheduledTimerTask类中,并把它传递给TimerFactoryBean。例13展示了一个简单的驱动程序来进行测试。

例13 使用MethodInvokingTimerTaskFactoryBean的测试类

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完全集成。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值