Quartz 学习总结

来源:互联网 发布:阿里云智能系统 编辑:程序博客网 时间:2024/06/09 20:14

Quartz是什么?

Quartz是一个开源的任务调度框架。基于定时、定期的策略来执行任务是它的核心功能,比如x年x月的每个星期五上午8点到9点,每隔10分钟执行1次。Quartz有3个核心要素:调度器(Scheduler)、任务(Job)、触发器(Trigger)。Quartz完全使用Java开发,可以集成到各种规模的应用程序中。它能够承载成千上万的任务调度,并且支持集群。它支持将数据存储到数据库中以实现持久化,并支持绝大多数的数据库。它将任务与触发设计为松耦合,即一个任务可以对应多个触发器,这样能够轻松构造出极为复杂的触发策略。

现在我们来看一个简单的示例:

public static void main(String arg[]) {        try {            //创建调度工厂            SchedulerFactory schedulerFactory = new StdSchedulerFactory();            //从工厂中获取调度器            Scheduler scheduler = schedulerFactory.getScheduler();            //启动调度器            scheduler.start();            //创建job详细信息:创建helloJob实例并设置jobName和所属group,并设为当程序挂掉重启之后可重新恢复轮询            JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity(JobKey.jobKey("jobName", "jobGroupName"))                    .requestRecovery().build();            //创建一个simpleSchedule,轮询五次,间隔5秒钟            SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withRepeatCount(5)                    .withIntervalInSeconds(5);            //创建一个simpleTrigger 触发器,设置触发器的身份标识,绑定调度器,并设为立即启动            SimpleTrigger simpleTrigger = TriggerBuilder.newTrigger().withIdentity("triggerName", "triggerGroupName")                    .withSchedule(scheduleBuilder).startNow().build();            scheduler.scheduleJob(jobDetail, simpleTrigger);        } catch (SchedulerException e) {            System.out.println("报错");        }    }

接下来看一下HelloJob类,是一个实现了Job接口的类,它只有execute这一个方法。我们来看一job源码,以及hellojob代码:

hellojob代码:
public class HelloJob implements Job {    private static Logger logger = LoggerFactory.getLogger(HelloJob.class);    @Override    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {        logger.info("hello jon执行时间: " + LocalTime.now());        System.out.print("hello jon执行时间: " + LocalTime.now());    }}
job源码:
public interface Job {    void execute(JobExecutionContext var1) throws JobExecutionException;}

至此我们实现了一个简单调度任,当Job的Trigger激活时,该方法将被Scheduler的一个线程调用执行。JobExecutionContext 是传递给该方法的运行时环境信息,包括调用它的调度器、触发该执行的Trigger、JobDetail对象以及其他信息。
接下来我们来看一下上面用到jobDetail以及schedule等核心api和触发器的介绍

核心api和触发器的介绍

Quartz API主要包含以下接口:

Scheduler:与调度器交互的主要API。

Job:一个接口,实现该接口的组件将被调度器运行。

JobDetail:用于定义Job实例。JobDetail是在Job被添加到Scheduler时由应用程序创建的,它包含了关于Job的各种属性信息,都在JobDataMap中

Trigger:定义了一个Job如何被调度器所运行。Trigger用于触发任务的执行。它也会关联到的一个JobDataMap–当需要把数据传递给触发器特定的某个任务时这很有用。Quartz提供了各种触发器,然而最常用的是SimpleTrigger 和CronTrigger。
JobBuilder:用于定义/构建JobDetail 实例。

TriggerBuilder:用于定义/构建Trigger实例。

DateBuilder:用于创建时间类型的实例。

Scheduler由SchedulerFactory创建,并随着shutdown方法的调用而终止。创建后它将可被用来添加、删除或列出Job和Trigger,或执行一些调度相关的工作,(比如暂停)。只有通过start()方法启动后它才会真的工作。

Job与JobDetail关系

JobDetail 定义的是任务数据,而真正的执行逻辑是在Job中,例子中是HelloJob。 为什么设计成JobDetail + Job,不直接使用Job?这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail & Job 方式,sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题。

身份识别——name与group

任务和触发器注册到调度器时都会有一个识别KEY。任务和触发器都可以添加到组,因此它们的KEY名称在同一个组内必须是唯一的,也就是上面使用的jobName与JobGroup,完整的KEY名由KEY名+组名组成。如果我们要更新一个JobDetail定义,只需要设置一个name相同的JobDetail实例即可。group是一个组织单元,sheduler会提供一些对整组操作的API,比如 scheduler.resumeJobs()。

通用属性介绍

下面列出这些通用属性:

jobkey

jobKey:指定了Triggers激活时将被执行的job的身份;

StartTime & EndTime

startTime:指定了调度器何时开始作用。该值是一个java.util.Date对象。某些类型的Triggers确实会在startTime激活,而另一些Triggers仅简单地标记下调度器将来应当启动的时间。这意味着你可以存储一个Trigger和一个“1月的第5天”这样的调度器,如果startTime设在了4月1号,那么第一次启动时间要在几个月以后。
endTime:指定了Trigger的调度器在何时不再生效。换句话说,一个触发器的如果设置为“每月的第5天”并且endTime为“7月1日”那么它最后一次激活应该是在6月5日。

优先级(priority)

有时候当你有许多触发器(或者Quartz 线程池中的线程很少)时,Quartz 可能没有足够的资源来同时激活所有的触发器。这时,你可能想要控制让哪一个触发器先被触发。出于这个目的,你可以设置触发器的priority 属性。如果有N个触发器将要同时被激活,然而Quartz 只有Z个线程,那么只有前Z个优先级最高的触发器将被激活。如果没有设置,优先级的默认值为5。优先级的取值适用于所有整数,包括正数和负数。

注意:仅当触发器将同时被激活时才会比较优先级。设定在10:59的触发器永远比设定在11:00的触发器先激活。
注意:如果检测到一个job要求恢复,那么恢复后的优先级不变。

激活失败指令(Misfire Instructions)

类似的Scheduler资源不足的时候,或者机器崩溃重启等,有可能某一些Trigger在应该触发的时间点没有被触发,也就是Miss Fire了。这个时候Trigger需要一个策略来处理这种情况。每种Trigger可选的策略各不相同。

这里有两个点需要重点注意:

  • MisFire的触发是有一个阀值,这个阀值是配置在JobStore的。比RAMJobStore是org.quartz.jobStore.misfireThreshold。只有超过这个阀值,才会算MisFire。小于这个阀值,Quartz是会全部重新触发。

所有MisFire的策略实际上都是解答两个问题:

  1. 已经MisFire的任务还要重新触发吗?
  2. 如果发生MisFire,要调整现有的调度时间吗?

比如SimpleTrigger的MisFire策略有:

  • MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
    这个不是忽略已经错失的触发的意思,而是说忽略MisFire策略。它会在资源合适的时候,重新触发所有的MisFire任务,并且不会影响现有的调度时间。
    比如,SimpleTrigger每15秒执行一次,而中间有5分钟时间它都MisFire了,一共错失了20个,5分钟后,假设资源充足了,并且任务允许并发,它会被一次性触发。
    这个属性是所有Trigger都适用。

  • MISFIRE_INSTRUCTION_FIRE_NOW

  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
    将startTime设置当前时间,立即重新调度任务,包括的MisFire的

  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT

  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT
    在下一次调度时间点,重新开始调度任务,包括的MisFire的

  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT

  • MISFIRE_INSTRUCTION_SMART_POLICY
    所有的Trigger的MisFire默认值都是这个,大致意思是“把处理逻辑交给聪明的Quartz去决定”。基本策略是,

    • 如果是只执行一次的调度,使用MISFIRE_INSTRUCTION_FIRE_NOW

    • 如果是无限次的调度(repeatCount是无限的),使用MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT

    • 否则,使用MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT

Calendar

Quartz Calendar 对象(不是java.util.Calendar对象)可以在触发器被定义时被关联并存储到调度器。在从触发器的激活策略中排除时间块时Calendar 非常有用。它的作用是在于补充Trigger的时间。可以排除或加入某一些特定的时间点。比如说,你可以添加一个触发器,它在每天早上的9:30激活,然后添加一个Calendar 来排除掉所有的商业假日。

接下来我们来简单的看一下代码:

    Date exDate = DateBuilder.dateOf(1,1,1); //该build类返回一个java.util.Date类型的时间,其中具有很多api,可自行查看源码学习    HolidayCalendar holidayCalendar = new HolidayCalendar();//创建一个特定的日期日历,精确到天    holidayCalendar.addExcludedDate(exDate);//将需要排除的日期加入到日历中    /*    在调度器中加入这个时间日历,第一个参数为calendar名词,第二个参数为calendar对象,    第三个参数为是否重置calendar若为true则会替换原先存在的相同名词的calendar,第四个参数为更新已使用calendar的触发器    */    scheduler.addCalendar("holiday", holidayCalendar, false, false);//给调度器添加日历    scheduler.scheduleJob(jobDetail, simpleTrigger);/*---------以上就是一个使用日历的小例子---------------*/    AnnualCalendar annualCalendar = new AnnualCalendar();    Calendar calendar = new GregorianCalendar(2012, 5, 12);    annualCalendar.setDayExcluded(calendar, false);    SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("triggerName", "triggerGroupName").withSchedule(scheduleBuilder).modifiedByCalendar("holiday").build();/*---------以上就是一个在触发器中添加日历的小例子---------------*/

Trigger

Quartz有以下几种Trigger实现:

SimpleTrigger

指定从某一个时间开始,以一定的时间间隔(单位是毫秒)执行的任务。
它适合的任务类似于:9:00 开始,每隔1小时,执行一次。

CalendarIntervalTrigger

类似于SimpleTrigger,指定从某一个时间开始,以一定的时间间隔执行的任务。 但是不同的是SimpleTrigger指定的时间间隔为毫秒,没办法指定每隔一个月执行一次(每月的时间间隔不是固定值),而CalendarIntervalTrigger支持的间隔单位有秒,分钟,小时,天,月,年,星期。
相较于SimpleTrigger有两个优势:1、更方便,比如每隔1小时执行,你不用自己去计算1小时等于多少毫秒。 2、支持不是固定长度的间隔,比如间隔为月和年。但劣势是精度只能到秒。
它适合的任务类似于:9:00 开始执行,并且以后每周 9:00 执行一次

DailyTimeIntervalTrigger

指定每天的某个时间段内,以一定的时间间隔执行任务。并且它可以支持指定星期。
它适合的任务类似于:指定每天9:00 至 18:00 ,每隔70秒执行一次,并且只要周一至周五执行。

CronTrigger

适合于更复杂的任务,它支持类型于Linux Cron的语法(并且更强大)。基本上它覆盖了以上三个Trigger的绝大部分能力(但不是全部)—— 当然,也更难理解。
它适合的任务类似于:每天0:00,9:00,18:00各执行一次。
需要学习cron的可以看这篇博文:
http://blog.csdn.net/wujiaqi0921/article/details/78394778

JobDataMap

Job都次都是newInstance的实例,那我怎么传值给它? 比如我现在有两个发送邮件的任务,一个是发给”liLei”,一个发给”hanmeimei”,不能说我要写两个Job实现类LiLeiSendEmailJob和HanMeiMeiSendEmailJob。实现的办法是通过JobDataMap。
每一个JobDetail都会有一个JobDataMap。JobDataMap本质就是一个Map的扩展类,只是提供了一些更便捷的方法,比如getString()之类的。
使用例子:

newJob().usingJobData("age", 18) //加入属性到ageJobDataMapjob.getJobDataMap().put("name", "quertz"); //加入属性name到JobDataMap

任务状态和并发

这里介绍一些关于任务状态和并发的额外的说明。有许多标注可以被添加到job class中,影响到Quartz’s 在各方面的行为。

@DisallowConcurrentExecution

这个标注添加到job class中就是告诉Quartz,我不能被并行执行。拿前面的例子来说,如果SalesReportJob添加了这个标注,那么在同一时间只能有一个SalesReportForJoe的实例在执行,但是SalesReportForMike的实例可以同时执行。这个限制是基于JobDetail而不是 job class的实例。然而它被决定(在Quartz的设计中)要作用于类自身之上,因为它经常会影响到类的编写方式。

@PersistJobDataAfterExecution

它告诉Quartz,在成功执行完(没有抛出异常)之后要更新JobDetail的JobDataMap。这样下一次执行时该任务就会收到最新的数据。与@DisallowConcurrentExecution 一样,它也作用于一个 job definition 实例,而不是job类的实例。

@PersistJobDataAfterExecution

如果使用了本标注,那么你要强烈考虑同时使用DisallowConcurrentExecution 标注,以避免当同一个任务的2个实例并行执行时最终保存的数据是什么样的(竞争条件下)这种冲突。

触发器与任务监听器

触发器监听器(TriggerListeners)和任务监听器(JobListeners )分别接收关于Trigger和Job的事件。触发器相关的事件包括:触发器激活、激活失败以及激活完成(执行的任务运行完毕)。

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);}

