Quartz Job Scheduling Framework[翻译]第四章. 部署 Job (第一部分)

第四章. 部署 Job


在上一章中,你首次尝试使用了 Quartz 来部署 Job。无可否认地,那些 Job 都不是很复杂,但这个不是重点。你应该轻松的对如何构造并部署 Job 有了相当的了解,更重要的是,由此热情的希望学得更多的东西。在本章中将会继续给你讲述。

第四章将带领你深入到 Quart 框架的核心部分。可证明的是,这一章对于阅读和理解本书是非常之重要的。调度器(Scheduler) 是此框架的心脏。本章关注于如何使用 Scheduler 来管理你的 Job;如何创建并关联触发器以使 Job 能被触发执行;以及如可选择 calendar 为给定的时程安排提供更多的灵活性。


此刻,什么也没发生,下一刻,仍旧什么也没发生。 道格拉斯.亚当斯,《银河系漫游指南》

1. Quartz 调度器

Quartz 框架包含许多的类和接口,它们分布在大概 11 个包中。多数你所要使用到的类或接口放置在 org.quartz 包中。这个包含盖了 Quartz 框架的公有 API.

我们不打算对这个框架的所有类和接口都面面俱到。而所要介绍的是那些有助于你理解 Quartz 如何做它该做事情的组件的子集。图 4.1 展示了一个只留下必要的调度器的精简类图。

图 4.1. Quartz 类图(仅显示主要组件)

Scheduler 是 Quartz 的主要 API。对于 Quartz 用户来说,多数时候与框架的交互是发生于 Scheduler  之上的。客服端与 Scheduler 交互是通过 org.quartz.Scheduler 接口的。这个 Scheduler 的实现,在这种情况下,是一个代理,对其中方法调用会传递到 QuartzScheduler 实例上。QuartzScheduler 对于客户端是不可见的,并且也不存在与此实例的直接交互。

QuartzScheduler 处在框架根的位置,它是一个引擎驱动着整个框架。并非所有的功能都直接内建到 QuartzScheduler,然而,框架为灵活性和可配置性考虑而设计,所以许多重要的功能由分离的组件和子框架实现。这就意味着 Quartz 用户可以用自己某个关键特征实现来替换原有默认实现。即使 QuartzScheduler 代理了它的一些职责,但它仍然掌控着整个作业调度流程。

·Quartz Scheduler 类层次

客户端会同两种类型的 Scheduler 交互,如图 4.2. 它们都实现了 org.quartz.Scheduler 接口。

图 4.2. org.quartz.Scheduler 类层次

作为一个 Quartz 用户,你要与实现了 org.quartz.Scheduler 接口的类交互。在你调用它的任何 API 之前,你需要知道如何创建一个 Scheduler 的实例。

2. Quartz SchedulerFactory

尽管你已使用到了 Scheduler 类型了,但你未曾显式的去创建 Scheduler 的实例。取而代之的是用了某个工厂方法来确保了构造出 Sheduler 实例并正确的得到初始化。(工厂设计模式之所以谓之工厂模式是因为它承担了生产对象的职责。在这里是生产了一个 Scheduler 实例) Quartz 框架为这一目的提供了 org.quartz.SchedulerFactory 接口。角色 SchedulerFactory 就是用来产生 Scheduler 实例的。当 Scheduler 实例被创建之后,就会存到一个仓库中(org.quartz.impl.SchedulerRepository),这个仓库还提供了通过一个 class loader 查询实例的机制。要使用 Scheduler 实例,客户端必须从工厂(和随同的仓库中)使用不同方法调用来获取到它们。换句话说,要通过工厂创建一个 Scheduler 实例并获取到它需要经由两次方法调用。有一些方便的方法封装了那两个方法,你将很快能看到。

你可使用两种不同类型的 SchedulerFactory 来创建 Scheduler 实例 (看图 4.3)

图 4.3. 所有的 Scheduler 实例应该由 SchedulerFactory 来创建

这两个 Scheduler 工厂分别是 org.quartz.impl.DirectoSchedulerFactoryorg.quartz.impl.StdSchedulerFactory. 让我们来逐个检视它们。

·使用 DirectSchedulerFactory

DirectSchedulerFactory 是为那些想绝对控制 Scheduler 实例是如何生产出的人所设计的。代码 4.1 显示了最简单的方式去使用 DirectSchedulerFactory 来创建一个 Scheduler 实例。

代码 4.1. 使用

DirectSchedulerFactory
 1public class Listing_4_1 {
 2     static Log logger = LogFactory.getLog(Listing_4_1.class);
 3
 4     public static void main(String[] args) {
 5          Listing_4_1 example = new Listing_4_1();
 6          example.startScheduler();
 7     }
 8
 9     public void startScheduler() {
10          DirectSchedulerFactory factory=DirectSchedulerFactory.getInstance();
11
12          try {
13              // Initialize the Scheduler Factory with 10 threads
14              factory.createVolatileScheduler(10);
15
16              // Get a scheduler from the factory
17              Scheduler scheduler = factory.getScheduler();
18
19              // Start the scheduler running
20              logger.info("Scheduler starting up...");
21              scheduler.start();
22
23          } catch (SchedulerException ex) {
24               logger.error(ex);
25          }
26     }
27}

