Quartz Job Scheduling Framework[翻译]第三章. Hello Quartz (第二部分)

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 实例的必要且基本的步骤。

代码 3.3 运行一个简单的 Quartz 调度器

运行上面 3.3 的代码,会有日志输出,你会看到类似如下的输出:

INFO [main] (SimpleScheduler.java:30) - Scheduler started at Mon Sep 05 13:06:38 EDT 2005

关闭 Quartz Info 级别的日志信息

假如你搭配着 Log4J 使用 Commons Logging 日志框架,就像本书的例子那样,你也许需要把除本书例子外,其他的所有 Info 级别以上的日志信息关闭掉。这是因为 Quartz 中 Debug 和 Info 级别的日志信息数量上大体相当。当你明白了 Quartz 在做什么的时候,你真正关注的信息却淹没在大量的日志信息中。为了不至于这样,你可以创建一个文件 log4j.properties 指定只输出 ERROR 级别信息,但是要为本书中的例子设置定显示 INFO 级别的信息。这里有一个 log4j.properties 的例子文件来达到这个目的:

文件 log4j.properties 配置了默认向标准输出只输出 ERROR 级别以上的日志信息,但是任何在包 org.cavaness.quartzbook 中的 INFO 以上级别的信息也会输出。这有赖于以上属性文件最后一行配置。

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

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

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

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

·编程式安排一个 Quartz Job

所有的要 Quartz 来执行的作业必须通过调度器来注册。大多情况下,这会在调度器启动前做好。正如本章前面说过,这一操作也提供了声明式与编程式两种实现途径的选择。首先,我们讲解如何用编程的方式;接下来在本章,我们会用声明的方式重做这个练习。

因为每一个 Job 都必须用 Scheduler 来注册,所以先定义一个 JobDetail,并关联到这个 Scheduler 实例。见代码 3.5。

代码 3.5. 编程式安排一个 Job

上面程序提供了一个理解如何编程式安排一个 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 是两个之中简单的那个,它主要用来激发单事件的 Job,Trigger 在指定时间激发,并重复 n 次--两次激发时间之间的延时为 m,然后结束作业。CronTrigger 非常复杂且强大。它是基于通用的公历,当需要用一种较复杂的时间表去执行一个 Job 时用到。例如,四月至九月的每个星期一、星期三、或星期五的午夜。

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

正如你从代码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 的多个实例

代码 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()方法)。

·运行代码 3.6 中的程序

如果我们执行类 Listing_3_6,会得到类似如下的输出:

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

类别: Quartz. 标签: , . 阅读(839). 订阅评论. TrackBack.

Leave a Reply

16 Comments on "Quartz Job Scheduling Framework[翻译]第三章. Hello Quartz (第二部分)"

avatar
隔叶黄莺
Guest

你的 Quartz 版本太低了吧,换个新一点的来试试。

bobbie
Guest
bobbie

版本:Quartz scheduler version: 1.4.5

楼主我运行你的代码时发现:

trigger必须设置group,否则报trigger‘s group is not null

trigger.setGroup(Scheduler.DEFAULT_GROUP);

隔叶黄莺
Guest

OK,已经加上去了,谢谢!

kaka
Guest
kaka

代码3.5

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

后面少一个分号结束符.

隔叶黄莺
Guest

的确是 3.6, 已改过来了. 谢谢,你看得真的是很仔细.

WaveFly
Guest
WaveFly

之所以可以出现上面的情况,是不是因为,如果没有设置相关的Job和JobDetail,那么调用Scheduler的start方法,只是使Scheduler处于就绪状态,这个时候对它进行修改不会影响 开始运行后的状态?

WaveFly
Guest
WaveFly

在 Scheduler 启动之前还是之后安排 Job 代码

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

第一个“3.5”,应该是3.6 吧???^_^

坚持学习,每天进步一些
Guest

翻译的精致

andy一叶知秋
Guest

翻译的确实不错 跟e文一样好理解

隔叶黄莺
Guest

把org.quartz.jobStore.misfireThreshold设置小一点的值,丢弃过期太久的作业。

paul
Guest
paul

怀疑是这样:

作业执行时间较长,第一次还没执行完,调度器又从线程池取得一个线程来调度下一个作业,这样累计下去,可能达到最大线程,导致无法继续运行,一直处于阻塞状态 回去试试

paul
Guest
paul

线程的管理应该是quartz自己控制的啊

隔叶黄莺
Guest

主线程是否能看看 Thread.activeCount()

当前活动的线程是不是等于所配置的线程数

检查你的程序会不会是非正常情况下,执行完仍占着线程,这种问题就像数据库连接池一样,如果程序没有正确释放连接,不管设置多大的连接数,结果是总能够耗尽。

paul
Guest
paul
又来麻烦楼主了 lz帮忙看个问题,不知你是否遇到过 我有一个扫描 表的作业,用的是简单触发器那个类 可是发现一段时间后程序就停在那里了,不只是什么原因? 启动时的日志 2007-11-05 16:02:40 jobscheduler.slascheduler.ScanSla.run(ScanSla.java:229) DEBUG: 1--Mon Nov 05 16:02:40 CST 2007 2007-11-05 16:02:40 jobscheduler.slascheduler.ScanSla.run(ScanSla.java:230) DEBUG: 2--null 2007-11-05 16:02:40 jobscheduler.slascheduler.ScanSla.run(ScanSla.java:231) DEBUG: 3--0 2007-11-05 16:02:40 jobscheduler.slascheduler.ScanSla.run(ScanSla.java:232) DEBUG: 4---600 2007-11-05 16:02:40 org.quartz.simpl.SimpleThreadPool.initialize(SimpleThreadPool.java:247) INFO : Job execution threads will use class loader of thread: main 2007-11-05 16:02:40 org.quartz.core.QuartzScheduler.<init>(QuartzScheduler.java:195) INFO : Quartz Scheduler v.1.5.2 created. 2007-11-05 16:02:40 org.quartz.simpl.RAMJobStore.initialize(RAMJobStore.java:138) INFO : RAMJobStore initialized. 2007-11-05 16:02:40 org.quartz.impl.StdSchedulerFactory.instantiate(StdSchedulerFactory.java:1014) INFO : Quartz scheduler 'DefaultQuartzScheduler' initialized from default file in current working dir: 'quartz.properties' 2007-11-05 16:02:40 org.quartz.impl.StdSchedulerFactory.instantiate(StdSchedulerFactory.java:1018) INFO : Quartz scheduler version: 1.5.2 2007-11-05 16:02:40 org.quartz.core.QuartzScheduler.start(QuartzScheduler.java:400) INFO : Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started. 2007-11-05 16:02:40 org.quartz.simpl.SimpleJobFactory.newJob(SimpleJobFactory.java:46) DEBUG:… Read more »
glaciert
Guest
glaciert

翻译的很好啊,我现在每天都上你blog就等着看Quartz的内容,就跟看连载一样,呵呵

andy
Guest
andy

翻译的还不错嘛