摘要: Quartz是一个任务日程管理系统,这个系统能够与任何其余软件系统集成或者一块儿使用。术语“日程进度管理器”可能对于不一样的人有不一样的理解。当你阅读这个指南以后, 你会对这个术语有固定的理解。简而言之,“任务进度管理器”就是一个在预先肯定(被归入日程)的时间到达时,负责执行(或者通知)其余软件组件的系统。java
Quartz中的触发器web
Quartz中提供了两种触发器,分别是CronTrigger和SimpleTrigger。spring
SimpleTriggerapache
每 隔若干毫秒来触发归入进度的任务。所以,对于夏令时来讲,根本不须要作任何特殊的处理来“保持进度”。它只是简单地保持每隔若干毫秒来触发一次,不管你的 SimpleTrigger每隔10秒触发一次仍是每隔15分钟触发一次,仍是每隔24小时触发一次。编程
CronTriggerapi
在特定“格林日历”时刻触发归入进程的任务,所以,若是建立一个在天天上午10点触发的触发器,那么,在夏令时执行以前,系统将继续如此运做。可是,取决因而 春季仍是秋季夏令时,由于对于特定的星期日,从星期六上午10点到星期日上午10点之间的时间间隔将不是24小时,而多是23或者25个小时。tomcat
总之,若是你记住下面的两条规则,则会感受良好而且很容易记忆:
• SimpleTrigger 老是每隔若干秒触发,而同夏令时没有关系。
• CronTrigger 老是在给定的时间出发而后计算它下次触发的时间。若是在给定的日期内没有该时间,则触发器将会被忽略,若是在给定的日期内该时间发生了两次,它只触发一次。由于是在第一次触发发生后计算当天下次触发的时间。并发
构建app
能够在http://sourceforge.net/projects/quartz/连接中下载Quartz包,也能够经过Maven来进行构建,若是读者以为麻烦,能够采用下载jar包的方法,由于Quartz依赖于其它包。我这里经过Maven来进行构建:框架
首先构建公共依赖项,用于公共模块使用:
<properties> <!--Spring版本号--> <spring.version>3.2.4.RELEASE</spring.version> <!--log4j日志文件管理包版本--> <slf4j.version>1.6.6</slf4j.version> <log4j.version>1.2.12</log4j.version> </properties> <dependencies> <!--Junit测试包--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> </dependency> <!--日志文件管理包--> <!--log start--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> </dependency> <!--log end--> <!--Apache 组件--> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> </dependencies>
下面添加Quartz依赖项:
<dependencies> <!--添加Quartz框架--> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.1.1</version> </dependency> <!--组件可选项--> <!--start--> <dependency> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>commons-digester</groupId> <artifactId>commons-digester</artifactId> <version>1.8</version> </dependency> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.8.3</version> </dependency> <!--end--> </dependencies>
在公共模块记得要将模块打包方式设置为pom。
我这里的Quartz为较新的2.1.x版本,由于在与Spring整合时,Spring3.1.0以前的版本只支持Quartz1.6以往的版本。
至于log4j如何配置,这里就不在赘言了,读者能够参考网上自行搜索。
单一Job调度
建立一个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异常。
下面是咱们的第一个 Quartz job,它被设计来扫描一个目录中的文并显示文件的详细信息。
import ***; /** * 定义一个Job扫描一个目录中的文件,并显示文件的详细信息 * * @author Barudisshu */ public class ScanDirectoryJob implements Job { private static Logger logger = Logger.getLogger(ScanDirectoryJob.class); /** * Job接口中的execute方法体内为一个逻辑事务,全部工做事务在这里完成 * * @param context 执行上下文环境, * 包含jobDetail、trigger、jobDataMap、calendar等一系列组合子项 * @throws JobExecutionException */ @Override 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 = ((JobDetailImpl)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"); // 只统计XML文件 File[] files = dir.listFiles(filter); if (files == null || files.length <= 0) {//目录下没有XML文件 logger.info("No XML files found in " + dir); // Return since there were no files return; } // The number of XML files int size = files.length; logger.info("The number of XML files: " + size); // Iterate through the files found for (File file : files) { // Log something interesting about each file File aFile = file.getAbsoluteFile(); long fileSize = file.length(); String msg = aFile + " - Size: " + fileSize; logger.info(msg); //记录下文件的路径和大小 } } }
当 Quartz 调用 execute() 方法,会传递一个 org.quartz.JobExecutionContext 上下文变量,里面封装有 Quartz 的运行时环境和当前正执行的 Job。经过 JobexecutionContext,你能够访问到调度器的信息,做业和做业上的触发器的信息,还有更多更多的信息。在代码 中,JobExecutionContext 被用来访问 org.quartz.JobDetail 类,JobDetail 类持有 Job 的详细信息,包括为 Job 实例指定的名称,Job 所属组,Job 是否被持久化(易失性),和许多其余感兴趣的属性。
JobDetail 又持有一个指向 org.quartz.JobDataMap 的引用。JobDataMap 中有为指定 Job 配置的自定义属性。例如,在代码中咱们从 JobDataMap 中得到欲扫描的目录名,咱们能够在 ScanDirectoryJob 中硬编码这个目录名,可是这样的话咱们难以重用这个 Job 来扫描别的目录了。在后面你将会看到目录是如何配置到 JobDataMap 的。
execute() 方法中剩下的就是标准 Java 代码了:得到目录名并建立一个 java.io.File 对象。它还对目录名做为简单的校验,确保是一个有效且存在的目录。接着调用 File 对象的 listFiles() 方法获得目录下的文件。还建立了一个 java.io.FileFilter 对象做为参数传递给 listFiles() 方法。org.quartzbook.cavaness.FileExtensionFileFilter 实现了 java.io.FileFilter 接口,它的做用是过滤掉目录仅返回 XML 文件。默认状况下,listFiles() 方法是返回目录中全部内容,不论是文件仍是子目录,因此咱们必须过滤一下,由于咱们只对 XML 文件感兴趣。
FileExtensionFileFilter 被用来屏蔽名称中不含字符串 “.xml” 的文件。它还屏蔽了子目录--这些子目录本来会让 listFiles() 方法正常返回。过滤器提供了一种很便利的方式选择性的向你的 Quartz 做业提供它能接受的做为输入的文件。
import ***; /** * @author Barudisshu */ public class FileExtensionFileFilter implements FileFilter { private String extension; //文件后缀 public FileExtensionFileFilter(String extension) { this.extension = extension; } @Override public boolean accept(File pathname) {//只接受指定后缀的文件 //若是file是个目录 if(pathname.isDirectory()) return false; // Lowercase the filename for easier comparison String LCaseFilename = pathname.getName().toLowerCase(); //文件名转换为小写 return (pathname.isFile() && (LCaseFilename.indexOf(extension) > 0)); } }
到 目前为止,咱们已经建立了一个 Quartz job,但尚未决定怎么处置它--明显地,咱们需以某种方式为这个 Job 设置一个运行时间表。时间表能够是一次性的事件,或者咱们可能会安装它在除周日以外的每一个午夜执行。你即刻将会看到,Quartz Schduler 是框架的心脏与灵魂。全部的 Job 都经过 Schduler 注册;必要时,Scheduler 也会建立 Job 类的实例,并执行实例的 execute() 方法。
单元测试
Quartz2.x以上版本和以往版本有许多的不一样,主要是JobDetail和Trigger不在经过实例构建,而是由构造器进行构建,经过减小实例的构建来优化内存处理,这样对效率有很大的提升。
Quartz 提供了四种类型的 Trigger,但其中两种是最为经常使用的,它们就是上面提到的 SimpleTrigger 和 CronTrigger。CronTrigger用法比SimpleTrigger稍微复杂一点,可是用法都同样,所以这里不作介绍,能够参考 CronTrigger表达式相关内容。
import ***; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; /** * @author Barudisshu */ public class SimpleSchedulerTests { private static Logger logger = Logger.getLogger(SimpleSchedulerTests.class); private Scheduler createScheduler() throws SchedulerException {//建立调度器 SchedulerFactory schedulerFactory = new StdSchedulerFactory(); return schedulerFactory.getScheduler(); } private void scheduleJob(Scheduler scheduler) throws SchedulerException { // Create a job detail for the job JobDetail jobDetail = newJob(ScanDirectoryJob.class) .withIdentity("ScanDirectory", "jobDetail-group") .withDescription("ScanDirectory from tomcat conf") .build(); // Configure the directory to scan jobDetail.getJobDataMap() .put("SCAN_DIR", "\apache-tomcat-7.0.39\conf"); // Create a trigger that fires every 10 seconds,forever Trigger trigger = newTrigger() .withIdentity("scanTrigger", "trigger-group") .startNow() .withSchedule(simpleSchedule() .withIntervalInSeconds(10)) // 每十秒触发一次 .build(); //Associate the trigger with the job in the schedule scheduler.scheduleJob(jobDetail, trigger); } @Test public void ScanDirectoryTests() { SimpleSchedulerTests simpleSchedulerTests = new SimpleSchedulerTests(); try { Scheduler scheduler = simpleSchedulerTests.createScheduler(); // Start the scheduler running scheduler.start(); logger.info("Scheduler started at " + new Date()); simpleSchedulerTests.scheduleJob(scheduler); // Stop the scheduler after 10 second Thread.sleep(10000); scheduler.shutdown(); } catch (SchedulerException e) { logger.error(e); } catch (InterruptedException e) { e.printStackTrace(); } } }
多个Job调度
Quartz容许多个Job调度,Job做业是创建在Schedule上的,这和Quartz1.6.x以前的版本的概念稍微有点不一样,不过不用在乎这些概念上细节。下面经过单元测试来讲明如何进行多个Job调度。
import ***; import static org.quartz.JobBuilder.newJob; import static org.quartz.SimpleScheduleBuilder.simpleSchedule; import static org.quartz.TriggerBuilder.newTrigger; /** * @author Barudisshu */ public class MultiJobSchedulerTests { private static Logger logger = Logger.getLogger(MultiJobSchedulerTests.class); private Scheduler createScheduler() throws SchedulerException {//建立调度器 return new StdSchedulerFactory().getScheduler(); } private void scheduleJob(Scheduler scheduler, String jobName, Class<? extends Job> jobClass, String scanDir, String triggerName, int scanInterval) throws SchedulerException { // Create a job detail for the job JobDetail jobDetail = newJob(jobClass) .withIdentity(jobName, "jobDetail-group") .build(); // Configure the directory to scan jobDetail.getJobDataMap() .put("SCAN_DIR", scanDir); // Create a trigger that fires every 10 seconds,forever Trigger trigger = newTrigger() .withIdentity(triggerName, "trigger-group") .startNow() .withSchedule(simpleSchedule() .withIntervalInSeconds(scanInterval)) // 每十秒触发一次 .build(); //Associate the trigger with the job in the schedule scheduler.scheduleJob(jobDetail, trigger); } @Test public void multiScanTests() { MultiJobSchedulerTests multiJobSchedulerTests = new MultiJobSchedulerTests(); try { Scheduler scheduler = multiJobSchedulerTests.createScheduler(); // Scheduler the first job multiJobSchedulerTests.scheduleJob(scheduler, "ScanTomcat", ScanDirectoryJob.class, "\apache-tomcat-7.0.39\conf", "tomcatTrigger", 10); // Scheduler the second job multiJobSchedulerTests.scheduleJob(scheduler, "ScanGlassfish", ScanDirectoryJob.class, "\glassfish4\glassfish\domains\domain1\config", "glassfishTrigger", 15); // Start the scheduler running scheduler.start(); logger.info("Scheduler started at " + new Date()); // Stop the scheduler after 10 second Thread.sleep(10000); scheduler.shutdown(); } catch (SchedulerException e) { logger.error(e); } catch (InterruptedException e) { e.printStackTrace(); } } }
加载XML文件实现Job调度
文 件 quartz.properties 定义了 Quartz 应用运行时行为,还包含了许多能控制 Quartz 运转的属性。这个文件应该放在classpath所指的路径下,好比咱们这个java工程,就将它和下面将介绍的jobs.xml一块儿放在项目根目录下就 是。若是不清楚就查看.classpath文件,它里面就配置了你的项目的classpath。
构建本身quartz.properties
可 以经过自定quartz.properties来进行Job调度,须要注意的是,在实际项目中不要以quartz.properties同名进行命名,因 为StdSchedulerFactory默认加载quartz.properties文件,所以我这里命名为scan- quartz.properties,配置以下:
# 固定前缀org.quartz # 主要分为scheduler、threadPool、jobStore、plugin等部分 # # org.quartz.scheduler.instanceName = DefaultQuartzScheduler org.quartz.scheduler.rmi.export = false org.quartz.scheduler.rmi.proxy = false org.quartz.scheduler.wrapJobExecutionInUserTransaction = false # 实例化ThreadPool时,使用的线程类为SimpleThreadPool org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool # threadCount和threadPriority将以setter的形式注入ThreadPool实例 # 并发个数 org.quartz.threadPool.threadCount = 5 # 优先级 org.quartz.threadPool.threadPriority = 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true org.quartz.jobStore.misfireThreshold = 60000 # 默认存储在内存中 org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore # 经过插件获取声明Job org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin org.quartz.plugin.jobInitializer.fileNames = jobs.xml org.quartz.plugin.jobInitializer.failOnFileNotFound = true org.quartz.plugin.jobInitializer.scanInterval = 10 org.quartz.plugin.jobInitializer.wrapInUserTransaction = false
插件配置
在这个简单的 quartz.properties 文件中最后一部分是你要用到的 Quart 插件的配置。插件经常在别的开源框架上使用到,好比 Apache 的 Struts 框架(见 http://struts.apache.org/)。
一个声明式扩框架的方法就是经过新加实现了 org.quartz.spi.SchedulerPlugin 接口的类。SchedulerPlugin 接口中有给调度器调用的三个方法。
要在咱们的例子中声明式配置调度器信息,咱们会用到一个 Quartz 自带的叫作 org.quartz.plugins.xml.JobInitializationPlugin 的插件。
默认时,这个插件会在 classpath 中搜索名为 quartz_jobs.xml 的文件并从中加载 Job 和 Trigger 信息。
XML配置
下面就是目录扫描例子的 Job 定义的 XML 文件。正如上一篇所示例子那样,这里咱们用的是声明式途径来配置 Job 和 Trigger 信息的:
<?xml version='1.0' encoding='utf-8'?> <job-scheduling-data xmlns="http://www.quartz-scheduler.org/xml/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.quartz-scheduler.org/xml/JobSchedulingData http://www.quartz-scheduler.org/xml/job_scheduling_data_1_8.xsd" version="1.8"> <schedule> <job> <name>ScanDirectory</name> <group>jobDetail-group</group> <description>ScanDirectory from tomcat conf</description> <job-class>net.individuals.quartz.ScanDirectoryJob</job-class> <job-data-map> <entry> <key>SCAN_DIR</key> <value>\apache-tomcat-7.0.39\conf</value> </entry> </job-data-map> </job> <trigger> <simple> <name>scanTrigger</name> <group>trigger-group</group> <!--jobName和jobGroup必须与对应的Job匹配--> <job-name>ScanDirectory</job-name> <job-group>jobDetail-group</job-group> <!--注意日期和时间--> <start-time>2008-09-03T14:43:00</start-time> <!-- repeat indefinitely every 10 seconds --> <repeat-count>-1</repeat-count> <repeat-interval>10000</repeat-interval> </simple> </trigger> </schedule> </job-scheduling-data>
在jobs.xml 中 的格式是:
<start-time>2008-09-03T14:43:00</start-time>
其中T隔开日期和时间,默认时区
或者:
<start-time>2008-09-03T14:43:00+08:00</start-time>
其中+08:00 表示东八区
元素描述了一个要注册到调度器上的 Job,至关于咱们在前面章节中使用 scheduleJob() 方法那样。你所看到的 和 这两个元素就是咱们在代码中以编程式传递给方法 schedulerJob() 的参数。
这里的XML配置和Quartz1.6.x也有许多不一样,但实质都是同样,不用在乎这些细节。
XML配置单元测试
下面进行上述描述文件的加载和测试:
import ***; /** * @author Barudisshu */ public class LoadXmlTests { private static Logger logger = Logger.getLogger(LoadXmlTests.class); private Scheduler createScheduler() throws SchedulerException {//建立调度器 return new StdSchedulerFactory("scan-quartz.properties").getScheduler(); } @Test public void ScanDirectoryTests() { LoadXmlTests loadXmlTests = new LoadXmlTests(); try { Scheduler scheduler = loadXmlTests.createScheduler(); // Start the scheduler running scheduler.start(); logger.info("Scheduler started at " + new Date()); // Stop the scheduler after 10 second Thread.sleep(20000); scheduler.shutdown(); } catch (SchedulerException e) { logger.error(e); } catch (InterruptedException e) { e.printStackTrace(); } } }
怎样?是否是以为简单了不少(我就不明白为何有些人反对XML配置,XML配置不是挺好的吗?一目了然)
Quartz的Web构建依赖项须要将打包方式改成war便可,下面以一个简单的HelloWorld为例进行说明:
import ***; /** * @author Barudisshu */ public class HelloWorld implements Job { private static Logger logger = Logger.getLogger(HelloWorld.class); @Override public void execute(JobExecutionContext context) throws JobExecutionException { logger.info("\n---------------------------------" + "\n\nHello World! \n\n" + "---------------------------------\n" + new Date()); } }
WEB构建基本上依赖配置文件,咱们只须要的web工程的web.xml中配置相应项便可:
<!--quartz配置1:--> <context-param> <param-name>quartz:config-file</param-name> <param-value>web-quartz.properties</param-value> </context-param> <context-param> <param-name>quartz:shutdown-on-unload</param-name> <param-value>true</param-value> </context-param> <context-param> <param-name>quartz:wait-on-shutdown</param-name> <param-value>false</param-value> </context-param> <context-param> <param-name>quartz:start-scheduler-on-load</param-name> <param-value>true</param-value> </context-param> <listener> <listener-class> org.quartz.ee.servlet.QuartzInitializerListener </listener-class> </listener> <!--quartz配置2:--> <servlet> <servlet-name>QuartzInitializer</servlet-name> <servlet-class>org.quartz.ee.servlet.QuartzInitializerServlet</servlet-class> <init-param> <param-name>shutdown-on-unload</param-name> <param-value>true</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>QuartzInitializer</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
这样,在启动Tomcat的时候,QuartzInitializerServlet这个Servlet就会自动读取quartz.properties这个配置文件,并初始化调度信息,启动Scheduler。
Spring 的scheduling.quartz包中对Quartz框架进行了封装,使得开发时不用写任何QuartSpring的代码就能够实现定时任务。 Spring经过JobDetailBean,MethodInvokingJobDetailFactoryBean实现Job的定义。后者更加实用, 只需指定要运行的类,和该类中要运行的方法便可,Spring将自动生成符合Quartz要求的JobDetail。
构建依赖项
<!--添加Spring框架--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.1.1</version> </dependency>
建立任务类
这里的任务类不须要实现Job接口,而且注意任务的方法不能带参数,不然Spring会报错找不到该方法:
import ***; /** * @author Barudisshu */ public class HelloWorld{ private static Logger logger = Logger.getLogger(HelloWorld.class); public HelloWorld() { } /** * spring 检测要求不带参数 */ public void execute() { logger.info("-----------------------------------------" + "\n\nKick your ass and fuck your mother! \n\n" + "-----------------------------------------" + new Date()); } }
Spring的applicationContext.xml配置文件修改以下:
<!--要调用的工做类--> <bean id="quartzJob" class="net.individuals.quartz.HelloWorld"/> <!--定义调用对象和调用对象的方法--> <bean id="jobtask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <!--调用类--> <property name="targetObject" ref="quartzJob"/> <!--调用方法--> <property name="targetMethod" value="execute"/> </bean> <!--定义触发时间--> <bean id="doTime" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail" ref="jobtask"/> <!--cron表达式--> <property name="cronExpression" value="0/5 * * * * ?"/> </bean> <!--总管理类--> <bean id="startQuartz" lazy-init="false" autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <!--任务列表--> <list> <ref bean="doTime"/> </list> </property> </bean>
这里Bean的注入方式因我的喜爱,也能够自动注入,另外web.xml的配置这里也不在赘言,请自行Spring文档。
注意,若是运行期间出错,多是Spring的版本或者某些包冲突,我这里的Spring版本为3.2.四、Quartz为2.1.1 。