Quartz

介绍Quartz

Quartz是一个任务调度框架。在某一个有规律的时间点干某件事。并且时间的触发的条件可以非常复杂(比如每月最后一个工作日的17:50),复杂到需要一个专门的框架来干这个事。 Quartz就是来干这样的事,你给它一个触发条件的定义,它负责到了时间点,触发相应的Job起来干活。

框架搭建

导入Quartz的相关jar包

slf4j-log4j12-1.7.7.jar
quartz-jobs-2.2.3.jar
quartz-2.2.3.jar
c3p0-0.9.1.1.jar
log4j-1.2.16.jar
slf4j-api-1.7.7.jar

TestQuartz

触发器 Trigger: 什么时候工作
任务 Job: 做什么工作
调度器 Scheduler: 搭配 Trigger和Job

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
public class TestQuartz {
public static void main(String[] args) throws Exception{
//创建调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

//定义一个触发器
Trigger trigger = newTrigger().withIdentity("trigger1", "group1") //定义名称和所属的组
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(2) //每隔2秒执行一次
.withRepeatCount(10)) //总共执行11次(第一次执行不基数)
.build();

//定义一个JobDetail
JobDetail job = newJob(MailJob.class) //指定干活的类MailJob
.withIdentity("mailjob1", "mailgroup") //定义任务名称和分组
.usingJobData("email", "admin@10086.com") //定义属性
.build();

//调度加入这个job
scheduler.scheduleJob(job, trigger);

//启动
scheduler.start();

//等待20秒,让前面的任务都执行完了之后,再关闭调度器
Thread.sleep(20000);
scheduler.shutdown(true);
}
}

MailJob

MailJob 实现了 Job 接口,提供 execute

1
2
3
4
5
6
7
8
9
10
11
public class MailJob implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDetail detail = context.getJobDetail();
String email = detail.getJobDataMap().getString("email");

SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
String now = sdf.format(new Date());

System.out.printf("给邮件地址 %s 发出了一封定时邮件, 当前时间是: %s%n" ,email, now);
}
}

分组

1
.withIdentity("mailjob1", "mailgroup")

比如一个系统有3个job 是备份数据库的,有4个job 是发邮件的,那么对他们进行分组,可以方便管理,类似于一次性停止所有发邮件的这样的操作

log4j.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

<appender name="default" class="org.apache.log4j.ConsoleAppender">
<param name="target" value="System.out"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%p] %d{dd MMM hh:mm:ss.SSS aa} %t [%c]%n%m%n%n"/>
</layout>
</appender>

<logger name="com.how2java">
<level value="error" />
</logger>

<root>
<level value="error" />
<appender-ref ref="default" />
</root>

</log4j:configuration>

Job管理

Job组成部分

JobDetail: 用于描述这个Job是做什么的
实现Job的类: 具体干活的
JobDataMap: 给 Job 提供参数用的

JobDataMap 除了usingJobData 方式之外,还可以是其他方式,像这样

1
job.getJobDataMap().put("email", "admin@taobao.com");

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
private static void jobDataMap() throws SchedulerException, InterruptedException {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

Trigger trigger = newTrigger().withIdentity("trigger1", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(2)
.withRepeatCount(10))
.build();

//定义一个JobDetail
JobDetail job = newJob(MailJob.class)
.withIdentity("mailjob1", "mailgroup")
.usingJobData("email", "admin@10086.com")
.build();

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

//调度加入这个job
scheduler.scheduleJob(job, trigger);

//启动
scheduler.start();

//等待20秒,让前面的任务都执行完了之后,再关闭调度器
Thread.sleep(20000);
scheduler.shutdown(true);
}

Job并发

默认的情况下,无论上一次任务是否结束或者完成,只要规定的时间到了,那么下一次就开始。

有时候会做长时间的任务,比如数据库备份,这个时候就希望上一次备份成功结束之后,才开始下一次备份,即便是规定时间到了,也不能开始,因为这样很有可能造成 数据库被锁死 (几个线程同时备份数据库,引发无法预计的混乱)。

