参考文档:

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工程,添加坐标:

1
2
3
4
5
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>

创建一个JobDetail

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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", "admin@10086.com")
.build();

//用jobdatamap修改email
jobDetail.getJobDataMap().put("email", "admin@taobao.com");

//调度加入这个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类上,即可让任务单线程执行。

当任务的执行时间超过任务的时间间隔时,下一个任务会等待上一个任务结束,并非丢弃。

异常

异常的处理分为两种:

  1. 当发生异常时,停止这个任务
  2. 当发生异常时,调整任务,重新运行

示例

创建JobException1(情况1):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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,分别运行看到结果如下:

异常情况1异常情况2

Job中断

在任务进行时,我们有时候需要在达到某个条件之后需要终端这个任务的进行,那么这个Job需要实现 InterruptableJob 接口,来实现对任务的中断。

示例

创建任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//必须实现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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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秒的倍数(只针对开始时间)

    1
    2
    3
    //从当前开始,8的倍数秒开始执行
    Date startTime = DateBuilder.nextGivenSecondDate(null, 8);
    SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1").startAt(startTime).build();
  • 10秒后开始执行

    1
    2
    Date startTime = DateBuilder.futureDate(10, IntervalUnit.SECOND);
    SimpleTrigger trigger = (SimpleTrigger) newTrigger().withIdentity("trigger1", "group1").startAt(startTime).build();
  • 累计9次,间隔3秒

    1
    2
    3
    4
    5
    6
    7
    8
    9
    SimpleScheduleBuilder scheduleBuilder =SimpleScheduleBuilder.simpleSchedule()
    .withRepeatCount(8)
    .withIntervalInSeconds(3)

    SimpleTrigger trigger = (SimpleTrigger) newTrigger()
    .withIdentity("trigger1", "group1")
    .withSchedule(scheduleBuilder)
    .startNow() //立即开始
    .build();
  • 永久执行,间隔1秒

    1
    2
    3
    4
    5
    6
    7
    SimpleScheduleBuilder scheduleBuilder =SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(1)

    SimpleTrigger trigger = (SimpleTrigger) newTrigger()
    .withIdentity("trigger1", "group1")
    .startAt(startTime)
    .withSchedule(scheduleBuilder)
    .build();

CronTrigger

和Cron表达式一起使用,推荐!!!

1
2
3
4
5
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1","group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")) //重点是这一句
.build();

监听器(Listener)

关于Listener,在quartz中有Job监听器,Trigger监听器,Scheduler监听器,监听器会对很多操作进行监控,开发人员也可以在此时进行一些操作,例如日志打印等。

JobListener

我们将简单案例中的MailJob进行监听,创建Listener实现类,里面有一些必要的重写方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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监听器

全局监听器注册

1
2
MailJobListener jobListener = new MailJobListener();
scheduler.getListenerManager().addJobListener(jobListener);

组Job监听器

1
2
3
MailJobListener jobListener = new MailJobListener();
GroupMatcher<JobKey> groupMatcher = GroupMatcher.jobGroupEquals(job.getKey().getGroup()); //组Job匹配
scheduler.getListenerManager().addJobListener(jobListener, groupMatcher); //绑定

唯一Job监听器

1
2
3
MailJobListener jobListener = new MailJobListener();
KeyMatcher<JobKey> 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和他们的监听器。