Quartz学习

Posted 汤高

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Quartz学习相关的知识,希望对你有一定的参考价值。

Quartz学习

介绍Quartz

Quartz是一个开源的任务调度系统,它能用来调度很多任务的执行。

运行环境

  • Quartz 能嵌入在其他应用程序里运行。
  • Quartz 能在一个应用服务器里被实例化(servlet容器), 并且参与XA事务
  • Quartz能独立运行(通过JVM,或者通过RMI
  • Quartz能被集群实例化

任务调度

当一个指定给任务的触发器发生时,任务就被调度执行. 触发器能被创建为:

  • 一天的某个时间(精确到毫秒级)
  • 一周的某些天
  • 一个月的某些天
  • 一年的某些天
  • 不在一个Calendar列出的某些天 (例如工作节假日)
  • 在一个指定的次数重复
  • 重复到一个指定的时间/日期
  • 无限重复
  • 在一个间隔内重复

能够给任务指定名称和组名.触发器也能够指定名称和组名,这样可以很好的在调度器里组织起来.一个加入到调度器里的任务可以被多个触发器注册。在J2EE环境里,任务能作为一个分布式(XA)事务的一部分来执行。

任务执行

  • 任务能够是任何实现Job接口的Java类。
  • 任务类能够被Quartz实例化,或者被你的应用框架。
  • 当一个触发器触发时,调度器会通知实例化了JobListener TriggerListener 接口的0个或者多个Java对象(监听器可以是简单的Java对象, EJBs, JMS发布者等). 在任务执行后,这些监听器也会被通知。
  • 当任务完成时,他们会返回一个JobCompletionCode ,这个代码告诉调度器任务执行成功或者失败.这个代码 也会指示调度器做一些动作-例如立即再次执行任务。

任务持久化

  • Quartz的设计包含JobStore接口,这个接口能被实现来为任务的存储提供不同的机制。
  • 应用JDBCJobStore, 所有被配置成“稳定”的任务和触发器能通过JDBC存储在关系数据库里。
  • 应用RAMJobStore, 所有任务和触发器能被存储在RAM里因此不必在程序重起之间保存-一个好处就是不必使用数据库。

事务

  • 使用JobStoreCMTJDBCJobStore的子类),Quartz 能参与JTA事务。
  • Quartz 能管理JTA事务(开始和提交)在执行任务之间,这样,任务做的事就可以发生在JTA事务里。

集群

  • Fail-over.
  • Load balancing.

监听器和插件

  • 通过实现一个或多个监听接口,应用程序能捕捉调度事件来监控或控制任务/触发器的行为。
  • 插件机制可以给Quartz增加功能,例如保持任务执行的历史记录,或从一个定义好的文件里加载任务和触发器。
  • Quartz 装配了很多插件和监听器。

1.使用Quartz

在我们用调度器之前,调度器需要实例化。我们用SchedulerFactory 来实例它。一旦调度器被实例,我们就可以启动它,置它为stand-by模式,最后关闭它。注意:一旦一个调度器被关闭了,如果我们不重新实例化它,它就不可能被再次启动。直到调度器启动了或者当调度器处于暂停状态,触发器才能够触发。下面有个简单的例子:

SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();

Scheduler sched = schedFact.getScheduler();

sched.start();

JobDetail jobDetail = new JobDetail("myJob",

null,

DumbJob.class);

Trigger trigger = TriggerUtils.makeHourlyTrigger(); // 每个小时触发

trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date())); // 在下个小时开始

trigger.setName("myTrigger");

sched.scheduleJob(jobDetail, trigger);

就象你看到的,使用Quartz是很简单的。在下一节我们介绍Jobs和Triggers。

2.Jobs  Triggers

就象以前提到的,一个实现了Job接口的Java类就能够被调度器执行。接口如下:

package org.quartz;

public interface Job

public void execute(JobExecutionContext context) throws JobExecutionException;

很简的,当Job的trigger触发时,Job的execute(..)方法就会被调度器调用。被传递到这个方法里来的JobExecutionContext对象提供了带有job运行时的信息:执行它的调度器句柄、触发它的触发器句柄、job的JobDetail对象和一些其他的项。

JobDetail对象是Job在被加到调度器里时所创建的,它包含有很多的Job属性设置,和JobDataMap一样,可以用来存储job实例时的一些状态信息。

Trigger对象是用来触发执行Job的。当调度一个job时,我们实例一个触发器然后调整它的属性来满足job执行的条件。触发器也有一个和它相关的JobDataMap,它是用来给被触发器触发的job传参数的。Quartz有一些不同的触发器类型,不过,用得最多的是SimpleTrigger和CronTrigger。

