Spring和Quartz实现的定时任务调度

Corn表达式

一、增加所依赖的JAR包

1. 增加Spring的Maven依赖

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <dependency>  
  2.  <groupId>org.springframework</groupId>  
  3.  <artifactId>spring-webmvc</artifactId>  
  4.  <version>3.0.5.RELEASE</version>  
  5. </dependency>  

2. 增加Quartz的Maven依赖

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <dependency>  
  2.  <groupId>org.quartz-scheduler</groupId>  
  3.  <artifactId>quartz</artifactId>  
  4.  <version>1.8.4</version>  
  5. </dependency>  

二、增加定时业务逻辑类

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public class ExpireJobTask {  
  2.     /** Logger */  
  3.     private static final Logger logger = LoggerFactory.getLogger(ExpireJobTask.class);  
  4.    
  5.     /** 
  6.      * 业务逻辑处理 
  7.      */  
  8.     public void doBiz() {  
  9.     // 执行业务逻辑  
  10.     // ........  
  11.     }  
  12. }  

ExpireJobTask业务逻辑类与一般普通的类没有任务区别,它定义的doBiz方法即为调度业务方法。

三、增加Spring配置

1. 增加一个线程池

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <!-- 线程执行器配置,用于任务注册 -->  
  2. <bean id="executor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">  
  3.  <property name="corePoolSize" value="10" />  
  4.  <property name="maxPoolSize" value="100" />  
  5.  <property name="queueCapacity" value="500" />  
  6. </bean>  

2. 定义业务逻辑处理类

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <!-- 业务对象 -->  
  2. <bean id="bizObject" class="com.aboy.potak.common.toolkit.scheduling.ExpireJobTask" />  

3. 增加调度业务逻辑

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <!-- 调度业务 -->  
  2. <bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">  
  3.  <property name="targetObject" ref="bizObject" />  
  4.  <property name="targetMethod" value="doBiz" />  
  5. </bean>  

上面的配置中,我们以bizObject.doBiz方法为将要调度的业务执行逻辑。

4. 增加调度触发器

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">  
  2.  <property name="jobDetail" ref="jobDetail" />  
  3.  <property name="cronExpression" value="10 0/1 * * * ?" />  
  4. </bean>  

Cron表达式“10 */1 * * * ?”意为:从10秒开始,每1分钟执行一次。

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <bean id="taskTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">  
  2.  <property name="jobDetail" ref="jobDetail" />  
  3.  <property name="startDelay" value="10000" />  
  4.  <property name="repeatInterval" value="60000" />  
  5. </bean>  

5. 增加调度

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <!-- 设置调度 -->  
  2. <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">  
  3.  <property name="triggers">  
  4.   <list>  
  5.    <ref bean="cronTrigger" />  
  6.   </list>  
  7.  </property>  
  8.  <property name="taskExecutor" ref="executor" />  
  9. </bean>  

triggers属性中,我们可以增加多个触发器。

到此,Spring已经与Quartz完美的结合了,我们接下来的工作就是启动系统,开始调度了。

四、Cron表达式的详细用法

Cron表达式是一个字符串,字符串以5或6个空格隔开,分开共6个或7个域,每一个域代表一个含义,Cron有如下两种语法,格式: 

Seconds Minutes Hours DayofMonth Month DayofWeek Year 或 

Seconds Minutes Hours DayofMonth Month DayofWeek 

字段

允许值

允许的特殊字符

0-59

- * /

0-59

- * /

小时

0-23

- * /

日期

1-31

- * ? / L W C

月份

1-12 或者 JAN-DEC

- * /

星期

1-7 或者 SUN-SAT

- * ? / L C #

年(可选) 留空

1970-2099

- * /

例子:

0/5 * * * * ? : 每5秒执行一次

“*”字符被用来指定所有的值。如:"*"在分钟的字段域里表示“每分钟”。 

“?”字符只在日期域和星期域中使用。它被用来指定“非明确的值”。当你需要通过在这两个域中的一个来指定一些东西的时候,它是有用的。看下面的例子你就会明白。 

月份中的日期和星期中的日期这两个元素是互斥的,应该通过设置一个问号来表明不想设置那个字段。

“-”字符被用来指定一个范围。如:“10-12”在小时域意味着“10点、11点、12点”。

“,”字符被用来指定另外的值。如:“MON,WED,FRI”在星期域里表示”星期一、星期三、星期五”。

“/”字符用于指定增量。如:“0/15”在秒域意思是每分钟的0,15,30和45秒。“5/15”在分钟域表示每小时的5,20,35和50。符号“*”在“/”前面(如:*/10)等价于0在“/”前面(如:0/10)。记住一条本质:表达式的每个数值域都是一个有最大值和最小值的集合,如:秒域和分钟域的集合是0-59,日期域是1-31,月份域是1-12。字符“/”可以帮助你在每个字符域中取相应的数值。如:“7/6”在月份域的时候只有当7月的时候才会触发,并不是表示每个6月。

L是‘last’的省略写法可以表示day-of-month和day-of-week域,但在两个字段中的意思不同,例如day-of-month域中表示一个月的最后一天。如果在day-of-week域表示‘7’或者‘SAT’,如果在day-of-week域中前面加上数字,它表示一个月的最后几天,例如‘6L’就表示一个月的最后一个星期五。

字符“W”只允许日期域出现。这个字符用于指定日期的最近工作日。例如:如果你在日期域中写 “15W”,表示:这个月15号最近的工作日。所以,如果15号是周六,则任务会在14号触发。如果15好是周日,则任务会在周一也就是16号触发。如果是在日期域填写“1W”即使1号是周六,那么任务也只会在下周一,也就是3号触发,“W”字符指定的最近工作日是不能够跨月份的。字符“W”只能配合一个单独的数值使用,不能够是一个数字段,如:1-15W是错误的。

“L”和“W”可以在日期域中联合使用,LW表示这个月最后一周的工作日。

字符“#”只允许在星期域中出现。这个字符用于指定本月的某某天。例如:“6#3”表示本月第三周的星期五(6表示星期五,3表示第三周)。“2#1”表示本月第一周的星期一。“4#5”表示第五周的星期三。

字符“C”允许在日期域和星期域出现。这个字符依靠一个指定的“日历”。也就是说这个表达式的值依赖于相关的“日历”的计算结果,如果没有“日历”关联,则等价于所有包含的“日历”。如:日期域是“5C”表示关联“日历”中第一天,或者这个月开始的第一天的后5天。星期域是“1C”表示关联“日历”中第一天,或者星期的第一天的后1天,也就是周日的后一天(周一)。

五、表达式举例

"0 0 12 * * ?" 每天中午12点触发

"0 15 10 ? * *" 每天上午10:15触发

"0 15 10 * * ?" 每天上午10:15触发

"0 15 10 * * ? *" 每天上午10:15触发

"0 15 10 * * ? 2005" 2005年的每天上午10:15触发

"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发

"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发

"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发

"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发

"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发

"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发

"0 15 10 15 * ?" 每月15日上午10:15触发

"0 15 10 L * ?" 每月最后一日的上午10:15触发

"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发 

"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发

"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发


Cronexpression表达式::

 

字段 允许值 允许的特殊字符 
 0-59 , - * / 
 0-59 , - * / 
