Quartz学习——Quartz大致介绍(一)

Quartz Application Job 的区别


我们在这里打断一下,有必要解释这个容易搞混的概念。我们用述语“Quartz
Application”来指代任何使用到 Quartz 框架的软件程序。通常一个应用程序中会使用许多
库,只要用上了 Quartz,我们认为这就是我们在本书上所讨论的 Quartz Application。另
一方面,我们所说的 Quartz Job,是指执行一些作业的特定的 Java 类。正常地,在一个
Quartz Application 中会包括许多不同类型的 Job,每一个作业会对应有一个具体 Java
类。这两个概念不能交换使用
 

创建一个 Quartz Job
 

每一个 Quartz Job 必须有一个实现了 org.quartz.Job 接口的具体类。这个接口仅有一个要你在 Job 中实现的方法,execute(),方法 execute() 的原型如下:
public void execute(JobExecutionContext context) throws JobExecutionException;
当 Quartz 调度器确定到时间要激发一个 Job 的时候,它就会生成一个 Job 实例,并调用这个实例的 execute() 方法。调度器只管调用 execute() 方法,而不关心执行的结果,除了在作业执行中出问题抛出的 org.quartz.JobExecutionException 异常。

你可以在 execute() 方法中执行你的业务逻辑:例如,也许你会调用其他构造的实例上的方法,发送一个电子邮件、FTP 传一个文件、调用一个 Web 服务、调用一个EJB、执行一个工作流
 

public void execute(JobExecutionContext context) throws JobExecutionException {
// Every job has its own job detail
JobDetail jobDetail = context.getJobDetail();

// The name is defined in the job definition
String jobName = jobDetail.getName();

// Log the time the job started
logger.info(jobName + " fired at " + new Date());

// The directory to scan is stored in the job map
JobDataMap dataMap = jobDetail.getJobDataMap();
String dirName = dataMap.getString("SCAN_DIR");

Quartz 调用 execute() 方法,会传递一个 org.quartz.JobExecutionContext 上下文变量,里面封装有 Quartz 的运行时环境和当前正执行的 Job。通过 JobexecutionContext,你可以访问到调度器的信息,作业和作业上的触发器的信息,还有更多更多的信息。在代码 3.1 中,JobExecutionContext 被用来访问 org.quartz.JobDetail 类,JobDetail 类持有 Job 的详细信息,包括为 Job 实例指定的名称,Job 所属组,Job 是否被持久化(易失性),和许多其他感兴趣的属性。

JobDetail 又持有一个指向 org.quartz.JobDataMap 的引用。JobDataMap 中有为指定 Job 配置的自定义属性。例如,在代3.1中,我们从 JobDataMap 中获得欲扫描的目录名,我们可以在 ScanDirectoryJob 中硬编码这个目录名,但是这样的话我们难以重用这个 Job 来扫描别的目录了。在后面有一节“编程方式调度一个 Quartz Job”,你将会看到目录是如何配置到JobDataMap 的。

声明式之于编程式配置


Quartz 中,我们有两种途径配置应用程序的运行时属性:声明式和编程式。有一些框架是使用外部配置文件的方式;我们都知道,在软件中硬编码设置有它的局限性。从其他方面来讲,你将要根据具体的需求和功能来选择用哪一种方式。下一节强调了何时用声明式何时选择编程式。因为多数的 Java 行业应用都偏向于声明的方式,这也是我们所推荐的。

在下一节中,我们讨论如何为调度器配置 Job 和运行 ScanDirectoryJob

2. 调度 Quartz ScanDirectoryJob


到目前为止,我们已经创建了一个 Quartz job,但还没有决定怎么处置它--明显地,我们需以某种方式为这个 Job 设置一个运行时间表。时间表可以是一次性的事件,或者我们可能会安装它在除周日之外的每个午夜执行。你即刻将会看到,Quartz Schduler 是框架的心脏与灵魂。所有的 Job 都通过 Schduler 注册;必要时,Scheduler 也会创建 Job 类的实例,并执行实例execute() 方法

Scheduler 会为每一次执行创建新的 Job 实例


Scheduler 在每次执行时都会为 Job 创建新的实例。这就意味着 Job 的任何实例变量在执行结束之后便会丢失。与此相反概念则可用述语有状态的J2EE世界里常见语)来表达,但是应用 Quartz ,一个有状态的 Job 并不用多少开销,而且很容易的配置。当你创建一个有状态的 Job 时,有一些东西对于 Quartz 来说是独特的。最主要的就是不会出现两个有着相同状态的 Job 实例并发执行。这可能会影响到程序的伸缩性。这些或更多的问题将在以后的章节中详细讨论。