那么在这种情况下,给数据库备份任务增加一个注解就好了
@DisallowConcurrentExecution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@DisallowConcurrentExecution
public class DatabaseBackupJob implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDetail detail = context.getJobDetail();
String database = detail.getJobDataMap().getString("database");

System.out.printf("给数据库 %s 备份, 耗时10秒 %n" ,database);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

Job异常

任务里发生异常是很常见的。 异常处理办法通常是两种:

当异常发生,那么就通知所有管理这个 Job 的调度,停止运行它
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ExceptionJob1  implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException {

int i = 0;
try {
//故意发生异常
System.out.println(100/i);

} catch (Exception e) {
System.out.println("发生了异常,取消这个Job 对应的所有调度");
JobExecutionException je =new JobExecutionException(e);
je.setUnscheduleAllTriggers(true);
throw je;
}
}
}
当异常发生,修改一下参数,马上重新运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ExceptionJob2  implements Job {
static int i = 0;
public void execute(JobExecutionContext context) throws JobExecutionException {

try {
//故意发生异常
System.out.println("运算结果"+100/i);

} catch (Exception e) {
System.out.println("发生了异常,修改一下参数,立即重新执行");
i = 1;
JobExecutionException je =new JobExecutionException(e);
je.setRefireImmediately(true);
throw je;
}
}
}

中断 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;
}
}

运作终止方法

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
private static void stop() throws Exception {
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);
}

SimpleTrigger

Trigger 就是触发器的意思,用来指定什么时间开始触发,触发多少次,每隔多久触发一次.
SimpleTrigger 可以方便的实现一系列的触发机制。

下一个8秒的倍数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) throws Exception{
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

Date startTime = DateBuilder.nextGivenSecondDate(null, 8);

JobDetail job = newJob(MailJob.class).withIdentity("mailJob", "mailGroup").build();

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

// schedule it to run!
Date ft = scheduler.scheduleJob(job, trigger);

System.out.println("当前时间是:" + new Date().toLocaleString());
System.out.printf("%s 这个任务会在 %s 准时开始运行,累计运行%d次,间隔时间是%d毫秒%n", job.getKey(), ft.toLocaleString(), trigger.getRepeatCount()+1, trigger.getRepeatInterval());

scheduler.start();

//等待200秒,让前面的任务都执行完了之后,再关闭调度器
Thread.sleep(200000);
scheduler.shutdown(true);
}

10 秒后运行

DateBuilder.futureDate()

可以方便的获取10秒后, 5分钟后, 3个小时候,2个月后这样的时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) throws Exception{
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

Date startTime = DateBuilder.futureDate(10, IntervalUnit.SECOND);

JobDetail job = newJob(MailJob.class).withIdentity("mailJob", "mailGroup").build();

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

// schedule it to run!
Date ft = scheduler.scheduleJob(job, trigger);

System.out.println("当前时间是:" + new Date().toLocaleString());
System.out.printf("%s 这个任务会在 %s 准时开始运行,累计运行%d次,间隔时间是%d毫秒%n", job.getKey(), ft.toLocaleString(), trigger.getRepeatCount()+1, trigger.getRepeatInterval());

scheduler.start();

//等待200秒,让前面的任务都执行完了之后,再关闭调度器
Thread.sleep(200000);
scheduler.shutdown(true);
}

累计n次,间隔n秒

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
public static void main(String[] args) throws Exception{
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

Date startTime = DateBuilder.nextGivenSecondDate(null, 8);

JobDetail job = newJob(MailJob.class).withIdentity("mailJob", "mailGroup").build();

SimpleTrigger trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger1", "group1")
.startAt(startTime)
.withSchedule(simpleSchedule()
.withRepeatCount(3)
.withIntervalInSeconds(1))
.build();

// schedule it to run!
Date ft = scheduler.scheduleJob(job, trigger);

System.out.println("当前时间是:" + new Date().toLocaleString());
System.out.printf("%s 这个任务会在 %s 准时开始运行,累计运行%d次,间隔时间是%d毫秒%n", job.getKey(), ft.toLocaleString(), trigger.getRepeatCount()+1, trigger.getRepeatInterval());

scheduler.start();

//等待200秒,让前面的任务都执行完了之后,再关闭调度器
Thread.sleep(200000);
scheduler.shutdown(true);
}

