本文是根据 Quartz定时器官方文档翻译的,只翻译了第1到第10课,如有翻译不精确的地方,请读者指正,互相学习,共同进步,谢谢。
Lesson 1. Using Quartz(简单使用Quartz定时器)
在使用调度程序之前,需要对其进行实例化。为此,您需要用到SchedulerFactory
。Quartz
的一些用户可能会在JNDI
中保留工厂的实例,其他用户可能会发现直接实例化和使用工厂实例(例如下面的示例)。
调度程序一旦被实例化,它就可以处于启动、关闭或待机模式。请注意,一旦调度程序关闭,想要重启它就必须重新实例化。在调度程序启动之前,作业不执行,触发器不会触发,处于暂停状态的触发器也不会被触发。
这是一段代码,用于快速实例化和启动调度程序,并安排执行作业:
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
sched.start();
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1")
.build();
// Trigger the job to run now, and then every 40 seconds
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule() .withIntervalInSeconds(40).repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
Lesson 2. The Quartz API, Jobs And Triggers(Quartz API,作业和触发器)
Quartz API的关键接口如下:
-
Scheduler
-Scheduler调度程序
的主要API. -
Job
- 定时任务组件. -
JobDetail
-Jobs
的实例. -
JobBuilder
- 用来构建JobDetail
实例,定义Jobs
. -
Trigger
- 触发器,定义Jobs
的执行计划的组件. -
TriggerBuilder
- 用来构建Trigger触发器
实例.
一个Scheduler调度程序
是由是否被创建作为边界,通过SchedulerFactory
的shutdown()
方法关闭。创建后,可以使用Scheduler
接口的add
remove
list
方法来对Jobs
和Trigger触发器
进行增删查或者执行其他与调度相关的操作(例如暂停Trigger触发器
)。但是,Scheduler
在使用start()
方法启动之前,实际上不会对任何Trigger触发器
(执行作业)执行操作。
Quartz
提供了 构建器 类,用于定义域特定语言(或DSL,有时也称为“流畅接口”)。在上一课中,您看到了一个示例,我们再次在此处介绍其中的一部分:
// define the job and tie it to our HelloJob class
JobDetail job = new Job(HelloJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.build();
// Trigger the job to run now, and then every 40 seconds
Trigger trigger = new Trigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
JobDetail
由JobBuilder
类的静态方法构建。
同样,Trigger触发器
由TriggerBuilder
的静态方法构建,另外SimpleScheduleBulder
类也可以构建Trigger触发器
。
可以通过以下导入语句实现DSL的静态导入:
import static org.quartz.JobBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.CalendarIntervalScheduleBuilder.*;
import static org.quartz.TriggerBuilder.*;
import static org.quartz.DateBuilder.*;
不同 ScheduleBuilder
类具有不同类型的调度方法。
DateBuilder
类有多种实现java.util.Date
类的构造方法(如果当前时间是9时43分27秒,表示下一偶数小时的日期则为10:00:00)
2.2 Jobs作业任务 and Triggers触发器
Job
接口只有一个简单的方法,如下:
package org.quartz;
public interface Job {
public void execute(JobExecutionContext context) throws JobExecutionException;
}
当job
的Trigger触发器
触发执行时,scheduler调度程序
的其中一个线程将调用execute()
方法。此方法的入参JobExecutionContext
对象包含了作业实例运行时环境的信息,执行它的调度程序的handle
,触发执行的触发器的handle
,Job
的JobDetail
对象以及其他一些项目。
Quartz
框架中,当作业任务被添加到scheduler调度程序
时,需要创建JobDetail
,JobDetail
包含Job
的各种属性设置,如JobDataMap
就是用来存储作业类实例的状态信息,它本质上是作业实例的定义,将在下一课中进一步详细讨论。
Trigger触发器
用于触发作业任务。当您准备去创建一个作业任务时,可以去实例化触发器并设定执行计划时间表(corn)。Trigger触发器
也有一个与之关联的JobDataMap
,JobDataMap
可以将参数传递给特定触发器触发的作业任务。Quartz
附带了一些不同类型的触发器,但最常用的类型是SimpleTrigger
和CronTrigger
触发器两种。
如果您需要在给定时间执行一次作业任务,或者在给定时间点每隔一段时间执行一次,重复N次,则SimpleTrigger
比较方便。
如果您希望类似日历时间表触发,如每个星期五中午、每个月10号10:15执行,则CronTrigger
比较适合。
那为什么要有作业任务和触发器这两个东西?许多schedulers调度程序
没有单独的作业任务和触发器概念。有些人将作业任务定义为执行时间(或计划)以及一些小作业标识符或者是很像Quartz中作业任务和触发器两者的结合。在创造Quartz时,我们认为将时间表
(Trigger触发器)和要按照该时间表执行的工作
(作业任务)分离开是有意义的。我们认为有很多好处。
例如,可以独立于Trigger触发器
,创建作业任务并将其存储在schedulers调度程序
中,并且许多Trigger触发器
可以与同一作业任务相关联。此松散耦合的另一个好处是能够配置在关联的Trigger触发器
到期后保留在schedulers调度程序
中的作业,以便以后可以重新调度,而无需重新定义它。它还允许您修改或替换Trigger触发器
,而无需重新定义其关联的作业。
2.3 Identities 身份唯一标识
作业和触发器在Quartz
调度程序中注册时被赋予标识密钥key。作业和触发器的key(JobKey和TriggerKey)允许将它们放入“group”中,这对于将作业和触发器组织成“报告作业”和“维护作业”等类别非常有用。作业或触发器key的名称部分在组内必须是唯一的,或者换句话说,作业或触发器的完整Key(或标识符)应该是名称+组。
Lesson 3. More About Jobs and Job Details(关于作业的作业任务的详情)
正如您在第2课中看到的,Jobs
的实现很简单,接口也只有一个execute()
方法。关于Jobs
的本质,Job
接口的execute()
方法以及JobDetails
,您还需要了解更多内容。
虽然您的作业类已经实现您需要的业务逻辑的的代码,但是作业任务的执行还需要一些配置一些属性,这就需要JobDetail
类来完成,在上一节中,已经简要提到过。
JobDetail
实例使用JobBuilder
类构建的。通常使用其所有方法的静态导入,以便在代码中具有DSL感觉。
import static org.quartz.JobBuilder.*;
现在让我们花点时间讨论一下Jobs
的本质以及Quartz
中作业实例的生命周期。首先让我们回顾一下我们在第1课中看到的一些代码片段:
// define the job and tie it to our HelloJob class
JobDetail job = new Job(HelloJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.build();
// Trigger the job to run now, and then every 40 seconds
Trigger trigger = new Trigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule().withIntervalInSeconds(40).repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
现在,将作业类HelloJob
定义为:
public class HelloJob implements Job {
public HelloJob() {
}
public void execute(JobExecutionContext context) throws JobExecutionException {
System.err.println("Hello! HelloJob is executing.");
}
}
请注意,我们为调度程序提供了一个JobDetail
实例,并根据入参的job.class
来执行对应的作业类型。调度程序每次执行作业,在调用作业的execute()
方法前都会创建一个该类的新实例。执行完成后,将删除对作业类实例的引用,然后对实例进行垃圾回收。这种设计下,当使用默认的JobFactory实现时,作业必须具有无参数构造函数。另一个分支是在作业类上定义状态数据字段没有意义 - 因为它们的值不会在作业执行之间保留。
您现在可能想问我如何为Job实例提供属性、配置?
和如何在执行之间跟踪作业的状态?
答案是:JobDataMap
,它是JobDetail
对象的一部分。
3.1 JobDataMap
JobDataMap
可用于保存您希望在作业实例执行时可用的任何数量的(可序列化的)数据对象。JobDataMap
是Java
Map
接口的一种实现,并且具有一些用于存储和检索基本类型数据的便利方法。
以下是在将作业添加到调度程序之前,在定义/构建JobDetail
时将数据放入JobDataMap
的一些代码段:
// define the job and tie it to our DumbJob class
JobDetail job = new Job(DumbJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.usingJobData("jobSays", "Hello World!")
.usingJobData("myFloatValue", 3.141f)
.build();
这是在作业执行期间从JobDataMap
获取数据的示例:
public class DumbJob implements Job {
public DumbJob() {
}
public void execute(JobExecutionContext context) throws JobExecutionException {
JobKey key = context.getJobDetail().getKey();
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
String jobSays = dataMap.getString("jobSays");
float myFloatValue = dataMap.getFloat("myFloatValue");
System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
}
}
如果您使用持久性JobStore
(在本教程的JobStore
部分中讨论),您应该谨慎地决定放置在JobDataMap
中的内容,因为其中的对象将被序列化,因此它们容易出现类版本问题。显然,标准Java类型应该是非常安全的,但除此之外,每当有人更改您已为其序列化实例的类的定义时,必须注意不要破坏兼容性。(可选)您可以将JDBC-JobStore
和JobDataMap
置于只允许将基础类型和字符串存储在映射中的模式,从而消除以后序列化问题的任何可能性。
如果向作业类添加setter方法,这些方法对应于JobDataMap
中键的名称(例如上例中数据的 setJobSays(String val)
方法),那么Quartz
的默认JobFactory
实现将自动调用那些setter job被实例化,因此无需在execute方法中显式地从map中获取值。
Triggers
还可以具有与之关联的JobDataMaps
。如果您有一个存储在调度程序中的作业以供多个触发器定期/重复使用,但是每次独立触发,您希望为作业提供不同的数据输入,这可能很有用。
在作业执行期间在JobExecutionContext
上找到的JobDataMap
可以方便地使用。它是在JobDetail
上找到的JobDataMap
和在Trigger
上找到的JobDataMap
的合并,后者中的值覆盖前者中的任何同名值。
以下是在作业执行期间从JobExecutionContext
的合并JobDataMap
获取数据的快速示例:
public class DumbJob implements Job {
public DumbJob() {
}
public void execute(JobExecutionContext context) throws JobExecutionException {
JobKey key = context.getJobDetail().getKey();
// Note the difference from the previous example
JobDataMap dataMap = context.getMergedJobDataMap();
String jobSays = dataMap.getString("jobSays");
float myFloatValue = dataMap.getFloat("myFloatValue");
ArrayList state = (ArrayList)dataMap.get("myStateData");
state.add(new Date());
System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
}
}
或者,如果您希望依赖JobFactory
将数据映射值注入到您的类中,它可能看起来像这样:
public class DumbJob implements Job {
String jobSays;
float myFloatValue;
ArrayList state;
public DumbJob() {
}
public void execute(JobExecutionContext context) throws JobExecutionException {
JobKey key = context.getJobDetail().getKey();
// Note the difference from the previous example
JobDataMap dataMap = context.getMergedJobDataMap();
state.add(new Date());
System.err.println("Instance " + key + " of DumbJob says: " + jobSays + ", and val is: " + myFloatValue);
}
public void setJobSays(String jobSays) {
this.jobSays = jobSays;
}
public void setMyFloatValue(float myFloatValue) {
myFloatValue = myFloatValue;
}
public void setState(ArrayList state) {
state = state;
}
}
您会注意到类的整体代码更长,但execute()
方法中的代码更清晰。人们还可以争辩说,虽然代码更长,但实际上它需要更少的编码,因为程序员的IDE能自动生成setter方法,而不是手动编写单个调用以从JobDataMap检索值。
3.2 Job Instances
许多用户对工作实例
的确切构成感到困惑。下面有关作业状态和并发性的部分将解决你的困惑。
您可以创建一个Job
作业类,并通过创建多个JobDetails
实例(每个实例都有自己的属性和JobDataMap
)并将它们全部添加到调度程序中,在调度程序中存储许多JobDetails
的实例定义。
例如,您可以创建一个名为SalesReportJob
类,实现Job
接口,希望通过运用JobDataMap
这个属性,根据销售人员的姓名发送相应的销售报告。我们可以创建多个JobDetails
,如:SalesReportForJoe
和SalesReportForMike
,只要在相应的JobDataMaps
中指定joe
和mike
作为相应作业的输入即可。
触发器触发时,将加载与其关联的JobDetail
(实例定义),并通过Scheduler
上配置的JobFactory
实例化其引用的作业类。默认的JobFactory
只是在作业类上调用new Instance()
,接着尝试在类上调用与JobDataMap
中的键名匹配的setter方法。您也可以自己创建JobFactory
来实现让应用程序的IoC或DI容器生成/初始化作业实例之类的事情。
在Quartz speak
中,我们将每个存储的JobDetail
称为作业定义或JobDetail实例,并且我们将每个执行作业称为“作业实例”或“作业定义的实例”。通常,如果我们只使用job
这个词,我们指的是命名定义或JobDetail
。当我们提到实现作业接口的类时,我们通常使用术语:作业类。
3.3 Job State and Concurrency(作业状态和并发)
下面描述下作业的状态数据(也称为JobDataMap
)和并发性的一些附加说明。有几个注释可以添加到您的Job
类中,这些注释会影响Quartz
在这些方面的行为。
-
@DisallowConcurrentExecution 是一个可以添加到
Job
类的注释,它告诉Quartz
不要同时执行给定作业定义的多个实例(指向给定的作业类)。请谨慎使用这个注解。在上一节的示例中,如果SalesReportJob
具有此批注,则只能在给定时间执行SalesReportForJoe
的一个实例,但它可以与SalesReportForMike
实例同时执行。注解的约束是基于实例定义(JobDetail
),而不是基于作业类的实例Job
。在Quartz
的设计期间,为了更符合编码习惯,决定在Job
类上编写这个注释,希望您能深刻理解这一点。 -
@PersistJobDataAfterExecution 是可以添加到
Job
类,告诉Quartz
在执行execute()
方法成功完成(未抛出异常)后,对JobDetail
的JobDataMap
进行更新,下次JobDetail
执行时,接收到的就是更新后的值而不是最初存储的值。与@DisallowConcurrentExecution
批注一样,这适用于作业定义实例JobDetail
,而不是作业类实例Job
。在Quartz
的设计期间,为了更符合编码习惯,决定在Job
类上编写这个注释,希望您能深刻理解这一点。如果使用@PersistJobDataAfterExecution
注解,则应强烈考虑使用@DisallowConcurrentExecutio
批注,以避免在同时执行同一作业的两个实例JobDetail
时可能存在竞争条件。
3.4 Other Attributes Of Jobs(其他属性)
以下是JobDetail
对象作为作业实例定义的其他属性:
- Durability — 如果作业不耐用,一旦不再有与之关联的活动触发器
Trigger
,它将自动从调度程序中删除。换句话说,非持久性作业Job
的寿命是由其触发器是否存在所限制。 - RequestsRecovery — 如果一个作业“请求恢复”,并且它正在调度程序的“硬关闭”期间执行(即它在崩溃中运行的进程,或者机器被关闭),那么它将被重新执行当调度程序再次启动时。在这种情况下,JobExecutionContext.isRecovering()方法将返回true。
3.5 JobExecutionException
最后,您需要知道Job.execute()
方法的一些细节。允许从execute()
方法抛出的唯一类型的异常(包括RuntimeExceptions
)是JobExecutionException
。因此,您通常应该使用try-catch
块来包装execute()
方法的全部内容。您还应该花一些时间查看JobExecutionException
的文档,因为调度程序需要根据它来做出相应的异常处理。
Lesson 4. More About Triggers(触发器详情)
就像作业Job
一样,触发器Triggers
使用起来很简单,但是在你熟练掌握Quartz
之前,你需要理解Triggers
各种可配置选项代表的含义。您可以选择不同类型的触发器以满足不同的调度需求。在第五节和第六节中,您将学习两种最常见的触发器类型Simple Triggers
和 Cron Triggers
。
4.1 常见的触发器属性
所有类型触发器都有TriggerKey
属性,用来跟踪标识其身份,除此之外,还有许多对所有触发器类型都通用的属性。在构建触发器定义时,用TriggerBuilder
来设置这些常用属性(后面将举例说明)。
以下是所有类型触发器都共有的属性列表:
- jobKey 属性:触发器触发应执行的作业的标识。
-
startTime 属性:触发器的计划什么时候首次生效。该值是一个
java.util.Date
对象,是一个确切的时间。对于某些触发类型,触发器会在startTime
时间点被触发,对于另外一些类型的触发器,它只是标记应该开始遵循调度的时间。这意味着您可以存储具有日程安排的触发器,例如每月的第5天,如果startTime
属性设置为4月1日,则将在第一次触发之前的几个月。 -
endTime 属性:触发器的计划什么时候不再有效。换句话说,执行计划如果是
每月的第5天
、endTime为7月1日
,触发器最后一次触发将在6月5日。
其他属性将在下文中讨论。
Priority(优先权)
有时,当您有许多触发器(或Quartz
线程池中的少数工作线程)时,Quartz
可能没有足够的资源来立即触发计划同时触发的所有触发器。在这种情况下,您可能希望控制哪些触发器在可用的Quartz
工作线程中首先触发。为此,您可以在Trigger
上设置priority
属性。如果N触发器同时触发,但当前只有Z工作线程可用,则首先执行具有最高优先级的第一个Z触发器。如果未在触发器上设置优先级,则它将使用默认优先级5。优先级的数字类型是Integer
类型,无论正负。
注意:仅当触发器具有相同的触发时间时才会比较优先级。计划在10:59触发的的触发器始终比计划在11:00触发的触发器之前触发。
注意:当检测到触发器的作业需要恢复时,其恢复的调度优先级与原始触发器相同。
Misfire Instructions(触发失败指令)
触发器的另一个重要特性是它的失火指令
。如果持久性触发器由于调度程序被关闭而错过其触发时间,或者因为Quartz
的线程池中没有可用于执行作业的线程,则会发生失败。不同类型的触发可以使用不同的失火指令。默认情况下,它们使用智能策略指令,该指令具有基于触发类型和配置的动态行为。当调度程序启动时,它会搜索任何已失效的持久触发器,然后根据各自配置的失火指令更新每个触发器。当您在自己的项目中开始使用Quartz
时,您应该熟悉在给定触发器类型上定义的Misfire Instructions
,并在文档中进行了解释。
Calendars(日历)
在定义触发器Trigger
并将其存储在调度程序中时,Quartz的Calendar
对象会与触发器Trigger
关联。(Quartz的Calendar
不是java.util.Calendar
对象)
任何可序列化对象只要是实现Calendar
接口的,那它就是Quartz的Calendar
,如下所示:
Calendar
接口代码
package org.quartz;
public interface Calendar {
public boolean isTimeIncluded(long timeStamp);
public long getNextIncludedTime(long timeStamp);
}
Calendar
通过addCalendar()
方法实例化日历并向调度程序注册日历。如果您使用HolidayCalendar
则在实例化之后,您应该使用addExcludedDate(Date date)
方法,从计划日期中剔除某些日期。相同的日历实例可以与多个触发器一起使用,例如:
HolidayCalendar cal = new HolidayCalendar();
cal.addExcludedDate( someDate );
cal.addExcludedDate( someOtherDate );
sched.addCalendar("myHolidays", cal, false);
Trigger t = new Trigger()
.withIdentity("myTrigger")
.forJob("myJob")
.withSchedule(dailyAtHourAndMinute(9, 30)) // execute job daily at 9:30
.modifiedByCalendar("myHolidays") // but not on holidays
.build();
// .. schedule job with trigger
Trigger t2 = new Trigger()
.withIdentity("myTrigger2")
.forJob("myJob2")
.withSchedule(dailyAtHourAndMinute(11, 30)) // execute job daily at 11:30
.modifiedByCalendar("myHolidays") // but not on holidays
.build();
// .. schedule job with trigger2
触发器构造/构建的细节将在接下来的几节课中给出。现在,只要相信上面的代码创建了两个触发器,每个触发器计划每天触发。但是,将跳过在日历排除的期间内发生的任何发射。
请参阅org.quartz.impl.calendar
包以获取更多Calendar
实现。
Lesson 5. SimpleTrigger
如果您需要在特定时刻执行一次作业,或者在特定时刻执行作业,然后按特定时间间隔重复,则SimpleTrigger
应满足您的日程安排需求。例如,如果您希望触发器在2015年1月13日上午11:23:54正好触发,或者如果您希望它在那时触发,然后每十秒触发五次。
通过此描述,您可能不会惊讶地发现SimpleTrigger
的属性包括:开始时间,结束时间,重复计数和重复间隔。所有这些属性正是您所期望的,只有一些与end-time
属性相关的特殊注释。
重复计数可以是零、正整数或常量值SimpleTrigger.REPEAT_INDEFINITELY。重复间隔属性必须为零或正长值,表示毫秒数。请注意,重复间隔为零将导致触发器的重复计数触发同时发生(或者与调度程序管理同时发生)。
如果您还不熟悉Quartz
的DateBuilder
类,您可能会发现它有助于计算触发器的触发时间,具体取决于您尝试创建的startTime
(或endTime
)。
该end-time属性(如果指定了的话)覆盖重复次数属性。如果您希望创建一个触发器(例如每隔10秒触发一次,直到给定的时刻 - 而不是必须计算它在开始时间和结束时间之间重复的次数,这可能很有用,可以简单地指定结束时间,然后使用REPEAT_INDEFINITELY的重复计数(你甚至可以指定一些巨大数字的重复计数,肯定会超过触发器在结束时间到来之前实际触发的次数) 。
SimpleTrigger
实例是使用TriggerBuilder
(用于触发器的主要属性)和SimpleScheduleBuilder
(用于SimpleTrigger
特定属性)构建的。要以DSL样式使用这些构建器,请使用静态导入:
import static org.quartz.TriggerBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.DateBuilder.*:
以下是使用简单计划定义触发器的各种示例,阅读下列实例,你将会知道新的、不同的点:
在特定时刻构建触发器,不重复:
SimpleTrigger trigger = (SimpleTrigger) new Trigger()
.withIdentity("trigger1", "group1")
.startAt(myStartTime) // some Date
.forJob("job1", "group1") // identify job with name, group strings
.build();
在特定时刻构建触发器,然后每十秒重复十次:
trigger = new Trigger()
.withIdentity("trigger3", "group1")
// if a start time is not given (if this line were omitted), "now" is implied
.startAt(myTimeToStartFiring)
// note that 10 repeats will give a total of 11 firings
.withSchedule(simpleSchedule().withIntervalInSeconds(10).withRepeatCount(10))
// identify job with handle to its JobDetail itself
.forJob(myJob)
.build();
构建一个触发器,将在未来五分钟内触发:
trigger = (SimpleTrigger) new Trigger()
.withIdentity("trigger5", "group1")
// use DateBuilder to create a date in the future
.startAt(futureDate(5, IntervalUnit.MINUTE))
// identify job with its JobKey
.forJob(myJobKey)
.build();
构建一个现在将触发的触发器,然后每五分钟重复一次,直到22:00:
trigger = new Trigger()
.withIdentity("trigger7", "group1")
.withSchedule(simpleSchedule()
.withIntervalInMinutes(5)
.repeatForever())
.endAt(dateOf(22, 0, 0))
.build();
构建一个触发器,在下一个小时的顶部触发,然后每2小时重复一次,永远:
trigger = new Trigger()
// because group is not specified, "trigger8" will be in the default group
.withIdentity("trigger8")
// get the next even-hour (minutes and seconds zero ("00:00"))
.startAt(evenHourDate(null))
// note that in this example, 'forJob(..)' is not called
// - which is valid if the trigger is passed to the scheduler along with the job
.withSchedule(simpleSchedule().withIntervalInHours(2).repeatForever())
.build();
scheduler.scheduleJob(trigger, job);
花一些时间查看TriggerBuilder
和SimpleScheduleBuilder
定义的语言中所有可用的方法,以便您熟悉调用,这些选项可能未在上面的示例中演示过。
请注意,
TriggerBuilder
(以及Quartz
的其他构建器)通常会为未明确设置的属性选择合理的值。例如:如果你没有调用withIdentity()
方法之一,那么TriggerBuilder
将为你的触发器生成一个随机名称;如果你不调用startAt()
则默认为当前时间。
SimpleTrigger Misfire Instructions(未正常触发指令)
SimpleTrigger
有几条指令可用于告知Quartz
发生Misfire时它应该做什么。(第4课:更多关于触发器)中介绍了Misfire情况。这些指令在SimpleTrigger
本身定义为常量(JavaDoc
中有描述)。说明包括:
SimpleTrigger
的Misfire指令常量
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
MISFIRE_INSTRUCTION_FIRE_NOW
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT
您应该从早期的课程中回忆一下,所有触发器都有 可供使用的Trigger.MISFIRE_INSTRUCTION_SMART_POLICY指令,并且该指令也是所有触发器类型的默认指令。
如果使用智能策略指令,SimpleTrigger
将根据给定SimpleTrigger
实例的配置和状态在其各种MISFIRE指令之间动态选择。SimpleTrigger.updateAfterMisfire()
方法的JavaDoc
解释了此动态行为的确切细节。
构建SimpleTriggers
时,将misfire指令指定为简单计划的一部分(通过SimpleSchedulerBuilder
):
trigger = new Trigger()
.withIdentity("trigger7", "group1")
.withSchedule(simpleSchedule()
.withIntervalInMinutes(5)
.repeatForever()
.withMisfireHandlingInstructionNextWithExistingCount())
.build();
Lesson 6. CronTrigger
CronTrigger
通常比SimpleTrigger
更有用,如果您不需要精确指定的SimpleTrigger
间隔重复发生的作业计划,而是类似日历的概念那就需要使用CronTrigger
。
使用CronTrigger
,您可以指定触发时间表,例如 每周五中午,或 每个工作日和上午9:30, 甚至 每周一,周三上午9:00到上午10:00之间每隔5分钟 和 1月的星期五 。
即便如此,像SimpleTrigger
一样,CronTrigger
有一个startTime
,用于指定计划何时生效,以及一个(可选的)endTime
,用于指定何时停止计划。
6.1 Cron Expressions(cron表达式)
Cron-Expressions
用于配置CronTrigger
的实例。Cron-Expressions
是实际上由七个子表达式组成的字符串,用于描述计划的各个细节。这些子表达式用空格分隔,表示:
- 秒
- 分钟
- 小时
- 日的日
- 月
- 某一天的周
- 年份(可选字段)
完整的cron表达式的一个例子是字符串0 0 12 ? * WED
— 表示每周三中午12:00:00
。
单个子表达式可以包含范围和/或列表。例如,前一周中的星期字段(其中读取WED
)示例可以替换为MON-FRI
,MON,WED,FRI
或甚至MON-WED,SAT
。
通配符( * 字符)可用于表示该字段的每个可能值。因此,前一个例子的 月 字段中的*字符仅表示每个月。因此,周日字段中的 * 显然意味着一周中的每一天。
所有字段都有一组可以指定的有效值。这些值的范围也都是常识。例如秒和分钟的数字就是:0到59,以及小时的值范围是:0到23。每月的日期的值范围是:1-31,但您需要注意一个月内的天数!月份可以指定为0到11之间的值,或者使用字符串JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV和DEC。星期几可以指定为1到7之间的值(注意:1 = 星期日)或使用字符串SUN,MON,TUE,WED,THU,FRI和SAT来表示。
'/'字符可用于指定值的增量。例如,如果在“分钟”字段中输入“0/15”,则表示“每小时的第15分钟,从零分钟开始”。如果您在“分钟”字段中使用“3/20”,则表示“每小时20分钟,从第3分钟开始” - 或者换句话说,它与在“分钟”中指定“3,23,43”相同领域。注意“ */ 35”所代表的意义并不是“每35分钟”,它意味着“每小时的第35分钟,从零分钟开始”,或者换句话说与表达式“0,35”相同。
'?' 字符允许使用字符表示日期和星期几字段。它用于指定“无特定值”。当您需要在两个字段之一中指定某些内容而不是另一个字段时,这非常有用。请参阅下面的示例(和CronTrigger JavaDoc
)以获得说明。
“L”字符允许用于日期和星期几字段。这个字符是“last”的简写,但它在两个字段的每一个中都有不同的含义。例如,日期字段中的值“L”表示“月份的最后一天” - 1月31日,非闰年2月28日。如果在星期几字段中单独使用,则仅表示“7”或“SAT”。但如果在星期几字段之后使用另一个值,则表示“该月的最后一个xxx日” - 例如“6L”或“FRIL”均表示“该月的最后一个星期五”。您还可以指定从该月的最后一天开始的偏移量,例如“L-3”,这意味着该日历月的倒数第三天。使用“L”选项时,重要的是不要指定列表或值范围,因为您会得到令人困惑/意外的结果。
'W'字符用于指定最接近给定日期的工作日(周一至周五)。例如,如果您指定“15W”作为日期字段的值,则含义为:“最近的工作日到该月的15日”。
'#'字符用于指定月份的“第n个”XXX个工作日。例如,星期几字段中的“6#3”或“FRI#3”的值意味着“该月的第三个星期五”。
以下是表达式及其含义的更多示例 - 您可以在JavaDoc
中找到更多org.quartz.CronExpression
6.2 Example Cron Expressions(cron表达式示例)
CronTrigger示例1 - 用于创建触发器的表达式,该触发器每5分钟触发一次
“0 0/5 * * *?”
CronTrigger示例2 - 用于创建触发器的表达式,该触发器在每分钟10秒后(即上午10:00:10,上午10:05:10等)每隔5分钟触发一次。
“10 0/5 * * *?”
CronTrigger示例3 - 用于创建触发器的表达式,该触发器在每周三和周五的10:30,11:30,12:30和13:30触发。
“0 30 10-13?* WED,FRI“
CronTrigger示例4 - 用于创建触发器的表达式,该触发器在每个月的5日和20日上午8点到上午10点之间每半小时触发一次。请注意,触发器不会在上午10:00,即8:00,8:30,9:00和9:30触发
“0 0/30 8-9 5,20 *?”
请注意,某些计划要求过于复杂,无法通过单个触发器表达 - 例如“上午9:00至上午10:00之间每隔5分钟,下午1:00至晚上10:00之间每20分钟”。此方案中的解决方案是简单地创建两个触发器,并注册它们以运行相同的作业。
6.3 Building CronTriggers(构建CronTriggers)
CronTrigger
实例是使用TriggerBuilder
(用于触发器的主要属性)和CronScheduleBuilder
(用于特定于CronTrigger
的属性)构建的。要以DSL样式使用这些构建器,请使用静态导入:
import static org.quartz.TriggerBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.DateBuilder.*:
建立一个触发器,每天上午8点到下午5点之间每隔一分钟触发一次:
trigger = new Trigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 0/2 8-17 * * ?"))
.forJob("myJob", "group1")
.build();
构建一个触发器,每天上午10:42触发:
trigger = new Trigger()
.withIdentity("trigger3", "group1")
.withSchedule(dailyAtHourAndMinute(10, 42))
.forJob(myJobKey)
.build();
或者:
trigger = new Trigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 42 10 * * ?"))
.forJob(myJobKey)
.build();
构建一个触发器,该触发器将在星期三上午10点42分在TimeZone中触发,而不是系统的默认值:
trigger = new Trigger()
.withIdentity("trigger3", "group1")
.withSchedule(weeklyOnDayAndHourAndMinute(DateBuilder.WEDNESDAY, 10, 42))
.forJob(myJobKey)
.inTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))
.build();
或者:
trigger = new Trigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 42 10 ? * WED"))
.inTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))
.forJob(myJobKey)
.build();
6.4 CronTrigger触发失败说明
以下说明可用于告知Quartz当CronTrigger发生失火时它应该做什么。(在本教程的“更多关于触发器”部分中介绍了失火情况)。这些指令在CronTrigger本身定义为常量(包括描述其行为的JavaDoc)。说明包括:
CronTrigger的失火指令常量
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY //发现misfire后,未被触发多少次就依次触发多少次
MISFIRE_INSTRUCTION_DO_NOTHING //发现misfire后什么也不做
MISFIRE_INSTRUCTION_FIRE_NOW //发现misfire后立即触发一次
使用场景举例说明:
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY:发现misfire后,未被触发多少次就依次触发多少次
当你需要确保每次调度任务都要被执行的时候,即时它意味着多个misfire的trigger会被触发,那么用ignore policies。试想一下,有一个任务,需要每小时,都根据上一小时的订单去生成报表。如果服务被关闭了8个小时,那你可能仍然是尽快得到那些报表的。这种情况下,配置ignore policies,调度器会尽快将那8小时的调度任务运行一遍的。尽管晚了几个小时,但是仍然是被执行了(最终报告到手了。^_^)。
MISFIRE_INSTRUCTION_DO_NOTHING:发现misfire后什么也不做
当你希望任务能在特定时间点运行的时候,使用next* policies不错。比如你需要在每个整点后15分钟抓取股票的价格。它们的变化非常快,然后现在已经整点后20分了,那么不必烦恼。你刚好错过了5分钟,但是现在你已经不在乎(那时候的价格)了。这时,一个时间间隙总好过一个不准确的值。这种情况Quartz只要跳过misfire的操作,等待下次执行就好了。
MISFIRE_INSTRUCTION_FIRE_NOW:发现misfire后立即触发一次
当你需要任务被定期执行,并且当出现misfire的情况后立即运行一次的时候,那么使用now* policies。试想一下,一个任务是每分钟清空文件夹 /tmp。如果调度器在20分钟内繁忙,最后终于可以执行这个任务了,那么你肯定不会希望它执行20次的!一次就足够了,但是要尽快执行。而后,再回到正常的执行间隔--1分钟。
所有触发器还具有可供使用的Trigger.MISFIRE_INSTRUCTION_SMART_POLICY指令,该指令也是所有触发类型的默认指令。智能策略指令由CronTrigge
r解释为MISFIRE_INSTRUCTION_FIRE_NOW
, CronTrigger.updateAfterMisfire()
方法的JavaDoc
解释了此行为的确切详细信息。
在构建CronTriggers时,您将misfire指令指定为简单计划的一部分(通过CronSchedulerBuilder):
trigger = new Trigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 0/2 8-17 * * ?")
.withMisfireHandlingInstructionFireAndProceed())
.forJob("myJob", "group1")
.build();
Lesson 7. TriggerListeners and JobListeners(触发器监听器和定时任务监听器)
监听器是您创建的对象,用于根据调度程序中发生的事件执行操作。正如您可能猜到的,TriggerListeners接收与触发器相关的事件,而JobListeners 接收与作业相关的事件。
与触发器相关的事件包括:触发器触发,触发错误触发(在本文档的“触发器”部分中已经讨论)和触发器完成触发(触发器完成触发)。
The org.quartz.TriggerListener
接口:
public interface TriggerListener {
public String getName();
public void triggerFired(Trigger trigger, JobExecutionContext context);
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);
public void triggerMisfired(Trigger trigger);
public void triggerComplete(Trigger trigger, JobExecutionContext context, int triggerInstructionCode);
}
与作业相关的事件包括:作业即将执行的通知,以及作业完成执行时的通知。
org.quartz.JobListener
接口:
public interface JobListener {
public String getName();
public void jobToBeExecuted(JobExecutionContext context);
public void jobExecutionVetoed(JobExecutionContext context);
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException);
}
7.1 Using Your Own Listeners(使用你自己的监听器)
创建一个监听器,只需要实现org.quartz.TriggerListener
和/或org.quartz.JobListener
接口,然后在运行时向调度程序注册这个监听器,并且必须为其指定名。(确切的说,是在调用getName()
方法时,必须有返回)。
除了实现这些接口之外,您的类还可以继承JobListenerSupport
或TriggerListenerSupport
类,并重写您感兴趣的部分。
监听器与调度程序的ListenerManager
一起注册,并附带一个Matcher
,用于描述监听器想要接收事件的Job作业
/Trigger触发器
。
监听器在调度程序运行时注册,且不与作业和触发器一起存储在
JobStore
中。这是因为监听器通常是与应用程序的集成点。因此,每次运行应用程序时,都需要使用调度程序重新注册监听器。
添加对特定作业感兴趣的JobListener
scheduler.getListenerManager()。
addJobListener(myJobListener,KeyMatcher.jobKeyEquals(new JobKey("myJobName","myJobGroup")));
您可能希望对matcher
和key
类使用静态导入,这将使您更清楚地定义匹配器:
import static org.quartz.JobKey.*;
import static org.quartz.impl.matchers.KeyMatcher.*;
import static org.quartz.impl.matchers.GroupMatcher.*;
import static org.quartz.impl.matchers.AndMatcher.*;
import static org.quartz.impl.matchers.OrMatcher.*;
import static org.quartz.impl.matchers.EverythingMatcher.*;
...etc.
这把上面的例子变成了这个:
scheduler.getListenerManager()
.addJobListener(myJobListener, jobKeyEquals(jobKey("myJobName", "myJobGroup")));
添加对特定组的所有作业感兴趣的JobListener
:
scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals("myJobGroup"));
添加对两个特定组的所有作业感兴趣的JobListener
:
scheduler.getListenerManager()
.addJobListener(myJobListener,
or(jobGroupEquals("myJobGroup"), jobGroupEquals("yourGroup")));
添加对所有作业感兴趣的JobListener
:
scheduler.getListenerManager().addJobListener(myJobListener, allJobs());
...注册TriggerListeners的工作方式相同。
大多数Quartz
用户不使用监听器,但是当应用程序需求创建需要事件通知时,监听器很方便,而Job本身不必显式通知应用程序。
Lesson 8. SchedulerListeners
SchedulerListeners
与TriggerListeners
和JobListeners
非常相似,SchedulerListeners
在Scheduler
内接受时间通知 - 不一定是与特定触发器或作业相关的事件。
与调度程序相关的事件包括:添加作业/触发器,删除作业/触发器,调度程序中的严重错误,调度程序关闭的通知等。
org.quartz.SchedulerListener接口
public interface SchedulerListener {
public void jobScheduled(Trigger trigger);
public void jobUnscheduled(String triggerName, String triggerGroup);
public void triggerFinalized(Trigger trigger);
public void triggersPaused(String triggerName, String triggerGroup);
public void triggersResumed(String triggerName, String triggerGroup);
public void jobsPaused(String jobName, String jobGroup);
public void jobsResumed(String jobName, String jobGroup);
public void schedulerError(String msg, SchedulerException cause);
public void schedulerStarted();
public void schedulerInStandbyMode();
public void schedulerShutdown();
public void schedulingDataCleared();
}
SchedulerListeners
在调度程序的ListenerManager
中注册。SchedulerListeners
几乎可以是任何实现org.quartz.SchedulerListener
接口的对象。
添加SchedulerListener:
scheduler.getListenerManager().addSchedulerListener(mySchedListener);
删除SchedulerListener:
scheduler.getListenerManager().removeSchedulerListener(mySchedListener);
Lesson 9. Job Stores
JobStore
负责跟踪您为调度程序提供的所有“工作数据”:作业,触发器,日历等。为Quartz
调度程序实例选择合适的JobStore
是一个重要的步骤。幸运的是,一旦你理解了它们之间的差异,选择应该非常简单。您应该在生成调度程序实例的SchedulerFactory
的属性文件(或对象)中使用申明使用哪个JobStore
(以及它的配置设置)。
切勿在代码中直接使用JobStore实例。出于某种原因,许多人试图这样做。JobStore用于Quartz本身的幕后使用。您必须告诉Quartz(通过配置)使用哪个JobStore,但是您应该只使用代码中的Scheduler接口。
RAMJobStore
是最简单的JobStore
,它也是性能最高的(就CPU执行时间来看)。RAMJobStore
得名于:它将所有数据保存在RAM中。这就是它闪电般快速的原因,也是配置如此简单的原因。缺点是,当您的应用程序结束(或崩溃)时,所有调度信息都将丢失 - 这意味着RAMJobStore
无法遵守作业和触发器上的“非易失性”设置。对于某些应用程序,这是可以接受的,但对于其他应用程序,这可能是灾难性的。
假设您正在使用StdSchedulerFactory
,要使用RAMJobStore
,只需将类名org.quartz.simpl.RAMJobStore
指定为用于配置quartz
的JobStore
类属性:
配置Quartz以使用RAMJobStore
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
您无需担心其他设置。
9.1 JDBCJobStore
JDBCJobStore
人如其名 ,它通过JDBC
将所有数据保存在数据库中。因此,配置比RAMJobStore
要复杂一些,而且速度也不快。但是,性能缺点不大,特别是如果您使用主键上的索引构建数据库表。在具有良好LAN(在调度程序和数据库之间)的机器上,检索和更新触发触发器的时间通常小于10毫秒。
JDBCJobStore
任何数据库几乎都适用,它已广泛应用于Oracle
,PostgreSQL
,MySQL
,MS SQLServer
,HSQLDB
和DB2
。要使用JDBCJobStore
,必须首先为Quartz
创建一组数据库表以供使用。您可以在Quartz
发行版的“docs / dbTables”目录中找到表创建SQL脚本。如果还没有适用于您的数据库类型的脚本,只需查看其中一个现有脚本,然后以您的数据库所需的任何方式对其进行修改。需要注意的一点是,在这些脚本中,所有表都以前缀“QRTZ_”开头(例如表“QRTZ_TRIGGERS”和“QRTZ_JOB_DETAIL”)。这个前缀实际上可以是你想要的任何东西,只要你通知JDBCJobStore前缀是什么(在你的Quartz属性中可以配置)。对于多个调度程序实例,使用不同的前缀对于创建多组表会很有用,
一旦创建了表,在配置和启动JDBCJobStore
之前,还有一个重要的决定。您需要确定应用程序所需的事务类型。如果您不需要将调度命令(例如添加和删除触发器)绑定到其他事务,那么您可以让Quartz
使用JobStoreTX
作为JobStore
来管理事务(这是最常见的选择)。
如果您需要Quartz
与其他事务一起工作(即在J2EE应用程序服务器中),那么您应该使用JobStoreCMT
- 在这种情况下,Quartz
将让app服务器容器管理事务。
最后一个难题是设置一个DataSource
,JDBCJobStore
可以从中获取与数据库的连接。DataSources
是使用几种不同方法之一在Quartz
属性中定义的。一种方法是让Quartz
创建和管理DataSource
本身 - 通过提供数据库的所有连接信息。另一种方法是让Quartz
使用由Quartz
在其中运行的应用程序服务器管理的DataSource
- 通过向JDBCJobStore
提供DataSource
的JNDI
名称。有关这些属性的详细信息,请参阅“docs / config”文件夹中的示例配置文件。
要使用JDBCJobStore
(假设你使用StdSchedulerFactory
),你首先需要设置你的Quartz
配置的作业存储类属性是要么org.quartz.impl.jdbcjobstore.JobStoreTX
或org.quartz.impl.jdbcjobstore.JobStoreCMT
- 视您根据上面几段中的解释做出的选择。
配置Quartz以使用JobStoreTx
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
接下来,您需要为JobStore选择要使用的DriverDelegate。DriverDelegate负责执行特定数据库可能需要的任何JDBC工作。StdJDBCDelegate是一个委托,它使用“vanilla”JDBC代码(和SQL语句)来完成它的工作。如果没有专门为您的数据库制作的另一个委托,请尝试使用此委托 - 我们只为使用StdJDBCDelegate发现问题的数据库创建了特定于数据库的委托(这似乎是最多的!)。其他代表可以在“org.quartz.impl.jdbcjobstore”包中找到,也可以在其子包中找到。其他委托包括DB2v6Delegate(用于DB2版本6及更早版本),HSQLDBDelegate(用于HSQLDB),MSSQLDelegate(用于Microsoft SQLServer),PostgreSQLDelegate(用于PostgreSQL),WeblogicDelegate(用于使用由Weblogic制作的JDBC驱动程序),
选择委托后,将其类名设置为JDBCJobStore要使用的委托。
配置JDBCJobStore以使用DriverDelegate
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
接下来,您需要通知JobStore您正在使用的表前缀(如上所述)。
使用表前缀配置JDBCJobStore
org.quartz.jobStore.tablePrefix = QRTZ_
最后,您需要设置JobStore应该使用哪个DataSource。还必须在Quartz属性中定义命名的DataSource。在这种情况下,我们指定Quartz应该使用DataSource名称“myDS”(在配置属性中的其他位置定义)。
使用要使用的DataSource的名称配置JDBCJobStore
org.quartz.jobStore.dataSource = myDS
如果您的调度程序繁忙(即几乎总是执行与线程池大小相同的作业数,那么您应该将DataSource中的连接数设置为大约线程池的大小+ 2。
“org.quartz.jobStore.useProperties”配置参数可以设置为“true”(默认为false),以指示JDBCJobStore JobDataMaps中的所有值都是字符串,因此可以存储为名称 - 值对,而不是而不是在BLOB列中以序列化形式存储更复杂的对象。从长远来看,这样更安全,因为您可以避免将非String类序列化为BLOB时出现的类版本问题。
9.2 TerracottaJobStore
TerracottaJobStore无需使用数据库即可提供扩展和稳健性的方法。这意味着您的数据库可以免于从Quartz加载,而是可以为您的应用程序的其余部分保存所有资源。
TerracottaJobStore可以运行群集或非群集,并且在任何一种情况下都可以为应用程序重新启动之间持久的作业数据提供存储介质,因为数据存储在Terracotta服务器中。它的性能比通过JDBCJobStore使用数据库要好得多(大约好一个数量级),但比RAMJobStore慢得多。
要使用TerracottaJobStore(假设您正在使用StdSchedulerFactory),只需将类名org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore指定为用于配置quartz的JobStore类属性,并添加一行额外的配置以指定Terracotta服务器的位置:
配置Quartz以使用TerracottaJobStore
org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore
org.quartz.jobStore.tcConfigUrl = localhost:9510
Lesson 10. Configuration, Resource Usage and SchedulerFactory(配置,resource用法和调度器工厂)
Quartz
的架构是模块化的,因此为了让它运行,需要将几个组件“拼接”在一起。幸运的是,有一些助手可以实现这一目标。
Quartz
需要配置的主要的组件是:
- ThreadPool
- JobStore
- DataSources (如果有必要的话)
- The Scheduler itself
该线程池为Quartz
提供执行Jobs
的线程。池中的线程越多,可以并发运行的作业数量就越多。但是,太多线程可能会使您的系统卡顿。大多数Quartz
用户发现5个左右的线程已经很多了,那是因为他们在任何给定时间的作业少于100个,这些作业通常不会安排在同一时间运行,并且作业执行时间很短暂(快速完成)。其他用户发现他们需要10,15,50或甚至100个线程,因为他们有数万个具有各种时间表的触发器,最终在任何时刻都平均有10到100个作业在执行。所以,线程池的大小完全取决于您使用调度程序的内容。让您的工作按时启动的前提下,遵循一条原则,节约机器的资源。请注意,如果触发器的触发时间到来,并且没有可用的线程,Quartz
将阻塞(暂停)直到线程可用,再执行Job
,可能会晚几毫秒。如果在调度程序配置的“失火阈值”的持续时间内没有可用的线程,这就有可能导致线程失效。
ThreadPool
接口在org.quartz.spi
包中定义,您可以以任何您喜欢的方式创建ThreadPool
实现。Quartz
附带了一个名为org.quartz.simpl.SimpleThreadPool
的简单(但非常令人满意)的线程池。这个ThreadPool只是在它的池中维护一组固定大小的线程,永不增长,永不收缩。但它非常强大并且经过了很好的测试,几乎所有使用Quartz
的人都使用这个池。
JobStores和DataSources在第九节中进行了讨论。值得注意的是,所有JobStores
都实现了org.quartz.spi.JobStore
接口,如果其中一个捆绑的JobStore
不能满足您的需求,那么您可以创建自己的。
最后,您需要创建Scheduler
实例。需要为Scheduler
本身指定一个名称,告知其RMI
设置,并传递JobStore
和ThreadPool
的实例。RMI
设置包括调度程序是否应将自身创建为RMI的服务器对象(使其自身可用于远程连接),要使用的主机和端口等.StdSchedulerFactory
(下面讨论)也可以生成实际代理的调度程序实例( RMI存根)到远程进程中创建的调度程序。
10.1 StdSchedulerFactory
StdSchedulerFactory
是org.quartz.SchedulerFactory
接口的实现。它使用一组属性(java.util.Properties
)来创建和初始化Quartz Scheduler
。这些属性通常存储在文件中并从文件中加载,但也可以由程序创建并直接传递给工厂。只需在工厂调用getScheduler()
就可以生成调度程序,初始化它(及其ThreadPool
,JobStore
和DataSources
),并返回其公共接口的句柄。
Quartz
发行版的“docs / config”目录中有一些示例配置(包括属性的描述)。您可以在Quartz
文档的“参考”部分下的“配置”手册中找到完整的文档。
10.2 DirectSchedulerFactory
DirectSchedulerFactory
是另一个SchedulerFactory
实现。对于那些希望以更加编程的方式创建Scheduler
实例的人来说,它非常有用。由于以下原因,通常不建议使用它:
- 它要求用户更好地理解他们正在做什么。
- 它不允许声明性配置,换句话说,你难以编码所有调度程序的设置。
10.3 记录
Quartz
使用SLF4J框架满足其所有日志记录需求。为了“调整”日志记录设置(例如输出量以及输出的位置),您需要了解SLF4J
框架,这超出了本文档的范围。
如果您想捕获有关触发器触发和作业执行的额外信息,您可能有兴趣启用org.quartz.plugins.history.LoggingJobHistoryPlugin和/或org.quartz.plugins.history.LoggingTriggerHistoryPlugin。