当使用 DirectSchedulerFactory 时,有三个基本的步骤。首先,你必须用静态方法 getInstance() 获取到工厂的实例。当你持有了工厂的实例之后,你必须调用其中的一个 createXXX 方法去初始化它。如代码 4.1 所示例子中是调用 createVolatileScheduler() 方法告诉工厂以十个工作者线程初始化它自己(至于工作者线程的更多内容将在本章的后部分讨论到)。第三步也就是最后一步是通过工厂的 getScheduler() 方法拿到 Scheduler 的实例。

在调用 getScheduler() 方法之后调用其中的一个 createXXX 方法 方法 createVolatileScheduler() 方法不会返回 scheduler 的实例。createXXX() 方法是告诉工厂如何配置要创建的 Scheduler 实例。你必须调用方法 getScheduler() 获取到在工厂上执行方法 createXXX() 产生的实例。实际上,在调用 getScheduler() 方法之前,你必须调用其中一个 createXXX() 方法;否则,你将有收到一个 SchedulerException 错误,因为根本没有 Scheduler 实例存在。

你可从数个不同的 createXXX() 方法中选择,依赖于你想要的 Scheduler 类型和你需要怎样的配置。代码 4.1 中用的是 createVolatileScheduler() 方法创建 Scheduler 实例的。方法 createVolatileScheduler() 带有单个参数:要创建的线程数量。在图 4.2 中,你已看到还有一个 RemoteScheduler 类。你必须要用一个不同的 createXXX() 方法去创建 RemoteScheduler 实例。有两个版本的方法可用:
1public void createRemoteScheduler(String rmiHost, int rmiPort)
2  throws SchedulerException;
3
4protected void createRemoteScheduler(String schedulerName,
5  String schedulerInstanceId, String rmiHost, int rmiPort)
6  throws SchedulerException;

RemoteScheduler 会在第十章,“J2EE 中使用 Quartz”讨论。假如你就是想要一个标准的 Scheduler, 可以调用以下三个版本的方法中的一个:
 1public void createScheduler(ThreadPool threadPool, JobStore jobStore)
 2  throws SchedulerException;
 3
 4public void createScheduler(String schedulerName,
 5  String schedulerInstanceId, ThreadPool threadPool, JobStore jobStore)
 6  throws SchedulerException;
 7
 8public void createScheduler(String schedulerName,
 9  String schedulerInstanceId, ThreadPool threadPool,
10  JobStore jobStore, String rmiRegistryHost, int rmiRegistryPort,
11  long idleWaitTime, long dbFailureRetryInterval)
12  throws SchedulerException;

在上一章上,我们用了一个属性文件来初始化 Scheduler。要通过 DirectSchedulerFactory 创建一个 Scheduler 实例,你必须传递配置参数给其中的一个 createXXX() 方法。在下一节中,我们讨论 StdSchedulerFactory,一个 SchedulerFactory 版本,它依赖于一系列的属性来配置 Scheduler,而不是通过 createXXX() 方法参数来传递配置参数。这样也避免了在代码中对 Scheduler 的配置选项的硬编码。

·使用 StdSchedulerFactory

DirectSchedulerFactory 形成鲜明对比的是,org.quartz.impl.StdSchedulerFactory 依赖于一系列的属性来决定如何生产出 Scheduler 实例。你可以通过以下三种途径向工厂提供那些属性:

    ·通过 java.util.Properties 实例提供

    ·通过外部属性文件提供

    ·通过含用属性文件内容的 java.io.InputStream 实例提供

Java 属性文件 我们这里使用述语“属性文件”,对于 Java 传统来说就是:在一个外部文件中指定一系列的 key=value  对,并且每个 key=value 对独占一行。

代码 4.2 演示了第一种途径,通过一个 java.util.Properties 实例来提供属性。

代码 4.2. 使用 java.util.Properties 实例创建 StdSchedulerFactory
 1public class Listing_4_2 {
 2     static Log logger = LogFactory.getLog(Listing_4_2.class);
 3
 4     public static void main(String[] args) {
 5          Listing_4_2 example = new Listing_4_2();
 6          example.startScheduler();
 7     }
 8
 9     public void startScheduler() {
10
11          // Create an instance of the factory
12          StdSchedulerFactory factory = new StdSchedulerFactory();
13
14          // Create the properties to configure the factory
15          Properties props = new Properties();
16
17          // required to supply threadpool class and num of threads
18
19          props.put(StdSchedulerFactory.PROP_THREAD_POOL_CLASS,
20                    "org.quartz.simpl.SimpleThreadPool");
21
22          props.put("org.quartz.threadPool.threadCount", "10");
23
24          try {
25
26              // Initialize the factory with properties
27              factory.initialize(props);
28
29              Scheduler scheduler = factory.getScheduler();
30
31              logger.info("Scheduler starting up...");
32              scheduler.start();
33
34         } catch (SchedulerException ex) {
35              logger.error(ex);
36         }
37    }
38}