无限重复,间隔1秒

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
public static void main(String[] args) throws Exception{
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

Date startTime = DateBuilder.nextGivenSecondDate(null, 8);

JobDetail job = newJob(MailJob.class).withIdentity("mailJob", "mailGroup").build();

SimpleTrigger trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger1", "group1")
.startAt(startTime)
.withSchedule(simpleSchedule()
.repeatForever()
.withIntervalInSeconds(1))
.build();

// schedule it to run!
Date ft = scheduler.scheduleJob(job, trigger);

System.out.println("当前时间是:" + new Date().toLocaleString());
System.out.printf("%s 这个任务会在 %s 准时开始运行,累计运行%d次,间隔时间是%d毫秒%n", job.getKey(), ft.toLocaleString(), trigger.getRepeatCount()+1, trigger.getRepeatInterval());

scheduler.start();

//等待200秒,让前面的任务都执行完了之后,再关闭调度器
Thread.sleep(200000);
scheduler.shutdown(true);
}

CronTrigger

Cron 是Linux下的一个定时器,功能很强大,但是表达式更为复杂
CronTrigger 就是用 Cron 表达式来安排触发时间和次数的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) throws Exception{
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

Date startTime = DateBuilder.nextGivenSecondDate(null, 8);

JobDetail job = newJob(MailJob.class).withIdentity("mailJob", "mailGroup").build();

CronTrigger trigger = newTrigger().withIdentity("trigger1", "group1").withSchedule(cronSchedule("0/2 * * * * ?"))
.build();

// schedule it to run!
Date ft = scheduler.scheduleJob(job, trigger);

System.out.println("使用的Cron表达式是:"+trigger.getCronExpression());
// System.out.printf("%s 这个任务会在 %s 准时开始运行,累计运行%d次,间隔时间是%d毫秒%n", job.getKey(), ft.toLocaleString(), trigger.getRepeatCount()+1, trigger.getRepeatInterval());

scheduler.start();

//等待200秒,让前面的任务都执行完了之后,再关闭调度器
Thread.sleep(200000);
scheduler.shutdown(true);
}

这里有个Cron表达式生成工具,可以参考一下: http://cron.qqe2.com/

Listener

Quartz 的监听器有Job监听器,Trigger监听器, Scheduler监听器,对不同层面进行监控。 实际业务用的较多的是Job监听器,用于监听器是否执行了

MailJobListener

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
public class MailJobListener implements JobListener {

@Override
public String getName() {
// TODO Auto-generated method stub
return "listener of mail job";
}

@Override
public void jobExecutionVetoed(JobExecutionContext context) {
// TODO Auto-generated method stub
System.out.println("取消执行:\t "+context.getJobDetail().getKey());
}

@Override
public void jobToBeExecuted(JobExecutionContext context) {
// TODO Auto-generated method stub
System.out.println("准备执行:\t "+context.getJobDetail().getKey());
}

@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException arg1) {
// TODO Auto-generated method stub
System.out.println("执行结束:\t "+context.getJobDetail().getKey());
System.out.println();
}

}

必须实现这四个方法

增加监听器

mailJob是当前JobDetail的对象

1
2
3
4
//增加Job监听
MailJobListener mailJobListener= new MailJobListener();
KeyMatcher<JobKey> keyMatcher = KeyMatcher.keyEquals(mailJob.getKey());
scheduler.getListenerManager().addJobListener(mailJobListener, keyMatcher);