本章对 Quartz 框架一个快速的入门介绍。
和现今许多在用的开源项目一样,Quartz之初也只是为个人开发者提供了一个简单的实现方案。但是随着日益增多的关键人员的积极参与和慷慨的贡献,Quartz 已经成为了一个为众人所知,并且能帮助人们解决更大问题的框架。
Quartz 项目 是由 James House 创立的,它在1998年就有该框架最初的构思。包括作业队列的概念,使用线程池来处理作业,也许它最早的模型已不为现今的Quartz使用者所知了。
在接下来的数年中,House 自己说他一直在关注着同一个需求:需要一个灵活的作业调度工具。他在找寻便宜且具有丰富特征的Java作业调度工具时,让他面临着以下几个选择:
·一个昂贵的商业化工具
·嵌入在大框架之中的,根本用不着这么一个大框架
·类似 Unix Cron 或者 Windows 的计划任务
·自己亲自定制的方案
House 有限的选择和在这个问题上的兴趣促成了他为作业调度器创建一个开源的项目。在2001年春天,他在 SourceForge 上创立了该项目,这一网址 http://sourceforge.net/projects/quartz 现在还是有效的,只是已经不再维护了。
自从 Quartz 的雏形一出来,众多的捐助者和开发人员加入到这个项目中来。然而应该说,Quartz 能象今天这么存在还是要感谢 House 以及他在作业调度领域中的兴趣。在众多人眼中,他那解决问题的决心很值得称颂的。
官网 http://www.quartz-scheduler.org/
Quartz 调度包的两个基本单元是作业和触发器。作业 是能够调度的可执行任务,触发器 提供了对作业的调度。虽然这两个实体很容易合在一起,但在 Quartz 中将它们分离开来是有原因的,而且也很有益处。
通过把要执行的工作与它的调度分开,Quartz 允许在不丢失作业本身或作业的上下文的情况下,修改调度触发器。而且,任何单个的作业都可以有多个触发器与其关联。
Quartz设计者做了一个设计选择来从调度分离开作业。Quartz中的触发器用来告诉调度程序作业什么时候触发。框架提供了一把触发器类型,但两个最常用的是SimpleTrigger和CronTrigger。SimpleTrigger为需要简单打火调度而设计。(打火:fire,可理解为触发)
典型地,如果你需要在给定的时间和重复次数或者两次打火之间等待的秒数打火一个作业,那么SimpleTrigger适合你。另一方面,如果你有许多复杂的作业调度,那么或许需要CronTrigger。
CronTrigger是基于Calendar-like调度的。当你需要在除星期六和星期天外的每天上午10点半执行作业时,那么应该使用CronTrigger。正如它的名字所暗示的那样,CronTrigger是基于Unix克隆表达式的。
通过实现 org.quartz.job
接口,可以使 Java 类变成可执行的。清单 1 提供了 Quartz 作业的一个示例。这个类用一条非常简单的输出语句覆盖了 execute(JobExecutionContext context)
方法。这个方法可以包含我们想要执行的任何代码。
SimpleQuartzJob.java
package com.ibm.developerworks.quartz;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class SimpleQuartzJob implements Job {
public SimpleQuartzJob() {
}
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("In SimpleQuartzJob - executing its JOB at "
+ new Date() + " by " + context.getTrigger().getName());
}
}
请注意,execute 方法接受一个 JobExecutionContext
对象作为参数。这个对象提供了作业实例的运行时上下文。特别地,它提供了对调度器和触发器的访问,这两者协作来启动作业以及作业的 JobDetail
对象的执行。Quartz 通过把作业的状态放在 JobDetail
对象中并让 JobDetail
构造函数启动一个作业的实例,分离了作业的执行和作业周围的状态。JobDetail
对象储存作业的侦听器、群组、数据映射、描述以及作业的其他属性。
触发器可以实现对任务执行的调度。Quartz 提供了几种不同的触发器,复杂程度各不相同。清单 2 中的 SimpleTrigger
展示了触发器的基础:
清单 2. SimpleTriggerRunner.java
public void task() throws SchedulerException
{
// Initiate a Schedule Factory
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
// Retrieve a scheduler from schedule factory
Scheduler scheduler = schedulerFactory.getScheduler();
// current time
long ctime = System.currentTimeMillis();
// Initiate JobDetail with job name, job group, and executable job class
JobDetail jobDetail =
new JobDetail("jobDetail-s1", "jobDetailGroup-s1", SimpleQuartzJob.class);
// Initiate SimpleTrigger with its name and group name
SimpleTrigger simpleTrigger =
new SimpleTrigger("simpleTrigger", "triggerGroup-s1");
// set its start up time
simpleTrigger.setStartTime(new Date(ctime));
// set the interval, how often the job should run (10 seconds here)
simpleTrigger.setRepeatInterval(10000);
// set the number of execution of this job, set to 10 times.
// It will run 10 time and exhaust.
simpleTrigger.setRepeatCount(100);
// set the ending time of this job.
// We set it for 60 seconds from its startup time here
// Even if we set its repeat count to 10,
// this will stop its process after 6 repeats as it gets it endtime by then.
//simpleTrigger.setEndTime(new Date(ctime + 60000L));
// set priority of trigger. If not set, the default is 5
//simpleTrigger.setPriority(10);
// schedule a job with JobDetail and Trigger
scheduler.scheduleJob(jobDetail, simpleTrigger);
// start the scheduler
scheduler.start();
}
清单 2 开始时实例化一个 SchedulerFactory
,获得此调度器。就像前面讨论过的,创建 JobDetail
对象时,它的构造函数要接受一个 Job
作为参数。顾名思义,SimpleTrigger
实例相当原始。在创建对象之后,设置几个基本属性以立即调度任务,然后每 10 秒重复一次,直到作业被执行 100 次。
还有其他许多方式可以操纵 SimpleTrigger
。除了指定重复次数和重复间隔,还可以指定作业在特定日历时间执行,只需给定执行的最长时间或者优先级(稍后讨论)。执行的最长时间可以覆盖指定的重复次数,从而确保作业的运行不会超过最长时间。
CronTrigger
支持比 SimpleTrigger
更具体的调度,而且也不是很复杂。基于 cron 表达式,CronTrigger
支持类似日历的重复间隔,而不是单一的时间间隔 —— 这相对 SimpleTrigger
而言是一大改进。
Cron 表达式包括以下 7 个字段:
Cron 触发器利用一系列特殊字符,如下所示:
所有这些定义看起来可能有些吓人,但是只要几分钟练习之后,cron 表达式就会显得十分简单。
清单 3 显示了 CronTrigger
的一个示例。请注意 SchedulerFactory
、Scheduler
和 JobDetail
的实例化,与 SimpleTrigger
示例中的实例化是相同的。在这个示例中,只是修改了触发器。这里指定的 cron 表达式(“0/5 * * * * ?”)安排任务每 5 秒执行一次。
public void task() throws SchedulerException
{
// Initiate a Schedule Factory
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
// Retrieve a scheduler from schedule factory
Scheduler scheduler = schedulerFactory.getScheduler();
// current time
long ctime = System.currentTimeMillis();
// Initiate JobDetail with job name, job group, and executable job class
JobDetail jobDetail =
new JobDetail("jobDetail2", "jobDetailGroup2", SimpleQuartzJob.class);
// Initiate CronTrigger with its name and group name
CronTrigger cronTrigger = new CronTrigger("cronTrigger", "triggerGroup2");
try {
// setup CronExpression
CronExpression cexp = new CronExpression("0/5 * * * * ?");
// Assign the CronExpression to CronTrigger
cronTrigger.setCronExpression(cexp);
} catch (Exception e) {
e.printStackTrace();
}
// schedule a job with JobDetail and Trigger
scheduler.scheduleJob(jobDetail, cronTrigger);
// start the scheduler
scheduler.start();
}
Quartz框架的核心是调度器。调度器负责管理Quartz应用运行时环境。调度器不是靠自己做所有的工作,而是依赖框架内一些非常重要的部件。Quartz不仅仅是线程和线程管理。为确保可伸缩性,Quartz采用了基于多线程的架构。
启动时,框架初始化一套worker线程,这套线程被调度器用来执行预定的作业。这就是Quartz怎样能并发运行多个作业的原理。Quartz依赖一套松耦合的线程池管理部件来管理线程环境。本文中,我们会多次提到线程池管理,但Quartz里面的每个对象是可配置的或者是可定制的。所以,例如,如果你想要插进自己线程池管理设施,我猜你一定能!
作业一旦被调度,调度器需要记住并且跟踪作业和它们的执行次数。如果你的作业是30分钟后或每30秒调用,这不是很有用。事实上,作业执行需要非常准确和即时调用在被调度作业上的execute()方法。Quartz通过一个称之为作业存储(JobStore)的概念来做作业存储和管理。
有效作业存储
Quartz提供两种基本作业存储类型。第一种类型叫做RAMJobStore,它利用通常的内存来持久化调度程序信息。这种作业存储类型最容易配置、构造和运行。对许多应用来说,这种作业存储已经足够了。
然而,因为调度程序信息是存储在被分配给JVM的内存里面,所以,当应用程序停止运行时,所有调度信息将被丢失。如果你需要在重新启动之间持久化调度信息,则将需要第二种类型的作业存储。
第二种类型的作业存储实际上提供两种不同的实现,但两种实现一般都称为JDBC作业存储。两种JDBC作业存储都需要JDBC驱动程序和后台数据库来持久化调度程序信息。这两种类型的不同在于你是否想要控制数据库事务或这释放控制给应用服务器例如BEA’s WebLogic或Jboss。(这类似于J2EE领域中,Bean管理的事务和和容器管理事务之间的区别)这两种JDBC作业存储是:
· JobStoreTX:当你想要控制事务或工作在非应用服务器环境中是使用
· JobStoreCMT:当你工作在应用服务器环境中和想要容器控制事务时使用。
JDBC作业存储为需要调度程序维护调度信息的用户而设计。