Quartz 项目应用笔记

jack_gogo 12年前

Quartz 项目应用笔记

Quartz 是一个强大的企业级 Schedule 工具,也是目前最好的开源 Schedule 工具,最近因为项目的需要,简单的用到了 Quartz 的一些功能,对项目中使用 Quartz 的一些问题做简单的记录。

 

Quartz 的应用中,我们用到了以下的一些东西,ScheduleFactory, Scheduler, Job, JobDetail, Trigger,简单说明一下他们的用途。

 

SchedulerFactory Scheduler 的工厂,我们可以从中获得受工厂管理的 Scheduler 对象。

 

SchedulerFactory scheduleFactory = new StdSchedulerFactory();

Scheduler scheduler = scheduleFactory.getScheduler();

 

Scheduler 是一个计划集,其中可以包含多个 JobDetail Trigger 组成的计划任务。

我们可以从 SchedulerFactory 中取得 Scheduler

 

接口Job是每个业务上需要执行的任务需要实现的接口,该接口只有一个方法:

 

public interface Job {

   public void execute(JobExecutionContext context)

       throws JobExecutionException;

}

 

我们可以在里面定义我们的 Job 执行逻辑,比如清除过期数据,更新缓存等。

 

JobDetail描述了一个任务具体的信息,比如名称,组名等等。

JobDetail jobDetail = new JobDetail("SayHelloWorldJob", Scheduler.DEFAULT_GROUP, SayHelloWorldJob.class);

在上面的构造方法中,第一个是任务的名称,第二个是组名,第三个就是实际当任务需要执行的回调类。

 

Trigger顾名思义就是触发器,Quartz有个很好的想法就是分离了任务和任务执行的条件。Trigger就是控制任务执行条件的类,当Trigger认为执行条件满足的时刻,Trigger会通知相关的Job去执行。分离的好处是:

1.你可以为某个Job关联多个Trigger,其中任何一个条件满足都可以触发job执行,这样可以完成一些组合的高级触发条件

2.Trigger失效后(比如:一个永远都不能满足的条件),你不必去声明一个新的job,代替的是你可以为job关联一个新的Triggerjob可以继续执行。

 

目前的Quartz实现中,存在两种Trigger,SimpleTriggerCronTrigger,SimpleTrigger用来完成一些比如固定时间执行的任务,比如:从现在开始1分钟后等等;而CronTrigger(没错,和unixcron进程的含意一样)用来执行calendar-like的任务,比如:每周五下午300,每月最后一天等等。

 

在我们项目中,都是一些固定时间的 Job,所以只用到了 SimpleTrigger

Trigger trigger = new SimpleTrigger("SayHelloWorldJobTrigger",Scheduler.DEFAULT_GROUP,new Date(),null,0,0L);

这个构造方法中,第一个是Trigger的名称,第二个是Trigger的组名,第三个是任务开始时间,第四个是结束时间,第五个是重复次数(使用SimpleTrigger.REPEAT_INDEFINITELY常量表示无限次),最后一个是重复周期(单位是毫秒),那么这样就创建了一个立刻并只执行一次的任务。

 

但我们定义好了 JobDetailJob,和 Trigger 后,就可以开始 Schedule 一个 Job 了。

 

scheduler.scheduleJob(jobDetail, trigger);

 

这条语句就是把jobTrigger关联,这样当Trigger认为应该触发的时候就会调用(实际上是Scheduler调用)job.execute方法了。

 

scheduler.start();

千万别忘了加上上面的语句,这条语句通知Quartz使安排的计划生效。

 

关于execute方法的参数JobExecutionContext

JobExecutionContext就和很多Context结尾的类功能一样,提供的运行时刻的上下文环境,JobExecutionContext中有Scheduler,JobDetail,Trigger等很多对象的引用,从而当你在execute方法内部须需要这些对象的时刻提供的便利。

 

在项目中,我们把需要执行的 Job 相对应的一些信息放在 JobExecutionContext 中,在 Job 执行的时候可以调用。

 