创建并运行 Quartz Scheduler

在具体谈论 ScanDirectoryJob 之前,让我们大略讨论一下如何实例化并运行 Quartz Scheduler 实例。代码 3.3 描述了创建和启动一个 Quartz Scheduler 实例的必要且基本的步骤

public void startScheduler() {
 	Scheduler scheduler = null;
	 try {
	 // Get a Scheduler instance from the Factory
	 scheduler = StdSchedulerFactory.getDefaultScheduler();
	 // Start the scheduler
	 scheduler.start();
	 logger.info("Scheduler started at " + new Date());
	 } catch (SchedulerException ex) {
	 // deal with any exceptions
	// deal with any exceptions
	logger.error(ex);
	}
}

代码 3.3 展示了启动一个 Quartz 调度器是那么的简单。当调度器起来之后,你可以利用它做很多事情或者获取到它的许多信息。例如,你也许需要安排一些 Job 或者改变又安排在调度器上 Job 的执行次数。你也许需要让调度器处于暂停模式,接着再次启动它以便重新执行在其上安排的作业。当调度器处于暂停模式时,不执行任何作业,即使是作业到了它所期待的执行时间。代码3.4 展示了怎么把调度器置为暂停模式然后又继续执行。

代码 3.4 设置调度器为暂停模式

private void modifyScheduler(Scheduler scheduler) {

	try {
		if (!scheduler.isInStandbyMode()) {
		// pause the scheduler
		scheduler.standby();
	}

	// Do something interesting here

	 // and then restart it
	 	scheduler.start();

	 } catch (SchedulerException ex) {
	 	logger.error(ex);
	 }
 }

代码 3.4 中的片断仅仅是一个最简单的例子,说明了当你执有一个 Quartz 调度器的引用,你可以利用它做一些你有感兴趣的事情。当然了,并非说 Scheduler 只有处于暂停模式才能很好的利用它。例如,你能在调度器处于运行状态时,安排新的作业或者是卸下已存在的作业。我们将通过本书的一个调度器尽可能的去掌握关于 Quartz 更多的知识

上面的例子看起来都很简单,但千万不要被误导了。我们还没有指定任何作业以及那些作业的执行时间表。虽然代码 3.3 中的代码确实能启动运行,可是我们没有指定任何作业来执行。这就是我们下一节要讨论的。

·编程式安排一个 Quartz Job
所有的要 Quartz 来执行的作业必须通过调度器来注册。大多情况下,这会在调度器启动前做好。正如本章前面说过,这一操作也提供了声明式与编程式两种实现途径的选择。首先,我们讲解如何用编程的方式;下来在本章,我们会用声明的方式重做这个练习。
因为每一个 Job 都必须用 Scheduler 来注册,所以先定义一个 JobDetail,并关联到这个 Scheduler 实例。

见代码 3.5

public class Listing_3_5 {
static Log logger = LogFactory.getLog(Listing_3_5.class);
public static void main(String[] args) {
Listing_3_5 example = new Listing_3_5();
try {
// Create a Scheduler and schedule the Job
Scheduler scheduler = example.createScheduler();
example.scheduleJob(scheduler);
// Start the Scheduler running
scheduler.start();
logger.info( "Scheduler started at " + new Date() )
} catch (SchedulerException ex) {
logger.error(ex);
}
}
/*
* return an instance of the Scheduler from the factory
*/
public Scheduler createScheduler() throws SchedulerException {
return StdSchedulerFactory.getDefaultScheduler();
}
// Create and Schedule a ScanDirectoryJob with the Scheduler
private void scheduleJob(Scheduler scheduler)
throws SchedulerException {

// Create a JobDetail for the Job
JobDetail jobDetail =
new JobDetail("ScanDirectory",Scheduler.DEFAULT_GROUP,ScanDirectoryJob.class);
// Configure the directory to scan
jobDetail.getJobDataMap().put("SCAN_DIR","c:\\quartz-book\\input");
// Create a trigger that fires every 10 seconds, forever
Trigger trigger = TriggerUtils.makeSecondlyTrigger(10);
trigger.setName("scanTrigger");
// Start the trigger firing from now
trigger.setStartTime(new Date());
// Associate the trigger with the job in the scheduler
scheduler.scheduleJob(jobDetail, trigger);
}
}

上面程序提供了一个理解如何编程式安排一个 Job 很好的例子。代码首先调用 createScheduler() 方法从 Scheduler 工厂获取一个 Scheduler 的实例。得到 Scheduler 实例之后,把它传递给 schedulerJob() 方法,由它把 Job Scheduler 进行关联。

