Quartz是一个完全由java编写的开源作业调度框架。为在 Java 应用程序中进行作业调度提供了简单却强大的机制。多用在轮询、定时任务处理等。相对于JDK 自带的Timer 有着更强大的功能。
使用Quartz需要在quartz.jar官网下载相应的jar包(访问可能比较慢) 或者 pom文件导入
org.quartz-scheduler
quartz
2.2.1
JobDetail
表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略,也就是业务逻辑。
Trigger
代表一个调度参数的配置,什么时候去调,先当于定时器。
Scheduler
代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
1. 实现 Job 接口,编写业务逻辑
public class xxxJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 业务逻辑...
}
}
2. JobDetail 配置 Job
JobDetail jobDetail = JobBuilder.newJob(xxxJob.class)
.withIdentity("Job", "Group") // 定义任务名及分组
.build();
除上述外还可定义任务描述、配置相关业务数据等功能,相见 quartz 2.2.2 API
3. 配置 Trigger 定时器
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("Trigger", "Group")
.startAt(DateBuilder.nextGivenMinuteDate(null, 5))
.withSchedule(CronScheduleBuilder.cronSchedule("0 0/1 * * * ?"))
.build();
定时器可分为 SimpleTrigger 和 CronTrigger 两类,前者相当于 Timer 中的定时配置,用于简单的定时规则,而后者则用于处理复杂的定时逻辑,可用表达式来进行配置。
SimpleTrigger
SimpleScheduleBuilder
.repeatMinutelyForever(10)
.withMisfireHandlingInstructionNextWithExistingCount()
.build();
上面的 repeatMinutelyForever(10) 根据字面意思就可得 定时任务为“每10分钟执行一次”,而且是不会停止一直执行下去。除此外还可以按小时或秒的单位进行配置,还可定义任务执行的总次数。
在任务执行中还可能存在这样一个问题:到了该触发执行时上一个执行任务还未完成,且线程池中没有空闲线程可以使用,我们将这样的任务就定义为错失触发(misfire)。产生这种问题可能是由于服务器重启或内存空间不足等原因导致,因此在配置中也要对错失触发有相应的处理。
比如:13:07:24开始执行,重复执行5次,开始执行时,quartz已经计算好每次调度的时间刻,分别如下:
03:33:36,03:33:39,03:33:42,03:33:45,03:33:48,03:33:51
如果第一次执行时间为11s,到03:33:47结束,03:33:47减去03:33:39的时间间隔是8s,如果misfireThreshold设置的时间小于等于8s间隔,则认为是misfire了,如果大于8s间隔,则认为没有misfire。
SimpleTrigger 错失、补偿执行:
① withMisfireHandlingInstructionFireNow(); // 以当前时间为触发频率立即触发执行(若trigger重复执行,执行同⑥)
② withMisfireHandlingInstructionIgnoreMisfires(); // 以错过的第一个频率时间立刻开始执行
③ withMisfireHandlingInstructionNextWithExistingCount(); // 不触发立即执行
④ withMisfireHandlingInstructionNextWithRemainingCount(); // 不触发立即执行
⑤ withMisfireHandlingInstructionNowWithExistingCount(); (default) // 以当前时间为触发频率立即触发执行
⑥ withMisfireHandlingInstructionNowWithRemainingCount(); // 以当前时间为触发频率立即触发执行
CronTrigger
CronScheduleBuilder
.cronSchedule("0 0/1 * * * ?")
.withMisfireHandlingInstructionDoNothing()
.build();
上面可以看出 CronTrigger 的执行时间是通过 表达式 配置的。分别对应 “秒、分、时、月份中的日期、月、[星期中的日期]、年份”。上面的配置就是“定时任务每分钟执行一次”。
Cron表达式:
时间 | 范围 | 可用字符 |
---|---|---|
秒 | 0-59 | , - * / |
分 | 0-59 | , - * / |
时 | 0-23 | , - * / |
日 | 1-31 | , - * ? / L W C |
月 | 1-12 or JAN-DEC | , - * / |
周 | 1-7 or SUN-SAT | , - * ? / L C # |
年(可选) | empty, 1970-2099 | , - * / |
*:表示匹配该域的任意值。假如在Minutes域使用*, 即表示每分钟都会触发事件。
-:表示范围。例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次。
,:表示列出枚举值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。
?: 只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和 DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。
/:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次。
L: 表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。
W: 表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一 到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份。
LW: 这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。
#: 用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。
C: 指和calendar联系后计算过的值。例:在day-of-month 字段用“5C”指在这个月第5天或之后包括calendar的第一天;在day-of-week字段用“1C”指在这周日或之后包括calendar的第一天。
CronTrigger错失、补偿执行:
① withMisfireHandlingInstructionDoNothing() // 不触发立即执行
② withMisfireHandlingInstructionIgnoreMisfires(); // 以错过的第一个频率时间立刻开始执行
③ withMisfireHandlingInstructionFireAndProceed(); (default) // 以当前时间为触发频率立刻触发一次执行
4. Scheduler 将 JobDetail、Trigger结合
// 创建工厂
SchedulerFactory schedulerFactory = new StdSchedulerFactory(); // method 1
// schedulerFactory = DirectSchedulerFactory.getInstance(); // method 2
// 从工厂获取调度器
Scheduler scheduler = schedulerFactory.getScheduler();
// 注册任务、定时器
scheduler.scheduleJob(jobDetail, trigger);
// 启动
scheduler.start();
经过上述配置之后就可以执行定时任务了。
由于Quartz被公认为是比较好用的定时器设置工具,因此在spring框架中也有将其进行进一步的封装。
1. 实现 QuartzJobBean 接口
@DisallowConcurrentExecution
public class xxxJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
// 业务逻辑...
}
}
2. JobDetailFactoryBean 配置
JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
jobDetailFactoryBean.setJobClass(xxxJob.class);
jobDetailFactoryBean.setJobDataAsMap(map);
jobDetailFactoryBean.setDurability(true);
3. xxxTriggerFactoryBean 配置
SimpleTriggerFactoryBean simpleTriggerFactoryBean = new SimpleTriggerFactoryBean();
simpleTriggerFactoryBean.setJobDetail(jobDetailFactoryBean.getObject());
simpleTriggerFactoryBean.setRepeatInterval(60 * 1000);
CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean.getObject());
cronTriggerFactoryBean.setCronExpression("10 0/5 * * * ?");
4. SchedulerFactoryBean 结合
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setTriggers(
simpleTriggerFactoryBean.getObject(),
cronTriggerFactoryBean.getObject()
);
由于 spring 只是将其进行了封装整合,只是使用上会方便很多,但具体实现方法还是一样的,可参考上述的配置步骤。