原文地址:Getting Started with Quartz Scheduler
本文中“工作”对应“job”,“任务”对应“task”。关系是工作是若干任务的集合,任务是工作中需要做的具体事情。通俗的例子,“教师”是一个“工作”,它包含的具体“任务”有{“备课”,“上课”,“批改作业”,... }
什么是Quartz Scheduler?
Quartz Scheduler是一个功能丰富的开源的工作调度库,可以被真实地整合到任何Java应用中--从最小的独立应用到最大的电商系统。Quartz可以被用来构建简单或者复杂的调度,用来执行十个、百个或者甚至万个工作。任务被定义为标准的Java组件,这些组件会真实地执行任何你通过编程指定给它们的事情。Quartz Schedulers包含了许多的企业级类的特性,比如它支持JTA事务和集群。
Quartz可以为你做什么?
如果你的应用中有一些任务需要在给定的一个时候及时地执行,或者如果你的系统有一些循环的维护工作,那么Quartz会是你理想中的解决方案。
使用Quartz来工作调度例子:
- 驱动过程工作流:当一个新的订单被初始化后,调度一个工作来在2个小时内精确的执行,它会检查订单的状态。并且如果一个关于这个订单确认信息还没有收到的话,会触发一个警告通知,同时改变订单的状态为“等待介入”。
- 系统维护:调度一个工作在每个工作日的11:30PM,去将数据库的内容倒入一个XML文件。
- 在一个应用中提供提醒服务。
Quartz特色功能
运行时环境
- Quartz可以嵌入式地运行在另一个完全独立的应用中
- Quartz可以在一个应用服务器(或者servlet容器)中被实例化,并且加入到XA事务中
- Quartz可以作为一个独立的程序运行(使用它自己的JVM),通过RMI的方式被使用
- Quartz可以作为一个独立程序(有着负载均衡和容错能力)的集群被实例化,来执行工作
工作调度
当一个给定的触发器发生的时候,工作将被调度。触发器几乎可以被创建为任何下面的策略,包括:
- 一天中一个确定的时间(精确到毫秒)
- 一周中确定的日子
- 一月中确定的日子
- 一年中确定的日子
- 在一个注册的日历中排除一些确定的日子(比如法定假日)
- 重复一个确定的次数
- 重复直到一个确定的时间/日期
- 无限重复
- 延迟一段时间重复
工作被它们的创建者给定名字,并同样可以通过组名将它们分组。触发器同样会被指定名字并放入组中,为了更容易地在调度过程中组织它们。工作可以被一次性地添加到调度器中,然而被多个触发器所注册。在一个企业级的Java环境中,工作会执行它们的任务作为一个分布式(XA)事务的一部分。
工作执行
- 工作可以是任何实现了简单Job接口的Java类,为你工作能做的事情留下了无限的可能。
- 工作类的实例可以通过Quartz被实例化,活着通过你的应用的框架。
- 当一个触发器发生的时候,调度器会通知0个或更多Java对象去实现JobListener和TriggerListener接口(listener可以是简单的Java对象,或者EJB,或者JMS发布者,等等)。这些监听器在工作执行以后同样会被通知。
- 当工作完成,它们返回一个JobCompletionCode来告诉调度器是成功还是失败。JobCompletionCode同样会指示调度器的任何它应该根据成功/失败码所进行的行为,比如立刻重新执行工作。
工作持久化
- Quartz的设计包含了一个JobStore接口,它可以被实现来提供各种各样的机制来存储工作。
- 通过使用JDBCJobStore,所有的工作和触发器都被配置为“non-volatile”被通过JDBC存储在一个关系型数据库中。
- 通过使用RAMJobStore,所有的工作和触发器都被存在RAM中,并且在程序执行之间不会持久化--但是这有一个好处就是不需要一个外部的数据库。
事务
- Quartz可以参与到JTA事务中,通过使用JobStoreCMT(一个JDBCJobStore的子类)。
- Quartz可以管理JTA事务(开始并提交它们)在工作执行的时候,以便于工作在做的事情在一个JTA事务中可以自动地发生。
集群特性
- 提供容错
- 提供负载均衡
- Quartz的内建集群特性依赖于通过JDBCJobStore的数据库实例化。
- Terracotta对Quartz提供的扩展提供了集群能力,而不需要一个后端的数据库。
监听器 & 插件
- 应用可以通过实现一个或多个监听器接口来捕获调度时间来监视或者控制job/trigger的行为。
- 插件机制可以被用来添加功能到Quartz,如此保持一个工作执行的历史,或者从一个文件中加载job和trigger。
- Quartz通过整合一些“工厂构建”插件和监听器来运行。
下载和安装Quartz
首先,从http://quartz-scheduler.org/downloads下载最新的稳定版本。注册不是必须的。解压发行包并安装使得你的应用可以看到它。
Quartz的jar文件
Quartz的包里有若干jar文件,在发行包的根目录下。主要的Quartz库的名字叫做quartz-xxx.jar(xxx是版本号)。为了使用任何Quartz的特性,这个jar必须放置在你应用的classpath下面。
如果你使用Quartz主要在一个应用服务器的环境,你可以将Quartz JAR放置在你的应用中(.ear或者.war文件)。如果你想使Quartz对于许多应用都可用,确保它在你应用服务器中classpath下面。如果你正在一个独立的应用中使用它,将它放置在应用的classpath下,并把你应用所依赖的其它JAR文件也放置过去。
Quartz依赖若干第三方的库(通过jar的形式),它们在.zip发布包的lib目录下。为了使用Quartz的所有特性,这些jar必须同样存在于你的classpath下。如果你在构建一个独立的Quartz应用,请添加所有的库到classpath下。
注意:在一个应用服务器的环境,如果环境中包含不同版本的相同的jar包,那么不可预见的结果可能会出现。比如,WebLogic包含一个J2EE的实现(在weblogic.jar里),这有可能和servlet.jar中的那个不同。在这种情况下,将servlet.jar排除在你的应用之外通常是更好的做法,以便于你知道哪个类正在被使用。
属性文件
Quartz使用一个叫做quartz.properties
的属性文件。这个文件最初不是必须的,但是当你想要使用任何基本配置以外的东西时,就需要用到它。当你使用这个文件,它必须在你的classpath下面。
如果你在构建一个web应用(比如,一个.war文件的形式),你可以将quartz.properties文件放在WEB-INF/classes
文件夹下,从而放置在了应用的classpath下。
提示:如果你使用WebLogic Workshop开发你的应用,你可以保留你的配置文件(包括quartz.properties)在你应用的根目录下面的一个项目中。当你打包你的应用到一个.ear文件中时,配置项目将会把最终的.ear中包含的打包到一个.jar中。这样做将会自动的将quartz.properties文件放置在你的classpath下。
配置Quartz调度器
为了配置Quartz,你编辑quartz.properties文件,并把它放置在你的application的classpath下面。
注意:Quartz调度器的发布包中包含了若干quartz.properties文件的例子在
example/directory
中。
下面的例子展示了一个基本的quartz.properties文件的内容:
org.quartz.scheduler.instanceName = MyScheduler
org.quartz.threadPool.threadCount = 3
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
这个例子指定了以下的属性:
-
org.quartz.scheduler.instanceName
属性为调度器分配了一个名字为“MyScheduler”。 -
org.quartz.threadPool.threadCount
属性在线程池中分配了三个线程,意味着最多三个job同时进行。 -
org.quartz.jobStore.class
属性配置调度器去使用RAMJobStore来进行数据存储。这意味着调度器将会保持这个job和trigger的数据在内存中而不是数据库。
开始一个示例应用
下面的代码获取一个调度器实力,启动它,然后关闭它。
Quartz.java
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.impl.StdSchedulerFactory;
import static org.quartz.JobBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
注意:一旦你使用
StdSchedulerFactory.getDefaultScheduler()
获得了一个调度器,你的应用将会一直运行直到你调用了scheduler.shutdown()
。因为这将是活动的线程。
注意这里使用了static引入,这将在下面的代码中发挥作用。
如果你还没有设置日志,日志的信息将会被发送到console中。你的输出将像下面这样:
[INFO] 21 Jan 08:46:27.857 AM main [org.quartz.core.QuartzScheduler]
Quartz Scheduler v.2.0.0-SNAPSHOT created.
[INFO] 21 Jan 08:46:27.859 AM main [org.quartz.simpl.RAMJobStore]
RAMJobStore initialized.
[INFO] 21 Jan 08:46:27.865 AM main [org.quartz.core.QuartzScheduler]
Scheduler meta-data: Quartz Scheduler (v2.0.0) 'Scheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 50 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support
persistence. and is not clustered.
[INFO] 21 Jan 08:46:27.865 AM main [org.quartz.impl.StdSchedulerFactory]
Quartz scheduler 'Scheduler' initialized from default resource file in Quartz
package: 'quartz.properties'
[INFO] 21 Jan 08:46:27.866 AM main [org.quartz.impl.StdSchedulerFactory]
Quartz scheduler version: 2.0.0
[INFO] 21 Jan 08:46:27.866 AM main [org.quartz.core.QuartzScheduler]
Scheduler Scheduler_$_NON_CLUSTERED started.
[INFO] 21 Jan 08:46:27.866 AM main [org.quartz.core.QuartzScheduler]
Scheduler Scheduler_$_NON_CLUSTERED shutting down.
[INFO] 21 Jan 08:46:27.866 AM main [org.quartz.core.QuartzScheduler]
Scheduler Scheduler_$_NON_CLUSTERED paused.
[INFO] 21 Jan 08:46:27.867 AM main [org.quartz.core.QuartzScheduler]
Scheduler Scheduler_$_NON_CLUSTERED shutdown complete.
为了做一些事情,你需要在start()
和shutdown()
的调用之间提供一些代码。你将同样需要允许一些时候在调用shutdown()
之前,对job进行触发和执行。对于一个简单的像这样的例子,你应该调用:
Thread.sleep(60000);
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("job1", "group1")
.build();
// Trigger the job to run now, and then repeat every 40 seconds
Trigger trigger = newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
scheduler.scheduleJob(job, trigger);