Quartz 定时器

关键字: quartz 定时 调度

这篇文章是英文教程的中文翻译,有些认为暂时使用不到的特性有省略,英文文档参见http://www.opensymphony.com/quartz/wikidocs/TutorialLesson1.html


如何使用
使用QUARTZ调试程序之前,必须使用SchedlerFactory实例化Scheduler。一旦实例化Scheduler后可以启动或者停止,需要注意的是一旦Scheduler关闭,必须重新实例化后才能够重启。任务只有在Scheduler启动后才会执行。

 

下面的代码片断实例化并启动Scheduler,然后执行一个任务。

Java代码 复制代码
  1. SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();       
  2. Scheduler sched = schedFact.getScheduler();       
  3. sched.start();       
  4. JobDetail jobDetail = new JobDetail("myJob"null, DumbJob.class);       
  5. Trigger trigger = TriggerUtils.makeHourlyTrigger(); // fire every hour       
  6. trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date()));       
  7. trigger.setName("myTrigger");       
  8. sched.scheduleJob(jobDetail, trigger);     
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(); // fire every hour    
trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date()));    
trigger.setName("myTrigger");    
sched.scheduleJob(jobDetail, trigger);   

  

任务/触发器
 

要定义一个任务,只需要实现Job接口即可,Job接口如下:

Java代码 复制代码
  1. package org.quartz;public interface Job {  public void execute(JobExecutionContext context)          
  2.       throws JobExecutionException;          
  3. }     
package org.quartz;public interface Job {  public void execute(JobExecutionContext context)       
      throws JobExecutionException;       
}   

  
当任务被触发时将调用execute方法,JobExecutionContext 参数提供关于任务的运行时环境,包括一个Scheduler的引用,触发这个任务的触发器的引用,任务的JobDetail实例和一些别的信息。

 

JobDetail 对象在添加任务到Scheduler时创建,这个对象和*JobDataMap* 都用来保存Job实例的状态信息。

Trigger 用来触发任务。要计划一个任务,需要实例化一个触发器并设置相关的属性,触发器也有一个关联的JobDataMap用来传递参数给指定的任务。Quartz提供几个不同的触发器实例,比较常用的是SimpleTrigger和CronTrigger。

 

如果需要在一个指定的时间,或者指定的时间后以一个指定的间隔对一个任务重复执行多次,使用SimpleTrigger。如果需要基于日历安排任务,使用CronTrigger,比如每个星期五中午。

 

很多任务调用器没有分离任务和触发器,Quartz这样做有很多好处。比如一个任务可以和多个触发器关联,可以更改或替换一个触发器,而不必重新定义任务。

 

任务和触发器都有唯一标识名称,也可以进行分组,在一个组中任务和触发器的名称必须是唯一的,这意味着任务和触发器是使用名称+组名唯一标识的。如果不指定组名,相当于使用缺省的组名: Scheduler.DEFAULT_GROUP 。

 

任务
以前的Quartz要求具体的Job实现类通过get/set方法传递数据,现在使用JobDetail类来传递数据。

 

我们先来看一个实例:

Java代码 复制代码
  1. JobDetail jobDetail = new JobDetail("myJob"// job name                                   
  2.                                     sched.DEFAULT_GROUP, // job group (you can also specify 'null' to use the default group)       
  3.                                     DumbJob.class);               // the java class to executeTrigger trigger = TriggerUtils.makeDailyTrigger(8, 30);       
  4. trigger.setStartTime(new Date());       
  5. trigger.setName("myTrigger");sched.scheduleJob(jobDetail, trigger);    
JobDetail jobDetail = new JobDetail("myJob", // job name                                
                                    sched.DEFAULT_GROUP, // job group (you can also specify 'null' to use the default group)    
                                    DumbJob.class);               // the java class to executeTrigger trigger = TriggerUtils.makeDailyTrigger(8, 30);    
trigger.setStartTime(new Date());    
trigger.setName("myTrigger");sched.scheduleJob(jobDetail, trigger);  

  
如下是DumbJob类的代码:

Java代码 复制代码
  1. public class DumbJob implements Job {       
  2.        public DumbJob() {       
  3.        }       
  4.        public void execute(JobExecutionContext context)       
  5.            throws JobExecutionException       
  6.        {       
  7.            System.err.println("DumbJob is executing.");       
  8.        }       
  9.    }     