任务相关的事件包括:任务即将被执行的通知、任务执行完毕的通知。JobListener 接口形式如下:

public interface JobListener {    public String getName();    public void jobToBeExecuted(JobExecutionContext context);    public void jobExecutionVetoed(JobExecutionContext context);    public void jobWasExecuted(JobExecutionContext context,            JobExecutionException jobException);}

如何使用自己的监听器?

实现TriggerListener和/或JobListener即可实现你自己的监听器。监听器需要注册到调度器,且必须给他一个名称(或者说它们必须能够通过其getName方法来获取其名称)。

为了方便起见,你可以继承JobListenerSupport 类或者TriggerListenerSupport ,然后重载你感兴趣的方法即可。

监听器需要与一个匹配器一起注册到调度器,该匹配器用于指定监听器想要接收哪个触发器/任务的事件。监听器在运行期间注册到调度器,且并没有与触发器和任务一起存储到JobStore 中。这是因为监听器通常是你应用的一个结合点,因此每次应用运行时它都需要重新注册到调度器。
在指定任务中添加感兴趣的任务监听器方法如下:
scheduler.getListenerManager().addJobListener(myJobListener, KeyMatcher.jobKeyEquals(new JobKey(“myJobName”, “myJobGroup”)));
通过以下对匹配器和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.*;