首先,创建了我们想要运行的 Job JobDetail 对象。JobDetail 构造器的参数中包含指派给 Job 的名称,逻辑组名,和实现org.quartz.Job 接口的全限类名称。我们可以使用 JobDetail 的别的构造器

public JobDetail();
public JobDetail(String name, String group, Class jobClass);
public JobDetail(String name, String group, Class jobClass,boolean volatility, boolean durability, boolean recover);

注 一
Job 在同一个 Scheduler 实例中通过名称和组名能唯一被标识。假如你增加两个具体相同名称和组名的 Job,程序会抛出 ObjectAlreadyExistsException 的异常。

在本章前面有说过,JobDetail 扮演着某一 Job 定义的角色。它带有 Job 实例的属性,能在运行时被所关联的 Job 访问到。其中在使用 JobDetail 时,的一个最重要的东西就是 JobDataMap,它被用来存放 Job 实例的状态和参数。在代码 3.5 中,待扫描的目录名称就是通过 scheduleJob() 方法存入到 JobDataMap 中的。

理解和使用 Quartz Trigger

Job 只是一个部分而已。注意到代码 3.5,我们没有在 JobDetail 对象中为 Job 设定执行日期和次数。这是 Quartz Trigger 做的事。顾名思义,Trigger 的责任就是触发一个 Job 去执行。当用 Scheduler 注册一个 Job 的时候要创建一个 Trigger 与这Job 相关联。Quartz 提供了四种类型的 Trigger,但其中两种是最为常用的,它们就是在下面章节中要用到的SimpleTrigger CronTrigger.

SimpleTrigger 是两个之中简单的那个,它主要用来激发单事件的 JobTrigger 在指定时间激发,并重复 n --两次激发时间之间的延时为 m,然后结束作业。CronTrigger 非常复杂且强大。它是基于通用的公历,当需要用一种较复杂的时间表去执行一Job 时用到。例如,四月至九月的每个星期一、星期三、或星期五的午夜

为更简单的使用 TriggerQuartz 包含了一个工具类,叫做 org.quartz.TriggerUtils. TriggerUtils 提供了许多便捷的方法简化了构造和配置 trigger. 本章的例子中有用的就是 TriggerUtils 类;SimpleTrigger CronTrigger 会在后面章节中用到

正如你从代码3.5中看到的那样,调用了 TriggerUtils 的方法 makeSecondlyTrigger() 来创建一个每10秒种激发一次的trigger(实际是由 TriggerUtils 生成了一个 SimpleTrigger 实例,但是我们的代码并不想知道这些)。我们同样要给这个 trigger实例一个名称并告诉它何时激发相应的 Job;在代码3.5 中,与之关联的 Job 会立即启动,因为由方法 setStartTime() 设定的是当前时间。

代码 3.5 演示的是如何向 Scheduler 注册单一 Job。假如你有不只一个个 Job (你也许就是),你将需要为每一个 Job 创建各自的 JobDetail。每一个 JobDetail 必须通过 scheduleJob() 方法一一注册到 Scheduler 上。


回到代码 3.1 中,我们从代码中看到要扫描的目录名属性是从 JobDataMap 中获取到的。
再看代码 3.5,你能发现这个属性是怎么设置的。

如果你想重用了一个 Job 类,让它产生多个实例运行,那么你需要为每个实例都创建一个 JobDetail。例如,假如你想重用ScanDirectoryJob 让它检查两个不同的目录,你需要创建并注册两个 JobDetail 实例。代码 3.6 显示了是如何做的。

代码 3.6. 运行 ScanDirectoryJob 的多个实例

public class Listing_3_6 {
static Log logger = LogFactory.getLog(Listing_3_6.class);

public static void main(String[] args) {
Listing_3_6 example = new Listing_3_6();

try {
// Create a Scheduler and schedule the Job
Scheduler scheduler = example.createScheduler();

// Jobs can be scheduled after Scheduler is running
scheduler.start();

logger.info("Scheduler started at " + new Date());

// Schedule the first Job
example.scheduleJob(scheduler, "ScanDirectory1",ScanDirectoryJob.class,"c:\\quartz-book\\input", 10);

// Schedule the second Job
example.scheduleJob(scheduler, "ScanDirectory2",ScanDirectoryJob.class,"c:\\quartz-book\\input2", 15);

} catch (SchedulerException ex) {
logger.error(ex);
}
}

/*
* return an instance of the Scheduler from the factory
*/
public Scheduler createScheduler() throws SchedulerException {
return StdSchedulerFactory.getDefaultScheduler();
}

// Create and Schedule a ScanDirectoryJob with the Scheduler
private void scheduleJob(Scheduler scheduler, String jobName,Class jobClass, String scanDir, int scanInterval)throws SchedulerException {

// Create a JobDetail for the Job
JobDetail jobDetail =new JobDetail(jobName,Scheduler.DEFAULT_GROUP, jobClass);

// Configure the directory to scan
jobDetail.getJobDataMap().put("SCAN_DIR", scanDir);

// Trigger that repeats every "scanInterval" secs forever
Trigger trigger =TriggerUtils.makeSecondlyTrigger(scanInterval);

trigger.setName(jobName + "-Trigger");

// Start the trigger firing from now
trigger.setStartTime(new Date());

// Associate the trigger with the job in the scheduler
scheduler.scheduleJob(jobDetail, trigger);
}
}