小时 0-23 , - * / 
日期 1-31 , - * ? / L W C 
月份 1-12 或者 JAN-DEC , - * / 
星期 1-7 或者 SUN-SAT , - * ? / L C # 
年(可选) 留空, 1970-2099 , - * / 
表达式意义 
"0 0 12 * * ?" 
每天中午12点触发 
"0 15 10 ? * *" 
每天上午10:15触发 
"0 15 10 * * ?" 
每天上午10:15触发 
"0 15 10 * * ? *" 
每天上午10:15触发 
"0 15 10 * * ? 2005" 2005
年的每天上午10:15触发 
"0 * 14 * * ?" 
在每天下午2点到下午2:59期间的每1分钟触发 
"0 0/5 14 * * ?" 
在每天下午2点到下午2:55期间的每5分钟触发 
"0 0/5 14,18 * * ?" 
在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 
"0 0-5 14 * * ?" 
在每天下午2点到下午2:05期间的每1分钟触发 
"0 10,44 14 ? 3 WED" 
每年三月的星期三的下午2:102:44触发 
"0 15 10 ? * MON-FRI" 
周一至周五的上午10:15触发 
"0 15 10 15 * ?" 
每月15日上午10:15触发 
"0 15 10 L * ?" 
每月最后一日的上午10:15触发 
"0 15 10 ? * 6L" 
每月的最后一个星期五上午10:15触发 
"0 15 10 ? * 6L 2002-2005" 2002
年至2005年的每月的最后一个星期五上午10:15触发 
"0 15 10 ? * 6#3" 
每月的第三个星期五上午10:15触发 
每天早上6 
0 6 * * * 
每两个小时 
0 */2 * * * 
晚上11点到早上8点之间每两个小时,早上八点 
0 23-7/2
8 * * * 
每个月的4号和每个礼拜的礼拜一到礼拜三的早上11 
0 11 4 * 1-3 
1月
1日早上4点 
0 4 1 1 *


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 {

    public String getName();

    public void jobToBeExecuted(JobExecutionContext context);

    public void jobExecutionVetoed(JobExecutionContext context);

    public void jobWasExecuted(JobExecutionContext context,

                                        JobExecutionException jobException);

}

使用Listeners

创建一个监听器,就是创建一个实现了org.quartz.TriggerListener  org.quartz.JobListener接口的对象。在运行的期间用调度器注册监听器,必须要给它提供一个名字。监听器能够注册成为全局的或者不是全局的,全局监听器接受所有的事件,而非全局的则仅接受指定给triggers/jobs了的事件。

监听器是在运行期间被调度器注册的,他们没有伴随jobs和triggers储存在JobStore里。Jobs和triggers仅储存和它们相关的监听器的名字。因此,每次程序运行时,监听器需要被调度器再次注册。

scheduler.addGlobalJobListener(myJobListener);

scheduler.addJobListener(myJobListener);

监听器在Quartz并不是经常使用的。

8.SchedulerListeners

和调度器相关的事件有:job/trigger的加入和移出,一些调度器里的错误,调度器关闭等等。

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

}

创建和注册SchedulerListeners和其他监听器一样,全局和非全局的没有区别。

9.JobStores

JobStore负责保存所有配置到调度器里的工作数据:jobs,triggers,calendars等等。在用SchedulerFactory得到一个调度器的实例时,我们可以给SchedulerFactory提供一个属性文件或者一个属性对象来声明使用哪个JobStore。

注意,不要在代码里使用JobStore的实例,这些Quartz都做好了。我们要做的就仅仅告诉Quartz(通过配置)用哪个JobStore,然后就调用Scheduler接口函数了。

RAMJobStore

利用内存来持久化调度程序信息。这种作业存储类型最容易配置、构造和运行,但是当应用程序停止运行时,所有调度信息将被丢失。

在属性文件里指定:

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

JDBCJobStore

支持的数据库有:Oracle, MySQL, MS SQLServer2000, HSQLDB, PostreSQL and DB2。使用JDBCJobStore,首先要在数据库里建一些Quartz要使用的表。我们可以使用Quartz发布包里的建表脚本,在docs/dbTables目录下。如果没有你所要的数据库类型的脚本,可以在已有的脚本作一些修改。所有这些标都是以“QRTZ_”作为前缀的,这个前缀是可以在属性文件里更改的。在为多个调度器实例创建多个系列的表时,用不同的前缀是很有用的。

一旦我们创建了这些表,在配置和触发JDBCJobStore之前就要做更多的事情了。我们需要决定应用需要哪种类型的事务处理。如果我们不需要给其他的事务处理一些调度命令(增加删除trigger),我们就可以让Quartz利用JobStoreTX处理这个事务(这用的很多)。

如果我们需要Quartz和其他的事务处理(在J2EE应用服务器里)一起工作,我们就应该用JobStoreCMT-这会使Quartz让应用服务器容器管理事务。

最后一点是从哪个JDBCJobStore启动数据库能够得到该数据库的连接。在属性文件里是用一个不同的方法来定义数据源的。一种是Quartz自己创建和管理数据源-提供所有的数据库连接信息;另外一种是利用应用服务器管理的数据源,其中Quartz运行在这个应用服务器里-JDBCJobStore提供数据库的JNDI名称。

JDBCJobStore(假设我们是用的StdSchedulerFactory),我们首先要设置org.quartz.jobStore.class属性为org.quartz.impl.jdbcjobstore.JobStoreTX或者org.quartz.impl.jdbcjobstore.JobStoreCMT,这取决于我们的选择。

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

下一步,我们需要选择一个驱动代理。StdJDBCDelegate是一个用“vanillaJDBC代码实现的代理。如果没有其他为你数据库指定的代理,就使用这个。Quartz开发者们解决的问题都是根据这个代理的来实现的。其他的代理在org.quartz.impl.jdbcjobstore包或者子包里。包括DB2v6DelegateDB2 version 6 或早期版本使用的),HSQLDBDelegateHSQLDB使用),MSSQLDelegatemicrosoft SQLServer 2000使用),PostgreSQLDelegatePostgreSQL 7.x使用),WeblogicDelegateWeblogicJDBC驱动器使用),OracleDelegateOracle 8i and 9i使用)。

org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate

在下一步,我们要配置表的前缀:

org.quartz.jobStore.tablePrefix = QRTZ_

最后,我们需要设置用哪个数据源,数据源的名称必须在Quartz属性里定义好。例如,我们可以给Quartz指定使用“myDS”(在配置属性里的其他地方定义好了)作为数据源的名字。

org.quartz.jobStore.dataSource = myDS

如果调度器很繁忙(例如,执行job的个数和线程池的大小一样),那么我们应该设置数据源的连接个数在线程池大小+1之上。

org.quartz.jobStore.useProperties这个属性能够设置为“true”(默认为false),用来指示JDBCJobStore:在JobDataMaps里的所有值都应该是String,这样在能作为name-value方式储存,而不是在BLOB列里以序列化的格式储存复杂的对象。从长远看,这样做会很安全,因为你可以避免将非String的类序列化到BLOB里的类版本问题。

10.配置,资源使用和调度器工厂

Quartz是以标准组件的方式组织的,所以,使它运行起来,一些组件需要被联合起来。

在Quartz能够工作之前,需要配置的主要组件有:

  • 线程池
  • 作业储存
  • 数据源(需要的话)
  • 调度器自己

在运行jobs时,线程池为Quartz提供了一系列的线程。在线程池里的线程越多,能够并行执行的jobs就越多。但是,太多的线程会使系统瘫痪。大部分的Quartz用户发现,5个线程就足够了-因为他们在指定时间里只有少于100jobs,这些jobs并不都是在同一时刻执行,jobs完成得也很快的。其他的用户发现他们需要101550或者100个线程-因为他们在不同的调度器里用了上万个触发器,在给定的时间里,平均在10100jobs试着执行。为调度器找到合适的线程数量完全依赖于你用调度起来做什么。不在乎线程数量,而要确保你有足够的线程来使jobs执行。如果一个触发器的触发时间到来了,可是没有一个能够用的线程,Quartz将会等到可用线程的来临,然后job将会在几毫秒后执行。这可能会引起不触发-如果不在属性文件里给调度器配置“misfire threshold”的话。