public class DumbJob implements Job {    
       public DumbJob() {    
       }    
       public void execute(JobExecutionContext context)    
           throws JobExecutionException    
       {    
           System.err.println("DumbJob is executing.");    
       }    
   }   

 
注意在添加一个任务时,传递了一个JobDetail实例作为参数,构建这个JobDetail实例时需要一个任务类参数。每次调用程序执行任务时,创建一个新的任务类实例并执行其execute方法,但是这种方法有一些限制,首先是所有的任务实现必须提供一个无参数的构造函数,还有就是任务实现不应该包含成员字段,因为在每次执行后这些值都会被消除。

 

那么应该如何给一个任务提供属性或者配置呢?如何在任务的不同执行过程中保存或跟踪任务的状态呢?这是通过JobDetail的JobDataMap来实现。

 

JobDataMap
JobDataMap可以用来保存任何需要传递给任务实例的对象(这些对象要求是可序列化的),JobDataMap是java的Map接口的实现,添加了一些便利方法,下面的代码片断描述了如何使用JobDataMap保存数据:

Java代码 复制代码
  1. jobDetail.getJobDataMap().put("jobSays""Hello World!");       
  2. jobDetail.getJobDataMap().put("myFloatValue"3.141f);       
  3. jobDetail.getJobDataMap().put("myStateData"new ArrayList());    
jobDetail.getJobDataMap().put("jobSays", "Hello World!");    
jobDetail.getJobDataMap().put("myFloatValue", 3.141f);    
jobDetail.getJobDataMap().put("myStateData", new ArrayList());  

  
下面的示例描述了如何在任务执行过程中从JobDataMap获取数据:

Java代码 复制代码
  1. public class DumbJob implements Job {              
  2.        public DumbJob() {       
  3.        }              
  4.       
  5.        public void execute(JobExecutionContext context)       
  6.            throws JobExecutionException       
  7.        {       
  8.            String instName = context.getJobDetail().getName();       
  9.            String instGroup = context.getJobDetail().getGroup();                  
  10.           JobDataMap dataMap = context.getJobDetail().getJobDataMap();                  
  11.            String jobSays = dataMap.getString("jobSays");       
  12.            float myFloatValue = dataMap.getFloat("myFloatValue");       
  13.            ArrayList state = (ArrayList)dataMap.get("myStateData");       
  14.            state.add(new Date());                  
  15.            System.err.println("Instance " + instName + " of DumbJob says: " + jobSays);       
  16.        }       
  17.    }      
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(随后会有讨论),需要小心决定将JobDataMap保存在什么地方,因为JobDataMap对象中保存的对象是可序列化的,因此可能会遇到类版本问题。

 

有状态/无状态
触发器也可以使用JobDataMap保存数据。当需要使用多个触发器重用保存在Scheduler中的单个任务实例时,并且针对每个触发器希望提供任务不同的数据时,这是比较有用的。

 

JobDataMap在任务执行过程中,可以在JobExecutionContext中找到,这里的JobDataMap是JobDetail中的JobDataMap和触发器中的JobDataMap合并的结果,如果遇到命名相同的元素,后者会重写前者。

 

如下是从JobExecutionContext中获取JobDataMap的代码片断:

Java代码 复制代码
  1. public class DumbJob implements Job {                 
  2.     public DumbJob() {       
  3.     }                
  4.     public void execute(JobExecutionContext context)       
  5.                 throws JobExecutionException       
  6.      {       
  7.                 String instName = context.getJobDetail().getName();       
  8.                 String instGroup = context.getJobDetail().getGroup();                       
  9.                 JobDataMap dataMap = context.getJobDataMap();    // Note the difference from the previous example                       
  10.                  String jobSays = dataMap.getString("jobSays");       
  11.                 float myFloatValue = dataMap.getFloat("myFloatValue");       
  12.                 ArrayList state = (ArrayList)dataMap.get("myStateData");       
  13.                 state.add(new Date());                       
  14.                  System.err.println("Instance " + instName + " of DumbJob says: " + jobSays);       
  15.           }       
  16.     }      
  17.    
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();    // Note the difference from the previous example                    
                 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);    
          }    
    }   
 

 

有状态任务
任务可以被定义成有状态或无状态的,无状态任务仅仅通过JobDataMap传递数据,这意味着每次任务执行后对JobDataMap的改变都会丢失,而有状态任务恰恰相反,每次任务执行后JobDataMap都被恢复。有状态任务不能并发执行。