它使代码变成这样:

scheduler.getListenerManager().addJobListener(myJobListener, jobKeyEquals(jobKey(“myJobName”, “myJobGroup”)));

在组中为所有任务添加感兴趣的任务监听器方法如下:
scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals(“myJobGroup”));

在两个指定组中为所有任务添加感兴趣的任务监听器方法如下:
scheduler.getListenerManager().addJobListener(myJobListener, or(jobGroupEquals(“myJobGroup”), jobGroupEquals(“yourGroup”)));

为所有任务添加感兴趣的任务监听器方法如下:
scheduler.getListenerManager().addJobListener(myJobListener, allJobs());

注册TriggerListeners 的方法与以上相同。大多数Quartz用户并不会用到监听器,然而应用程序需要得到事件通知的话它们是非常易用的,而且不需要任务本身显式地通知应用程序。

JobStores

JobStore的职责是记录所有你提供给调度器的“工作数据”:任务、触发器、日历等等。为你的调度器实例选择合适的JobStore的一个非常重要的步骤。幸运的是,一旦你理解了它们之间的不同,选择起来是非常容易的。你在配置文件中声明将要选择哪个JobStore,以此将它提供给SchedulerFactory,因为你的调度器实例是由它生成的。

永远不要在代码中直接使用JobStore实例。有的人因为某些原因想要这样做。JobStore是给Quartz 在后台使用的。你需要(通过配置)告诉Quartz 使用哪一个JobStore,然后你只应将代码集中在调度器接口上。