代码 3.6 和代码 3.5 非常的类似,只存在一点小小的区别。主要的区别是代码 3.6 中重构了允许多次调用 schedulerJob() 法。在设置上比如 Job 名称和扫描间隔名称通过参数传。因此从 createScheduler() 方法获取到 Scheduler 实例后,两个Job(同一个类) 用不同的参数就被安排到了 Scheduler 上了。(译者注:当用调 createScheduler() 方法得到 Scheduler 实例后,都还没有往上注册 Job,何来两个 Job )


Scheduler 启动之前还是之后安排 Job 代码
代码 3.6 中,我们在安排 Job 之前就调用了 Scheduler start() 方法。回到代码 3.5中,采用了另一种方式:我们是在 Job 安排了之后调用了 start() 方法。Job Trigger 在任何时候在 Scheduler 添加或删除 (除非是调用了它的 shutdown()方法)

INFO [main] (Listing_3_6.java:35) - Scheduler started at Mon Sep 05 15:12:15 EDT 2005
INFO [QuartzScheduler_Worker-0] ScanDirectory1 fired at Mon Sep 05 15:12:15 EDT 2005
INFO [QuartzScheduler_Worker-0] - c:\quartz-book\input\order-145765.xml - Size: 0
INFO [QuartzScheduler_Worker-0] - ScanDirectory2 fired at Mon Sep 05 15:12:15 EDT 2005
INFO [QuartzScheduler_Worker-0] - No XML files found in c:\quartz-book\input2
INFO [QuartzScheduler_Worker-1] - ScanDirectory1 fired at Mon Sep 05 15:12:25 EDT 2005
INFO [QuartzScheduler_Worker-1] - c:\quartz-book\input\order-145765.xml - Size: 0
INFO [QuartzScheduler_Worker-3] - ScanDirectory2 fired at Mon Sep 05 15:12:30 EDT 2005
INFO [QuartzScheduler_Worker-3] - No XML files found in c:\quartz-book\input2

第三章. Hello Quartz (第三部分)

3. 声明式部署一个 Job
前面我们讨论过,尽可能的用声明式处理软件配置,其次才才虑编程式。再来看代码 3.6,如果我们要在 Job 启动之后改变它的执行时间和频度,必须去修改源代码重新编译。这种方式只适用于小的例子程序,但是对于一个大且复杂的系统,这就成了一个问题了。因此,假如能以声明式部署 Quart Job 时,并且也是需求允许的情况下,你应该每次都选择这种方式

·配置 quartz.properties 文件
文件 quartz.properties 定义了 Quartz 应用运行时行为,还包含了许多能控制 Quartz 运转的属性。本章只会讲到它的基本配置;更多的高级设置将在以后讨论。在现阶段也不用太深入到每一项配置有效值的细节。现在我们来看看最基础的 quartz.properties 文件,并讨论其中一些设置。代码 3.7 是一个修剪版的 quartz.propertis 文件


Quartz 框架会为几乎所有的这些属性设定默认值

代码 3.7. 基本的 Quartz Properties 文件

#===============================================================
#Configure Main Scheduler Properties
#===============================================================
org.quartz.scheduler.instanceName = QuartzScheduler
org.quartz.scheduler.instanceId = AUTO

#===============================================================
#Configure ThreadPool
#===============================================================
 org.quartz.threadPool.threadCount = 5
 org.quartz.threadPool.threadPriority = 5
 org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

#===============================================================
#Configure JobStore
#===============================================================
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

#===============================================================
#Configure Plugins
#===============================================================
org.quartz.plugin.jobInitializer.class =org.quartz.plugins.xml.JobInitializationPlugin

org.quartz.plugin.jobInitializer.overWriteExistingJobs = true
org.quartz.plugin.jobInitializer.failOnFileNotFound = true
org.quartz.plugin.jobInitializer.validating=false

