现代的 Web 应用程序框架在范围和复杂性方面都有所发展,应用程序的每个底层组件也必须相应地发展。作业调度是现代系统中对 Java 应用程序的一般要求,而且也是对 Java 开发人员一贯的要求。虽然目前的调度技术比起原始的数据库触发器标志和独立的调度器线程来说,已经发展了许多,但是作业调度仍然不是个小问题。对这个问题最合适的解决方案就是来自 OpenSymphony 的 Quartz API。
Quartz 是个开源的作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。Quartz 允许开发人员根据时间间隔(或天)来调度作业。它实现了作业和触发器的多对多关系,还能把多个作业与不同的触发器关联。整合了 Quartz 的应用程序可以重用来自不同事件的作业,还可以为一个事件组合多个作业。虽然可以通过属性文件(在属性文件中可以指定 JDBC 事务的数据源、全局作业和/或触发器侦听器、插件、线程池,以及更多)配置 Quartz,但它根本没有与应用程序服务器的上下文或引用集成在一起。结果就是作业不能访问 Web 服务器的内部函数;例如,在使用 WebSphere 应用服务器时,由 Quartz 调度的作业并不能影响服务器的动态缓存和数据源。
本文使用一系列代码示例介绍 Quartz API,演示它的机制,例如作业、触发器、作业仓库和属性。
要开始使用 Quartz,需要用 Quartz API 对项目进行配置。步骤如下:
- 下载 Quartz API。
- 解压缩并把 quartz-x.x.x.jar 放在项目文件夹内,或者把文件放在项目的类路径中。
- 把 core 和/或 optional 文件夹中的 jar 文件放在项目的文件夹或项目的类路径中。
- 如果使用
JDBCJobStore
,把所有的 JDBC jar 文件放在项目的文件夹或项目的类路径中。
为了方便读者,我已经把所有必要的文件,包括 DB2 JDBC 文件,编译到一个 zip 文件中。请参阅 下载 小节下载代码。
现在来看一下 Quartz API 的主要组件。
Quartz 调度包的两个基本单元是作业和触发器。作业 是能够调度的可执行任务,触发器 提供了对作业的调度。虽然这两个实体很容易合在一起,但在 Quartz 中将它们分离开来是有原因的,而且也很有益处。
通过把要执行的工作与它的调度分开,Quartz 允许在不丢失作业本身或作业的上下文的情况下,修改调度触发器。而且,任何单个的作业都可以有多个触发器与其关联。
通过实现 org.quartz.job
接口,可以使 Java 类变成可执行的。清单 1 提供了 Quartz 作业的一个示例。这个类用一条非常简单的输出语句覆盖了 execute(JobExecutionContext context)
方法。这个方法可以包含我们想要执行的任何代码(所有的代码示例都基于 Quartz 1.5.2,它是编写这篇文章时的稳定发行版)。
请注意,execute 方法接受一个 JobExecutionContext
对象作为参数。这个对象提供了作业实例的运行时上下文。特别地,它提供了对调度器和触发器的访问,这两者协作来启动作业以及作业的 JobDetail
对象的执行。Quartz 通过把作业的状态放在 JobDetail
对象中并让 JobDetail
构造函数启动一个作业的实例,分离了作业的执行和作业周围的状态。JobDetail
对象储存作业的侦听器、群组、数据映射、描述以及作业的其他属性。
触发器可以实现对任务执行的调度。Quartz 提供了几种不同的触发器,复杂程度各不相同。清单 2 中的 SimpleTrigger
展示了触发器的基础:
清单 2. SimpleTriggerRunner.java
清单 2 开始时实例化一个 SchedulerFactory
,获得此调度器。就像前面讨论过的,创建 JobDetail
对象时,它的构造函数要接受一个 Job
作为参数。顾名思义,SimpleTrigger
实例相当原始。在创建对象之后,设置几个基本属性以立即调度任务,然后每 10 秒重复一次,直到作业被执行 100 次。
还有其他许多方式可以操纵 SimpleTrigger
。除了指定重复次数和重复间隔,还可以指定作业在特定日历时间执行,只需给定执行的最长时间或者优先级(稍后讨论)。执行的最长时间可以覆盖指定的重复次数,从而确保作业的运行不会超过最长时间。
CronTrigger
支持比 SimpleTrigger
更具体的调度,而且也不是很复杂。基于 cron 表达式,CronTrigger
支持类似日历的重复间隔,而不是单一的时间间隔 —— 这相对 SimpleTrigger
而言是一大改进。
Cron 表达式包括以下 7 个字段:
- 秒
- 分
- 小时
- 月内日期
- 月
- 周内日期
- 年(可选字段)
Cron 触发器利用一系列特殊字符,如下所示:
- 反斜线(/)字符表示增量值。例如,在秒字段中“5/15”代表从第 5 秒开始,每 15 秒一次。
- 问号(?)字符和字母 L 字符只有在月内日期和周内日期字段中可用。问号表示这个字段不包含具体值。所以,如果指定月内日期,可以在周内日期字段中插入“?”,表示周内日期值无关紧要。字母 L 字符是 last 的缩写。放在月内日期字段中,表示安排在当月最后一天执行。在周内日期字段中,如果“L”单独存在,就等于“7”,否则代表当月内周内日期的最后一个实例。所以“0L”表示安排在当月的最后一个星期日执行。
- 在月内日期字段中的字母(W)字符把执行安排在最靠近指定值的工作日。把“1W”放在月内日期字段中,表示把执行安排在当月的第一个工作日内。
- 井号(#)字符为给定月份指定具体的工作日实例。把“MON#2”放在周内日期字段中,表示把任务安排在当月的第二个星期一。
- 星号(*)字符是通配字符,表示该字段可以接受任何可能的值。
所有这些定义看起来可能有些吓人,但是只要几分钟练习之后,cron 表达式就会显得十分简单。
清单 3 显示了 CronTrigger
的一个示例。请注意 SchedulerFactory
、Scheduler
和 JobDetail
的实例化,与 SimpleTrigger
示例中的实例化是相同的。在这个示例中,只是修改了触发器。这里指定的 cron 表达式(“0/5 * * * * ?”)安排任务每 5 秒执行一次。
<script type="text/javascript"><!--google_ad_client = "pub-0068659036624865";/* 468x60, 创建于 09-6-8 */google_ad_slot = "2787955370";google_ad_width = 468;google_ad_height = 60;// --></script><script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"></script>