RAMJobStore

RAMJobStore 是最易于使用的obStore,也是性能最好的(从CPU时间的角度)。RAMJobStore 的名字就很说明问题:它将数据保存在内存中。这就是它为何快如闪电且已易于配置。它的缺点在于如果你的应用程序结束了或崩溃了,那么所有数据都将丢失,这说明RAMJobStore与触发器和任务设置中的”non-volatility”并不匹配。在某些应用中这是可以接受的甚至是要求的行为,但对于另一些来说这可能是灾难性的。

要使用RAMJobStore (假设你使用的是StdSchedulerFactory)只需要在配置文件中将JobStore class属性值设置为org.quartz.simpl.RAMJobStore即可。配置Quartz使用RAMJobStore的方法如下:
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

JDBCJobStore

JDBCJobStore 的作用也是显而易见的–它将所有数据通过JDBC保存在数据库中。正是如此它的配置要比RAMJobStore更困难一些,也没有它快。然而性能的缺陷也不是特别的糟糕,尤其是你的数据表使用主键作为索引。在相当现代且网络环境相当好(在调度器与数据库之间)的机器上,获取并更新一个激活的触发器所需要的时间通常小于 10毫秒。

JDBCJobStore 几乎兼容所有数据区,它被广泛使用到Oracle, PostgreSQL, MySQL, MS SQLServer, HSQLDB, 以及DB2上。使用JDBCJobStore时,你必须首先为quartz创建一个表格,你在Quartz 分发包的 “docs/dbTables” 目录下能够找到创建表格的SQL脚本。如果还没有针对你的数据库的脚本,那就随便看一个,并以任何方式进行修改。有一件事情要注意,在这些脚本中所有表格都是以QRTZ开头(比如”QRTZ_TRIGGERS”和 “QRTZ_JOB_DETAIL”)。这个前缀可以是任何值,你只需要告诉JDBCJobStore 即可(在配置文件中)。在同一个数据库中为不同的调度器实例使用不同的数据表时,使用不同的前缀是有用处的。

