随着现代Web应用程序范围和复杂性的不断增长,应用程序的每个基础组件也必须以相似的方式增长。 作业调度是现代系统中Java应用程序的常见要求,因此对于Java开发人员来说,这是始终不变的任务。 尽管当前的调度技术已经从数据库触发标志和单独的调度程序线程的更原始的方法发展而来,但是作业调度仍然是一个不小的问题。 解决此问题的最理想解决方案之一是OpenSymphony提供的Quartz API。
Quartz是一个开放源代码的作业调度框架,为Java应用程序中的作业调度提供了简单但功能强大的机制。 Quartz允许开发人员按时间间隔或一天中的时间安排作业。 它为作业和触发器实现了多对多关系,并且可以将多个作业与不同的触发器相关联。 包含Quartz的应用程序可以重用来自不同事件的作业,也可以将多个作业组合为一个事件。 虽然您可以通过属性文件(您可以在其中为JDBC事务,全局作业和/或触发器侦听器,插件,线程池等指定数据源)配置Quartz,但是它根本没有与应用程序服务器的集成在一起。上下文或参考。 其结果之一是,作业无法访问Web服务器的内部功能。 例如,对于WebSphere Application Server,Quartz调度的作业不会干扰服务器的Dyna缓存和数据源。
本文使用一系列代码示例介绍Quartz API,以说明诸如作业,触发器,作业存储和属性之类的机制。
入门
要开始使用Quartz,您需要使用Quartz API配置您的项目。 步骤如下:
- 下载Quartz API。
- 解压缩quartz-xxxjar并将其放入您的项目文件夹,或将文件放入您的项目类路径。
- 将jar文件从核心和/或可选文件夹放入您的项目文件夹或项目类路径中。
- 如果使用
JDBCJobStore
,请将所有JDBC jar文件放入项目文件夹或项目类路径。
为了您的方便,我们将所有必需的文件(包括DB2 JDBC文件)编译到一个zip中。 请参阅可下载资源部分以下载代码。
现在,让我们看一下Quartz API的主要组件。
工作和触发器
Quartz调度程序包的两个基本单元是作业和触发器。 作业是可以调度的可执行任务,而触发器提供了作业的调度。 虽然可以很容易地将这两个实体合并,但在Quartz中分离它们既有意又有益。
通过将要执行的工作与计划分开,Quartz允许您更改工作的计划触发器,而不会丢失工作本身或周围环境。 此外,任何单个作业都可以具有与其关联的许多触发器。
示例1:工作
您可以通过实现org.quartz.job
接口使Java类可执行。 清单1给出了一个Quartz作业的示例。该类使用非常简单的输出语句覆盖了execute(JobExecutionContext context)
方法。 该方法可以包含我们可能希望执行的任何代码。 (所有代码示例均基于Quartz 1.5.2,即撰写本文时的稳定版本。)
清单1. 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
对象存储作业的侦听器,组,数据映射,描述和其他作业属性。
示例2:简单触发器
触发器为作业制定时间表。 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
。 除了指定的重复次数和指定的重复间隔外,您还可以安排作业在特定的日历时间执行,给定最大执行时间或给定优先级,我们将在下面讨论。 最大执行时间将覆盖指定的重复次数,从而确保作业不会超过最大时间。
示例3:Cron触发器
与SimpleTrigger
相比, CronTrigger
允许更具体的调度,并且仍然不是很复杂。 基于cron表达式, CronTrigger
允许类似日历的重复间隔,而不是统一的重复间隔,这是对SimpleTrigger
的重大改进。
Cron表达式包含以下七个字段:
- 秒
- 分钟
- 小时
- 每月的一天
- 月
- 星期几
- 年(可选字段)
特殊的角色
Cron触发器使用一系列特殊字符,如下所示:
- 反斜杠(/)字符表示值的增量。 例如,秒字段中的“ 5/15”表示从第五秒开始每15秒。
- 问题(?)字符和字母L(L)字符仅在每月的日期和星期几字段中被允许。 问号表示该字段不应包含任何特定值。 因此,如果您指定星期几,则可以插入“?” 在“星期几”字段中表示“星期几”的值无关紧要。 字母L字符代表last 。 放置在“每月的日期”字段中,这计划在每月的最后一天执行。 在“星期几”字段中,“ L”等效于“ 7”(如果单独放置)或表示月份中星期几的最后一个实例。 因此,“ 0L”会将执行安排在该月的最后一个星期日。
- 每月日期字段中的字母-W(W)字符安排在最接近指定值的工作日执行。 在“月日”字段中放置“ 1W”可安排在最接近月初的工作日执行。
- 井号(#)字符指定给定月份的工作日的特定实例。 在“星期几”字段中放置“ MON#2”可将任务安排在该月的第二个星期一。
- 星号(*)字符是通配符,表示可以为该特定字段获取所有可能的值。
所有这些定义看似令人生畏,但是经过仅仅几分钟的练习,cron表达式就变得简单了。
清单3显示了CronTrigger
的示例。 请注意, SchedulerFactory
, Scheduler
和JobDetail
的实例化与SimpleTrigger
示例中的实例化相同。 在这种情况下,我们仅更改了触发器。 我们在此处指定的cron表达式(“ 0/5 * * * *?”)安排任务每5秒执行一次。
清单3. CronTriggerRunner.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("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将所有数据放置在数据库中。 数据持久性的权衡是较低的性能水平和较高的复杂性水平。
设置JDBCJobStore
在前面的示例中,您已经看到了一个RAMJobStore
实例在工作。 因为它是默认的作业存储,所以很明显,不需要其他设置即可使用它。 但是,使用JDBCJobStore
需要一些初始化。
设置JDBCJobStore
以在您的应用程序中使用需要两个步骤:首先,您必须创建要由作业存储使用的数据库表。 JDBCJobStore
与所有主要数据库兼容,并且Quartz提供了一系列表创建SQL脚本,这些脚本简化了设置过程。 您可以在Quartz发行版的“ docs / dbTables”目录中找到表创建SQL脚本。 其次,必须定义一些属性,如表1所示:
表1. JDBCJobStore属性
物业名称 | 值 |
---|---|
org.quartz.jobStore.class | org.quartz.impl.jdbcjobstore.JobStoreTX(或JobStoreCMT) |
org.quartz.jobStore.tablePrefix | QRTZ_(可选,可自定义) |
org.quartz.jobStore.driverDelegateClass | org.quartz.impl.jdbcjobstore.StdJDBCDelegate |
org.quartz.jobStore.dataSource | qzDS(可自定义) |
org.quartz.dataSource.qzDS.driver | com.ibm.db2.jcc.DB2Driver(可以是任何其他数据库驱动程序) |
org.quartz.dataSource.qzDS.url | jdbc:db2:// localhost:50000 / QZ_SMPL(可自定义) |
org.quartz.dataSource.qzDS.user | db2inst1(将用户标识放置到您自己的数据库中) |
org.quartz.dataSource.qzDS.password | pass4dbadmin(为用户输入您自己的密码) |
org.quartz.dataSource.qzDS.maxConnections | 30 |
清单4说明了JDBCJobStore
提供的数据持久性。 与前面的示例一样,我们从初始化SchedulerFactory
和Scheduler
。 接下来,我们先获取触发器组名称列表,然后再获取每个触发器组名称列表,而不是初始化作业和触发器。 请注意,应使用Scheduler.reschedule()
方法重新安排每个现有作业。 简单地重新初始化在先前的应用程序运行中终止的作业并不能准确地加载触发器的属性。
清单4. JDBCJobStoreRunner.java
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();
}
运行JDBCJobStore
当我们第一次运行示例时,触发器在数据库中初始化。 图1显示了初始化触发器之后但触发触发器之前的数据库。 因此,根据清单4中的setRepeatCount()
方法, 将 REPEAT_COUNT
设置为100,并且TIMES_TRIGGERED
为0。在使应用程序运行一段时间后,将其停止。
图1.使用JDBCJobStore的数据库中的数据(运行前)
图2显示了应用程序停止后的数据库。 在此图中, TIMES_TRIGGERED
设置为19,表示作业运行的次数。
图2. 19次迭代后的相同数据
当我们再次启动该应用程序时, REPEAT_COUNT
被更新。 这在图3中显而易见。在这里,我们看到REPEAT_COUNT
更新为81,因此新的REPEAT_COUNT
等于先前的REPEAT_COUNT
值减去先前的TIMES_TRIGGERED
值。 此外,我们在图3中看到新的TIMES_TRIGGERED
值为7,表明自重新启动应用程序以来,该作业已被触发了七次。
图3.第二次运行7次迭代后的数据
再次停止应用程序后, REPEAT_COUNT
值将再次更新。 如图4所示,该应用程序已停止但尚未重新启动。 同样,通过从先前的TIMES_TRIGGERED
值中减去先前的REPEAT_COUNT
值来更新REPEAT_COUNT
值。
图4.再次运行触发器之前的初始数据
使用属性
如JDBCJobStore
所见,可以使用许多属性来微调Quartz的行为。 您应该在quartz.properties文件中指定这些属性。 请参阅相关主题的配置属性的列表。 清单5显示了用于JDBCJobStore
示例的属性示例:
清单5.quartz.properties
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通过使原本繁琐的作业调度琐事对开发人员而言变得微不足道,从而成功地填补了开源领域的空白。
翻译自: https://www.ibm.com/developerworks/java/library/j-quartz/index.html