jobDetail.getJobDataMap().put(userid, id);

 

Job 中,我们可以拿到相关的 Context 信息:

 

jobExecutionContext.getJobDetail().getJobDataMap().getInt(userid);

 

JobDetailTriggernamegroup

Scheduler实例对应了很多jobtrigger的实例,为了方便的区分,Quartz使用namegroup这两个特性,正如你想向的一样,同一个group下不能有两个相同nameJobDetailTrigger同理,同一个Scheduler下不能有两个相同groupJobDetail,Trigger同理,JobDetailTrigger的完全限定名为:group + name

 

为了让服务器重启以后,我们的 Scheduler 信息仍然不丢失,我们通常采用数据库持久化 Scheduler 的信息。

DBScript Quartz 的下载包中的:quartz-1.6.0\docs\dbTables 下,选择自己使用的 DB 相应的 Script 导入数据库就可以了。

在应用中,我们需要配置一个 quartz.properties 才能正常使用 DB。我们可以在 quartz-1.6.0\examples\example10 中找到该文件的样例,稍作一些修改,就可以放到自己项目源码的根目录下使用了。

 

设置 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX 即可启用基于 JDBC Quartz 信息持久化。

 

根据项目情况设置以下配置信息:

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

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

org.quartz.jobStore.useProperties = false

org.quartz.jobStore.dataSource = myDS

org.quartz.jobStore.tablePrefix = QRTZ_

org.quartz.jobStore.isClustered = false

 

org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver

org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/myapplication

org.quartz.dataSource.myDS.user = root

org.quartz.dataSource.myDS.password =

org.quartz.dataSource.myDS.maxConnections = 5

 

但是光设置了 Database 不够,我们还需要在 Application 启动的时候自动启动 Scheduler 才行,我们只需要简单的写一个 Servlet Listener 并在 web.xml 中声明该 Listener ,在 Servlet 容易启动的时候,Scheduler 就开始自动执行。

 

public class ScheduleStartListener implements ServletContextListener {

   public void contextInitialized(ServletContextEvent servletContextEvent) {

       try {

          scheduleFactory.getScheduler().start();

       } catch (SchedulerException e) {

          // write log

       }

    }

 

   public void contextDestroyed(ServletContextEvent servletContextEvent) {

       try {

          scheduleFactory.getScheduler().shutdown();

       } catch (SchedulerException e) {

          // write log

       }

    }

}

 

web.xml 里面加入以下配置:

<listener>

   <listener-class>org.agilejava.scheduler.ScheduleStartListener</listener-class>

</listener>

 

以上简单的记录了在项目中关于 Quartz 的一些应用,如果有什么新的使用心得,会在后面继续加入的。

 

quartz的总结

关键字: Spring   spring quartz 学习心得    

Quartz 是一个强大的企业级 Schedule 工具,也是目前最好的开源 Schedule 工具。Spring中也集成了quartz的应用,下面就讲一下如何在spring中使用quartz

 

spring的配置:

 

xml 代码

1.<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">  

2. <property name="triggers">  

3. <list>  

4.  <ref bean="simpleTrigger"/>  

5.  <ref bean="cornTrigger"/>  

6. </list>  

7. </property>  

8.</bean>  

9.<bean id="schedulerControl" class="com.pheh.scheduler.Schedule">  

10. <property name="scheduler">  

11. <ref bean="schedulerFactoryBean"/>  

12. </property>  

13.</bean>  

14.  

15.<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">  

16. <property name="jobDetail">  

17. <ref bean="methodInvokingJobDetail"/>  

18. </property>  

19. <property name="startDelay">  

20. <value>1000</value>  

21. </property>  

22. <property name="repeatInterval">  

23. <value>3000</value>  

24. </property>  

25.</bean>  

26.<bean id="cornTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">  

27. <property name="jobDetail">  

28. <ref bean="methodInvokingJobDetail"/>  

29. </property>  

30. <property name="cronExpression">  

31. <value>0 0 */1 * * ?</value>  

32. </property>  

33.</bean>  

34.  

Job