一旦建立了数据表,在配置和使用JDBCJobStore 之前你还需要做一个重要的决定。你需要决定你的应用使用什么类型的事务(transaction)。如果你不需要将你的调度指令(例如增加或删除触发器)绑定到其他的事务,那你可以使用JobStoreTX (这是最常用的选项)让Quartz 来管理事务。

如果你要让Quartz 与其它事务(比如与一个J2EE应用服务器)一起工作,那你应当使用JobStoreCMT ,Quartz 将使用APP服务器容器来管理这些事务。

最后一部分是设置DataSource ,JDBCJobStore 要从它这里获取到你数据库的连接。DataSources 在属性文件中定义,且有一些不同的定义方式。一种是让Quartz 自己来创建和管理DataSource ,提供所有到数据库的连接信息。还有一种是让Quartz 使用自身所在的应用服务器提供的DataSource ,向JDBC提供DataSource的JNDI 名称。具体的配置请参阅 “docs/config”文件夹中的配置文件。

要使用JDBCJobStore (假设你使用的是StdSchedulerFactory)你首先要将配置文件中的JobStore class 设置为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语句)来做这些工作。如果没有为你的数据库指定其它的Delegate你可以尝试这个–我们仅为看起来使用StdJDBCDelegate 会有问题的数据库提供了特定的Delegate。其它的Delegate位于 “org.quartz.impl.jdbcjobstore”包或其子包内,包括: DB2v6Delegate (for DB2 version 6 及以前), HSQLDBDelegate (for HSQLDB),MSSQLDelegate (for Microsoft SQLServer), PostgreSQLDelegate (for PostgreSQL), WeblogicDelegate (for using JDBC drivers made by Weblogic), OracleDelegate (for using Oracle),等等。

当选择了Delegate以后,你需要在配置文件中设置它的名称。
配置JDBCJobStore 使用DriverDelegate
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate

接下来你需要通知JobStore 你使用的数据表前缀是什么。
配置JDBCJobStore 使用的数据表前缀:
org.quartz.jobStore.tablePrefix = QRTZ_

最后,你需要设置JobStore使用的DataSource 。你填写的DataSource 必须在配置文件中有定义,因此我们指定Quartz 使用”myDS”(已经在配置文件的某处定义)。
配置JDBCJobStore 使用的DataSource 名称:
org.quartz.jobStore.dataSource = myDS

如果你的调度器很繁忙(比如在运行任务数量几乎总是等于线程池的容量)那么你可能需要将DataSource 的连接数设置为线程池的容量+2。

“org.quartz.jobStore.useProperties”配置参数默认为false,为了使JDBCJobStore 的JobDataMaps的所有值均为String类型,而不是存储为更复杂的可序列化对象,该值可以设置为true。长远来看这样更加安全,因为你避免了将非字符串类序列化为BLOB类是的类版本问题。

TerracottaJobStore

TerracottaJobStore 提供了一种不使用数据库情况下的量化和鲁棒性手段。这意味着你的数据库可以与Quartz的负载无关,并将其节省下来的资源用于你的其它应用。

TerracottaJobStore 可被集群化或非集群化使用,在这些场景下都会为你的任务数据提供一个存储介质,它在你的应用重启期间也具有持久性,因为数据存储在Terracotta 服务器。它的性能比使用基于JDBCJobStore 的数据库要好得多(大约一个数量级),但仍比RAMJobStore慢得多。

要使用TerracottaJobStore (假设你使用的是StdSchedulerFactory),只需要将配置文件中的JobStore class 设置为org.terracotta.quartz.TerracottaJobStore,并额外添加一行配置指定Terracotta 服务器的位置。

