一文学习Quartz常用 API 的使用

任务调度服务Quartz简介与入门一篇我们简单的介绍了Quartz是什么和Quartz的使用,本篇我们介绍Quartz常用的API,这些API将贯穿Quartz的整个使用的过程,首先我们介绍SchedulerFactory的使用。

官网这样解释ScheduleFactory,Provides a mechanism for obtaining client-usable handles to Scheduler instances.在使用调度器(Scheduler)前,调度器需要被实例化。你可以使用SchedulerFactory创建调度器。SchedulerFactory有两个实现类,StdSchedulerFactory和DirectSchedulerFactory下面分别讲解。一些直接使用Factory实例进行实例化,还有一些Quartz用户通过使用JNDI中的Factory实例。ScheduleFactory类关系图如下所示:

DirectSchedulerFactory是SchedulerFactory的单例实现。DirectSchedulerFactory是protected模式,我们无法通过关键字new 创建一个新的实例。DirectSchedulerFactory提供了getInstance()方法获取实例。

 public void createVolatileScheduler(int maxThreads)用于创建一个跟数据库没有任何关系的调度器(Scheduler)它使用RAMJobStore存储调度器信息。并且调度器名称为SimpleQuartzScheduler,实例Id为SIMPLE_NON_CLUSTERED。代码示例如下:

public class DirectVolatileSchedulerTest {
    public static void main(String[] args) throws SchedulerException {
        DirectSchedulerFactory.getInstance().createVolatileScheduler(10);
	Scheduler scheduler = DirectSchedulerFactory.getInstance().getScheduler();
	scheduler.start();
	System.out.println(scheduler);
        cheduler.shutdown();
    }
}