如果我们需要在给定时刻执行一次job或者在给定时刻触发job随后间断一定时间不停的执行的话,SimpleTrigger是个简单的解决办法;如果我们想基于类似日历调度的触发job的话,比如说,在每个星期五的中午或者在每个月第10天的10:15触发job时,CronTrigger是很有用的。

为什么用jobs和triggers呢?很多任务调度器并没有任务和触发器的概念,一些任务调度器简单定义一个“job”为在一个执行时间伴随一些小任务标示,其他的更像Quartz里job和trigger对象的联合体。在开发Quartz时,开发者们决定,在调度时间表和在这上面运行的工作应该分开。这是很有用的。

例如,job能够独立于触发器被创建和储存在任务调度器里,并且,很多的触发器能够与同一个job关联起来。这个松耦合的另一个好处就是在与jobs关联的触发器终止后,我们能够再次配置保留在调度器里的jobs,这样的话,我们能够再次调度这些jobs而不需要重新定义他们。我们也可以在不重新定义一个关联到job的触发器的情况下,修改或替代它。

当Jobs和triggers被注册到Quartz的调度器里时,他们就有了唯一标示符。他们也可以被放到“groups”里,Groups是用来组织分类jobs和triggers的,以便今后的维护。在一个组里的job和trigger的名字必须是唯一的,换句话说,一个job和trigger的全名为他们的名字加上组名。如果把组名置为”null”,系统会自动给它置为Scheduler.DEFAULT_GROUP

现在,我们大概有了一些jobs和triggers的理解,随后2节我们将根多的了解它们。

3.更多关于Jobs & JobDetails

Jobs很容易实现,这儿有更多我们需要理解的东西:jobs的本质,job接口的execute(..)方法,关于JobDetails。

当我们实现的一个class是真正的”job”时,Quartz需要知道各种job有的属性,这是通过JobDetail类做到的。在没用JobDetail之前,JobDetail的功能的实现是通过在每个job的实现类上加上所有的现在JobDetail的get方法来实现的。这就在每个job类上强加了一些实现一样功能的代码,就显得每个job类很笨重,于是,Quartz开发者们就创造了JobDetail类。

现在,我们来讨论一下在Quartz里job的本质和job实例的生命周期。首先我们来看看第一节的代码片段: 

JobDetail jobDetail = new JobDetail("myJob",      // job 名称

                     sched.DEFAULT_GROUP, // job组名(可以写'null'来用default group)

                     DumbJob.class);         //要执行的java

Trigger trigger = TriggerUtils.makeDailyTrigger(8, 30);

trigger.setStartTime(new Date());

trigger.setName("myTrigger");

sched.scheduleJob(jobDetail, trigger);

现在我们定义“DumbJob”类:

public class DumbJob implements Job

    public DumbJob()

    

    public void execute(JobExecutionContext context)

      throws JobExecutionException

    

      System.err.println("DumbJob is executing.");

    

可以看到我们给调度器一个JobDetail实例,并且,它通过job的类代码引用这个job来执行。每次调度器执行job时,它会在调用job的execute(..)方法之前创建一个他的实例。这就带来了两个事实:一、job必须有一个不带参数的构造器,二、在job类里定义数据成员并没有意义,因为在每次job执行的时候他们的值会被覆盖掉。

你可能现在想要问“我怎样给一个job实例提供属性/配置?”和“在几次执行间我怎样能跟踪job的状态?”这些问题的答案是一样的:用JobDataMap- JobDetail对象的一部分。

JobDataMap

JobDataMap能够支持任何序列化的对象,当job执行时,这些对象能够在job实例中可用。JobDataMap实现了Java Map接口,它有一些附加的方法,这些方法用来储存和跟踪简单类型的数据。

如下代码可以很快地给job增加JobDataMap:

jobDetail.getJobDataMap().put("jobSays", "Hello World!");

jobDetail.getJobDataMap().put("myFloatValue", 3.141f);

jobDetail.getJobDataMap().put("myStateData", new ArrayList());

在job执行时,我们可以在job里通过如下代码得到JobDataMap:

public class DumbJob implements Job

    public DumbJob()

    

    public void execute(JobExecutionContext context)

      throws JobExecutionException

    

      String instName = context.getJobDetail().getName();

      String instGroup = context.getJobDetail().getGroup();

      JobDataMap dataMap = context.getJobDetail().getJobDataMap();

      String jobSays = dataMap.getString("jobSays");

      float myFloatValue = dataMap.getFloat("myFloatValue");

      ArrayList state = (ArrayList)dataMap.get("myStateData");

      state.add(new Date());

      System.err.println("Instance " + instName + " of DumbJob says: " + jobSays);

    

  