配置Quartz 使用TerracottaJobStore的方法:
org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore
org.quartz.jobStore.tcConfigUrl = localhost:9510

调度器监听器

调度器监听器(SchedulerListeners)与TriggerListeners 和JobListeners非常类似,除了它是接收来自于调度器自己的事件–不一定与某个特定的trigger或job相关。

Scheduler相关的事件包括:trigger/job的添加与删除、Scheduler内部的严重错误、调度器被关闭的通知,等等。

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。基本上任何实现了SchedulerListener 的类都可以作为调度器监听器。

添加SchedulerListener的方法如下:
scheduler.getListenerManager().addSchedulerListener(mySchedListener);

删除SchedulerListener的方法如下:
scheduler.getListenerManager().removeSchedulerListener(mySchedListener);

资源使用以及调度工厂

Quartz 的结构是模块化的,因此要让它运行起来的话各个组件之间必须很好地结合起来。在运行Quartz 之前必须先进行配置的主要组件有:

  • 线程池
  • JobStore
  • DataSources (如果需要的话)
  • 调度器自身

线程池为Quartz 提供了一个线程集以便在执行任务时使用。线程池中线程越多,可以并发执行的任务数越多。然而,过多的线程可能会是你的系统变慢。许多Quartz 用户发现5个左右的线程就足够了–因为在任意时刻的任务数都不会超过100,而且通常它们都不会要求要同时执行,而且这些任务的生命期都很短(很快就结束)。另一些用户发现他们需要10,15,50或者甚至100个线程,因为他们在任意时刻都有成千上万的触发器和大量的调度器–它们都平均至少有10-100个任务需要在任一时刻运行。为你的调度器池找到合适容量完全取决于你如何使用它。并没有严格的规律,除了保持线程数量尽可能的少(为了节约你机器的资源)–然而你要确保足以按时激活你的任务。注意,如果一个触发器达到了激活时间而没有可用线程,Quartz 将阻塞(暂停)直到有一个可用的线程,然后任务将被执行–在约定时间的数个毫秒之后。这甚至会导致线程的激活失败–如果在配置的”misfire threshold”期间都没有可用的线程。

在org.quartz.spi 包中定义了一个线程池接口,你可以根据你的洗好创建一个线程池应用。Quartz 包含一个简单(但非常令人满意的)线程池叫做org.quartz.simpl.SimpleThreadPool。这个线程池只是维护一个固定的线程集合–从不会增多也不会减少。但它非常健壮并且得到了非常好的测试–几乎所有使用Quartz 的用户都会使用它。

JobStores 和DataSources 在第9章已经讨论过。这里需要提醒的是一个事实,所有的JobStores 都实现了org.quartz.spi.JobStore接口–如果某个打包好的JobStores 不满足你的需要,你可以自己创建一个。

最后,你需要创建你自己的调度器实例。调度器自身需要一个名称,告知它的RMI参数并把JobStore 和线程池的实例传递给它。RMI 参数包含了调度器是否应该将自己创建为一个RMI的服务对象(使它对远程连接可用),使用哪个主机和端口等等。StdSchedulerFactory 也可以生产调度器实例,它实际上是到远端进程创建的调度器的代理。

StdSchedulerFactory

StdSchedulerFactory是一个org.quartz.SchedulerFactory接口的实现。它使用一系列的属性 (java.util.Properties)来创建和初始化Quartz 调度器。属性通常存储在文件中并从它加载,也可以由你的应用程序产生并直接传递到factory。直接调用factory 的getScheduler() 方法将产生调度器,初始化它(以及它的线程池、JobStore 和DataSources),然后返回它的公共接口的句柄。

在Quartz 分发包的”docs/config”目录下有一些示例配置(包含属性的描述)。你可以Quartz 文档的”Reference”章节的配置手册中寻找完整的文档。

DirectSchedulerFactory

DirectSchedulerFactory是另一个SchedulerFactory 实现。它对于那些想要以更程序化的方式来创建调度器实例的人来说是有用的。它一般不建议使用,因为:(1)它要求用户非常明白他在做什么(2)它不允许声明式的配置–换句话说,你需要对调度器的设置进行硬编码。

本文参考:
https://www.cnblogs.com/drift-ice/p/3817269.html
https://www.cnblogs.com/pzy4447/p/5203308.html
更多参考资料可以查看官方文档,传送门:
http://www.quartz-scheduler.org/documentation

原创粉丝点击