参考文档:
https://blog.csdn.net/noaman_wgs/article/details/80984873
https://www.w3cschool.cn/quartz_doc/quartz_doc-2put2clm.html
Quartz定时框架
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:
- 持久性作业 - 就是保持调度定时的状态;
- 作业管理 - 对调度作业进行有效的管理;
举例
拿火车票购票来说,当你下单后,后台就会插入一条待支付的task(job),一般是30分钟,超过30min后就会执行这个job,去判断你是否支付,未支付就会取消此次订单;当你支付完成之后,后台拿到支付回调后就会再插入一条待消费的task(job),Job触发日期为火车票上的出发日期,超过这个时间就会执行这个job,判断是否使用等。
基本组成
- 调度器(Scheduler)
- 触发器(Trigger)
- 任务(Job)
调度器
将触发器和任务组合加入到调度器中,调度器来决定该任务的执行。
触发器
Trigger有两个:
- SingleTrigger:可以方便的实现一系列的触发机制。
- CronTrigger:和Cron表达式一块儿使用
触发器用来指定什么时间开始触发,触发多少次,每隔多久触发一次.
任务
Job 其实是由 3 个部分组成:
- JobDetail: 用于描述这个Job是做什么的
- 实现Job的类: 具体干活的
- JobDataMap: 给 Job 提供参数用的
每个任务JobDetail可以绑定多个Trigger,但一个Trigger只能绑定一个任务。
简单案例
创建一个Maven工程,添加坐标:
org.quartz-scheduler
quartz
2.3.0
创建一个JobDetail
public class MailJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDetail jobDetail = context.getJobDetail();
String email = jobDetail.getJobDataMap().getString("email");
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("hh:mm:ss");
String now = simpleDateFormat.format(new Date());
System.out.printf("给邮件地址 %s 发出了一封定时邮件, 当前时间是: %s%n", email, now);
}
}
创建一个Test
public class TestQuartz {
public static void main(String[] args) {
try {
//创建一个调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
//创建触发器的触发规则
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(2)
.withRepeatCount(10);
//创建一个触发器
SimpleTrigger trigger = TriggerBuilder
.newTrigger()
.withIdentity("trigger1", "group1") //标识区别唯一的一个触发器
.startNow()
.withSchedule(scheduleBuilder)
.build();
//创建job,指定JobDetail,datamap给jobdetail传值
JobDetail jobDetail = JobBuilder.newJob(MailJob.class)
.withIdentity("mailjob1", "mailgroup") //标识区别唯一的一个任务
.usingJobData("email", "[email protected]")
.build();
//用jobdatamap修改email
jobDetail.getJobDataMap().put("email", "[email protected]");
//调度加入这个job和触发器
scheduler.scheduleJob(jobDetail, trigger);
//调度启动 触发定时
scheduler.start();
//等待20秒,让前面的任务执行完成后,在关闭调度器
try {
Thread.sleep(20 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
scheduler.shutdown(true);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
执行main函数打印如下结果
任务(Job)
并行
默认情况下,无论上一次任务是否结束,只要设置的时间到了,下一次任务就会重新开启一个新的线程执行。
比如在备份文件的时候,我们当然不会让并行执行,那么添加注解 @DisallowConcurrentExecution
在任务的JobDetail类上,即可让任务单线程执行。
当任务的执行时间超过任务的时间间隔时,下一个任务会等待上一个任务结束,并非丢弃。
异常
异常的处理分为两种:
- 当发生异常时,停止这个任务
- 当发生异常时,调整任务,重新运行
示例
创建JobException1(情况1):
public class ExceptionJob1 implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
int i = 0;
try {
System.out.println(100/i);
}catch (Exception e){
System.out.println("发生异常,取消这个job对应的所有调度");
JobExecutionException exception = new JobExecutionException(e);
exception.setUnscheduleAllTriggers(true);
throw exception;
}
}
}
创建JobException2(情况2):
public class ExceptionJob2 implements Job {
static int i = 0;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
try {
System.out.println(100/i); //主动报错抛出异常
}catch (Exception e){
System.out.println("发生了异常,修改一下参数,立即重新执行");
i = 1;
JobExecutionException exception = new JobExecutionException(e);
exception.setRefireImmediately(true);
throw exception;
}
}
}
保留简单案例中Test类,修改 JobDetail jobDetail = JobBuilder.newJob(MailJob.class)
为JobException1,JobException2,分别运行看到结果如下:
Job中断
在任务进行时,我们有时候需要在达到某个条件之后需要终端这个任务的进行,那么这个Job需要实现 InterruptableJob
接口,来实现对任务的中断。
示例
创建任务
//必须实现InterruptableJob 而非 Job才能够被中断
public class StoppableJob implements InterruptableJob {
private boolean stop = false;
public void execute(JobExecutionContext context) throws JobExecutionException {
while(true){
if(stop)
break;
try {
System.out.println("每隔1秒,进行一次检测,看看是否停止");
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("持续工作中。。。");
}
}
public void interrupt() throws UnableToInterruptJobException {
System.out.println("被调度叫停");
stop = true;
}
}
创建Test
public static void main(String[] args) {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
Trigger trigger = newTrigger().withIdentity("trigger1", "group1")
.startNow()
.build();
//定义一个JobDetail
JobDetail job = newJob(StoppableJob.class)
.withIdentity("exceptionJob1", "someJobGroup")
.build();
//调度加入这个job
scheduler.scheduleJob(job, trigger);
//启动
scheduler.start();
Thread.sleep(5000);
System.out.println("过5秒,调度停止 job");
//key 就相当于这个Job的主键
scheduler.interrupt(job.getKey());
//等待20秒,让前面的任务都执行完了之后,再关闭调度器
Thread.sleep(20000);
scheduler.shutdown(true);
}
说明:实现 InterruptableJob
是为了让任务中有一个内置的 interrupt
来进行中断操作,并不是整整的中断,需要根据自身业务做标识进行实现,
触发器(Trigger)
SimpleTrigger
简单案例中已经对触发器继续了一些应用,在这里对一些常用的定时进行举例:
-
下一个8秒的倍数(只针对开始时间)
//从当前开始,8的倍数秒开始执行 Date startTime = DateBuilder.nextGivenSecondDate(null, 8); SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1").startAt(startTime).build();
-
10秒后开始执行
Date startTime = DateBuilder.futureDate(10, IntervalUnit.SECOND); SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1").startAt(startTime).build();
-
累计9次,间隔3秒
SimpleScheduleBuilder scheduleBuilder =SimpleScheduleBuilder.simpleSchedule() .withRepeatCount(8) .withIntervalInSeconds(3) SimpleTrigger trigger = (SimpleTrigger) newTrigger() .withIdentity("trigger1", "group1") .withSchedule(scheduleBuilder) .startNow() //立即开始 .build();
-
永久执行,间隔1秒
SimpleScheduleBuilder scheduleBuilder =SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(1) SimpleTrigger trigger = (SimpleTrigger) newTrigger() .withIdentity("trigger1", "group1") .startAt(startTime) .withSchedule(scheduleBuilder) .build();
CronTrigger
和Cron表达式一起使用,推荐!!!
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1","group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")) //重点是这一句
.build();
监听器(Listener)
关于Listener,在quartz中有Job监听器,Trigger监听器,Scheduler监听器,监听器会对很多操作进行监控,开发人员也可以在此时进行一些操作,例如日志打印等。
JobListener
我们将简单案例中的MailJob进行监听,创建Listener实现类,里面有一些必要的重写方法。
public class MailJobListener implements JobListener {
@Override
public String getName() {
return "发送邮件进行定时操作";
}
/**
* 准备执行时的操作
* @param jobExecutionContext
*/
@Override
public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
System.out.println("准备执行:\t" + jobExecutionContext.getJobDetail().getKey());
}
/**
* 取消操作时的方法
* @param jobExecutionContext
*/
@Override
public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
System.out.println("取消操作:\t" + jobExecutionContext.getJobDetail().getKey());
}
/**
* 执行结束时的操作
* @param jobExecutionContext
* @param e
*/
@Override
public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
System.out.println("执行结束:\t" + jobExecutionContext.getJobDetail().getKey());
System.out.println();
}
}
注册监听器到调度器中,监听器注册也分为三种:
- 全局Job监听器
- 组Job监听器
- 唯一Job监听器
全局监听器注册
MailJobListener jobListener = new MailJobListener();
scheduler.getListenerManager().addJobListener(jobListener);
组Job监听器
MailJobListener jobListener = new MailJobListener();
GroupMatcher groupMatcher = GroupMatcher.jobGroupEquals(job.getKey().getGroup()); //组Job匹配
scheduler.getListenerManager().addJobListener(jobListener, groupMatcher); //绑定
唯一Job监听器
MailJobListener jobListener = new MailJobListener();
KeyMatcher keyMatcher = KeyMatcher.keyEquals(job.getKey());
scheduler.getListenerManager().addJobListener(jobListener, keyMatcher);
TriggerListener和SchedulerListener
这两个监听器的实现方式和JobListener的实现方式类似,这里不在赘述,近介绍监听器内方法的作用。
TriggerListener
方法名 | 解释 |
---|---|
triggerFired() | 触发器被激发,job即将被运行时 |
vetoJobExecution() | 触发器被激发,job即将被运行:triggerFired先执行,此方法后执行,如果返回true,则任务被终止 |
triggerMisfired() | 当Trigger错过被激发时执行,比如当前时间有很多触发器都需要执行,但是线程池中的有效线程都在工作,那么有的触发器就有可能超时,错过这一轮的触发。 |
triggerComplete() | 触发器完成后执行 |
getName() | 返回一个字符串主要说明该监听器的名称等,一般用作日志记录 |
SchedulerListener
调度器监听器方法较多,这里选择几个常用的以做举例
方法名 | 解释 |
---|---|
jobScheduled() | 在有新的 JobDetail 部署时调用此方法。 |
jobUnscheduled | 在有新的 JobDetail卸载时调用此方法 |
triggersPaused() | 在Trigger被挂起时调用此方法 |
triggerResumed() | 在Trigger被重新激活时调用此方法 |
schedulerStarted() | 调度器启动完成之后执行 |
schedulerShuttingdown() | 调度器正在被终止之后执行 |
schedulerShutdown() | 调度器终止完成之后执行 |
schedulerStarting() | 调度器正在被启动时执行 |
尾言
此篇主要介绍了Quartz定时任务框架的基本内容合大概用法,限于在main方法中测试是否有效。
包括定时任务三大基本组件:Job,Trigger,Scheduler和他们的监听器。