org.quartz.Job是一个接口,只定义了void execute(JobExecutionContext context)throws JobExecutionException;一个方法。当定时任务被触发后,系统会自动调用实现了该接口的方法。在spring中,org.springframework.scheduling.quartz.QuartzJobBean对其进行了封装,使用了Template Method模式。主要是为了在使用jobDataMap时更加方便。QuartzJobBean有两个方法,

public final void execute(JobExecutionContext context) throws JobExecutionException

Job接口中定义的,spring在该方法里进行了些处理,将jobDataMap中的值注入到该Job的实现者中

 

protected abstract void executeInternal(JobExecutionContext context) throws JobExecutionException

这是一个抽象方法,用户在扩展了QuartzJobBean后,要自己实现该方法,在其中添加相应的业务逻辑

 

JobDetail

JobDetail描述了一个任务具体的信息。在Spring中,JobDetailBeanJobDetail进行了封装(继承了JobDetail)。

private String name;//名称

private String group = Scheduler.DEFAULT_GROUP;//

private String description;//描述

private Class jobClass;//定时任务触发时,回调的class,该class要实现Job接口或继承QuartzJobBean

private JobDataMap jobDataMap;//该任务存储的数据,在回调的时候也可以使用

private boolean volatility = false;//是否持久化到org.quartz.spi.JobStore

private boolean durability = false;//当该任务完成后,是否还在JobStore中继续保留该任务

private boolean shouldRecover = false;//当系统重新启动后,是否再次执行该任务

对于jobDataMap,它是是一个封装过的Map,使用方法同Map,如

jobDetailBean.getJobDataMap().put(target,value);

如果使用了QuartzJobBean,在使用jobDetailBean时,可将target的值设成QuartzJobBean的子类的属性名称,这样,在定时触发时,spring会自动将与target对应的value值注入到QuartzJobBean的子类中去。如。

 

 

java 代码

1....   

2.public class ReminderManager extends QuartzJobBean{   

3. private String reminderStr = "";   

4.}   

5....   

6.jobDetailBean.getJobDataMap().put(reminderStr,"abcdefg");   

7....  

 

这样当该任务被触发后,在ReminderManager中,reminderStr的值就会被注入为"abcdefg"

 

Trigger

trigger就是触发器。Quartz有个很好的想法就是分离了任务和任务执行的条件。Trigger就是控制任务执行条件的类,当Trigger认为执行条件满足的时刻,Trigger会通知相关的Job去执行。分离的好处是:

1.你可以为某个Job关联多个Trigger,其中任何一个条件满足都可以触发job执行,这样可以完成一些组合的高级触发条件

2.Trigger失效后(比如:一个永远都不能满足的条件),你不必去声明一个新的job,代替的是你可以为job关联一个新的Triggerjob可以继续执行。

目前的Quartz实现中,存在两种TriggerSimpleTriggerCronTrigger,在spring中分别用SimpleTriggerBeanCronTriggerBean对其进行封装。SimpleTrigger是简单触发器,如从某日到某日,每个一定时间进行一次提醒,在这段时间内进行多少次提醒;CronTrigger是复杂触发器,用来执行calendar-like的任务,可设定一些复杂的触发规则,如每年的x月的第y个星期五,或是每个星期天的几点进行提醒。后面附加一个日常语义与cronTrigger的转化

 

Trigger

private String name;//名称

private String group = Scheduler.DEFAULT_GROUP;//

private String jobName;//所关联的jobDetail的名称

private String jobGroup = Scheduler.DEFAULT_GROUP;//所关联的jobDetail的组

private String description;//描述

private JobDataMap jobDataMap;//该触发器存储的数据,在回调的时候也可以使用

private boolean volatility = false;//是否持久化到org.quartz.spi.JobStore

 

SimpleTrigger

private Date startTime = null;//开始日期

private Date endTime = null;//结束日期

private Date nextFireTime = null;//下次的触发时间

private Date previousFireTime = null;//上次的触发时间

private int repeatCount = 0;//重复次数

private long repeatInterval = 0;//重复间隔