实现*StatefulJob*接口的任务是有状态的

 

任务属性
下面是通过JobDetail对象定义的一些任务属性:

Durability - 如果这个值为false,每次任务没有活动的触发器关联时都将从Scheduler中删除。
Volatility - 如果任务是暂态的,在每次重启Scheduler时将不会被持久化RequestsRecovery - 如果任务是"requests recovery",当他在Scheduler关闭的时间正在执行时,当Scheduler再次启动时将再次被执行。

JobListeners - 任务可以添加多个JobListener实例,当任务执行时,这些监听器将接收到通知。

JobExecutionException - Job的execute方法只能抛出JobExecutionException,这就意味着通常你需要try-catch方法中的所有代码。详细信息可以参考JAVADOC。

 

触发器
Calendars
Quartz Calendar 对象 (不是 java.util.Calendar对象) 可以和触发器关联,当需要从触发器中排除一些时间时,Calendar是比较有用的。比如你希望创建一个触发器,在每个星期三的上午九点激活一个任务,然后通过加一个Calendar实例排除所有的节假日。Calendar接口如下:

Java代码 复制代码
  1. package org.quartz;            
  2. public interface Calendar        
  3. {                    
  4.     public boolean isTimeIncluded(long timeStamp);        
  5.     public long getNextIncludedTime(long timeStamp);            
  6. }    
package org.quartz;         
public interface Calendar     
{                 
    public boolean isTimeIncluded(long timeStamp);     
    public long getNextIncludedTime(long timeStamp);         
}  

  
注意这些方法的参数单位是微秒,这意味着Calendar可以精确到微秒,但是通常我们只关心天,Quartz提供一个org.quartz.impl.HolidayCalendar类用来简化Calendar的使用。

 

Calendar必须使用Scheduler的addCalendar方法进行注册。如果使用HolidayCalendar,实例化后应该调用addExcludedDate(Date date)添加那些需要排除的日期,同一个Calendar可以用于多个触发器。

 

Java代码 复制代码
  1. HolidayCalendar cal = new HolidayCalendar();       
  2. cal.addExcludedDate( someDate );           
  3. sched.addCalendar("myHolidays", cal, false);           
  4.       
  5. Trigger trigger = TriggerUtils.makeHourlyTrigger(); // fire every one hour interval       
  6. trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date()));    // start on the next even hour       
  7. trigger.setName("myTrigger1");          
  8. trigger.setCalendarName("myHolidays");    // .. schedule job with trigger           
  9.       
  10. Trigger trigger2 = TriggerUtils.makeDailyTrigger(80); // fire every day at 08:00       
  11. trigger.setStartTime(new Date()); // begin immediately       
  12. trigger2.setName("myTrigger2");          
  13. trigger2.setCalendarName("myHolidays");    // .. schedule job with trigger2     
HolidayCalendar cal = new HolidayCalendar();    
cal.addExcludedDate( someDate );        
sched.addCalendar("myHolidays", cal, false);        
   
Trigger trigger = TriggerUtils.makeHourlyTrigger(); // fire every one hour interval    
trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date()));    // start on the next even hour    
trigger.setName("myTrigger1");       
trigger.setCalendarName("myHolidays");    // .. schedule job with trigger        
   
Trigger trigger2 = TriggerUtils.makeDailyTrigger(8, 0); // fire every day at 08:00    
trigger.setStartTime(new Date()); // begin immediately    
trigger2.setName("myTrigger2");       
trigger2.setCalendarName("myHolidays");    // .. schedule job with trigger2   

TriggerUtils
TriggerUtils 类包含一些创建触发器和日期的便利方法。使用这个类可以很容易的创建基于分钟,小时,天,星期,月的触发器。

 

TriggerListener
触发器也可以注册监听器,监听器必须实现*TriggerListener* 接口。

 

SimpleTrigger
如果需要计划一个任务在指定的时间执行,或者在指定的时间后以指定的间隔连续执行多次,比如希望在2005年1月12号上午11:22:54开始执行一个任务,在这之后每隔20分钟执行一次,共执行一次,这种情况下可以使用SimpleTrigger。

SimpleTrigger包含几个属性:开始时间,结束时间,重复次数和间隔。

 