如果用一个持久JobStore(在指南JobStore章节讨论),我们就应该注意在JobDataMap里放些什么,因为在它里面的对象将会被序列化,并且这些对象会因此产生一些class-versioning问题。明显的,标准Java类型应该是很安全的,但是,任何时候某人改变了一个你已经序列化的实例的类的定义时,我们就要注意不能够破坏兼容性了。在这个方面的进一步信息可以在Java Developer Connection Tech Tip: Serialization In The Real World里找到。我们能把JDBC-JobStore和JobDataMap放到一个模式里,在那里,只有简单类型和String型能被储存在Map里,从而消去任何以后的序列化问题。

Stateful vs. Non-Stateful Jobs

触发器也有与它们关联的JobDataMaps。假设我们有一个储存在调度器里被多个触发器关联的job,然而,对于每个独立的触发器,我想提供给job不同的数据输入,在这个时候,JobDataMaps就很有用了。

在job执行期间,JobDataMaps能够在JobExecutionContext里获得。JobDataMap融合在Trigger和JobDetail类里,JobDataMap里面的值能够利用key来更新。

以下例子显示,在job执行期间从JobExecutionContext里的JobDataMap得到数据:

public class DumbJob implements Job

public DumbJob()

    

    public void execute(JobExecutionContext context)

      throws JobExecutionException

    

      String instName = context.getJobDetail().getName();

      String instGroup = context.getJobDetail().getGroup();

      JobDataMap dataMap = context.getJobDataMap();  // 注意:不同于以前的例子

      String jobSays = dataMap.getString("jobSays");

      float myFloatValue = dataMap.getFloat("myFloatValue");

      ArrayList state = (ArrayList)dataMap.get("myStateData");

      state.add(new Date());

      System.err.println("Instance " + instName + " of DumbJob says: " + jobSays);

    

  

StatefulJob

现在,关于job状态数据的一些附加要点:一个job实例能定义为"有状态的"或者"无状态的"。无状态的jobs仅当它们在被加入到调度器里时才存储JobDataMap。这就意味着,在jobs执行期间对JobDataMap里数据的任何改变都会丢失,下次执行时job将看不到这些数据。你可能会猜到,一个有状态的job就是它的反面例子-它的JobDataMap是在每次执行完job后再次储存的。一个缺点就是有状态的job不能够并发执行。换句话说,如果job是有状态的,一个触发器尝试触发这个已经执行了的job时,这个触发器就会等待直到这次执行结束。

用实现StatefulJob 接口来标记一个job是有状态的。

Job 'Instances'

我们能够创建一个单独的job类,并且通过创建多个JobDetails实例在调度器里储存很多它的“实例定义”,每个都有它自己的属性集和JobDataMap ,把它们都加入到调度器里。

当一个触发器触发时,与它关联的job就是通过配置在调度器上的JobFactory 来实例化的。默认的JobFactory 简单的调用在job类上的newInstance()方法,你可能想要创建自己的JobFactory实现来完成一些自己想要的事情,如:拥有应用程序的IoC或者DI容器进程/初始化job实例。

job的其他属性

这儿有一个其他属性的总结,这些属性是通过JobDetail对象为一个job实例定义的。

  • 持久性– 如果一个job是非持久的,一旦没有任何可用的触发器与它关联时,他就会自动得从调度器里被删除。
  • 不稳定性-如果一个job是不稳定的,他就不会在重起Quartz调度器之间持久化。
  • 请求恢复– 如果一个job“请求恢复”,在调度器“硬关闭”(如:该进程崩溃,机器被关掉)时这个job还在执行,过后,当调度器再次启动时,他就会再次执行。在这种情况下,JobExecutionContext.isRecovering() 方法将会返回true.
  • Job监听器 –一个job能够有0个或者多个与它关联的监听器。当job执行时,监听器就会被通知。在监听器的更多讨论请看TriggerListeners & JobListeners

JobExecutionException

最后,我们来看看Job.execute(..)方法的一些细节。你能够从execute方法里抛出的仅有的异常类型就是JobExecutionException。因为这样,我们应该使用try-catch块包围整个execute方法内容。我们还应该花一些时间看看JobExecutionException文档。当job执行发生异常时,通过设置JobExecutionException,可以让此job再次进入调度器或者今后不再运行。

4.更多关于Triggers

象jobs一样,triggers也相对来说很容易。但是,我们还是要理解它的一些特性。Quartz里也有很多类型的trigger提供给我们使用。

Calendars

