基于Spring的任务调度(1)
?
?? ?大多数应用程序逻辑是用来反馈某种形式的用户行为的,例如点击一个按钮或提交一个表单。然而,在很多应用程序中存在无需与用户交互来调用的某种处理,通常是在固定时间间隔运行一次。例如,也许我们有个进程每小时清理一次临时文件,或者每天午夜从数据库导出数据并发送到一个外部系统。多数重要的应用都要求某种形式的调度支持,该调度若不是和应用的业务逻辑直接相关就是为系统做一些辅助工作。
?? ?如果你正在为应用程序建立调度任务,那么创建一个任务让它每小时或者一天运行一次是相当简单的。但是该任务需要在每周一、周三和周五的下午三点运行呢?编写代码可能就有些困难了,这也使得选择一个现有的任务调度解决方案比创建自己的调度框架更合理。
?? ?如果从编程的角度来讨论任务调度,我们倾向于讨论3种不同的概念。一个任务是一个需要被调度以指定时间间隔运行的工作单元。一个触发器是一个引发任务运行的条件,可能是一个固定的时间间隔或者是既定片段的数据。一个调度计划是一组触发器的集合,它管理任务的整个时限。一般通过实现某个接口或者扩展某个特定基类来封装一个任务。我们可以使用任务调度框架支持的任何方式定义触发器。一些框架可能只支持简单的基于时间间隔的触发器,但是其他一些,比如Quartz,提供了更灵活的触发器模式。通常情况下,在调度计划中一个任务只有一个触发器,因此术语"调度"和"触发器"经常是交换使用。
?? ?Spring对任务调度的支持有两种不同的形式:基于JDK Timer和基于Quartz。基于JDK Timer方式的任务调度为所有JVM 1.3及后续版本提供了任务调度能力,并且它没有Spring以外的依赖。基于Timer的任务调度比较简单,在定义任务调度时也只能提供有限的灵活性。然而,Timer支持建立在Java标准内并且不需要外部的依赖库,当你受限于程序大小或者企业策略时可以从其中受益。基于Quartz的任务调度具有更好的灵活性,允许我们定义更加接近现实世界的触发器,比如先前的每周一、周三和周五的下午三点运行任务的例子。
?? 我们将讨论Spring包含的上述两种任务调度解决方案。本文讨论了3个核心主题:使用JDK Timer进行任务调度,基于Quartz的任务调度和任务调度时的需考虑因素。
1 使用JDK Timer调度任务
?? ?Spring支持的最基本的任务调度是基于JDK java.util.Timer类的。当使用Timer进行任务调度时,我们只能使用简单的基于时间间隔的触发器定义,这使得基于Timer的任务调度只适合那些需要在既定未来某个时间执行或者以固定周期运行的任务。
1.1? Timer触发器类型
?? ?基于Timer的任务调度给我们提供了以下3种触发器类型。
?? ? ?我们可能发现,要形象化地显示固定延迟触发器和定时触发器之间的不同是很困难的。为了清晰地展示它们之间的区别,我们需要创建一个在执行中引起足够长延迟的示例,但是这相当困难。
?? ?考虑一个在13:00开始执行的任务,两次运行之间的指定间隔为30 min。任务一直运行良好,直到16:30,此时系统的负载过重,导致执行垃圾收集,而这导致实际运行完成时间迟于预计时间--任务运行时已经16:31了。现在如果使用固定延迟来调度任务,那么重要的是时间间隔,也就是说,我们希望两次实际执行的时间间隔是30 min,因此下次任务调度执行的时间是17:01而不是17:00。如果我们使用定时调度,时间间隔只定义了预期的调度,也就是说,我们期望以程序开始执行的时间作为基准点,每隔30 min运行一次,而不是基于上次执行的时间,所以任务调度在17:00执行。
?? ?两个触发器类型都有各自的用途。一般情况下,定时触发器用于下述情况:两次执行间的时间间隔规律的情况或者你想避免某次执行被延迟太长时间时,会导致两次执行发生的时间点太近的情况。使用定时触发器可以得到以上效果。一般来说,你在实时敏感系统中使用定时触发器,例如必须每小时整点执行的任务。
?
?
1.2 创建一个简单任务
为了创建是一个使用Timer类的任务,你可以简单扩展TimerTask类并实现run()方法执行你的任务逻辑。
例1 展示了一个简单TimerTask实现,它打印"Hello World!"到标准输出
?
package cn.hurraysoft.HelloWorldTask;import java.util.Calendar;import java.util.Timer;public class SimpleBirthdayReminderScheduling {public static final long MILLIS_IN_YEAR=1000*60*60*24*365;public static void main(String[] args) {Calendar cal=Calendar.getInstance();cal.set(2008, Calendar.NOVEMBER, 30);Timer t=new Timer();t.scheduleAtFixedRate(new HelloWorldTask(),cal.getTime(),MILLIS_IN_YEAR);}}
在这个例子中,我们可以看到我们先计算了代表一年的毫秒数,然后创建一个Calendar实例并用它定义开始时间为11月30日而时间间隔为一年。现在,只要该程序一直运行,每年的11月30日"Hello World!"消息就会打印到标准输出。记住它并不是功能完善的示例,所以它没有真实的通知机制,并且每次我们希望增加一个新的生日提示时都需要修改代码。在之后的文章中,我们将使用Spring 的JDK Timer支持类来创建一个更稳健的生日提示程序。
?