重复次数可以是大于等于0,或者是常量值SimpleTrigger.REPEAT_INDEFINITELY,间隔必须大于等于0的长整数,单位是微秒。如果间隔为0表示并发执行重复次数。

 

如果不熟悉java.util.Calendar类,可能经常需要根据开始时间计算触发时间,org.quartz.helpers.TriggerUtils 可以帮助完成这些任务。

 

结束时间属性重写重复次数属性。如果希望创建一个触发器,每隔10秒执行一次,直到一个指定的时间,可以简单的指定结束时间, 重复次数值为REPEAT_INDEFINITELY。

 

SimpleTrigger有几个构造函数,下面是其中一个:

Java代码 复制代码
  1. public SimpleTrigger(String name,       
  2.                        String group,       
  3.                        Date startTime,       
  4.                        Date endTime,       
  5.                        int repeatCount,       
  6.                        long repeatInterval)      
public SimpleTrigger(String name,    
                       String group,    
                       Date startTime,    
                       Date endTime,    
                       int repeatCount,    
                       long repeatInterval)    

 

创建一个10秒钟后只执行一次的触发器:

Java代码 复制代码
  1. long startTime = System.currentTimeMillis() + 10000L;       
  2.       
  3. SimpleTrigger trigger = new SimpleTrigger("myTrigger",       
  4.                                             null,       
  5.                                             new Date(startTime),       
  6.                                             null,       
  7.                                             0,       
  8.                                             0L);      
long startTime = System.currentTimeMillis() + 10000L;    
   
SimpleTrigger trigger = new SimpleTrigger("myTrigger",    
                                            null,    
                                            new Date(startTime),    
                                            null,    
                                            0,    
                                            0L);    

 

创建一个每隔60秒重复执行的触发器:

Java代码 复制代码
  1. SimpleTrigger trigger = new SimpleTrigger("myTrigger",       
  2.                                             null,       
  3.                                             new Date(),       
  4.                                             null,       
  5.                                             SimpleTrigger.REPEAT_INDEFINITELY,       
  6.                                             60L * 1000L);     
SimpleTrigger trigger = new SimpleTrigger("myTrigger",    
                                            null,    
                                            new Date(),    
                                            null,    
                                            SimpleTrigger.REPEAT_INDEFINITELY,    
                                            60L * 1000L);   


创建一个40秒后开始执行,每隔10秒执行一次的触发器:

Java代码 复制代码
  1. long endTime = System.currentTimeMillis() + 40000L;       
  2.       
  3. SimpleTrigger trigger = new SimpleTrigger("myTrigger",       
  4.                                             "myGroup",       
  5.                                             new Date(),       
  6.                                             new Date(endTime),       
  7.                                             SimpleTrigger.REPEAT_INDEFINITELY,       
  8.                                             10L * 1000L);      
long endTime = System.currentTimeMillis() + 40000L;    
   
SimpleTrigger trigger = new SimpleTrigger("myTrigger",    
                                            "myGroup",    
                                            new Date(),    
                                            new Date(endTime),    
                                            SimpleTrigger.REPEAT_INDEFINITELY,    
                                            10L * 1000L);    

 创建一个触发器,在2002年3月17日开始执行,重复5次,每次间隔为30秒:

 

Java代码 复制代码
  1. java.util.Calendar cal = new java.util.GregorianCalendar(2002, cal.MARCH, 17);       
  2.   cal.set(cal.HOUR, 10);       
  3.   cal.set(cal.MINUTE, 30);       
  4.   cal.set(cal.SECOND, 0);       
  5.   cal.set(cal.MILLISECOND, 0);  Data startTime = cal.getTime()  SimpleTrigger trigger = new SimpleTrigger("myTrigger",       
  6.                                             null,       
  7.                                             startTime,       
  8.                                             null,       
  9.                                             5,       
  10.                                             30L * 1000L);     
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);   

 

CronTrigger

如果需要基于日历指定触发器,可以使用CronTrigger。使用CronTrigger可以实现类似的触发器,比如:每个星期五的下午。比如每个星期一,三和五的上午9点到10点之间每隔5分钟。

 

CronTrigger也有一个开始时间和结束时间属性,用来指定什么时候任务开始和结束。

 