线程池接口是在org.quartz.spi包里定义的,你能够创建一个线程池以自己的方法。Quartz装配了一个简单(但是很好的)的线程池,是org.quartz.simpl.SimpleThreadPool。这个线程池简单的维护一些在池里固定的线程-不会增加也不会减少。但是它能够做很多事而且经过测试了的,几乎每个Quartz用户用这个线程池。

      JobStores  DataSrouces在前面讨论过了,这里值得一提的是,所有JobStores都实现了org.quartz.spi.JobStore接口,如果在打包里的任何一个JobStore不能够满足你的需求的话,你可以自己做一个。

最后,你需要创建你的Scheduler实例。Scheduler需要提供他的名称,说明RMI的设置,处理JobStoreThreadPool的实例。RMI设置包括调度器是否作为一个RMI服务器而创建。StdSchedulerFactory也能够产生调度器的实例,这些实例实际上是创建在远程进程中的调度器代理(RMI桩)。

StdSchedulerFactory

StdSchedulerFactory实现了org.quartz.SchedulerFactory接口。它用了一系列的属性(java.util.Properties)来创建和初始化一个Quartz的调度器。这些属性通常保存和加载在一个文件里,但是也可以通过你的程序创建直接交给工厂处理。在工厂上调用getScheduler()就可以产生调度器,初始化它(还有线程池,JobStore和数据源),然后返回一个句柄到这个公共的接口。

// 默认调度器是quartz.propeties文件定义的,这个文件可以在当前目录下找到,也可以在//classpath里找到,如果都找不到了,就用quartz.jar里的quartz.propeties文件。

SchedulerFactory sf = new StdSchedulerFactory();

Scheduler scheduler = sf.getScheduler();

scheduler.start();

用指定的属性对象初始化:

SchedulerFactory sf = new StdSchedulerFactory();

sf.initialize(schedulerProperties);// schedulerProperties是属性对象

Scheduler scheduler = sf.getScheduler();

scheduler.start();

用指定的属性文件初始化:

SchedulerFactory sf = new StdSchedulerFactory();

sf.initialize(fileName);//属性文件全名

Scheduler scheduler = sf.getScheduler();

scheduler.start();

DirectSchedulerFactory

DirectSchedulerFactory是另外的一个SchedulerFactory实现。在更多的编程方法里创建调度器时,他很有用。他的用法不被赞成,原因有:1.它需要用户更清楚的知道他们在做什么。2.它不允许配置,就是说,你必须要在代码里配置所有的调度器属性。

Logging

Quartz给它所有需要的日志是使用org.apache.commons.logging框架的。Quartz没有产生很多的日志信息。仅有一些在初始化时关于一些jobs正在执行的问题的信息。为了调整日志设置,我们需要了解Jakarta Commons Logging框架,超过了本文档讨论的范围。

 

11.高级(企业)特性

集群

目前集群仅以JDBC-Jobstore (JobStoreTX or JobStoreCMT)工作。这些特性包含load-balancing和任务fail-over(如果JobDetail的"request recovery"标志设为true的话)。

通过设置org.quartz.jobStore.isClustered属性为“true”来使用集群。在集群里的每个调度器实例应该用一样的quartz.properties文件。集群会有如下异常:线程池大小不同,属性org.quartz.scheduler.instanceName值不同。其实在集群的每个节点都有一个唯一的实例ID,要达到这样也很简单,也不需要不同的属性文件,只要将属性org.quartz.scheduler.instanceId的值设置为“AUTO”。

不要在一个分离开的机器上运行集群,除非他们的时钟是用时钟同步服务同步过的。如果不熟悉怎样同步,参考:http://www.boulder.nist.gov/timefreq/service/its.htm

其他调度器实例在用数据表时,不要触发一个也用到这些数据表的不是集群的调度器实例。你会得到一些没用的数据。

 

JTA 事务

在第9节解释过JobStores,JobStoreCMT允许Quartz调度一些具有很大JTA事务的操作。

通过设置“org.quartz.scheduler.wrapJobExecutionInUserTransaction”属性为trueJobs也能够在一个JTA事务里执行。有了这个设置,一个JTA事务会在jobexecute()方法调用前开始(begin),然后在调用execute()方法结束后提交(commit)。

除了在JTA事务里Quartz自动地和job的执行挂钩之外,当使用JobStoreCMT时也可以调用你在调度器接口里的实现的方法,确保你在调用一个调度器上的方法之前开始了事务。你也可以直接自己做,使用UserTransaction,或者把用了调度器的代码放在一个使用容器的SessionBean里来管理事务。

12. Quartz 的其他特性

Plug-Ins

Quartz 提供了一个接口(org.quartz.spi.SchedulerPlugin) 来实现plugging-in 的功能。

装配给Quartz的Plugins能提供不同的有用的功能。在org.quartz.plugins包里有详细说明。他们提供的功能例如:调度器启动时自动调度jobs,记录job和triggers事件的历史,当JVM退出时确保调度器关闭。

可以通过配置属性文件来使用自己实现或Quartz自带的插件。

JobFactory

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

Scheduler.setJobFactory(fact)方法联合起来察看org.quartz.spi.JobFactory接口,

Jobs工具

Quartz也提供一些有用的job,你能够用这些job来发邮件或者调用EJB。我们能在org.quartz.jobs包里找到它们。

13.配置文件里配置项总结

设置主要调度器

属性名

必须

类型

缺省值

org.quartz.scheduler.instanceName

no

string

'QuartzScheduler'

org.quartz.scheduler.instanceId

no

string

'NON_CLUSTERED'

org.quartz.scheduler.threadName

no

string

instanceName + '_QuartzSchedulerThread'

org.quartz.scheduler.idleWaitTime

no

long

30000

org.quartz.scheduler.dbFailureRetryInterval

no

long

15000

org.quartz.scheduler.classLoadHelper.class

no

string (class name)

org.quartz.simpl.CascadingClassLoadHelper

org.quartz.context.key.SOME_KEY

no

string

none

org.quartz.scheduler.userTransactionURL

no

string (url)

'java:comp/UserTransaction'

org.quartz.scheduler.wrapJobExecutionInUserTransaction

no

booelan

false

org.quartz.scheduler.jobFactory.class

no

string (class name)

org.quartz.simpl.SimpleJobFactory

org.quartz.scheduler.instanceName 
任意的String,对于调度器自己并没有意义。但是当多个调度器实例用在一个程序里时,他就可以用来为客户端代码区别每个调度器。如果你用集群这个特性,你必须为在集群里的每个实例用一样的名字,实现逻辑上的一样的调度器。

org.quartz.scheduler.instanceId 
任意的String,如果在一个集群里多个实例是一个逻辑上一样的调度器时,每个实例的这项属性必须唯一。你可以设置这项为“AUTO”从而自动收集ID。

org.quartz.scheduler.idleWaitTime 
当调度器空闲时,在再次查询可用triggers之前,调度器将要等等待的毫秒数。正常情况下,我们不调整这个参数,除非我们用XA事务,或者在立即触发trigger时结果延误了。

org.quartz.scheduler.classLoadHelper.class 
不需要更改。

org.quartz.context.key.SOME_KEY

设置org.quartz.context.key.MyKey = MyValue等价于scheduler.getContext().put("MyKey", "MyValue")

org.quartz.scheduler.userTransactionURL 
是一个JNDI URL,Quartz用它来定位应用服务器的UserTransaction管理器。Websphere用户可能需要设置它为“jta/usertransaction”。在Quartz配置用到JobStoreCMT时并且属性org.quartz.scheduler.wrapJobExecutionInUserTransaction设置为true才有用