Quartz Calendar 对象(不是java.util.Calendar对象)能够在trigger储存在调度器时和trigger关联起来。Calendars主要用来在trigger配置时排除一些时间。例如,你能够创建一个在每个工作日早上9:30触发的trigger,然后为这个trigger增加一个排除所有商业的节假日的Calendar。

Calendars能够是任何序列化的对象,只要这些对象实现了Calendar接口:

package org.quartz;

  public interface Calendar

    public boolean isTimeIncluded(long timeStamp);

    public long getNextIncludedTime(long timeStamp);

  

注意到这些方法的参数类型是long。这意味着calendars能够排除毫秒级的时间段。大部分地,我们感兴趣的是一整天的,所以在Quartz里,有个实现类提供了方便:org.quartz.impl.HolidayCalendar

Calendars必须被实例化并且通过addCalendar(..)方法注册到调度器里。如果你用HolidayCalendar,在实例它之后,你应该用它的addExcludedDate(Date date)方法以便组装上你想排除的那几天。一个calendar实例能够被多个triggers使用:

HolidayCalendar cal = new HolidayCalendar();

  cal.addExcludedDate( someDate );

  sched.addCalendar("myHolidays", cal, false);

  Trigger trigger = TriggerUtils.makeHourlyTrigger(); // 每小时触发

  trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date()));  //下一个小时开始 trigger.setName("myTrigger1");

  trigger.setCalendarName("myHolidays");

  // .. schedule job with trigger

  Trigger trigger2 = TriggerUtils.makeDailyTrigger(8, 0); // 每天早上8点触发

  trigger2.setStartTime(new Date()); //立即开始

  trigger2.setName("myTrigger2");

  trigger2.setCalendarName("myHolidays");

  // .. schedule job with trigger2

不触发(misfire)指令

触发器的另外一个重要的属性是“不触发指令”。如果一个持久的触发器由于调度器被关闭了而没有找到它的触发时间,那么一个不触发将会发生。不同的触发器类型有不同的不触发指令。默认的,他们都用“smart policy”指令-这是一个基于触发器类型和配置的动态行为。当调度器启动时,他将会搜寻所有没触发的持久化的triggers,然后基于他们各个配置的不触发指令来更新他们。当你用Quartz,你应该熟悉各个不触发指令,我们在以下章节有一些介绍。给一个trigger实例配置不触发指令,要用此实例的setMisfireInstruction(..)方法。

TriggerUtils - Triggers Made Easy

TriggerUtils类(在org.quartz包里)包含了很多方便的工具。能够帮你创建triggers和datas。用这个类能够很容易制造一些trigges,这些triggers能够在每分钟,每小时,每周,每个月等等触发。用它也能产生一些接近某个秒、分钟、小时的天-这在设置trigger的启动时间很有帮助。

TriggerListeners

最后,triggers有一些注册了的监听器,象job一样。实现了TriggerListener接口的对象将接受一个trigger被触发的通知。

5. SimpleTrigger

详细介绍一下它的构造器:

public SimpleTrigger(String name, //trigger名称

                  String group, //trigger的组名

                  Date startTime, //开始时间

                  Date endTime, //结束时间

                   int repeatCount, //重复次数

                   long repeatInterval)//重复间隔

举几个常用例子:

从现在开始10秒后执行一次:

long startTime = System.currentTimeMillis() + 10000L;

  SimpleTrigger trigger = new SimpleTrigger("myTrigger",

                                            null,

                                            new Date(startTime),

                                            null,

                                            0,

0L);

立即执行,60秒间隔无限制重复:

SimpleTrigger trigger = new SimpleTrigger("myTrigger",

                                            null,

                                            new Date(),

                                            null,

                                            SimpleTrigger.REPEAT_INDEFINITELY,

60L * 1000L);

从现在开始立即执行,每10秒重复,直到40秒后:

long endTime = System.currentTimeMillis() + 40000L;

  SimpleTrigger trigger = new SimpleTrigger("myTrigger",

                                            "myGroup",

                                            new Date(),

                                            new Date(endTime),

                                            SimpleTrigger.REPEAT_INDEFINITELY,

10L * 1000L);

在2002年3月17号10:30am触发,重复5次(一共6次),30秒间隔:

java.util.Calendar cal = new java.util.GregorianCalendar(2002, cal.MARCH, 17);

  cal.set(cal.HOUR, 10);

  cal.set(cal.MINUTE, 30);

  cal.set(cal.SECOND, 0);

  cal.set(cal.MILLISECOND, 0);

  Data startTime = cal.getTime();

  SimpleTrigger trigger = new SimpleTrigger("myTrigger",

                                            null,

                                            startTime,

                                            null,

                                            5,

                                            30L * 1000L);