Cron表达式:*Cron*表达式用来配置CronTrigger。Cron表达式是一个由七个部分组成的字符串,这七个部分用空隔进行分隔:

       Seconds 
       Minutes
       Hours
       Day-of-Month (月内日期)
       Month
       Day-of-Week (周内日期)
       Year (可选字段)

 

       每个字段都有一些有效值。比如秒和分可以取值0-59,小时可以取值0-23。Day-of-Month可以取值0-31,需要注意一个月有多少天。 月可以取值0-11,或者通过使用JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV 和 DEC。 Days-of-Week可以取值1-7(1==Sunday)或者SUN, MON, TUE, WED, THU, FRI 和SAT。

 

'/' 字符可以用来指定增量,比如如果指定Minute字段为"0/15"表示在第0分钟启动,每隔15分钟的间隔;"3/20"表示每三分钟启动,每隔20分钟的间隔。

 

       '?' 字符可以在day-of-month和day-of-week 字段中使用。问号表示这个字段不包含具体值。所以,如果指定月内日期,可以在周内日期字段中插入“?”,表示周内日期值无关紧要。

 

'L'字符可以在day-of-month和day-of-week 字段中使用,这个字符表示最后一个的意思。比如在day-of-month字段中表示这个月的最后一天,如果在day-of-week字段表示"7"或者"SAT",但是如果在day-of-week字段L在另一个值后面,意味着这个月的最后XXX天,比如"6L"表示这个月的最后一个星期五。使用这个字符,不能指定列表,范围值。

 

'W'字符用来指定离指定天最近的星期XXX,比如如果day-of-month字段值为"15W",表示离这个月15号最近的一个

weekday。

 

'#'字符用来表示这个月的第几个XXX,比如day-of-week字段的"6#3"表示这个月的第三个星期五。

 

 '*'字符表示是通配字符,表示该字段可以接受任何可能的值,比如Day-Of-Week字段的*表示每天。

 

下面是一些示例:
创建一个每五分钟激活一次的触发器: 

Java代码 复制代码
  1. "0 0/5 * * * ?"    
"0 0/5 * * * ?"  

 创建一个触发器在当前分钟的第10秒后,每五分钟执行一次,比如上午10:00:10 am,上午10:05:10:

Java代码 复制代码
  1. "10 0/5 * * * ?"      
"10 0/5 * * * ?"    

 创建一个触发器,在每个星期三和星期五的10:30, 11:30, 12:30, 和13:30执行。

Java代码 复制代码
  1. "0 30 10-13 ? * WED,FRI"   
"0 30 10-13 ? * WED,FRI" 

    创建一个触发器,在每个月的第5天和第20天的上午8点到10点执行,每隔半小时执行一次,注意上午10:00不会执行:

Java代码 复制代码
  1. "0 0/30 8-9 5,20 * ?"    
"0 0/30 8-9 5,20 * ?"  

  
监听器

基于触发器的监听器接口如下:

Java代码 复制代码
  1. public interface TriggerListener {       
  2.       
  3.     public String getName();       
  4.       
  5.     public void triggerFired(Trigger trigger, JobExecutionContext context);       
  6.       
  7.     public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);       
  8.       
  9.     public void triggerMisfired(Trigger trigger);       
  10.       
  11.     public void triggerComplete(Trigger trigger, JobExecutionContext context,       
  12.             int triggerInstructionCode);       
  13. }      
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);    
}    


基于任务的监听器接口如下:

 

Java代码 复制代码
  1. public interface JobListener {       
  2.       
  3.     public String getName();       
  4.       
  5.     public void jobToBeExecuted(JobExecutionContext context);       
  6.       
  7.     public void jobExecutionVetoed(JobExecutionContext context);       
  8.       
  9.     public void jobWasExecuted(JobExecutionContext context,       
  10.             JobExecutionException jobException);       
  11.       
  12. }      
public interface JobListener {    
   
    public String getName();    
   
    public void jobToBeExecuted(JobExecutionContext context);    
   
    public void jobExecutionVetoed(JobExecutionContext context);    
   
    public void jobWasExecuted(JobExecutionContext context,    
            JobExecutionException jobException);    
   
}    

 

注册监听器
要创建一个监听器,只需要实现相应的接口就可以了。监听器需要在Scheduler中注册,监听器可以被注册为全局的或者本地的,注册监听器时必须指定一个名字,或者监听器本身的getName方法返回一个值。

 

Java代码 复制代码
  1. scheduler.addGlobalJobListener(myJobListener);       
  2. or       
  3. scheduler.addJobListener(myJobListener);      
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值