org.quartz.scheduler.wrapJobExecutionInUserTransaction 
设置这项为true使我们在调用job的execute()之前能够开始一个UserTransaction。在job的execute()完成之后,事务将会提交,并且,JobDataMap也更新了(是有状态的job)。

设置线程池

属性名

必须

类型

缺省值

org.quartz.threadPool.class

yes

string (clas name)

null

org.quartz.threadPool.threadCount

yes

int

-1

org.quartz.threadPool.threadPriority

no

int

Thread.NORM_PRIORITY (5)

org.quartz.threadPool.makeThreadsDaemons

no

boolean

false

org.quartz.threadPool.threadsInheritGroupOfInitializingThread

no

boolean

true

org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread

no

boolean

false

org.quartz.threadPool.class 
通常使用org.quartz.simpl.SimpleThreadPool

org.quartz.threadPool.threadPriority 
在 Thread.MIN_PRIORITY (1) 和Thread.MAX_PRIORITY (10)之间

org.quartz.threadPool.makeThreadsDaemons、org.quartz.threadPool.threadsInheritGroupOfInitializingThread 和org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread 三个属性是指定的SimpleThreadPool的属性。

如果用自己实现的线程池,可如下配置:

org.quartz.threadPool.class = com.mycompany.goo.FooThreadPool

org.quartz.threadPool.somePropOfFooThreadPool = someValue

设置全局监听器

全局监听器要有一个无参数的构造器,它的属性是通过反射设置的,仅支持简单数据和String

Trigger监听器:

org.quartz.triggerListener.NAME.class = com.foo.MyListenerClass

org.quartz.triggerListener.NAME.propName = propValue

org.quartz.triggerListener.NAME.prop2Name = prop2Value

job监听器:

org.quartz.jobListener.NAME.class = com.foo.MyListenerClass

org.quartz.jobListener.NAME.propName = propValue

org.quartz.jobListener.NAME.prop2Name = prop2Value

设置Plugins

配置自己的插件(和全局监听器差不多):

org.quartz.plugin.NAME.class = com.foo.MyPluginClass

org.quartz.plugin.NAME.propName = propValue

org.quartz.plugin.NAME.prop2Name = prop2Value

也可以配置Quartz实现的插件

1.trigger历史日志记录插件(属性配置中的{数字}参考JavaDoc):

org.quartz.plugin.triggHistory.class=org.quartz.plugins.history.LoggingTriggerHistoryPlugin

org.quartz.plugin.triggHistory.triggerFiredMessage=

Trigger {1}.{0} fired job {6}.{5} at:{4, date, HH:mm:ss MM/dd/yyyy}

org.quartz.plugin.triggHistory.triggerCompleteMessage =

Trigger {1}.{0} completed firing job {6}.{5} at {4, date, HH:mm:ss MM/dd/yyyy}

2.XML文件中初始化job插件(属性配置中的文件名是加载jobs用到的xml文件,这个文件必须在classPath里):

org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin

org.quartz.plugin.jobInitializer.fileName =data/my_job_data.xml

org.quartz.plugin.jobInitializer.overWriteExistingJobs = false

org.quartz.plugin.jobInitializer.failOnFileNotFound = true

在上例中,JobInitializationPlugin只支持一个xml文件的初始化,Quartz还提供多个xml文件的初始化,用JobInitializationPluginMultiple,文件名用“,”隔开。

含有多个Jobs的一个xml文件的一个例子:

<?xml version='1.0' encoding='utf-8'?>

<quartz xmlns="http://www.opensymphony.com/quartz/JobSchedulingData"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.opensymphony.com/quartz/JobSchedulingData

http://www.opensymphony.com/quartz/xml/job_scheduling_data_1_5.xsd"

version="1.5">

 

  <calendar class-name="org.quartz.impl.calendar.HolidayCalendar" replace="true">

    <name>holidayCalendar</name>

    <description>HolidayCalendar</description>

    <base-calendar class-name="org.quartz.impl.calendar.WeeklyCalendar">

      <name>weeklyCalendar</name>

      <description>WeeklyCalendar</description>

      <base-calendar class-name="org.quartz.impl.calendar.AnnualCalendar">

        <name>annualCalendar</name>

        <description>AnnualCalendar</description>

      </base-calendar>

    </base-calendar>

  </calendar>

 

  <job>

    <job-detail>

      <name>testJob1</name>

      <group>testJobs</group>

      <description>Test Job Number 1</description>

      <job-class>personal.ruanyang.quartz.plugin.SimpleJob</job-class>

      <volatility>false</volatility>

      <durability>false</durability>

      <recover>false</recover>     

      <job-data-map allows-transient-data="true">

        <entry>

          <key>test1</key>

          <value>test1</value>

        </entry>

        <entry>

          <key>test2</key>

          <value>test2</value>

        </entry>

      </job-data-map>     

    </job-detail>

    <trigger>

      <cron>

        <name>testTrigger1</name>

        <group>testJobs</group>

        <description>Test Trigger Number 1</description>

        <job-name>testJob1</job-name>

        <job-group>testJobs</job-group>

       

        <!--

        <start-time>2003-12-17 2:15:00 pm</start-time>     

        <end-time>2013-12-17 2:15:00 pm</end-time>     

       -->

      

        <cron-expression>0/15 * * ? * *</cron-expression>

        <!-- every 15 seconds... -->

      </cron>

    </trigger>

  </job>

 

  <job>

    <job-detail>

      <name>testJob2</name>

      <group>testJobs</group>

      <description>Test Job Number 2</description>

      <job-class>personal.ruanyang.quartz.plugin.SimpleJob</job-class>

      <volatility>false</volatility>

      <durability>false</durability>

      <recover>false</recover>

    </job-detail>

    <trigger>

      <simple>

        <name>testTrigger2</name>

        <group>testJobs</group>

        <description>Test Trigger Number 2</description>

        <calendar-name>holidayCalendar</calendar-name>

        <job-name>testJob2</job-name>

        <job-group>testJobs</job-group>

        <start-time>2004-02-26T12:26:00</start-time>

        <repeat-count>10</repeat-count>

        <repeat-interval>5000</repeat-interval>

      </simple>

    </trigger>

  </job>

 

</quartz>

 

3.Shutdown Hook(通过捕捉JVM关闭时的事件,来关闭调度器)插件:

org.quartz.plugin.shutdownhook.class = org.quartz.plugins.management.ShutdownHookPlugin

org.quartz.plugin.shutdownhook.cleanShutdown = true

设置RMI

RMI Server Scheduler Properties

没有必需的主要属性,所有的都是合理的缺省的。通过RMI使用Quartz时,我们需要启动一个配置好了的Quartz实例来通过RMI“输出”它的服务。然后我们通过配置Quartz的调度器创建一个客户端来“代理”它连到服务器上的工作。

一些用户在客户端和服务器端经历过类可用性(jobs classes)的问题,为了解决这些问题,我们需要理解RMI的“codebase”和RMI的安全管理。以下资源在这方面会很有用:

RMIcodebase的精彩描叙:http://www.kedwards.com/jini/codebase.html  重要的一点要意识到,codebase是被客户端使用的。

安全管理的快速信息:http://gethelp.devx.com/techtips/java_pro/10MinuteSolutions/10min0500.asp

最后读来自于java API文档的RMISecurityManager

http://java.sun.com/j2se/1.4.2/docs/api/java/rmi/RMISecurityManager.html

属性名

需要

缺省值

org.quartz.scheduler.rmi.export

no

false

org.quartz.scheduler.rmi.registryHost

no

'localhost'

org.quartz.scheduler.rmi.registryPort

no

1099

org.quartz.scheduler.rmi.createRegistry

no

'never'

org.quartz.scheduler.rmi.serverPort

no

random

org.quartz.scheduler.rmi.proxy

no

false

org.quartz.scheduler.rmi.export

