首先写一个Quartz的简单例子:
1.新建一个maven项目,pom.xml中加入quartz的依赖:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
2.新建quartz.properties文件,放在classpath下,添加如下配置:
#此调度程序的名称将为“MyScheduler”
org.quartz.scheduler.instanceName = MyScheduler
#线程池中有3个线程,这意味着最多可以同时运行3个job。
org.quartz.threadPool.threadCount = 3
#quartz的所有数据,包括job和trigger的配置,都会存储在内存中(而不是数据库里)
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
3.新建一个类继承org.quartz.Job:
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class QuartzJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(date));
}
}
4.创建Scheduler执行任务:
import com.quartzdemo.QuartzJob;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
public class QuartzTest {
public static void main(String[] args) {
try {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
JobDetail job = newJob(QuartzJob.class).withIdentity("job1","group1").build();
Trigger trigger = newTrigger().withIdentity("trigger1","group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever())
.build();
scheduler.scheduleJob(job,trigger);
// Thread.sleep(6000);可以指定执行多长时间后关闭Scheduler
// scheduler.shutdown();关闭Scheduler
} catch (SchedulerException | InterruptedException e) {
e.printStackTrace();
}
}
}
该程序会一直执行,也可以通过设置一定时间后结束执行。
5.执行结果:
...
下面学习Quartz中比较常用到的组件:
1.Job表示一个工作,要执行的具体内容。此接口中只有一个方法,就是execute()。
每当Scheduler执行job时,在调用其execute()方法之前会创建该类的一个新的实例,执行完后该实例就会被丢弃,被垃圾回收。
2.JobDetail表示一个具体的可执行的调度程序,Job是这个可执行调度程序所要执行的内容。另外JobDetail还包含这个任务调度的方案和策略。
3.Trigger是一个调度参数的设置,什么时候去调以及多久调一次。
Quartz自带了各种不同类型的Trigger,最常用的主要是SimpleTrigger和CronTrigger。
SimpleTrigger主要用来设置在特定时间点执行一次任务,重复执行N次,每次执行间隔T个时间单位。
CronTrigger在基于日历的调度上执行,比如在每月的第十天执行,或者每个星期5的上午执行。
Trigger的公共属性有:
jobKey(当trigger触发时执行的job身份),startTime(设置trigger第一次执行的时间点),endTime(表示trigger失效的时间点)
4.Scheduler是一个调度容器,一个调度容器中可以注册多个JobDetail和Trigger.当JobDetail和Trigger组合,就可以被Scheduler调度了。
Scheduler可以启动(start),暂停(stand-by)和停止(shutdown)。Scheduler创建后,可以增加,删除和列举Job和Trigger,以及执行其他和调度相关的工作(如:暂停trigger)
5.JobBuilder用于定义、构建JobDetail的实例,用于定义作业的实例
6.TriggerBuilder用于定义、构建Trigger的实例。
Key
将Job和Trigger注册到一个Scheduler时,可以为他们设置key。key由name和group组成,同一个分组下的Job和Trigger的名称必须唯一。
newJob(QuartzJob.class).withIdentity("job1","group1")
newTrigger().withIdentity("trigger1","group1")
JobDataMap
前面提到,每次执行execute时,都会新建一个job类,执行完后,该实例被垃圾回收。那么如何追踪Job执行的状态,执行的进度或者执行的结果呢?答案就是JobDataMap,JobDetail的一部分。
JobDataMap可以包含不限量(序列化)的数据对象,Job执行的时候可以获取其中的数据。
下面就是Job存取JobDataMap数据的例子:
import org.quartz.*;
public class UsingDataMapJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
//在Job执行过程中可以把数据从JobDataMap中取出来
String name = jobDataMap.getString("name");
String gender = jobDataMap.getString("gender");
System.out.println(name+":"+gender);
}
}
********************************************************************************************************
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class QuartzJobDataMapDemo {
public static void main(String[] args) throws SchedulerException, InterruptedException {
//在构建JobDetail的时候,把数据放入JobDataMap
JobDetail jobDetail = JobBuilder.newJob(UsingDataMapJob.class)
.usingJobData("name","jenny")
.usingJobData("gender","female")
.withIdentity("job1","group1")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1","group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule())
.build();
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
scheduler.scheduleJob(jobDetail,trigger);
// Thread.sleep(2000);
// scheduler.shutdown();
}
}
需要注意的是,如果是JobDataMap是持久化的存储机制(JobStore),JobDataMap中存储的对象会被序列化。所以就有可能会遇到存储完后,该类后续又发生变化,导致类的版本不一致的问题。也可以配置JDBC-JobStore和JobDataMap来限制map中只能存储基本类型和String类型。
JobExecutionContext 中的JobDataMap是JobDetail和Trigger中的DataMap的并集,如果有相同的key,则后者会把前者覆盖。
Trigger的misfire机制(https://www.cnblogs.com/skyLogin/p/6927629.html)
misfire产生的条件是:到了该触发执行时上一个执行还未完成,且线程池中没有空闲线程可以使用(或有空闲线程可以使用但job设置为@DisallowConcurrentExecution)且过期时间已经超过misfireThreshold就认为是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的MisFire策略:
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
MISFIRE_INSTRUCTION_FIRE_NOW
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT
CronTrigger的MisFire策略:
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
MISFIRE_INSTRUCTION_DO_NOTHING
MISFIRE_INSTRUCTION_FIRE_NOW
日历示例Calendar:Calendar用于从trigger指定的执行计划中排除时间段,比如trigger计划每天的上午10点执行任务,可以另外设置一个Calendar来排除国家节假日。
CronExpressions(http://www.quartz-scheduler.org/api/2.3.0/index.html)
Cron expressions are comprised of 6 required fields and one optional field separated by white space. The fields respectively are described as follows:
Field Name | Allowed Values | Allowed Special Characters | ||
---|---|---|---|---|
Seconds |
0-59 |
, - * / |
||
Minutes |
0-59 |
, - * / |
||
Hours |
0-23 |
, - * / |
||
Day-of-month |
1-31 |
, - * ? / L W |
||
Month |
0-11 or JAN-DEC |
, - * / |
||
Day-of-Week |
1-7 or SUN-SAT |
, - * ? / L # |
||
Year (Optional) |
empty, 1970-2199 |
, - * / |
所有字段都有一组可以指定的有效值。这些值应该是相当明显的 - 例如秒和分钟的数字0到59,数小时的值0到23。日期可以是1-31的任何值,但是您需要注意在给定的月份中有多少天!月份可以指定为0到11之间的值,或者使用字符串JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV和DEC。星期几可以指定为1到7(1 =星期日)之间的值,或者使用字符串SUN,MON,TUE,WED,THU,FRI和SAT。
CronTrigger例子:
import com.quartzdemo.QuartzJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class CronTriggerDemo {
public static void main(String[] args) {
try {
JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class).withIdentity("job1", "group1").build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0/2 16-17 ? * 3")
.withMisfireHandlingInstructionFireAndProceed())
.build();
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
scheduler.scheduleJob(jobDetail,trigger);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
Listener:
要创建一个listener,只需创建一个实现org.quartz.TriggerListener和/或org.quartz.JobListener接口的对象。然后,listener在运行时会向调度程序注册,并且必须给出一个名称(或者,他们必须通过他们的getName()方法来宣传自己的名字)。
JobListener例子:
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
public class MyJobListener implements JobListener {
@Override
public String getName() {
return "MyJobListener";
}
@Override
public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
System.out.println("Job 要被执行。");
}
@Override
public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
System.out.println("Job 执行被否决。");
}
@Override
public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
System.out.println("Job 已经被执行");
}
}
************************************************************************************
public class CronTriggerDemo {
public static void main(String[] args) {
try {
JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class).withIdentity("job1", "group1").build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("* * * 12 2 ?")
// .withSchedule(CronScheduleBuilder.cronSchedule("0 0/2 16-17 ? * 3")
.withMisfireHandlingInstructionFireAndProceed())
.build();
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
scheduler.getListenerManager().addJobListener(new MyJobListener(),KeyMatcher.keyEquals(new JobKey("job1", "group1")));
scheduler.scheduleJob(jobDetail,trigger);
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
执行结果:
Quartz的运行环境
- Quartz 可以运行嵌入在另一个独立式应用程序。
- Quartz 可以在应用程序服务器(或 servlet 容器)内被实例化,并且参与 XA 事务。
- Quartz 可以作为一个独立的程序运行(其自己的 Java 虚拟机内),可以通过 RMI 使用。
- Quartz 可以被实例化,作为独立的项目集群(负载平衡和故障转移功能),用于作业的执行。