代码 4.2 提供了一个使用 StdSchedulerFactory 创建 Scheduler 实例的很简单的例子。在这个例子中向工厂传递了两个属性,它们分别是实现了 org.quartz.spi.ThreadPool 接口的类名和 Scheduler 用来处理 Job 的线程的数量。这两个属性是必须的,因为它们并没有被指定默认值(我们很快在后面讨论它们)

在代码 4.1 中的 DirectSchedulerFactory,我们调用它其中一个 createXXX() 方法来初始化工厂。而对于 StdSchedulerFactory, 你使用的是一个有效的 initialize() 方法。在工厂初始化之后,你就可以调用它的 getScheduler() 方法获取到 Scheduler 的实例。使用 java.util.Properties 对象传递属性给工厂是一种配置 SchedulerFactory 的方式之一。硬编码配置属性的做法基本不推荐,也是可能的时候应尽力避免的做法。假如你需要修改上一例子中的线程数量,你将不得不修改代码然后重新编译。

幸运的是,StdSchedulerFactory 还有其他的方式来提供必须的属性。工厂也能通过传入一个外部文件名而被初始化,在这个外部文件中包含了这些配置项。应使用 initialize() 的替代方法形式如下:
1public void initialize(String filename) throws SchedulerException;

要使文件和属性能被成功加载的话,这个文件必须对于 classloader 是可见的。也就是说它必须在你的应用程序的 classpath 中。假如你用的是 java.io.InputStream 去加载文件,你可以使用另一个 initialize() 的替代方法如下:
1public void initialize(InputStream propertiesStream) throws SchedulerException;

在第三章,“Hello, Quartz” 你已看到为 SchedulerFactory 从一个叫做 quartz.properties 的外部文件中加载设置的例子。这个外部属性文件就是要用前面的方法来加载。假如你没有为 initialize() 方法指定从哪儿读取属性,那么 StdSchedulerFactory 会试图从名为 quartz.properties 的文件中加载它们。这就是你在第三章看到的行为。

·使用默认的 quartz.properties 文件创建 Scheduler

假如你使用无参的 initialize() 方法,StdSchedulerFactory 会执行以下几个步骤去尝试为工厂加载属性:

1.  检查 System.getProperty("org.quartz.properties") 中是否设置了别的文件名

2.  否则,使用 quartz.properties 作为要加载的文件名

3.  试图从当前工作目录中加载这个文件

4.  试图从系统 classpath 下加载这个文件

在 Quartz Jar 包中的默认 quartz.properties 文件

上面第4步总是能成功的,因为在 Quartz Jar 包中有一个默认的 quartz.properties 文件。假如你想使用另一个替代文件,你必须自己创建一个并确保它在 classpath 上。

使用 StdSchedulerFactory 来创建 Scheduler 实例的方式很普遍,因此在 StdSchedulerFactory 直接提供了一个方便的静态方法 getDefaultScheduler(),它就是使用前面列出的几个步骤来初始化工厂的。这如代码 4.3 所示。

代码 4.3. 使用静态的 getDefaultScheduler() 方法创建 Scheduler
 1    public class Listing_4_3 {
 2         static Log logger = LogFactory.getLog(Listing_4_3.class);
 3
 4         public static void main(String[] args) {
 5              Listing_4_3 example = new Listing_4_3();
 6              example.startScheduler();
 7         }
 8
 9         public void startScheduler() {
10
11              try {
12                  // Create a default instance of the Scheduler
13                  Scheduler scheduler =
14                             StdSchedulerFactory.getDefaultScheduler();
15                  logger.info("Scheduler starting up...");
16                  scheduler.start();
17
18              } catch (SchedulerException ex) {
19                   logger.error(ex);
20              }
21         }}

在静态方法 getDefaultScheduler() 方法中调用了空的构造方法。假如之前未调用过任何一个 initialize() 方法,那么无参的 initialize() 方法会被调用。这会开始去按照前面说的顺序加载文件。默认情况下,quartz.properties 会被定位到,并从中加载属性。

·Scheduler 的功能

本章到此为止大部分笔墨都在着重论述如何获得一个 Scheduler 的实例。那么一旦你拿到 Scheduler 的实例之后,你能能此做些什么呢?好,现在开始,上面的例子中告诉了你可以调用它的 start() 方法。Scheduler 的 API 大概包括了 65 个不同的方法。我们不在此全部枚举出来,但是你需要理解其中的一小部分 API。

Scheduler 的 API 可以分组成以下三个类别:

    ·管理 Scheduler

    ·管理 Job

    ·管理 Trigger 和 Calendar 永久链接 https://yanbin.blog/quartz-job-scheduling-framework-4-1/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。