注 这里讨论的并没有涉及到所有可能的设置,仅仅是一些基本的设置。也是你需要去熟悉的,能使声明式例子运转起来的必须的设置项。quartz.properties 中的所有属性配置将会分散在本书中的各章节中依据所在章节涉及内容详细讨论。

调度器属性
第一部分有两行,分别设置调度器的实例名(instanceName) 和实例 ID (instanceId)。属性org.quartz.scheduler.instanceName 可以是你喜欢的任何字符串。它用来在用到多个调度器区分特定的调度器实例。多个调度器通常用在集群环境中。(Quartz 集群将会在第十一章,“Quartz 集群”中讨论)。现在的话,设置如下的一个字符串就行:

org.quartz.scheduler.instanceName = QuartzScheduler
实际上,这也是当你没有该属性配置时的默认值。代码 3.7 中显示的调度器的第二个属性是 org.quartz.scheduler.instanceId。和 instaneName 属性一样,instanceId 属性
也允许任何字符串。这个值必须是在所有调度器实例中是唯一的,尤其是在一个集群当中。假如你想 Quartz 帮你生成这个值的话,可以设置为 AUTO。如果 Quartz 框架是运行在非集群环境中,那么自动产生的值将会是 NON_CLUSTERED。假如是在集群环境下使用 Quartz,这个值将会是主机名加上当前的日期和时间。大多情况下,设置为 AUTO 即可。

线程池属性
接下来的部分是设置有关线程必要的属性值,这些线程在 Quartz 中是运行在后台担当重任的。threadCount 属性控制了多少个工作者线程被创建用来处理 Job。原则上是,要处理的 Job 越多,那么需要的工作者线程也就越多。threadCount 的数值至少1Quartz 没有限定你设置工作者线程的最大值,但是在多数机器上设置该值超过100的话就会显得相当不实用了,特别是在你Job 执行时间较长的情况下。这项没有默认值,所以你必须为这个属性设定一个值。

threadPriority 属性设置工作者线程的优先级。优先级别高的线程比级别低的线程更优先得到执行。threadPriority 属性的最大值是常量 java.lang.Thread.MAX_PRIORITY,等于10。最小值为常量 java.lang.Thread.MIN_PRIORITY,为1。这个属性的正常值是 Thread.NORM_PRIORITY,为5。大多情况下,把它设置为5,这也是没指定该属性的默认值。

最后一个要设置的线程池属性是 org.quartz.threadPool.class。这个值是一个实现了 org.quartz.spi.ThreadPool 接口的类的全限名称。Quartz 自带的线程池实现类是 org.quartz.smpl.SimpleThreadPool,它能够满足大多数用户的需求。这个线程池实现具备简单的行为,并经很好的测试过。它在调度器的生命周期中提供固定大小的线程池。你能根据需求创建自己的线程池实现,如果你想要一个随需可伸缩的线程池时也许需要这么做。这个属性没有默认值,你必须为其指定值

作业存储设置
作业存储部分的设置描述了在调度器实例的生命周期中,Job Trigger 信息是如何被存储的。我们还没有谈论到作业存储和它的目的;因为对当前例子是非必的,所以我们留待以后说明。现在的话,你所要了解就是我们存储调度器信息在内存中而不是在关系型数据库中就行了。

把调度器信息存储在内存中非常的快也易于配置。当调度器进程一旦被终止,所有的 Job Trigger 的状态就丢失了。要使 Job存储在内存中需通过设置 org.quartz.jobStrore.class 属性为 org.quartz.simpl.RAMJobStore,就像在代码 3.7 所做的那样。假如我们不希望在 JVM 退出之后丢失调度器的状态信息的话,我们可以使用关系型数据库来存储这些信息。这需要另一个作业存储(JobStore) 实现,我们在后面将会讨论到。第五章“Cron Trigger 和其他”和第六章“作业存储和持久化”会提到你需要用到的不同类型的作业存储实现。