public void createScheduler(ThreadPool threadPool, JobStore jobStore)createScheduler是一个重载方法,我们可以使用该方法创建适合的调度器。具体的方法可以查看官方API文档(http://www.quartz-scheduler.org/api/2.4.0-SNAPSHOT/index.html)。

public class DirectCreateSchedulerTest {
    public static void main(String[] args) throws SchedulerException {
        DirectSchedulerFactory diSchedulerFactory = DirectSchedulerFactory.getInstance();
        ThreadPool threadPool = new SimpleThreadPool(10,5);
	JobStore jobStore = new RAMJobStore();
	diSchedulerFactory.createScheduler("My Quartz Scheduler", "My Instance", threadPool, jobStore); 
	Scheduler scheduler = diSchedulerFactory.getScheduler("My Quartz Scheduler");
	scheduler.start();
	System.out.println(scheduler);
	scheduler.shutdown();
    }
}

上面的例子中我们使用了RAMJobStore,我们也可以使用JDBCJobStore作为JobStore存储调度器信息。后续Job Stores会更详细的讲解。

DBConnectionManager.getInstance().addConnectionProvider("someDatasource", new JNDIConnectionProvider("someDatasourceJNDIName")); 
JobStoreTX jdbcJobStore = new JobStoreTX(); 
jdbcJobStore.setDataSource("someDatasource"); 
jdbcJobStore.setPostgresStyleBlobs(true); 
jdbcJobStore.setTablePrefix("QRTZ_"); 
jdbcJobStore.setInstanceId("My Instance");

public void createRemoteScheduler(String rmiHost, int rmiPort)创建一个远程调度器的代理,此调度器可通过directschedulerFactory.getScheduler()获取。DirectSchedulerFactory提供了getScheduler方法和getAllSchedulers获取调度器。public Scheduler getScheduler()方法获取调度器名称为SimpleQuartzScheduler的调度器;public Scheduler getScheduler(String schedName)方法获取调度器名称为scheName的调度器; public Collection<Scheduler> getAllSchedulers()获取所有调度器。

StdSchedulerFactory是SchedulerFactory的另一个实现。它通过Properties文件创建一个QuartzScheduler实例。你可以通过构造方法创建一个SchedulerFactory实例。默认情况下会自动加载在当前工作目录的名为“quartz.properties”的文件,如果加载失败,则加载加载位于org/quartz包中的文件。要使用这些默认值以外的文件,必须定义系统属性“org.quartz.properties”指向所需的文件。当配置quartz.properties至少需要配置org.quartz.threadPool.threadCount这一项,值为大于0的整数,否则会抛出异常。quartz.properties文件的内容会在配置部分详解。如下是StdSchedulerFactory的构造方法,可以通过构造方法创建一个SchedulerFactory实例:

/创建一个未初始化的StdSchedulerFactory。再调用getScheduler()方法时,会判断是否初始化,如果
//未初始化则会先初始化。初始化所使用的的配置文件如上面所述。
public StdSchedulerFactory() {
}
//使用自己创建的Properties实例而非配置文件创建StdSchedulerFactory。
public StdSchedulerFactory(Properties props) throws SchedulerException {
    initialize(props);
}
//传入文件名称,使用指定的文件创建StdSchedulerFactory
public StdSchedulerFactory(String fileName) throws SchedulerException {
    initialize(fileName);
}

StdSchedulerFactory会在调用getScheduler()时创建调度器;你也可以通过在调用GetScheduler()之前调用initialize(xx)方法来初始化。

public Scheduler getScheduler() throws SchedulerException {
    //如果配置为空,初始化配置
    if (cfg == null) {
        initialize();
    }
    //调度器资源中获取调度器
    SchedulerRepository schedRep = SchedulerRepository.getInstance();
    Scheduler sched = schedRep.lookup(getSchedulerName());
    //如果获取到调度器
    if (sched != null) {
        //如果调度器已关闭
        if (sched.isShutdown()) {
            //移除
            schedRep.remove(getSchedulerName());
        } else {
            //否则,返回
            return sched;
        }
    }
    //否则,初始化调度器
    sched = instantiate();
    return sched;
}

DirectSchedulerFactory是一个SchedulerFactory的单例实现。对于希望以更加程序化的方式创建其Scheduler实例的用户是有用的。但是通常不鼓励使用它的用法,因为这种方式最终会以硬编码的方式将配置写到代码里,当修改的时候只能去修改代码,这种操作无疑是繁杂的。StdSchedulerFactory是org.quartz.SchedulerFactory接口的另一个实现。它使用一组属性(java.util.Properties)来创建和初始化Quartz Scheduler。属性通常默认从quartz.properties文件中加载,但也可以由程序创建并直接传递到StdSchedulerFactory,也可以指定properties文件进行加载。调用工厂中的getScheduler()将生成调度程序,并初始化它(和它的ThreadPool,JobStore和DataSources)并返回一个实例到它的公共接口。

与调度程序交互的主要API是Scheduler,从上面可知道我们使用SchedulerFactory创建Scheduler实例。它的生命周期是从SchedulerFactory创建它时开始,到Scheduler调用shutdown()方法时结束;Scheduler被创建后,可以增加、删除和列举Job和Trigger,以及执行其它与调度相关的操作(如暂停Trigger)。但是,Scheduler只有在调用start()方法后,才会真正地触发trigger(即执行job)。注意:scheduler被停止后,除非重新实例化,否则不能重新启动;只有当scheduler启动后,即使处于暂停状态也不行,trigger才会被触发(job才会被执行)。Scheduler类关系图如下所示:

调度程序负责维护JobDetail和Trigger,它们一旦被注册到Scheduler,调度程序就会负责执行与其关联的作业。通过下面的方法将作业和触发器注册到Scheduler。Date scheduleJob(JobDetail jobDetail, Trigger trigger) throws SchedulerException;

在上面提到的JobDetail 需要定义一个job任务,该任务需要实现Job接口,该接口很简单很简单,里面一共一个方法,实现该接口的方法一般被称为作业类,用于完成作业:

package org.quartz;
public interface Job {
    void execute(JobExecutionContext context) throws JobExecutionException;
}

我们的作业类需要定义自己的类实现该接口,并且实现execute方法。这个类仅仅表明该job需要完成什么类型的任务。Quartz还需要知道该Job实例所包含的属性;在上面我们我们定义了调度器和作业任务,但是我们并不直接将作业实例化交给调度器去执行,而是通过JobDetail实例,并且给作业类添加额外的实例信息。JobDetail实例一般是通过JobBuilder创建。

JobDetail job = JobBuilder.newJob(HelloJob.class).withIdentity("myJob", "group1").build();
  1.   withDescription方法用于描述这个作业的信息,作业类用来做什么。
  2.   withIdentity方法,默认情况下作业类会分配一个默认的组和一个默认的名称用于标识这个作业类。我们可以使用withIdentity方法为作业类指定组和名称。它是一个重载方法,可以方便的为作业类指定组或者名称。
  3.   usingJobData或者setJobData 在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。那么如何给job实例。增加属性或配置呢?如何在job的多次执行中,跟踪job的状态呢?答案就是:JobDataMap,JobDetail对象的一部分。可以使用usingJobData或者setJobData方法设置。usingJobData也是一个重载方法。很方便的设置属性值。obDataMap的值是不可变的,假如想要每次执行任务时改变里面的值则可以在作业类添加注解:@PersistJobDataAfterExecution@DisallowConcurrentExecution告诉Quartz不要并发地执行同一个job定义(这里指特定 的job类)的多个实例。
  4.   requestRecovery方法,当一个Sheduler实例在执行某个Job时失败了,有可能由另一正常工作的Scheduler实例接过这个Job重新运行。要实现这种行为,配置给JobDetail对象的Job可恢复属性必须设置为true(job.setRequestsRecovery(true))。如果可恢复属性被设置为false(默认为false),当某个Scheduler在运行该job失败时,它将不会重新运行;而是由另一个Scheduler实例在下一次触发时间触发。Scheduler实例出现故障后多快能被侦测到取决于每个Scheduler的检入间隔(org.quartz.jobStore.clusterCheckinInterval)
  5.   storeDurably方法 默认情况下,当 Job 没有对应的 Trigger 时,Job 是不能直接被加入调度器中的,或者在 Trigger 过期之后,没有关联 Trigger 的 Job 也会被删除。我们可以通过 JobBuilder 的 StoreDurably 使 Job 独立存储于调度器中。

前面我们还提到了Trigger,Trigger即触发器,Trigger用于定义作业执行的条件,即什么时候触发这个调度器,开始执行作业。Trigger实例是通过TriggerBuilder类创建的,可以给Trigger添加额外的实例信息。

Trigger trigger = TriggerBuilder.newTrigger().withIdentity("myTrigger", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever()).build();
  1. withDescription方法 用于描述这个触发器的的信息,触发器用来做什么。
  2. withIdentity方法 默认情况下触发器会分配一个默认的组和一个默认的名称用于标识这个触发器我们可以使用withIdentity方法为触发器指定组和名称。它是一个重载方法,可以方便的为触发器指定组或者名称。
  3. startAt方法 设置触发器应开始的时间-触发器可以此时不启动-取决于为配置的计划。Trigger(在withSchedule方法中配置)。不过,在此之前触发器不会触发,不管触发器的时间表如何。
  4. startNow方法 将触发器应开始的时间设置为当前时刻-触发器此时可能触发,也可能不触发-取决于为触发器配置的计划(在withSchedule方法中配置)。默认为startNow
  5. endAt方法 设置触发器不再触发的时间-即使它计划还有剩余的重复。
  6. withPriority 如果你的trigger很多(或者Quartz线程池的工作线程太少),Quartz可能没有足够的资源同时触发所有的trigger;这种情况下,你可能希望控制哪些trigger优先使用.Quartz的工作线程,要达到该目的,可以在trigger上设置priority属性。比如,你有N个trigger需要同时触发,但只有Z个工作线程,优先级最高的Z个trigger会被首先触发。如果没有为trigger设置优先级,trigger使用默认优先级,值为5;priority属性的值可以是任意整数,正数、负数都可以。注意:只有同时触发的trigger之间才会比较优先级。10:59触发的trigger总是在11:00触发的trigger之前执行。注意:如果trigger是可恢复的,在恢复后再调度时,优先级与原trigger是一样的。
  7. usingJobData方法 给触发器添加额外的数据,用法和Job中用法相似
  8. forJob方法 给Job设置一个应该由哪个触发器触发的标识。通过制定的JobKey指定。
  9. modifiedByCalendar方法设置Calendar触发器触发。
  10. withSchedule方法设置将用于定义触发器的计划。该定义知识触发器的具体类型,以及不同的触发器该怎么执行。如下图为触发器的类型的类关系图

 我们是通过ScheduleBuilder来创建不同的触发器的,如下图为不同的Builder的类关系图。

ScheduleBuilder<SimpleTrigger> schedBuilder =SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever();
Trigger trigger = newTrigger().withIdentity("start_trigger", "start_group").startNow()
.withSchedule(schedBuilder).build();

不同的Trigger下有很多不同的方式去创建自己需要的触发器,具体的方法可以查看Quartz的API文档。我们只需要知道有这些触发器,以及触发器什么时候用哪个比较方便即可。

Quartz提供了监听器接口,用于根据调度程序发生的事件执行操作。Quartz提供了三个与调度程序相关的监听器。TriggerListener,JobListener和SchedulerListener。

TriggerListeners接收到与触发器(trigger)相关的事件.与触发相关的事件包括:触发器触发,触发失灵,并触发完成(触发器关闭的作业完成)。如下是该接口的方法与解释。

  1. String getName();  获取TriggerListener的名称
  2. void triggerFired(Trigger trigger, JobExecutionContext context);Trigger被激发时被调用。与它关联的Job即将被运行,它在VoteJobExecution(...)方法被调用之前被调用。
  3. boolean vetoJobExecution(Trigger trigger, JobExecutionContext context); Trigger被激发时被调用,与它关联的Job即将被运行,如果该方法的实现返回True那么Job将不会被调用。它在triggerFired(...)之后被调用
  4. void triggerMisfired(Trigger trigger); 当触发器错过被激发时执行。比如当前时间有很多触发器都需要执行,但是线程池中有效线程都在工作,那么触发器可能会超时,错过这一轮触发。应该考虑这方法会花费多少时间执行,因为它会影响所有没有被激发的触发器。一旦你有大量应该被激发的触发器没有被激发,这个方法能帮助你解决很多问题。
  5. void triggerComplete(Trigger trigger, JobExecutionContext context,int triggerInstructionCode);  任务完成时出发。

JobListeners接收到与作业(Job)相关的事件.与作业相关的事件包括:job即将执行的通知,以及job完成执行时的通知。如下是该接口的方法与解释。

  1. String getName(); 获取JobListener的名称
  2. void jobToBeExecuted(JobExecutionContext context);Job将被执行时执行这个方法,这个方法不会被执行,当这个Job被TriggerListener否决的时候,
  3. void jobExecutionVetoed(JobExecutionContext context); 当Job被TriggerListener否决的时候被调用
  4. VoidjobWasExecuted(JobExecutionContext context, JobExecutionException jobException);Job已经被执行的时候被调用

SchedulerListeners非常类似于TriggerListeners和JobListeners,除了它们在Scheduler本身中接收到事件的通知 - 不一定与特定触发器(trigger)或job相关的事件。与计划程序相关的事件包括:添加job/触发器,删除job/触发器,调度程序中的严重错误,关闭调度程序的通知等。

  1. void jobScheduled(Trigger trigger);Job被调度的时候执行调用
  2. void jobUnscheduled(TriggerKey triggerKey);Job被取消调度的时候调用
  3. void triggerFinalized(Trigger trigger);Trigger变成永远不会被激发状态的时候调用
  4. void triggerPaused(TriggerKey triggerKey);暂停触发器的时候调用
  5. void triggersPaused(String triggerGroup);暂停触发器的时候调用
  6. void triggerResumed(TriggerKey triggerKey);恢复触发器的时候调用
  7. void triggersResumed(String triggerGroup);恢复触发器的时候调用
  8. void jobAdded(JobDetail jobDetail);添加Job的时候调用
  9. void jobDeleted(JobKey jobKey);删除Job的时候调用
  10. void jobPaused(JobKey jobKey);暂停Job的时候调用
  11. void jobsPaused(String jobGroup);暂停Job的时候调用
  12. void jobResumed(JobKey jobKey);恢复Job的时候调用
  13. void jobsResumed(String jobGroup);恢复Job的时候调用
  14. void schedulerError(String msg, SchedulerException cause);当有调度器内部有严重错误发生的时候被调用。
  15. void schedulerInStandbyMode();调度器切换到待机模式的时候调用
  16. void schedulerStarted();调度器已经开始的时候调用
  17. void schedulerStarting();调度器正在开始的时候调用
  18. void schedulerShutdown();调度器已经关闭的时候调用
  19. void schedulerShuttingdown();调度器开始关闭的时候调用
  20. void schedulingDataCleared();当所有Job,Trigger,Calendar被删除的时候调用

要创建自己的Listener我们只需要创建一个实现org.quartz.TriggerListener或org.quartz.JobListener或import org.quartz.SchedulerListener接口的对象。listener在运行时会向调度程序注册。为了方便起见,类也可以扩展JobListenerSupport类或TriggerListenerSupport类或者SchedulerListenerSupport,并且只需覆盖您感兴趣的事件。他们是分别实现了上面三个接口的抽象类所有监听器都注册到调度程序的ListenerManager进行管理。

  1. 添加监听器:sched.getListenerManager().addJobListener(jobListener); sched.getListenerManager().addTriggerListener(triggerListener);sched. getListenerManager().addSchedulerListener(schedulerListener)。
  2. 移除监听器sched.getListenerManager().removeJobListener(jobListener); sched.getListenerManager().removeTriggerListener(triggerListener); sched.getListenerManager().removeedulerListener(schedulerListener);

Quartz的大多数用户并不使用Listeners,但是当应用程序需求创建需要事件通知时不需要Job本身就必须明确地通知应用程序,这些用户就很方便。

JobStore负责跟踪您提供给调度程序的所有“工作数据”:jobs,triggers等,为你的Quartz调度程序实例选择适当的JobStore是重要的一步。Quartz支持数据内存化和持久化。下面的例子都是以硬编码的形式给出,一般我们会使用配置文件,而不是直接硬编码指定JobStore.

 RAMJobStore是使用最简单的JobStore,它也是性能最高的。顾名思义,它将所有的数据保存在RAM中缺点是当您的应用程序结束(或崩溃)时,所有调度信息都将丢失 - 这意味着RAMJobStore无法履行作业和triggers上的“非易失性”设置。在没有进行任何配置时,Quartz默认使用RAMJobStore。也就是说我们上面的例子,都是使用RAMJobStore。

JobStore jobStore = new RAMJobStore();
diSchedulerFactory.createScheduler("My Quartz Scheduler", "My Instance", threadPool, jobStore);

Quartz也可以通过JDBC将其所有数据保存在数据库中。配置比RAMJobStore要复杂一点,而且也不是那么快。但其速度与其优点相比,已经是不足为道了。JDBCJobStore几乎与任何数据库一起使用。您可以在Quartz发行版的“docs / dbTables”目录中找到表创建SQL脚本。

创建表后,在配置和启动JDBCJobStore之前,您还有一个重要的决定。您需要确定应用程序需要哪种类型的事务。如果您不需要将调度命令(例如添加和删除triggers)绑定到其他事务,那么可以通过使用JobStoreTX作为JobStore 来管理事务(这是最常见的选择)。如果您需要Quartz与其他事务(即J2EE应用程序服务器)一起工作,那么您应该使用JobStoreCMT - 在这种情况下,Quartz将让应用程序服务器容器管理事务。

最后一个难题是设置一个DataSource,JDBCJobStore可以从中获取与数据库的连接。DataSources在Quartz属性中使用几种不同的方法之一进行定义。一种方法是让Quartz创建和管理DataSource本身 - 通过提供数据库的所有连接信息。另一种方法是让Quartz使用由Quartz正在运行的应用程序服务器管理的DataSource,通过提供JDBCJobStore DataSource的JNDI名称。

JobStoreTX jobStoreTX = new JobStoreTX();
jobStoreTX.setTablePrefix("QRTZ_");
jobStoreTX.setDataSource("pharos_datasource");

TerracottaJobStore提供了一种缩放和鲁棒性的手段,而不使用数据库。这意味着您的数据库可以免受Quartz的负载,可以将其所有资源保存为应用程序的其余部分。TerracottaJobStore可以运行群集或非群集,并且在任一情况下,为应用程序重新启动之间持续的作业数据提供存储介质,因为数据存储在Terracotta服务器中。它的性能比通过JDBCJobStore使用数据库要好得多(约一个数量级更好),但比RAMJobStore要慢。

TerracottaJobStore terracottaJobStore = new TerracottaJobStore();
terracottaJobStore.setTcConfigUrl("localhost:9050");
diSchedulerFactory.createScheduler("My Quartz Scheduler", "My Instance", threadPool, terracottaJobStore);

猜你喜欢

转载自blog.csdn.net/wk19920726/article/details/108844072