private int timesTriggered = 0;/已触发的次数

 

SimpleTriggerBean

private JobDetail jobDetail;//所关联的JobDetail,方便在配置文件中使用

 

CornTrigger

private CronExpression cronEx = null;//触发条件表达式,它有一个String型的setter

private Date startTime = null;//开始日期

private Date endTime = null;//结束日期

private Date nextFireTime = null;//下次的触发时间

private Date previousFireTime = null;//上次的触发时间

private transient TimeZone timeZone = null;//所在时区

 

CronTriggerBean

private JobDetail jobDetail;//所关联的JobDetail,方便在配置文件中使用

 

Scheduler的常用方法

添加一个定时任务:

Date scheduleJob(JobDetail jobDetail,Trigger trigger)

 

修改一个定时任务,主要是更改trigger

Date rescheduleJob(String triggerName, String groupName, Trigger newTrigger)

 

删除一个定时任务,同时也会将于该jobDetail关联的trigger一并删除:

boolean deleteJob(String jobName,String jobGroup)

 

取得所有的jobDetail

String[] getJobGroupNames() 

 

取得某个group下的所有的jobDetail

String[] getJobNames(String groupName)

 

取得指定的jobDetail

JobDetail getJobDetail(String jobName, String jobGroup)

 

取得指定的jobDetail的所有的Trigger

Trigger[] getTriggersOfJob(String jobName, String groupName)

 

取得指定的Trigger

Trigger getTrigger(String triggerName, String triggerGroup)

 

Quartz的存储:

Quartz默认的是使用RAM存储所有的信息,但是这样的话,当我们重启服务器后,之前的所有的定时任务就全消失了。为了让服务器重启以后,我们的定时任务仍不丢失,我们可采用数据库持久化定时任务。

首先要先建立数据库,在quartz-1.6.0\docs\dbTables下,选择自己使用的数据库的sql脚本,建立相应的数据库表。

WEB-INF下加一个quartz.properties。我们可以在 quartz-1.6.0\examples\example10 中找到该文件的样例

 

 

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX #表明使用JDBC进行持久化

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

org.quartz.jobStore.useProperties = false

org.quartz.jobStore.dataSource = myDS

org.quartz.jobStore.tablePrefix = QRTZ_     #该值尽量不要改动,如改动,还要相应的修改sql脚本

org.quartz.jobStore.isClustered = false

 

org.quartz.dataSource.myDS.driver = net.sourceforge.jtds.jdbc.Driver

org.quartz.dataSource.myDS.URL = jdbc:jtds:sqlserver://192.168.1.101:1433/Northwind;autoReconnect=true

org.quartz.dataSource.myDS.user = sa

org.quartz.dataSource.myDS.password =

org.quartz.dataSource.myDS.maxConnections = 5

 

 

日常语义与cronTrigger的转化,以下setter,getter省略

 

java 代码

1.public class TDateRange{   

2. private int startType = 2;//开始类型。默认的使用2表示使用开始日期   

3. private Date startDate = new Date();//开始日期    

4. private int endType = 0;//结束类型。0表示无结束时间;1表示重复n次后结束;2表示使用结束日期   

5. private Date endDate = new Date();//结束日期   

6. private int occurrences;//执行次数   

7.}   

8.public class TFrequency{   

9. //0:无重复提醒   

10. //1:每every   

11. //2:每个工作日detail=1,2,3,4,5   

12. //3:每every周后的星期detail   

13. //4:每every月的detail   

14. //5:每every月的第num1个星期num2   

15. //6:每年num1num2   

16. //7:每年every月的第num1个月的星期num2   

17. private int type = 0;//频率类型   

18. private int every = 0;   

19. private String detail = "";   

20. private String num1 = "";   

21. private String num2 = "";   

22.}   

23.  

