什么是Quartz
ublic void execute(JobExecutionContext context) throws JobExecutionExceptio
在Job接口实现类里面,添加需要的逻辑到execute()方法中。配置好Job实现类并设定好调度时间表,Quartz就会自动在设定的时间调度作业执行execute()。
整合了Quartz的应用程序可以重用不同事件的作业,还可以为一个事件组合多个作业。Quartz通过属性文件来配置JDBC事务的数据源、全局作业、触发器侦听器、插件、线程池等等。
Quartz是由James House创建并最初于2001年春天被加入sourceforge工程。接下来的几年里,有很多的新特性和版本出现,但是直到项目迁移到新的站点并成为OpenSymphony项目家族的一员,才开始真正启动并受到也有的关注。
目前的版本已经是2.0以上,v2.x相对于v1.x有很多新特性出现,并有很多的改动,具体参见Quartz官网上说明。这里介绍的仍然是v1.x(v1.8.6)。
"Hello, Quartz"
配置环境:
1. 下载Quartz
2. 阅读Readme.txt,了解每个jar包的作用,将quartz.jar包和lib/下的几个jar包、以及相关依赖的jar包放在工程的classpath中
先来看一个简单的Quartz应用,让它每隔5s打印"Hello, Quartz",打印10次。
代码清单1:创建任务
import java.util.Date; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class HelloQuartzJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("Hello, Quartz! - executing its JOB at "+ new Date() + " by " + context.getTrigger().getName()); } }
为了调度此任务执行,需要先得到一个Schedule实例,然后创建一个包含任务信息的JobDetail,最后创建一个Trigger管理任务的执行。
代码清单2:调度任务
import java.sql.Date; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.SimpleTrigger; import org.quartz.impl.StdSchedulerFactory; public class HelloQuartzScheduling { public static void main(String[] args)throws SchedulerException { SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); JobDetail jobDetail = new JobDetail("helloQuartzJob", Scheduler.DEFAULT_GROUP, HelloQuartzJob.class); SimpleTrigger simpleTrigger = new SimpleTrigger("simpleTrigger", Scheduler.DEFAULT_GROUP); simpleTrigger.setStartTime(new Date(System.currentTimeMillis())); simpleTrigger.setRepeatInterval(5000); simpleTrigger.setRepeatCount(10); scheduler.scheduleJob(jobDetail, simpleTrigger); scheduler.start(); } }
运行结果:
可以看到,其实它执行了11次。此处没有配置log4j.properties属性文件。
整个任务创建及调度的简单示意图如下。
Job接口包含唯一方法execute(),将任务逻辑添加到该方法中。StdSchedulerFactory.getScheduler()返回一个可运行的实例,然后创建调度任务的JobDetail实例,并传递3个参数给构造方法。第一个参数是任务名,用于引用该任务。第二个参数是任务组名,这里使用默认名,任务组名用于引用集合起来的一组任务,如可以使用Scheduler.pauseJobGroup()来暂停一组任务,每个组中的任务名是唯一的。第三个参数是实现特定任务的类。创建JobDetail实例后,需要创建一个Trigger,这里使用的是SimpleTrigger类,它提供了JDK Timer风格的触发器行为。传递给SimpleTrigger构造方法的两个参数分别是触发器名和任务组名,触发器名在它所在的任务组中必须是唯一的。接下来是设置触发器的一些属性,setStartTime()是设置启动时间,setRepeatInterval()是设置重复间隔,setRepeatCount()是设置重复次数。最后,scheduler.start()启动调度,终止调度可以用stop()方法。
CronTrigger类
Quartz有两大触发器,除了上面使用的SimpleTrigger外,就是CronTrigger。CronTrigger能够提供复杂的触发器表达式的支持。CronTrigger是基于Unix Cron守护进程,它是一个调度程序,支持简单而强大的触发器语法。
使用CronTrigger主要的是要掌握Cron表达式。Cron表达式包含6个必要组件和一个可选组件,如下表所示。
位置 |
含义 |
允许的特殊字符 |
1 |
秒(0~59) |
, - * / |
2 |
分(0~59) |
, - * / |
3 |
小时(0~24) |
, - * / |
4 |
日期(1~31) |
, - * / ? L W C |
5 |
月(JAN~DEC或1~12) |
, - * / |
6 |
星期(SUN~SAT或1~7) |
, - * / ? L C # |
7 |
年(可选,1970~2099),若为空,表示全部时间范围 |
, - * / |
位置
含义
允许的特殊字符
秒(0~59)
, - * /
分(0~59)
, - * /
小时(0~24)
, - * /
日期(1~31)
, - * / ? L W C
月(JAN~DEC或1~12)
, - * /
星期(SUN~SAT或1~7)
, - * / ? L C #
年(可选,1970~2099),若为空,表示全部时间范围
, - * /
特殊字符的含义,见下表。
特殊字符 |
说明 |
* |
通配符,任意值 |
? |
无特定值。通常和其他指定的值一起使用,表示必须显示该值但不能检查 |
- |
范围。e.g.小时部分10-12表示10:00,11:00, 12:00 |
, |
列分隔符。可以让你指定一系列的值。e.g.在星期域中指定MON、TUE和WED |
/ |
增量。表示一个值的增量,e.g.分钟域中0/1表示从0开始,每次增加1mi |
L |
表示Last。它在日期和星期域中表示有所不同。在日期域中,表示这个月的最后一天,而在星期域中,它永远是7(星期六)。当你希望使用星期中某一天时,L字符非常有用。e.g.星期域中6L表示每一个月的最后一个星期五 |
W |
在本月内离当天最近的工作日触发,所谓的最近工作日,即当天到工作日的前后最短距离,如果当天即为工作日,则距离是0;所谓本月内指的是不能跨月取到最近工作日,即使前/后月份的最后一天/第一天确实满足最近工作日。e.g. LW表示本月的最后一个工作日触发,W强烈依赖月份。 |
# |
表示该月的第几个星期,e.g. 1#2表示每一个月的第一个星期一 |
C |
日历值。日期值是根据一个给定的日历计算出来的。在日期域中给定一个20C将在20日(日历包括20日)或20日后日历中包含的第一天(不包括20日)激活触发器。例如在一个星期域中使用6C表示日历中星期五(日历包括星期五)或者第一天(日历不包括星期五) |
特殊字符
说明
通配符,任意值
无特定值。通常和其他指定的值一起使用,表示必须显示该值但不能检查
范围。e.g.小时部分10-12表示10:00,11:00, 12:00
列分隔符。可以让你指定一系列的值。e.g.在星期域中指定MON、TUE和WED
增量。表示一个值的增量,e.g.分钟域中0/1表示从0开始,每次增加1mi
表示Last。它在日期和星期域中表示有所不同。在日期域中,表示这个月的最后一天,而在星期域中,它永远是7(星期六)。当你希望使用星期中某一天时,L字符非常有用。e.g.星期域中6L表示每一个月的最后一个星期五
在本月内离当天最近的工作日触发,所谓的最近工作日,即当天到工作日的前后最短距离,如果当天即为工作日,则距离是0;所谓本月内指的是不能跨月取到最近工作日,即使前/后月份的最后一天/第一天确实满足最近工作日。e.g. LW表示本月的最后一个工作日触发,W强烈依赖月份。
表示该月的第几个星期,e.g. 1#2表示每一个月的第一个星期一
日历值。日期值是根据一个给定的日历计算出来的。在日期域中给定一个20C将在20日(日历包括20日)或20日后日历中包含的第一天(不包括20日)激活触发器。例如在一个星期域中使用6C表示日历中星期五(日历包括星期五)或者第一天(日历不包括星期五)
Cron表达式举例:
"30 * * * * ?" 每半分钟触发任务
"30 10 * * * ?" 每小时的10分30秒触发任务
"30 10 1 * * ?" 每天1点10分30秒触发任务
"30 10 1 20 * ?" 每月20号1点10分30秒触发任务
"30 10 1 20 10 ? *" 每年10月20号1点10分30秒触发任务
"30 10 1 20 10 ? 2011" 2011年10月20号1点10分30秒触发任务
"30 10 1 ? 10 * 2011" 2011年10月每天1点10分30秒触发任务
"30 10 1 ? 10 SUN 2011" 2011年10月每周日1点10分30秒触发任务
"15,30,45 * * * * ?" 每15秒,30秒,45秒时触发任务
"15-45 * * * * ?" 15到45秒内,每秒都触发任务
"15/5 * * * * ?" 每分钟的每15秒开始触发,每隔5秒触发一次
"15-30/5 * * * * ?" 每分钟的15秒到30秒之间开始触发,每隔5秒触发一次
"0 0/3 * * * ?" 每小时的第0分0秒开始,每三分钟触发一次
"0 15 10 ? * MON-FRI" 星期一到星期五的10点15分0秒触发任务
"0 15 10 L * ?" 每个月最后一天的10点15分0秒触发任务
"0 15 10 LW * ?" 每个月最后一个工作日的10点15分0秒触发任务
"0 15 10 ? * 5L" 每个月最后一个星期四的10点15分0秒触发任务
"0 15 10 ? * 5#3" 每个月第三周的星期四的10点15分0秒触发任务
"30 10 * * * ?" 每小时的10分30秒触发任务
"30 10 1 * * ?" 每天1点10分30秒触发任务
"30 10 1 20 * ?" 每月20号1点10分30秒触发任务
"30 10 1 20 10 ? *" 每年10月20号1点10分30秒触发任务
"30 10 1 20 10 ? 2011" 2011年10月20号1点10分30秒触发任务
"30 10 1 ? 10 * 2011" 2011年10月每天1点10分30秒触发任务
"30 10 1 ? 10 SUN 2011" 2011年10月每周日1点10分30秒触发任务
"15,30,45 * * * * ?" 每15秒,30秒,45秒时触发任务
"15-45 * * * * ?" 15到45秒内,每秒都触发任务
"15/5 * * * * ?" 每分钟的每15秒开始触发,每隔5秒触发一次
"15-30/5 * * * * ?" 每分钟的15秒到30秒之间开始触发,每隔5秒触发一次
"0 0/3 * * * ?" 每小时的第0分0秒开始,每三分钟触发一次
"0 15 10 ? * MON-FRI" 星期一到星期五的10点15分0秒触发任务
"0 15 10 L * ?" 每个月最后一天的10点15分0秒触发任务
"0 15 10 LW * ?" 每个月最后一个工作日的10点15分0秒触发任务
"0 15 10 ? * 5L" 每个月最后一个星期四的10点15分0秒触发任务
将上面HelloQuartz例子中SimpleTrigger换成CronTrigger,代码如下。
代码清单3:CronTrigger调度器
import java.text.ParseException; import org.quartz.CronTrigger; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.impl.StdSchedulerFactory; public class HelloQuartzScheduling { public static void main(String[] args) throws SchedulerException, ParseException { SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); JobDetail jobDetail = new JobDetail("helloQuartzJob", Scheduler.DEFAULT_GROUP, HelloQuartzJob.class); String cronExpression = "30/5 * * * * ?"; // 每分钟的30s起,每5s触发任务 CronTrigger cronTrigger = new CronTrigger("cronTrigger", Scheduler.DEFAULT_GROUP, cronExpression); scheduler.scheduleJob(jobDetail, cronTrigger); scheduler.start(); } }
运行结果:
CronTrigger使用HolidayCalendar类可以排除某一段时间,比如说国庆节不执行调度任务,代码示例如下:
代码清单4:HolidayCalendar的使用
import java.text.ParseException; import java.util.Calendar; import org.quartz.CronTrigger; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.calendar.HolidayCalendar; public class HelloQuartzScheduling { public static void main(String[] args) throws SchedulerException, ParseException { SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); JobDetail jobDetail = new JobDetail("helloQuartzJob", Scheduler.DEFAULT_GROUP, HelloQuartzJob.class); Calendar cal = Calendar.getInstance(); cal.set(2012, Calendar.OCTOBER, 1); // 国庆节 HolidayCalendar holidayCal = new HolidayCalendar(); holidayCal.addExcludedDate(cal.getTime()); // 排除该日期 // addCalendar(String calName, Calendar calendar, // boolean replace, boolean updateTriggers) scheduler.addCalendar("calendar", holidayCal, true, false); String cronExpression = "30/5 * * * * ?"; // 每5s触发任务 CronTrigger cronTrigger = new CronTrigger("cronTrigger", Scheduler.DEFAULT_GROUP, cronExpression); cronTrigger.setCalendarName("calendar"); scheduler.scheduleJob(jobDetail, cronTrigger); scheduler.start(); } }
JobStore: 任务持久化
Quartz支持任务持久化,这可以让你在运行时增加任务或者对现存的任务进行修改,并为后续任务的执行持久化这些变更和增加的部分。中心概念是JobStore接口。默认的是RAMJobStore。
上述没有用到任何的配置文件。Quartz支持配置文件,它的好处是比编写代码简单,且修改后不需要重新编译源码。
>> 配置quartz.properties特性文件
quartz.properties文件定义了Quartz应用运行时行为,还包含了许多能控制Quartz运转的属性。它应放在工程的classpath中。
代码清单5:quartz.propertie
#============================================================================ # Configure Main Scheduler Properties #============================================================================ # 实例名 org.quartz.scheduler.instanceName = QuartzScheduler # 实例ID org.quartz.scheduler.instanceId = AUTO #============================================================================ # Configure ThreadPool #============================================================================ org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool # 线程个数 org.quartz.threadPool.threadCount = 3 org.quartz.threadPool.threadPriority = 5 #============================================================================ # Configure JobStore #============================================================================ org.quartz.jobStore.misfireThreshold = 60000 org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore #============================================================================ # Configure Plugins #============================================================================ org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingJobHistoryPlugin # org.quartz.plugins.xml.JobInitializationPlugin是Quartz自带的插件, # 默认时,这个插件会在 classpath 中搜索名为 quartz_jobs.xml # 的文件并从中加载 Job 和 Trigger 信息 # v1.8之前用JobInitializationPlugin #org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin org.quartz.plugin.jobInitializer.fileNames = quartz_jobs.xml org.quartz.plugin.jobInitializer.failOnFileNotFound = true org.quartz.plugin.jobInitializer.scanInterval =10 org.quartz.plugin.jobInitializer.wrapInUserTransaction = false # 关闭quartz新版本检测功能 org.quartz.scheduler.skipUpdateCheck = true
>> 配置quartz_jobs.xml文件
在配置quart_jobs.xml时,遇到一个问题:
Exception in thread
"main"
org.quartz.SchedulerExceptio
SchedulerPlugin cla
'org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin;'
could
ot
e instantiated.
Exception in thread "main" org.quartz.SchedulerException: SchedulerPlugin class'org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin;' couldnot be instantiated.
因为quartz从版本1.8开始,配置文件有所改动,以前quartz自带的插件是JobInitializationPlugin,而1.8中是XMLSchedulingDataProcessorPlugin. xml schema亦有所改变,难道是改变后配置不对?错误提示是插件类找不到,jar包也都加入到工程了啊。最后终于发现,在quartz.properties特性文件中配置插件行最后多打了个分号。原来是一个多余的分号引发的错误!
下面是新的xml配置文件格式示例。
代码清单6:quartz_jobs.xml格式
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.quartz-scheduler.org/xml/JobSchedulingData http://www.quartz-scheduler.ogr/xml/job_scheduling_data_1_8.xsd"
version="1.8">
*
*
true or false
true or false
JobName
JobGroup
代码清单7:quartz_jobs.xml示例
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.quartz-scheduler.org/xml/JobSchedulingData http://www.quartz-scheduler.org/xml/job_scheduling_data_1_8.xsd"
version="1.8">
*
*
true
false
helloQuartzJob
DEFAULT
简单的quartz使用
HelloQuartzJob
false
true
false
trigger
DEFAULT
helloQuartzJob
DEFAULT
30/5 * * * * ?
代码清单8:Quartz任务调度
ublic class HelloQuartzScheduling { public static void main(String[] args) throws SchedulerException, ParseException { SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); scheduler.start(); } }
这里遇到个问题,提示错误:
Exception in thread "main" java.lang.NoClassDefFoundError: javax/transaction/UserTransactio
是由于缺少jta.jar,google出的结果是在quartz发行包的/lib中有此jar包,但是在1.8中没有找到。下载quartz 1.7中,也没有找到,因此,在网上搜一个jta.jar包放置过程classpath中,然后编译运行,ok.