[译者注:后面的章由于每章的内容较多,每章聚于一篇之中,过于臃肿,屏幕不比书本,三屏之后的内容一般不为读者乐意去阅读,此为第一部分]
第三章:Hello Quartz
多数读者都较容易从一个简明扼要的例子中明白一个东西。作为写作者,要注意避免把一章的内容精简地几乎什么都没了;作为读者呢,需要有耐心并且要进一步相信其后相关的章节应该去阅读,尽管这个例子看起来是如此之简单。
有了这种初衷,这一章将为你介绍如何用 Quartz 框架创建一个简单的应用程序,它展示了一个典型的应用。这个例子将让你领略到创建和执行一个简单应用的必要步骤。通过本章的学习,为你学习本书的后续章节打下了坚实的基础。
1. "Hello, World" Quartz 工程
本示例应用比起众所周知的 System.out.println("Hello world from Quartz") 来还是要有趣些。当我们用 Quartz 执行一个作业时,总是希望它能为我们执行一些有趣且有意义的任务。因此,接下来我们就要做一些有趣且有用的事情。
本章向您演示如何创建这么一个 Quartz 作业,Quartz 应用通知它要做事情的时候,就会去扫描指定的目录寻找 XML 文件。假如在指定目录中找到了一个或多个 XML 文件的话,它将会打印出文件的一些概要信息。是不是很有意义且有趣的,你说呢?但愿,你还能进一步延伸:作业在检测到某一目录下的特定文件后,还要依据那些文件做其他许多你感兴趣的事。你可能会想把它们 FTP 到一台远程机器上,或者把它们作为电子邮件的附件发送。也许那是些客户发过来的订单文件,我们需要读取它们后插入到数据库中。无限可能性;我们会在本书的后面部分讨论它们。
我们努力让这一部分阐述地直截了当并且只涉及本质要义。然而,我们也会研究到一些会影响到 Quartz 应用程序运行行为的常用配置选项。我们假定你很好的掌握了 Java 语言;我们基本不会花时间去解翻译 Java 语言方面东西。
最后,本章的结束部分会简单的讨论怎么打包这个示例应用。在构建和打包 Java 工程时 Apache Ant 是我们的选择;Quartz 应用程序也不例外。
·建立 Quartz 工程
首要步骤是要建立起本工程的开发环境。你可以选择任何自己喜欢的开发工具或者感觉比较好的IDE;Quartz 并不发固执的要求你用哪一个工具。假如你还是接触Java没多久的开发者,Eclipse 会让你感觉特别的舒适;我们在本书的所有例子都是在 Eclipse 中讲解。
如果你还没有 Eclipse 的话,你可以从 http://eclipse.org 下载。你可以选择下载 3.x 的某个版本。在 http://www.eclipse.org/eclipse/index.html 可查看 Eclipse 的文档;你会找到能帮助你上手 Eclipse 的所有需要的资料。
·在 Eclipse 中配置使用 Quartz
我们只为本书中的所有例子创建一个 Java 工程;每一章的源代被放在单独的目录中。图 3.1 显示了Eclipse 中的 Quartz 工程。
你必须引入几个 JAR 到工程中才能成功构建它们。首先,你需要 Quartz 的二进制版本,包的名字是 quartz-<version>.jar。Quartz 还需要几个第三方库;这依赖于你要用到框架的什么功能而定,Commons Digester 库可以在 <QUARTZ_HOME>/lib/core 和 <QUARTZ_HOME>/lib/optional 目录中找到。表 3.1 列出了Quartz 依赖包的更多信息。
把 Quartz 源代码加入到 Eclipse 中来是个很好主意。这可以给你带来两方面的益处。其一,它允许你设置断点并跟入到 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、执行一个工作流,或者像我们的例子中那样,检查某个特定的目录下是否存在文件。
代码 3.1 是我们的第一个 Quartz job,它被设计来扫描一个目录中的文并显示文件的详细信息。
代码 3.1. ScanDirectoryJob 例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
package org.cavaness.quartzbook.chapter3; import java.io.File; import java.util.Date; import org.apache.commons.logging.Log; import Org.apache.commons.logging.LogFactory; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** * <p> * A simple Quartz job that, once configured, will scan a * directory and print out details about the files found * in the directory. * </p> * Subdirectories will filtered out by the use of a * <code>{@link FileExtensionFileFilter}</code>. * * @author Chuck Cavaness * @see java.io.FileFilter */ public class ScanDirectoryJob implements Job { static Log logger = LogFactory.getLog(ScanDirectoryJob.class); 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"); // Validate the required input if (dirName == null) { throw new JobExecutionException( "Directory not configured" ); } // Make sure the directory exists File dir = new File(dirName); if (!dir.exists()) { throw new JobExecutionException( "Invalid Dir "+ dirName); } // Use FileFilter to get only XML files FileFilter filter = new FileExtensionFileFilter(".xml"); File[] files = dir.listFiles(filter); if (files == null || files.length <= 0) { logger.info("No XML files found in " + dir); // Return since there were no files return; } // The number of XML files int size = files.length; // Iterate through the files found for (int i = 0; i < size; i++) { File file = files[i]; // Log something interesting about each file. File aFile = file.getAbsoluteFile(); long fileSize = file.length(); String msg = aFile + " - Size: " + fileSize; logger.info(msg); } } } |
让我们来细细看看代码 3.1 中做了些什么。
当 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 的。
execute() 方法中剩下的就是标准 Java 代码了:获得目录名并创建一个 java.io.File 对象。它还对目录名作为简单的校验,确保是一个有效且存在的目录。接着调用 File 对象的 listFiles() 方法得到目录下的文件。还创建了一个 java.io.FileFilter 对象作为参数传递给 listFiles() 方法。org.quartzbook.cavaness.FileExtensionFileFilter 实现了 java.io.FileFilter 接口,它的作用是过滤掉目录仅返回 XML 文件。默认情况下,listFiles() 方法是返回目录中所有内容,不管是文件还是子目录,所以我们必须过滤一下,因为我们只对 XML 文件感兴趣。
注: FileExtensionFileFilter 并非 Quartz 框架的一部分;它是 java.io.FileFilter 的子类,而是 Java 核心的一部分。FileExtensionFileFilter 被创建为我们例子的一部分,用来滤除其他内容而只保留 XML 文件。它相当有用,你可以考虑为你的应用建一系列的文件过滤器,然后在你的 Quartz Job 中重用。 |
代码 3.2 是 FileExtensionFileFilter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package org.cavaness.quartzbook.chapter3; import java.io.File; import java.io.FileFilter; /** * A FileFilter that only passes Files of the specified extension. * <p> * Directories do not pass the filter. * * @author Chuck Cavaness */ public class FileExtensionFileFilter implements FileFilter { private String extension; public FileExtensionFileFilter(String extension) { this .extension = extension; } /* * Pass the File if it has the extension. */ public boolean accept(File file) { // Lowercase the filename for easier comparison String lCaseFilename = file.getName().toLowerCase(); return (file.isFile() && (lCaseFilename.indexOf(extension) > 0 )) ? true : false ; } } |
FileExtensionFileFilter 被用来屏蔽名称中不含字符串 “.xml” 的文件。它还屏蔽了子目录--这些子目录原本会让 listFiles() 方法正常返回。过滤器提供了一种很便利的方式选择性的向你的 Quartz 作业提供它能接受的作为输入的文件。
声明式之于编程式配置 在 Quartz 中,我们有两种途径配置应用程序的运行时属性:声明式和编程式。有一些框架是使用外部配置文件的方式;我们都知道,在软件中硬编码设置有它的局限性。 从其他方面来讲,你将要根据具体的需求和功能来选择用哪一种方式。下一节强调了何时用声明式何时选择编程式。因为多数的 Java 行业应用都偏向于声明的方式,这也是我们所推荐的。 |
在下一节中,我们讨论如何为调度器配置 Job 和运行 ScanDirectoryJob。
[译者注] 翻译中还得细细考量,那些词要转换成中文,那些词保留成英文。例如,前面的 Job->作业、Application->应用、Task->任务、Scheduler之于调度器;具体下来 Quartz Application 可能比 Quartz 应用好理解,Quart Job 也没有 Quart 作业读来生硬。平时不细究,只是阅读英文资料的话,一眼掠过是不会太在意的,本人翻译中也有混用,还没能太明晰。
本文链接 https://yanbin.blog/quartz-job-scheduling-framework-3-1/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。