SimpleTrigger 不触发指令

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

6.CronTrigger

构造器

CronTrigger(String name, //触发器名称

String group, //触发器的组名

String jobName, //job名称

String jobGroup, //job的组名

Date startTime, //开始时间

Date endTime, //结束时间

String cronExpression, //克隆表达式

TimeZone timeZone)//时区

还有一些其它参数少一些的构造器,参考JavaDoc。通常我们如下简单地使用CronTrigger

Trigger trigger = new CronTrigger("trigger1", "group1");//设置触发器名称和组名

trigger.setCronExpression("0 0 15 * * ?");//设置克隆表达式

克隆表达式

一个克隆表达式是一个由空白间隔6个或者7个字段的字符串。

格式:

字段名

必须有?

值范围

允许的特殊字符

Seconds

YES

0-59

, - * /

Minutes

YES

0-59

, - * /

Hours

YES

0-23

, - * /

Day of month

YES

1-31

, - * ? / L W C

Month

YES

1-12 or JAN-DEC

, - * /

Day of week

YES

1-7 or SUN-SAT

, - * ? / L C #

Year

NO

empty, 1970-2099

, - * /

例子:

* * * * ? *

0 0/5 14,18,3-39,52 ? JAN,MAR,SEP MON-FRI 2002-2010

特殊字符

  • * 表示所有值 
  • ? 表示未说明的值,即不关心它为何值;
  • - 表示一个指定的范围;
  • , 表示附加一个可能值;
  • / 符号前表示开始时间,符号后表示每次递增的值;
  • L ("last") "L" 用在day-of-month字段意思是 "这个月最后一天";用在 day-of-week字段它简单意思是 "7" or "SAT" 如果在day-of-week字段里和数字联合使用,它的意思就是 "这个月的最后一个星期几– 例如: "6L" means "这个月的最后一个星期五"当我们用“L”时,不指明一个列表值或者范围是很重要的,不然的话,我们会得到一些意想不到的结果。
  • W ("weekday") –只能用在day-of-month字段。用来描叙最接近指定天的工作日(周一到周五)。例如:在day-of-month字段用“15W”指“最接近这个月第15天的工作日”,即如果这个月第15天是周六,那么触发器将会在这个月第14天即周五触发;如果这个月第15天是周日,那么触发器将会在这个月第16天即周一触发;如果这个月第15天是周二,那么就在触发器这天触发。注意一点:这个用法只会在当前月计算值,不会越过当前月。“W”字符仅能在day-of-month指明一天,不能是一个范围或列表。

也可以用“LW”来指定这个月的最后一个工作日。

  • # -只能用在day-of-week字段。用来指定这个月的第几个周几。例:在day-of-week字段用"6#3"指这个月第3个周五(6指周五,3指第3个)。如果指定的日期不存在,触发器就不会触发。
  • C ("calendar") – 指和calendar联系后计算过的值。例:在day-of-month 字段用“5C”指在这个月第5天或之后包括calendar的第一天;在day-of-week字段用“1C”指在这周日或之后包括calendar的第一天。

MONTHDay of week字段里对字母大小写不敏感。

一些例子

表达式

意思(触发时刻)

0 0 12 * * ?

每天中午12

0 15 10 * * ? 2005

在2005年的每天10:25

0 10,44 14 ? 3 WED

在3月里每个周三的14:10和14:44

0 15 10 ? * 6L 2002-2005

从2002年到2005年里,每个月的最后一个星期五的10:15

0 0 12 1/5 * ?

从当月的第一天开始,然后在每个月每隔5天的12:00

0 15 10 ? * 6#3

每个月第3个周五的10:15

注意在day-of-weekday-of-month字段里使用“?”和“*”的效果。

注意

  • 对“C”的支持并不很完全。
  • 对在day-of-week字段 和在day-of-month字段同时使用也不是很完全(目前你必须在这两个字段中的一个用“?”指定)。
  • 当设置在午夜和凌晨1点之间触发时要仔细。

不触发指令:

MISFIRE_INSTRUCTION_FIRE_ONCE_NOW

MISFIRE_INSTRUCTION_DO_NOTHING

7.TriggerListeners JobListeners

Trigger相关的事件有:触发器触发,触发器的不触发(参考先前章节),触发器完成。

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

job相关的事件有:job准备执行,job执行完毕。

public interface JobListener

以上是关于Quartz学习的主要内容,如果未能解决你的问题,请参考以下文章

Quartz简单实例

Quartz.Net

Quartz任务调度框架

基于zookeeper和quartz实现分布式定时调度

quartz表达式介绍 Quartz介绍

quartz表达式介绍