如果我们想要Quartz调度器通过RMI输出服务,那么我们就把“rmi.export”标志执为true

org.quartz.scheduler.rmi.registryHost
能够找到的RMI注册的主机(常为“localhost”)。

org.quartz.scheduler.rmi.registryPort
RMI注册的监听端口(常为1099).

org.quartz.scheduler.rmi.createRegistry
设置“rmi.createRegistry” 依照我们想要Quartz怎样创建RMI注册。如果我们不想Quartz创建一个注册,就可以用“false”或“never”(如已经有了一个外部的注册在运行了)。如果我们想先要Quartz尝试使用一个存在的注册并且然后返回再建一个,就用“true”或者“as_needed”。如果我们想要Quartz尝试创建一个注册然后返回使用一个存在的,就用“always”。如果注册被创建,它将会绑定属性“org.quartz.scheduler.rmi.registryPort”提供的端口,“org.quartz.rmi.registryHost”应该是主机。

 org.quartz.scheduler.rmi.serverPort
Quartz调度器服务将绑定和监听连接的端口。缺省的,RMI服务将随机选择一个端口。

org.quartz.scheduler.rmi.proxy
如果想要连接到远程的调度器服务,我们就要设置“org.quartz.scheduler.rmi.proxy”为true。然后必需指定一个主机和它注册了的端口号。

在同一个文件里给“org.quartz.scheduler.rmi.export”和“org.quartz.scheduler.rmi.proxy”同时设置为true并没有意义。如果你这样做的话,“export”项会被忽略。如果你没有通过RMI用Quartz,给这两项同时设置为false当然也没有用。

设置RAMJobStore

RAMJobStore用来在内存里储存调度时的信息(job,trigger,calendars)。RAMJobStore很快并且是轻量级的,但是当进程终止时所有的信息都将丢失。

通过设置“org.quartz.jobStore.class”属性来选用RAMJobStore:

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

RAMJobStore 能够通过下面的属性来调整:

属性名

需要

类型

缺省值

org.quartz.jobStore.misfireThreshold

no

int

60000

org.quartz.jobStore.misfireThreshold 
在触发器被认为没有触发之前,调度器能承受一个触发器再次触发的一个毫秒级数字。

设置JDBC-JobStoreTX

JobStoreTX是在每次行为(如增加一个job)之后,通过调用commit() (或者 rollback())来管理事务。如果你在一个单机应用里或者当在一个servlet容器里用Quartz而且应用没有用JTA事务时,JDBCJobStore是正确的。

JobStoreTX是通过设置“org.quartz.jobStore.class”属性来选用的:

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

JobStoreTX能够通过以下属性来调整:

属性名

必须

类型

缺省值

org.quartz.jobStore.driverDelegateClass

yes

string

null

org.quartz.jobStore.dataSource

yes

string

null

org.quartz.jobStore.tablePrefix

no

string

"QRTZ_"

org.quartz.jobStore.useProperties

no

boolean

false

org.quartz.jobStore.misfireThreshold

no

int

60000

org.quartz.jobStore.isClustered

no

boolean

false

org.quartz.jobStore.clusterCheckinInterval

no

long

15000

org.quartz.jobStore.maxMisfiresToHandleAtATime

no

int

20

org.quartz.jobStore.dontSetAutoCommitFalse

no

boolean

false

org.quartz.jobStore.selectWithLockSQL

no

string

"SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE"

org.quartz.jobStore.txIsolationLevelSerializable

no

boolean

false

org.quartz.jobStore.driverDelegateClass

  • org.quartz.impl.jdbcjobstore.StdJDBCDelegate (所有JDBC兼容的驱动)
  • org.quartz.impl.jdbcjobstore.MSSQLDelegate (Microsoft SQL ServerSybase)
  • org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
  • org.quartz.impl.jdbcjobstore.WebLogicDelegate (WebLogic驱动)
  • org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
  • org.quartz.impl.jdbcjobstore.oracle.WebLogicOracleDelegate (用在Weblogic里的Oracle驱动)
  • org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate (用在Weblogic里的Oracle驱动)
  • org.quartz.impl.jdbcjobstore.CloudscapeDelegate
  • org.quartz.impl.jdbcjobstore.DB2v6Delegate
  • org.quartz.impl.jdbcjobstore.DB2v7Delegate
  • org.quartz.impl.jdbcjobstore.HSQLDBDelegate
  • org.quartz.impl.jdbcjobstore.PointbaseDelegate

org.quartz.jobStore.misfireThreshold 
同RAM

org.quartz.jobStore.clusterCheckinInterval
影响着核查出失败实例的速度。

org.quartz.jobStore.dontSetAutoCommitFalse
设置这个属性为“true”是让Quartz不去在JDBC连接上调用setAutoCommit(false)这个函数。

org.quartz.jobStore.selectWithLockSQL
在“LOCKS”表里选择一行并且锁住这行的SQL语句。缺省的语句能够为大部分数据库工作。“{0}”是在运行时你配置的表前缀。

org.quartz.jobStore.txIsolationLevelSerializable
设置“true”让Quartz(当用JobStoreTX或CMT)在JDBC连接上调用setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE)。这可以阻止数据库在高加载或长时间的事务情况下的锁超时。

设置JDBC-JobStoreCMT

JobStoreCMT是依赖与被用Quartz的应用管理着的事务。JTA事务必须在尝试调度(或卸载调度)jobs/triggers之前处在进程中。这允许调度工作成为应用加大事务的一部分。JobStoreCMT实际上需要用到两个数据源,一个数据源要连到被应用服务器管理的事务(通过JTA),另外一个数据源的连接在全局(JTA)事务中并不参加。当应用用JTA事务(例如通过EJB Session Beans)来执行他们的工作时,JobStoreCMT是正确的。

通过设置 'org.quartz.jobStore.class'属性来选用JobStore:

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreCMT

JobStoreCMT通过以下属性来调整:

属性名

必须

类型

缺省值

org.quartz.jobStore.driverDelegateClass

yes

string

null

org.quartz.jobStore.dataSource

yes

string

null

org.quartz.jobStore.nonManagedTXDataSource

yes

string

null

org.quartz.jobStore.tablePrefix

no

string

"QRTZ_"

org.quartz.jobStore.useProperties

no

boolean

false

org.quartz.jobStore.misfireThreshold

no

int

60000

org.quartz.jobStore.isClustered

no

boolean

false

org.quartz.jobStore.clusterCheckinInterval

no

long

15000

org.quartz.jobStore.maxMisfiresToHandleAtATime

no

int

20

org.quartz.jobStore.dontSetAutoCommitFalse

no

boolean

false

org.quartz.jobStore.dontSetNonManagedTXConnectionAutoCommitFalse

no

boolean

false

org.quartz.jobStore.selectWithLockSQL

no

string

"SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE"

org.quartz.jobStore.txIsolationLevelSerializable

no

boolean

false

org.quartz.jobStore.txIsolationLevelReadCommitted

no

boolean

false

org.quartz.jobStore.dataSource

对于JobStoreCMT,数据源需要包含能够加入JTA(容器管理)事务里的连接。这就意味着数据源将在应用服务器里被配置和管理,并且,Quartz将通过JNDI获得一个句柄。

org.quartz.jobStore.nonManagedTXDataSource

JobStoreCMT需要一个数据源(以上说的第二个)连到不是容器管理的事务。这个值将是定义在配置属性文件的一个数据源名称,这个数据源必须包含非CMT的连接,换句话说,就是Quartz直接在连接上调用commit()和rollback()。

org.quartz.jobStore.dontSetNonManagedTXConnectionAutoCommitFalse
除了它应用于非TX数据源管理,其他的和org.quartz.jobStore.dontSetAutoCommitFalse一样

