Quartz API 采用多面方式在 Java 应用程序中进行任务调度 php
Quartz 是个开放源码项目,提供了丰富的做业调度集。在这篇文章中,软件工程师 Michael Lipton 和 IT 架构师 Soobaek Jang 对 Quartz API 进行了介绍,从对框架的通常概述开始,并以一系列展现 Quart 基本特性的代码示例做为结束。在阅读完本文并看过代码示例后,您应当可以把 Quartz 的基本特性应用到任何 Java™ 应用程序中。 html
Quartz 是个开源的做业调度框架,为在 Java 应用程序中进行做业调度提供了简单却强大的机制。Quartz 容许开发人员根据时间间隔(或天)来调度做业。它实现了做业和触发器的多对多关系,还能把多个做业与不一样的触发器关联。整合了 Quartz 的应用程序能够重用来自不一样事件的做业,还能够为一个事件组合多个做业。虽然能够经过属性文件(在属性文件中能够指定 JDBC 事务的数据源、全局做业和/或触发器侦听器、插件、线程池,以及更多)配置 Quartz,但它根本没有与应用程序服务器的上下文或引用集成在一块儿。结果就是做业不能访问 Web 服务器的内部函数;例如,在使用 WebSphere 应用服务器时,由 Quartz 调度的做业并不能影响服务器的动态缓存和数据源。 java
本文使用一系列代码示例介绍 Quartz API,演示它的机制,例如做业、触发器、做业仓库和属性。 数据库
要开始使用 Quartz,须要用 Quartz API 对项目进行配置。步骤以下: 编程
为了方便读者,我已经把全部必要的文件,包括 DB2 JDBC 文件,编译到一个 zip 文件中。请参阅 下载 小节下载代码。 缓存
如今来看一下 Quartz API 的主要组件。 服务器
Quartz 调度包的两个基本单元是做业和触发器。做业 是可以调度的可执行任务,触发器 提供了对做业的调度。虽然这两个实体很容易合在一块儿,但在 Quartz 中将它们分离开来是有缘由的,并且也颇有益处。 架构
经过把要执行的工做与它的调度分开,Quartz 容许在不丢失做业自己或做业的上下文的状况下,修改调度触发器。并且,任何单个的做业均可以有多个触发器与其关联。 app
经过实现 org.quartz.job 接口,可使 Java 类变成可执行的。清单 1 提供了 Quartz 做业的一个示例。这个类用一条很是简单的输出语句覆盖了 execute(JobExecutionContext context) 方法。这个方法能够包含咱们想要执行的任何代码(全部的代码示例都基于 Quartz 1.5.2,它是编写这篇文章时的稳定发行版)。 框架
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 展现了触发器的基础:
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 提供了两种不一样的方式用来把与做业和触发器有关的数据保存在内存或数据库中。第一种方式是 RAMJobStore 类的实例,这是默认设置。这个做业仓库最易使用,并且提供了最佳性能,由于全部数据都保存在内存中。这个方法的主要不足是缺少数据的持久性。由于数据保存在 RAM 中,因此应用程序或系统崩溃时,全部信息都会丢失。
为了修正这个问题,Quartz 提供了 JDBCJobStore。顾名思义,做业仓库经过 JDBC 把全部数据放在数据库中。数据持久性的代价就是性能下降和复杂性的提升。
在前面的示例中,已经看到了 RAMJobStore 实例的工做状况。由于它是默认的做业仓库,因此显然不须要额外设置就能使用它。可是,使用JDBCJobStore 须要一些初始化。
在应用程序中设置使用 JDBCJobStore 须要两步:首先必须建立做业仓库使用的数据库表。 JDBCJobStore 与全部主流数据库都兼容,并且 Quartz 提供了一系列建立表的 SQL 脚本,可以简化设置过程。能够在 Quartz 发行包的 “docs/dbTables”目录中找到建立表的 SQL 脚本。第二,必须定义一些属性,如表 1 所示:
属性名称 | 值 |
---|---|
org.quartz.jobStore.class | org.quartz.impl.jdbcjobstore.JobStoreTX (or JobStoreCMT) |
org.quartz.jobStore.tablePrefix | QRTZ_ (optional, customizable) |
org.quartz.jobStore.driverDelegateClass | org.quartz.impl.jdbcjobstore.StdJDBCDelegate |
org.quartz.jobStore.dataSource | qzDS (customizable) |
org.quartz.dataSource.qzDS.driver | com.ibm.db2.jcc.DB2Driver (could be any other database driver) |
org.quartz.dataSource.qzDS.url | jdbc:db2://localhost:50000/QZ_SMPL (customizable) |
org.quartz.dataSource.qzDS.user | db2inst1 (place userid for your own db) |
org.quartz.dataSource.qzDS.password | pass4dbadmin (place your own password for user) |
org.quartz.dataSource.qzDS.maxConnections | 30 |
清单 4 展现了 JDBCJobStore 提供的数据持久性。就像在前面的示例中同样,先从初始化 SchedulerFactory 和 Scheduler 开始。而后,再也不须要初始化做业和触发器,而是要获取触发器群组名称列表,以后对于每一个群组名称,获取触发器名称列表。请注意,每一个现有的做业都应当用 Scheduler.reschedule() 方法从新调度。仅仅从新初始化在先前的应用程序运行时终止的做业,不会正确地装载触发器的属性。
public void task() throws SchedulerException { // Initiate a Schedule Factory SchedulerFactory schedulerFactory = new StdSchedulerFactory(); // Retrieve a scheduler from schedule factory Scheduler scheduler = schedulerFactory.getScheduler(); String[] triggerGroups; String[] triggers; triggerGroups = scheduler.getTriggerGroupNames(); for (int i = 0; i < triggerGroups.length; i++) { triggers = scheduler.getTriggerNames(triggerGroups[i]); for (int j = 0; j < triggers.length; j++) { Trigger tg = scheduler.getTrigger(triggers[j], triggerGroups[i]); if (tg instanceof SimpleTrigger && tg.getName().equals("simpleTrigger")) { ((SimpleTrigger)tg).setRepeatCount(100); // reschedule the job scheduler.rescheduleJob(triggers[j], triggerGroups[i], tg); // unschedule the job //scheduler.unscheduleJob(triggersInGroup[j], triggerGroups[i]); } } } // start the scheduler scheduler.start(); }
在第一次运行示例时,触发器在数据库中初始化。图 1 显示了数据库在触发器初始化以后但还没有击发以前的状况。因此,基于 清单 4 中的setRepeatCount() 方法,将 REPEAT_COUNT 设为 100,而 TIMES_TRIGGERED 是 0。在应用程序运行一段时间以后,应用程序中止。
图 2 显示了数据库在应用程序中止后的状况。在这个图中,TIMES_TRIGGERED 被设为 19,表示做业运行的次数。
当再次启动应用程序时,REPEAT_COUNT 被更新。这在图 3 中很明显。在图 3 中能够看到 REPEAT_COUNT 被更新为 81,因此新的REPEAT_COUNT 等于前面的 REPEAT_COUNT 值减去前面的 TIMES_TRIGGERED 值。并且,在图 3 中还看到新的 TIMES_TRIGGERED 值是 7,表示做业从应用程序从新启动以来,又触发了 7 次。
当再次中止应用程序以后,REPEAT_COUNT 值再次更新。如图 4 所示,应用程序已经中止,尚未从新启动。一样,REPEAT_COUNT 值更新成前一个 REPEAT_COUNT 值减去前一个 TIMES_TRIGGERED 值。
正如在使用 JDBCJobStore 时看到的,能够用许多属性来调整 Quartz 的行为。应当在 quartz.properties 文件中指定这些属性。请参阅 参考资料得到能够配置的属性的列表。清单 5 显示了用于 JDBCJobStore 示例的属性:
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 10 org.quartz.threadPool.threadPriority = 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true # Using RAMJobStore ## if using RAMJobStore, please be sure that you comment out the following ## - org.quartz.jobStore.tablePrefix, ## - org.quartz.jobStore.driverDelegateClass, ## - org.quartz.jobStore.dataSource #org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore # Using JobStoreTX ## Be sure to run the appropriate script(under docs/dbTables) first to create tables org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX # Configuring JDBCJobStore with the Table Prefix org.quartz.jobStore.tablePrefix = QRTZ_ # Using DriverDelegate org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate # Using datasource org.quartz.jobStore.dataSource = qzDS # Define the datasource to use org.quartz.dataSource.qzDS.driver = com.ibm.db2.jcc.DB2Driver org.quartz.dataSource.qzDS.URL = jdbc:db2://localhost:50000/dbname org.quartz.dataSource.qzDS.user = dbuserid org.quartz.dataSource.qzDS.password = password org.quartz.dataSource.qzDS.maxConnections = 30
Quartz 做业调度框架所提供的 API 在两方面都表现极佳:既全面强大,又易于使用。Quartz 能够用于简单的做业触发,也能够用于复杂的 JDBC 持久的做业存储和执行。OpenSymphony 在开放源码世界中成功地填补了一个空白,过去繁琐的做业调度如今对开发人员来讲不过是小菜一碟。
描述 | 名字 | 大小 |
---|---|---|
带有依赖 jar 的基于 Quartz 的示例 Java 代码 | j-quartz-withJars.zip | 3173KB |
不带依赖 jar 的基于 Quartz 的示例 Java 代码 | j-quartz-noJars.zip | 10KB |