·插件配置
在这个简单的 quartz.properties 文件中最后一部分是你要用到的 Quart 插件的配置。插件常常在别的开源框架上使用到,比如 Apache 的 Struts 框架(见 http://struts.apache.org)。一个声明式扩框架的方法就是通过新加实现了 org.quartz.spi.SchedulerPlugin 接口的类。SchedulerPlugin 接口中有给调度器调用的三个方法

Quartz 插件会在第八章“使用 Quartz 插件”中详细讨论
要在我们的例子中声明式配置调度器信息,我们会用到一个 Quartz 自带的叫做org.quartz.plugins.xml.JobInitializationPlugin 的插件。默认时,这个插件会在 classpath 中搜索名为 quartz_jobs.xml 的文件并从中加载 Job 和 Trigger 信息。在下一节中讨论 quartz_jobs.xml 文件,这是我们所参考的非正式的 Job 定义文件。

默认时,插件 JobInitializationPlugin 在 classpath 中寻找 quartz_jobs.xml 文件。你可以覆盖相应设置强制这个插件使用不同的文件名查找。要做到这个,你必须设置上一节讨论quartz.properties 中的文件名。目前,我们就使用默认的文件名 quartz_jobs.xml,至于如何修改 quartz.properties 中相应设置会在本章中后面讲到。

·使用 quartz_jobx.xml 文件
代码 3.8 就是目录扫描例子的 Job 定义的 XML 文件。正如代码 3.5 所示例子那样,这里我们用的是声明式途径来配置 JobTrigger 信息的。

<?xml version='1.0' encoding='utf-8'?>

<quartz>

<job>
<job-detail>
<name>ScanDirectory</name>
<group>DEFAULT</group>
<description>
 A job that scans a directory for files
 </description>
 <job-class>
 org.cavaness.quartzbook.chapter3.ScanDirectoryJob
 </job-class>
 <volatility>false</volatility>
 <durability>false</durability>
 <recover>false</recover>
 <job-data-map allows-transient-data="true">
 <entry>
 <key>SCAN_DIR</key>
 <value>c:\quartz-book\input</value>
 </entry>
 </job-data-map>
 </job-detail>

 <trigger>
 <simple>
 <name>scanTrigger</name>
 <group>DEFAULT</group>
 <job-name>ScanDirectory</job-name>
 <job-group>DEFAULT</job-group>
 <start-time>2005-06-10 6:10:00 PM</start-time>
 <!-- repeat indefinitely every 10 seconds -->
 <repeat-count>-1</repeat-count>
 <repeat-interval>10000</repeat-interval>
 </simple>
 </trigger>

 </job>
 </quartz>

 

<job> 元素描述了一个要注册到调度器上的 Job,相当于我们在前面章节中使用 scheduleJob() 方法那样。你所看到的<jobdetail> <trigger> 这两个元素就是我们在代码 3.5 中以编程式传递给方法 schedulerJob() 的参数。前面本质上是与这里一样的,只是现在用的是一种较流行声明的方式。你还可以对照着代码 3.5 中的例子来看在代码3.8 中我们是如何设置 SCAN_DIR属性到 JobDataMap 中的。<trigger>元素也是非常直观的:它使用前面同样的属性,但更简单的建立一个 SimpleTrigger。因此代码 3.8 仅仅是一种不同的(可论证的且更好的)方式做了代码 3.5 中同样的事情。显然,你也可以支持多个 Job。在代码3.6 中我们编程的方式那么做的,也能用声明的方式来支持。代码 3.9 显示了与代码 3.6

代码 3.9. 你能在一个 quartz_jobs.xml 文件中指定多个 Job
多个Job 多个 Trigger的示例

为插件修改 quartz.properties 配置
在本章前面,告诉过你的是,JobInitializationPlugin 找寻 quartz_jobs.xml 来获得声明的 Job 信息。假如你想改变这个文件名,你需要修改 quartz.properties 来告诉插件去加载那个文件。例如,假如你想要 Quartz 从名为 my_quartz_jobs.xml XML 文件中加载 Job 信息,你不得不为插件指定这一文件名。代码 3.10 显示了怎么完成这个配置;我们现在是最后一次在这里重复说明这一插件部分。

代码 3.10. JobInitializationPlugin 修改 quartz.properties

org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin
org.quartz.plugin.jobInitializer.fileName = my_quartz_jobs.xml
org.quartz.plugin.jobInitializer.overWriteExistingJobs = true
org.quartz.plugin.jobInitializer.validating = false
org.quartz.plugin.jobInitializer.overWriteExistingJobs = false
org.quartz.plugin.jobInitializer.failOnFileNotFound = true

在代码 3.10中,我们添加了属性 org.quartz.plugin.jobInitializer.fileName 并设置该属性值为我们想要的文件名。这个文件名要对 classloader 可见,也就是说要在 classpath 下。当 Quartz 启动后读取 quartz.properties 文件,然后初始化插件。它会传递上面配置的所有属性给插件,这时候插件也就得到通知去搜寻不同的文件。

译者后记:
想了又想,关于动词的 “Schedule” 还是选择“部署”,此前用的是“安排”,感觉不那么正式。当然英语中“部署”基本都用Deploy”对应,平时与同事交流 Quartz 方面的技术都是说“往调度器上部署一个 Job”的,只要词能达意就行。对于 “register with the Scheduler”,有时候是用的“通过调度器来注册”,有时候是“注册到调度器上”,意思基本一致

第三章. Hello Quartz (第四部分)

4. 打包 Quartz 应用程序
让我们最后简单讨论打包一个用到了 Quarts 框架的应用程序的流程,也以此来结束本章的内容。
·Quartz 第三方依赖包
1.5 版的发行包开始,你会看到一个 <QUARTZ_HOME>\lib 目录,在这个目录,你会发现几个子目录:
·<QUARTZ_HOME>\lib\core
·<QUARTZ_HOME>\lib\optional
·<QUARTZ_HOME>\lib\build
作为开发呢,你绝对是需求 Quartz JAR 包,也需要其他一些依赖包。需要哪些第三方包还依赖于你是运行在独立环境中还是作为一个 J2EE 发行包的一部份。典型的,jakarta Commons (commons-loggin, commons-beanutils,还有其他的) 总是要用到。然而,当你是部署到一个应用服务器环境中,你需要确保不能把那些在应用服务器上已存在的包拷过去;如果你这样做的,你可能回得到非常奇怪的结果。
3.1

3.1. Quartz 第三方包,必须/可选
120205_QDke_3101476.png

120312_gB7l_3101476.png

 

 

=============================================================

Quartz大致介绍

1. 介绍 

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,是完全由java开发的一个开源的任务日程管理系统,“任务进度管理器”就是一个在预先确定(被纳入日程)的时间到达时,负责执行(或者通知)其他软件组件的系统。 
Quartz用一个小Java库发布文件(.jar文件),这个库文件包含了所有Quartz核心功能。这些功能的主要接口(API)是Scheduler接口。它提供了简单的操作,例如:将任务纳入日程或者从日程中取消,开始/停止/暂停日程进度。 
2. 定时器种类 

Quartz 中五种类型的 Trigger:SimpleTrigger,CronTirgger,DateIntervalTrigger,NthIncludedDayTrigger和Calendar 类( org.quartz.Calendar)。 
最常用的: 
SimpleTrigger:用来触发只需执行一次或者在给定时间触发并且重复N次且每次执行延迟一定时间的任务。 
CronTrigger:按照日历触发,例如“每个周五”,每个月10日中午或者10:15分。 

3. 存储方式 

RAMJobStore和JDBCJobStore 
对比:

类型优点缺点
RAMJobStore不要外部数据库,配置容易,运行速度快因为调度程序信息是存储在被分配给JVM的内存里面,所以,当应用程序停止运行时,所有调度信息将被丢失。另外因为存储到JVM内存里面,所以可以存储多少个Job和Trigger将会受到限制
JDBCJobStore支持集群,因为所有的任务信息都会保存到数据库中,可以控制事物,还有就是如果应用服务器关闭或者重启,任务信息都不会丢失,并且可以恢复因服务器关闭或者重启而导致执行失败的任务运行速度的快慢取决与连接数据库的快慢

4. 表关系和解释

  • 表关系

这里写图片描述

  • 解释

表名称说明
qrtz_blob_triggersTrigger作为Blob类型存储(用于Quartz用户用JDBC创建他们自己定制的Trigger类型,JobStore 并不知道如何存储实例的时候)
qrtz_calendars以Blob类型存储Quartz的Calendar日历信息, quartz可配置一个日历来指定一个时间范围
qrtz_cron_triggers存储Cron Trigger,包括Cron表达式和时区信息。
qrtz_fired_triggers存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息
qrtz_job_details存储每一个已配置的Job的详细信息
qrtz_locks存储程序的非观锁的信息(假如使用了悲观锁)
qrtz_paused_trigger_graps存储已暂停的Trigger组的信息
qrtz_scheduler_state存储少量的有关 Scheduler的状态信息,和别的 Scheduler 实例(假如是用于一个集群中)
qrtz_simple_triggers存储简单的 Trigger,包括重复次数,间隔,以及已触的次数
qrtz_triggers存储已配置的 Trigger的信息
qrzt_simprop_triggers

 

5. 核心类和关系

  1. 核心类 
    (1)核心类 
    QuartzSchedulerThread :负责执行向QuartzScheduler注册的触发Trigger的工作的线程。 
    ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提供运行效率。 
    QuartzSchedulerResources:包含创建QuartzScheduler实例所需的所有资源(JobStore,ThreadPool等)。 
    SchedulerFactory :提供用于获取调度程序实例的客户端可用句柄的机制。 
    JobStore: 通过类实现的接口,这些类要为org.quartz.core.QuartzScheduler的使用提供一个org.quartz.Job和org.quartz.Trigger存储机制。作业和触发器的存储应该以其名称和组的组合为唯一性。 
    QuartzScheduler :这是Quartz的核心,它是org.quartz.Scheduler接口的间接实现,包含调度org.quartz.Jobs,注册org.quartz.JobListener实例等的方法。 
    Scheduler :这是Quartz Scheduler的主要接口,代表一个独立运行容器。调度程序维护JobDetails和触发器的注册表。 一旦注册,调度程序负责执行作业,当他们的相关联的触发器触发(当他们的预定时间到达时)。 
    Trigger :具有所有触发器通用属性的基本接口,描述了job执行的时间出发规则。 - 使用TriggerBuilder实例化实际触发器。 
    JobDetail :传递给定作业实例的详细信息属性。 JobDetails将使用JobBuilder创建/定义。 
    Job:要由表示要执行的“作业”的类实现的接口。只有一个方法 void execute(jobExecutionContext context) 
    (jobExecutionContext 提供调度上下文各种信息,运行时数据保存在jobDataMap中) 
    Job有个子接口StatefulJob ,代表有状态任务。 
    有状态任务不可并发,前次任务没有执行完,后面任务处于阻塞等到。
  2. 关系-自己理解

这里写图片描述

一个job可以被多个Trigger 绑定,但是一个Trigger只能绑定一个job!

6. 配置文件 

quartz.properties 
//调度标识名 集群中每一个实例都必须使用相同的名称 (区分特定的调度器实例) 
org.quartz.scheduler.instanceName:DefaultQuartzScheduler 
//ID设置为自动获取 每一个必须不同 (所有调度器实例中是唯一的) 
org.quartz.scheduler.instanceId :AUTO 
//数据保存方式为持久化 
org.quartz.jobStore.class :org.quartz.impl.jdbcjobstore.JobStoreTX 
//表的前缀 
org.quartz.jobStore.tablePrefix : QRTZ_ 
//设置为TRUE不会出现序列化非字符串类到 BLOB 时产生的类版本问题 
//org.quartz.jobStore.useProperties : true 
//加入集群 true 为集群 false不是集群 
org.quartz.jobStore.isClustered : false 
//调度实例失效的检查时间间隔 
org.quartz.jobStore.clusterCheckinInterval:20000 
//容许的最大作业延长时间 
org.quartz.jobStore.misfireThreshold :60000 
//ThreadPool 实现的类名 
org.quartz.threadPool.class:org.quartz.simpl.SimpleThreadPool 
//线程数量 
org.quartz.threadPool.threadCount : 10 
//线程优先级 
org.quartz.threadPool.threadPriority : 5(threadPriority 属性的最大值是常量 java.lang.Thread.MAX_PRIORITY,等于10。最小值为常量 java.lang.Thread.MIN_PRIORITY,为1) 
//自创建父线程 
//org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true 
//数据库别名 
org.quartz.jobStore.dataSource : qzDS 
//设置数据源 
org.quartz.dataSource.qzDS.driver:com.mysql.jdbc.Driver 
org.quartz.dataSource.qzDS.URL:jdbc:mysql://localhost:3306/quartz 
org.quartz.dataSource.qzDS.user:root 
org.quartz.dataSource.qzDS.password:123456 
org.quartz.dataSource.qzDS.maxConnection:10

7.JDBC插入表顺序

主要的JDBC操作类,执行sql顺序。 
Simple_trigger :插入顺序 
qrtz_job_details —> qrtz_triggers —> qrtz_simple_triggers 
qrtz_fired_triggers 
Cron_Trigger:插入顺序 
qrtz_job_details —> qrtz_triggers —> qrtz_cron_triggers 
qrtz_fired_triggers

8.参考文章

官网: http://www.quartz-scheduler.org/ 
Quartz任务调度快速入门 :http://sishuok.com/forum/posts/list/405.html 
深入解读Quartz的原理 :http://lavasoft.blog.51cto.com/62575/181907/ 
基于 Quartz 开发企业级任务调度应用 :http://www.ibm.com/developerworks/cn/opensource/os-cn-quartz/ 
quartz 数据库表含义解释 :http://blog.youkuaiyun.com/tengdazhang770960436/article/details/51019291 
Quartz源码分析: https://my.oschina.net/chengxiaoyuan/blog/664833 
http://blog.youkuaiyun.com/u010648555/article/category/6601767 
Quartz系列:http://blog.youkuaiyun.com/Evankaka/article/category/3155529

 

 

 

 

 

 

 

 

 

转载于:https://my.oschina.net/LucasZhu/blog/1476073

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值