org.quartz.jobStore.txIsolationLevelReadCommitted
设置“true”,让Quartz在没有被管理的JDBC连接上调用setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED)。这可以阻止一些数据库(如DB2)在高加载和长时间事务的情况下发生的锁超时。

设置数据源

如果你用JDBC-JobStore,你将需要一个数据源(或在JobStoreCMT里要2个)。

数据源能通过2种方法配置:

  1. Quartz搜集所有指定在quartz.properties 文件里的属性来创建数据源。
  2. 指定一个定位于管理数据源的应用服务器的JNDI,这样Quartz能用它。

每个定义的数据源必须有个名字,你为这个数据源定义的一些属性必须包含这个名字,象下面的。数据源的“NAME”可以随便取,只有当我们把数据源赋给JDBCJobStore时,这个名字起到标示的作用,其他情况下没什么用。

Quartz自己创建数据源通过以下属性:

属性名

必须

类型

缺省值

org.quartz.dataSource.NAME.driver

yes

String

null

org.quartz.dataSource.NAME.URL

yes

String

null

org.quartz.dataSource.NAME.user

no

String

""

org.quartz.dataSource.NAME.password

no

String

""

org.quartz.dataSource.NAME.maxConnections

no

int

10

org.quartz.dataSource.NAME.validationQuery

no

String

null

org.quartz.dataSource.NAME.validationQuery
是一个可选的SQL查询字符串,数据源用它来核查和替代失败/被破坏的连接。例如,一个Oracle用户可能选择“select table_name from user_tables”-这是一个决不可能失败的查询,除非连接是坏的。

org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver

org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@10.0.1.23:1521:demodb

org.quartz.dataSource.myDS.user = myUser

org.quartz.dataSource.myDS.password = myPassword

org.quartz.dataSource.myDS.maxConnections = 30

引用应用服务器的数据源:

属性值

必须

类型

缺省值

org.quartz.dataSource.NAME.jndiURL

yes

String

null

org.quartz.dataSource.NAME.java.naming.factory.initial

no

String

null

org.quartz.dataSource.NAME.java.naming.provider.url

no

String

null

org.quartz.dataSource.NAME.java.naming.security.principal

no

String

null

org.quartz.dataSource.NAME.java.naming.security.credentials

no

String

null

org.quartz.dataSource.NAME.java.naming.factory.initial 
JNDI上下文初始化工厂的类名。

org.quartz.dataSource.NAME.java.naming.provider.url 
连接到JNDI上下文的URL。

org.quartz.dataSource.NAME.java.naming.security.principal 
连接到JNDI上下文的首要用户。

org.quartz.dataSource.NAME.java.naming.security.credentials 
连接到JNDI上下文的用户验证密码。

org.quartz.dataSource.myOtherDS.jndiURL=jdbc/myDataSource

org.quartz.dataSource.myOtherDS.java.naming.factory.initial=

com.evermind.server.rmi.RMIInitialContextFactory

org.quartz.dataSource.myOtherDS.java.naming.provider.url=ormi://localhost

org.quartz.dataSource.myOtherDS.java.naming.security.principal=admin

org.quartz.dataSource.myOtherDS.java.naming.security.credentials=123

设置集群

集群可以通过fail-overload balancing功能给调度器带来既高可靠性又可伸缩性两大优点。

集群目前仅能和JDBC-JobStore(JobStoreTX或JobStoreCMT)一起工作,本质上是让集群的每个节点共享一个数据库来工作的。

Load-balancing是自动出现的,集群的每个节点尽可能快地触发job。当一个触发器触发时刻到了,第一个将获取触发器(并加锁)的节点就是将要触发它的节点。

Fail-over是一个节点正在执行一个或多个jobs时失败了出现的。当一个节点失败了,其他的节点就会在数据库里核查条件和鉴别jobs,这些是节点失败时记录到了数据库的。在恢复节点时,任何标记了恢复(JobDetail里的"requests recovery"属性)的jobs将会被再次执行,没有标记的将会简单地释放掉。

通过设置“org.quartz.jobStore.isClustered”属性来使用集群。在集群里每个实例应该用一样的quartz.properties文件。用到的异常也应该是一样的:不同线程池大小,不同“org.quartz.scheduler.instanceName”属性值。每个节点应该用唯一的instanceId。我们可以设置org.quartz.scheduler.instanceId的值为“AUTO”来达到这个目的。

不要在一个分离开的机器上运行集群,除非他们的时钟是用时钟同步服务同步过的。如果不熟悉怎样同步,参考:http://www.boulder.nist.gov/timefreq/service/its.htm

其他实例在用数据表时,不要触发一个不是集群的也用这些数据表的实例。你会得到一些没用的数据。

#=================================================================# Configure Main Scheduler Properties 

#=================================================================org.quartz.scheduler.instanceName = MyClusteredScheduler

org.quartz.scheduler.instanceId = AUTO

#=================================================================# Configure ThreadPool 

#=================================================================

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

org.quartz.threadPool.threadCount = 25

org.quartz.threadPool.threadPriority = 5

#=================================================================# Configure JobStore 

#=================================================================

org.quartz.jobStore.misfireThreshold = 60000

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.OracleDelegate

org.quartz.jobStore.useProperties = false

org.quartz.jobStore.dataSource = myDS

org.quartz.jobStore.tablePrefix = QRTZ_

org.quartz.jobStore.isClustered = true

org.quartz.jobStore.clusterCheckinInterval = 20000

#=================================================================

# Configure Datasources 

#=================================================================

org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver

org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@polarbear:1521:dev

org.quartz.dataSource.myDS.user = quartz

org.quartz.dataSource.myDS.password = quartz

org.quartz.dataSource.myDS.maxConnections = 5

org.quartz.dataSource.myDS.validationQuery=select 0 from dual

14.Web应用中用Quartz

初始化调度器

我们可以在Web应用中的配置文件web.xml里设置一个Quartz的Servlet-QuartzInitializerServlet:

<web-app>

 

<servlet>

<servlet-name>QuartzInitializer</servlet-name> 
  <display-name>Quartz Initializer Servlet</display-name>
<servlet-class>
    org.quartz.ee.servlet.QuartzInitializerServlet
  </servlet-class> 
 
  <load-on-startup>1</load-on-startup>
 
  <init-param>
    <param-name>config-file</param-name>
    <param-value>/some/path/my_quartz.properties</param-value>
  </init-param>
 
  <init-param>
    <param-name>shutdown-on-unload</param-name>
    <param-value>true</param-value>
  </init-param>
 
  <init-param>
    <param-name>start-scheduler-on-load</param-name>
    <param-value>true</param-value>
  </init-param>
 
 </servlet> 
 
 <!-- other web.xml items here -->
 

</web-app>

说明:config-file参数值是StdSchedulerFactory用来实例化调度器的,可以把自己写的Quartz属性文件放在classPathWEB-INF/classes路径下。

访问调度器

Quartz1.5开始,QuartzInitializerServlet将自动储存StdSchedulerFactory实例在ServletContext里:

// Session中获得ServletContext

              ServletContext ctx =

                    request.getSession().getServletContext();

              // ServletContext中获得StdSchedulerFactory

StdSchedulerFactory factory = (StdSchedulerFactory)ctx.getAttribute(

QuartzFactoryServlet.QUARTZ_FACTORY_KEY);

              // StdSchedulerFactory中获得Scheduler

              Scheduler scheduler = factory.getScheduler();

              // 启动Scheduler

              scheduler.start();

FAQ

1.       怎样控制Job实例?

看看org.quartz.spi.JobFactory  the org.quartz.Scheduler.setJobFactory(..) 方法。

 

2.       在一个job完成之后,我怎样阻止它被删掉?