24.private String formatQuartzString(){   

25. String quartzStr = "";   

26. tiggernote="";   

27. //     

28. quartzStr = "0 "+this.dateRange.getStartDate().getMinutes()+" "+this.dateRange.getStartDate().getHours()+" ";   

29. switch(this.frequency.getType()){   

30. case 0://无重复提醒   

31. quartzStr += this.dateRange.getStartDate().getDate()+" "+(this.dateRange.getStartDate().getMonth()+1)+" ? "+(this.dateRange.getStartDate().getYear()+1900);   

32. tiggernote+="起始时间:"+quartzStr;   

33. break;   

34. case 1://XX天提醒   

35. quartzStr += "*/"+this.frequency.getEvery()+" * ? ";   

36. tiggernote+=""+this.frequency.getEvery()+"提醒";   

37. break;   

38. case 2://每个工作日detail=1,2,3,4,5   

39. //quartzStr += "? * 2-6";   

40. quartzStr ="0 */1 * * * ?"; //测试   

41. tiggernote+="每个工作日1,2,3,4,5提醒";   

42. break;   

43. case 3://every周后的星期detail   

44. quartzStr += "? * "+this.frequency.getDetail()+"/"+this.frequency.getEvery();   

45. tiggernote+=""+this.frequency.getEvery()+"周星期"+this.frequency.getDetail()+"";   

46. break;   

47. case 4://every个月的detail   

48. quartzStr += this.frequency.getDetail()+" */"+this.frequency.getEvery()+" ?";   

49. tiggernote+=""+this.frequency.getEvery()+""+this.frequency.getDetail()+"";   

50. break;   

51. case 5://every个月的第num1个星期num2   

52. quartzStr += "? */"+this.frequency.getEvery()+" "+this.frequency.getNum2();   

53. //星期   

54. if(Integer.valueOf(this.frequency.getNum1()).intValue()>0){   

55.  quartzStr += "#"+this.frequency.getNum1();   

56.  tiggernote+=""+this.frequency.getEvery()+"月第"+this.frequency.getNum1()+"个星期"+this.frequency.getNum2()+"";   

57. }else{   

58.  quartzStr += "L";   

59.  tiggernote+=""+this.frequency.getEvery()+"月星期"+this.frequency.getNum2();   

60. }   

61. break;   

62. case 6://每年num1num2   

63. quartzStr += this.frequency.getNum2()+" "+this.frequency.getNum1()+" ?";   

64.  tiggernote+="每年"+this.frequency.getNum1()+""+this.frequency.getNum2()+"";   

65. break;   

66. case 7://每年every月的第num1个星期num2   

67. quartzStr += "? "+this.getFrequency().getEvery()+" "+this.getFrequency().getNum2();   

68. //星期   

69. if(Integer.valueOf(this.frequency.getNum1()).intValue()>0){   

70.  quartzStr += "#"+this.frequency.getNum1();   

71.  tiggernote+="每年"+this.getFrequency().getEvery()+"月的第"+this.frequency.getNum1()+"个星期"+this.getFrequency().getNum2()+"";   

72. }else{    

73.  quartzStr += "L";   

74.  tiggernote+="每年"+this.getFrequency().getEvery()+"月的"+this.getFrequency().getNum2()+"";   

75. }    

76. break;   

77. default :   

78. }   

79. log.debug("quartzStr="+quartzStr);   

80. return quartzStr;   

81.}   

Scheduler

Scheduler 是一个计划集,其中可以包含多个 JobDetail Trigger 组成的计划任务。

Quartz中,我们可以通过

SchedulerFactory scheduleFactory = new StdSchedulerFactory();

Scheduler scheduler = scheduleFactory.getScheduler();

来取得scheduler,通过调用scheduler.start()来启动quartz

spring中,org.springframework.scheduling.quartz.SchedulerFactoryBean是对Quartzorg.quartz.Scheduler的封装,通过上面的配置,在spring启动的时候,quartz就会跟随着启动,不需要再用scheduler.start()来启动。在spring中,如果要取得scheduler,可通过上面的配置文件那样,将SchedulerFactoryBean注入到schdeuler中。

 

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/andilyliao/archive/2007/12/07/1922153.aspx

 

官方地址:http://www.quartz-scheduler.org/