设置JobDetail.setDurability(true)-job是一个“孤儿”(没有trigger引用这个job)时,这将指示Quartz不要删掉它。

 

3.       怎样阻止job并行触发?

使job类实现StatefulJob接口而不是job接口。察看StatefulJob JavaDoc

 

4.       怎样使一个正在执行的job停下来?

看看org.quartz.InterruptableJob接口和Scheduler.interrupt(String, String)方法。

 

5.       怎样使Jobs的执行串联起来?

有两个方法:

一、用监听器

二、用JobDataMap

 

6.       怎样提高JDBC-JobStore的性能?

除了硬件的提高外,我们可以给我们建的Quartz表建索引:

create index idx_qrtz_t_next_fire_time on qrtz_triggers(NEXT_FIRE_TIME);

create index idx_qrtz_t_state on qrtz_triggers(TRIGGER_STATE);

create index idx_qrtz_t_nf_st on qrtz_triggers(TRIGGER_STATE,NEXT_FIRE_TIME);

create index idx_qrtz_ft_trig_name on qrtz_fired_triggers(TRIGGER_NAME);

create index idx_qrtz_ft_trig_group on qrtz_fired_triggers(TRIGGER_GROUP);

create index idx_qrtz_ft_trig_name on qrtz_fired_triggers(TRIGGER_NAME);

create index idx_qrtz_ft_trig_n_g on

qrtz_fired_triggers(TRIGGER_NAME,TRIGGER_GROUP);

create index idx_qrtz_ft_trig_inst_name on qrtz_fired_triggers(INSTANCE_NAME);

create index idx_qrtz_ft_job_name on qrtz_fired_triggers(JOB_NAME);

create index idx_qrtz_ft_job_group on qrtz_fired_triggers(JOB_GROUP);



实例  --  通过页面动态设置定时触发时间


1. 目的:动态设置时间,触发相应的任务 

2. 系统架构为 struts + spring + hibernate 

3. 实现步骤:
  • 在页面上设置时间;
  • 将时间转换为Unix Cron Expression;
  • 将转换后的时间规则表达式记录到数据库中(也可以写入xml文件中,这里是项目需要记录入数据库中);
  • 从数据库中得到相应的时间规则表达式;
  • 更新您的任务触发器的时间设置;
  • RESCHEDULE THE JOB。; - )
4. 具体实现细节:
1) 在页面上设置时间
根据具体的业务需求,设置时间规则,下面以某一项目为例,需要  按每月、每周、自定义分为三种规则。 
 1    < tr  > 
 2              < th  >  执行这个任务  </ th  > 
 3              < td   style  ="font-weight:bold;"  > 
 4               < html:radio   property  ="everyWhat"   styleClass  ="InputBorderNone"   value  ="monthly"   onclick  ="changeStatus(this.value)"  >  每月  </ html:radio  > 
 5               < html:radio   property  ="everyWhat"   styleClass  ="InputBorderNone"   value  ="weekly"   onclick  ="changeStatus(this.value)"  >  每周  </ html:radio  > 
 6               < html:radio   property  ="everyWhat"   styleClass  ="InputBorderNone"   value  ="userDefined"   onclick  ="changeStatus(this.value)"  >  自定义  </ html:radio  > 
 7               < html:hidden   property  ="jobName"   value  ="compare"     /> 
 8              </ td  > 
 9    </ tr  > 
10 

每月则需要选择该月的第几个星期的星期几 
 1    < tr   style  ="display:"   id  ="whichWeek"  > 
 2              < th  >  选择第几个星期  </ th  > 
 3              < td   style  ="font-weight:bold;"  > 
 4               < html:select   property  ="week"  > 
 5                < html:option   value  ="1"  >    </ html:option  > 
 6                < html:option   value  ="2"  >    </ html:option  > 
 7                < html:option   value  ="3"  >    </ html:option  > 
 8                < html:option   value  ="4"  >    </ html:option  > 
 9               </ html:select  > 
10               < html:select   property  ="dayOfMonth"  > 
11                < html:option   value  ="1"  >  星期日  </ html:option  > 
12                < html:option   value  ="2"  >  星期一  </ html:option  > 
13                < html:option   value  ="3"  >  星期二  </ html:option  > 
14                < html:option   value  ="4"  >  星期三  </ html:option  > 
15                < html:option   value  ="5"  >  星期四  </ html:option  > 
16                < html:option   value  ="6"  >  星期五  </ html:option  > 
17                < html:option   value  ="7"  >  星期六  </ html:option  > 
18               </ html:select  > 
19             </ td  >   
20     </ tr  > 
21 

每周则需要选择星期几 
 1    < tr   style  ="display:none"   id  ="whichDay"  > 
 2              < th  >  选择星期  </ th  > 
 3              < td   style  ="font-weight:bold;"  > 
 4               < html:select   property  ="dayOfWeek"  > 
 5                < html:option   value  ="1"  >  星期日  </ html:option  > 
 6                < html:option   value  ="2"  >  星期一  </ html:option  > 
 7                < html:option   value  ="3"  >  星期二  </ html:option  > 
 8                < html:option   value  ="4"  >  星期三  </ html:option  > 
 9                < html:option   value  ="5"  >  星期四  </ html:option  > 
10                < html:option   value  ="6"  >  星期五  </ html:option  > 
11                < html:option   value  ="7"  >  星期六  </ html:option  > 
12               </ html:select  > 
13            </ td  >   
14     </ tr  > 
15 

自定义则选择具体的日期,如 2007-1-10

三种规则都需要设定时间点
 1    < tr  > 
 2              < th  >  起始时间  </ th  > 
 3              < td   style  ="font-weight:bold;"  > 
 4               < html:select   property  ="timeType"   styleId  ="type"   onchange  ="changeStatus2(this.value)"  > 
 5                < html:option   value  ="AM"  >  上午  </ html:option  > 
 6                < html:option   value  ="PM"  >  下午  </ html:option  > 
 7               </ html:select  > 
 8               < html:select   property  ="hour"   styleId  ="amHours"  > 
 9                < html:option   value  ="1"  >  1  </ html:option  > 
10                < html:option   value  ="2"  >  2  </ html:option  > 
11                < html:option   value  ="3"  >  3  </ html:option  > 
12                < html:option   value  ="4"  >  4  </ html:option  > 
13                < html:option   value  ="5"  >  5  </ html:option  > 
14                < html:option   value  ="6"  >  6  </ html:option  > 
15                < html:option   value  ="7"  >  7  </ html:option  > 
16                < html:option   value  ="8"  >  8  </ html:option  > 
17                < html:option   value  ="9"  >  9  </ html:option  > 
18                < html:option   value  ="10"  >  10  </ html:option  > 
19                < html:option   value  ="11"  >  11  </ html:option  > 
20                < html:option   value  ="12"  >  12  </ html:option  > 
21               </ html:select  > 
22               < html:select   property  ="hour"   styleId  ="pmHours"   style  ="display:none"  > 
23                < html:option   value  ="13"  >  13  </ html:option  > 
24                < html:option   value  ="14"  >  14  </ html:option  > 
25                < html:option   value  ="15"  >  15  </ html:option  > 
26                < html:option   value  ="16"  >  16  </ html:option  > 
27                < html:option   value  ="17"  >  17  </ html:option  > 
28                < html:option   value  ="18"  >  18  </ html:option  > 
29                < html:option   value  ="19"  >  19  </ html:option  > 
30                < html:option   value  ="20"  >  20  </ html:option  > 
31                < html:option   value  ="21"  >  21  </ html:option  > 
32                < html:option   value  ="22"  >  22  </ html:option  > 
33                < html:option   value  ="23"  >  23  </ html:option  > 
34                < html:option   value  ="0"  >  0  </ html:option  > 
35               </ html:select  >   点
36                < html:text   property  ="minute"   name  ="minute"   style  ="width:20px;"   value  ="0"   onchange  ="valTime(this.value)"     />   分
37                < html:text   property  ="second"   name  ="second"   style  ="width:20px;"   value  ="0"   onchange  ="valTime(this.value)"     />   秒(0-59之间的整数)
38          </ td  >   
39    </ tr  > 
40 

OK. 这样我们的页面设置就完成了。: - ) 
上回说到,我们的设置页面已经做好了,接下来就是将时间转换为Unix Cron Expression。

2) 将时间转换为Unix Cron Expression

需要ActionForm将页面表单数据映射到Action中,然后在Action中转换为cron expression:
 
 1    SchedulerForm schedulerForm   =   (SchedulerForm) form;
 2          String jobName   =   schedulerForm.getJobName();
 3          String cronExpression   =     ""  ;
 4           String[] commonNeeds   =     {schedulerForm.getSecond(), schedulerForm.getMinute(), schedulerForm.getHour()}  ;
 5           String[] monthlyNeeds   =     {schedulerForm.getWeek(), schedulerForm.getDayOfMonth()}  ;
 6          String weeklyNeeds   =   schedulerForm.getDayOfWeek();
 7          String userDefinedNeeds   =   schedulerForm.getDate();
 8          String everyWhat   =   schedulerForm.getEveryWhat();
 9            //  得到时间规则 
10            cronExpression   =   CronExpConversion.getCronExpression(everyWhat, commonNeeds,
11                  monthlyNeeds, weeklyNeeds, userDefinedNeeds);
12 

我定义了一个  规则类来处理转换规则(写得不是很好 能用就行 嘿嘿) 
 1   
 2    /** 
 3  * 页面设置转为UNIX cron expressions 转换类
 4  * CronExpConversion
 5   */ 

 6    public     class   CronExpConversion   {
 7     
 8       /** 
 9      * 页面设置转为UNIX cron expressions 转换算法
10      *  @param  everyWhat
11      *  @param  commonNeeds 包括 second minute hour
12      *  @param  monthlyNeeds 包括 第几个星期 星期几
13      *  @param  weeklyNeeds  包括 星期几
14      *  @param  userDefinedNeeds  包括具体时间点
15      *  @return  cron expression
16       */ 

17      public   static  String convertDateToCronExp(String everyWhat,
18             String[] commonNeeds, String[] monthlyNeeds, String weeklyNeeds,
19              String userDefinedNeeds)  {
20         String cronEx  =   "" ;
21         String commons  =  commonNeeds[ 0  +   "   "   +  commonNeeds[ 1  +   "   " 
22                  +  commonNeeds[ 2  +   "   " ;
23         String dayOfWeek  =   "" ;
24           if  ( " monthly " .equals(everyWhat))  {
25              //  eg.: 6#3 (day 6 = Friday and "#3" = the 3rd one in the
26              //  month) 
27              dayOfWeek  =  monthlyNeeds[ 1 ]
28                      +  CronExRelated.specialCharacters
29                             .get(CronExRelated._THENTH)  +  monthlyNeeds[ 0 ];
30             cronEx  =  (commons
31                      +  CronExRelated.specialCharacters.get(CronExRelated._ANY)
32                      +   "   " 
33                      +  CronExRelated.specialCharacters.get(CronExRelated._EVERY)
34                      +   "   "   +  dayOfWeek  +   "   " ).trim();
35          } 
  else   if  ( " weekly " .equals(everyWhat))  {
36             dayOfWeek  =  weeklyNeeds;  //  1 
37              cronEx  =  (commons
38                      +  CronExRelated.specialCharacters.get(CronExRelated._ANY)
39                      +   "   " 
40                      +  CronExRelated.specialCharacters.get(CronExRelated._EVERY)
41                      +   "   "   +  dayOfWeek  +   "   " ).trim();
42          } 
  else   if  ( " userDefined " .equals(everyWhat))  {
43             String dayOfMonth  =  userDefinedNeeds.split( " - " )[ 2 ];
44               if  (dayOfMonth.startsWith( " 0 " ))  {
45                 dayOfMonth  =  dayOfMonth.replaceFirst( " 0 "  "" );
46             } 

47             String month  =  userDefinedNeeds.split( " - " )[ 1 ];
48               if  (month.startsWith( " 0 " ))  {
49                 month  =  month.replaceFirst( " 0 "  "" );
50             } 

51             String year  =  userDefinedNeeds.split( " - " )[ 0 ];
52              // FIXME 暂时不加年份 Quartz报错 
53               /* cronEx = (commons + dayOfMonth + " " + month + " "
54                     + CronExRelated.specialCharacters.get(CronExRelated._ANY)
55                     + " " + year).trim(); */ 

56             cronEx  =  (commons  +  dayOfMonth  +   "   "   +  month  +   "   " 
57                      +  CronExRelated.specialCharacters.get(CronExRelated._ANY)
58                      +   "   " ).trim();
59         } 

60          return  cronEx;
61     } 
    
62 } 

63 
这样就将页面的时间设置转为了Cron Expression。 
书接上回,上回说到,我们已经将页面的时间设置转为了Cron Expression,下面我记录了时间规则。

3) 记录时间规则

      我将时间规则存入数据库中,目的是为了生成历史日志,也可以存入XML文件中。当然您也可以省略此步,直接将转换后的规则放入相应的Quartz trigger中。

4) 更新任务触发器的时间设置

到了关键的一步了,也是最简单的一步,一个方法就可以实现了。
首先,我们需要通过trigger的名称得到一个CronTriggerBean;
其次,通过trigger的setCronExpression(String cronExp)方法将新的表达式注入;
最后,RESCHEDULE THE JOB,OK!
 
 1       /** 
 2      * 自定义定时器调度时间
 3      *  @param 
 triggerName 触发器名称
 4       *  @throws 
 Exception 
 5       */ 

 6      public   void   updateNotificationInterval(String triggerName, String triggerId)
 7               throws  SchedulerException, ParseException  
{
 8          // 得到trigger 

 9          CronTriggerBean trigger  =  (CronTriggerBean) scheduler.getTrigger(
10 
                triggerName, Scheduler.DEFAULT_GROUP);
11          // 得到cron expression         

12          String cronExpression  =  schedulerDAO.getCronExpression(triggerId);
13          // 设置trigger的时间规则 

14          trigger.setCronExpression(cronExpression);
15          // 重置job 

16          scheduler.rescheduleJob(triggerName, Scheduler.DEFAULT_GROUP, trigger);
17     } 

18 
至此,目的达到了,快乐的看着您的任务在您自定义的时间下快乐的执行,您是否也想像'Happy Feet'中那只快乐的企鹅一样,Show段踢踏呢 ; - D




PS:忘了说我的Quartz任务是怎么配置的了,罪过,罪过。

  < bean  id ="compareJob" 
        class 
="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean" > 
         
< property  name ="targetObject"  ref ="compareService"   /> 
         
< property  name ="targetMethod"  value ="compare"   /> 
         
< property  name ="concurrent"  value ="false"   /> 
     
</ bean >   

    
 < bean  id ="compareTrigger" 

        class 
="org.springframework.scheduling.quartz.CronTriggerBean" > 
         
< property  name ="jobDetail"  ref ="compareJob"   /> 
         
< property  name ="cronExpression" >              
             
< value > 0 39 16 20 * ? </ value > 
         
</ property > 
     
</ bean > 
 
 
< bean  id ="schedulerFactory" 
        class 
="org.springframework.scheduling.quartz.SchedulerFactoryBean" > 
         
< property  name ="triggers" > 
             
< list >                      
                     < ref  local ="compareTrigger"   /> 

             
</ list > 
         
</ property > 
     
</ bean >

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值