简单说明
简单的定时任务使用Timer或者ScheduledExecutorService
quartz支持复杂的定时执行功能。支持ram存储(内存存储)和持久化存储。quartz有分布式和集群能力
简单使用
- 获取任务调度器Schedule。任务调度器可以管理任务。
- 创建任务实例。使用JobBuilder(任务类.class)创建,会返回一个JobDetail。任务是一个实现了Job,并实现execute方法的类而已,execute方法记录要执行的事情。
- 创建触发器Trigger。Trigger是用来指定触发策略的,包括什么时候开始执行、循环多少次、多久执行一次呀,等等。支持链式编程。
- 把任务和触发器告诉任务调度器,使用scheduleJob方法来完成。
- 使用任务调度器启动任务
- 使用任务调度器关闭任务
例子:
java
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class SimpleJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("SimpleJob is executing at: " + new java.util.Date());
}
}
java
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class QuartzExample {
public static void main(String[] args) {
try {
// 1. 获取任务调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2. 创建任务实例
JobDetail job = JobBuilder.newJob(SimpleJob.class)
.withIdentity("simpleJob", "group1")
.build();
// 3. 创建触发器
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("simpleTrigger", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(10) // 每10秒执行一次
.repeatForever()) // 无限重复
.build();
// 4. 把任务和触发器告诉任务调度器
scheduler.scheduleJob(job, trigger);
// 5. 使用任务调度器启动任务
scheduler.start();
// 让任务运行一段时间
Thread.sleep(60000); // 60秒
// 6. 使用任务调度器关闭任务
scheduler.shutdown();
} catch (SchedulerException | InterruptedException e) {
e.printStackTrace();
}
}
}
使用了 Builder 模式(建造者模式):
java
JobDetail job = JobBuilder.newJob(SimpleJob.class)
.withIdentity("simpleJob", "group1")
.build();
java
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("simpleTrigger", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(10)
.repeatForever())
.build();
Quartz基本的实现原理
- job和jobdetail的关系是什么?
Job(任务)是一个接口,表示一个具体的任务。你需要实现这个接口,并在 execute 方法中定义任务的具体逻辑。Job 只关注任务的执行逻辑,即 做什么。
JobDetail(任务详情)JobDetail 是一个类,用于描述任务的详细信息。它包含了任务的元数据,例如任务类、任务名称、任务组、任务数据等。JobDetail 关注任务的 元信息,即 谁来做 和 怎么做。
java
JobDetail job = JobBuilder.newJob(SimpleJob.class)
.withIdentity("simpleJob", "group1")
.build();
这里 JobDetail 描述了任务的详细信息:
- 任务类是 SimpleJob.class。
- 任务名称是 "simpleJob"。
- 任务组是 "group1"。
总结:
- Job:定义任务的具体逻辑(做什么)。
- JobDetail:描述任务的元信息(谁来做、怎么做)。
- 关系:JobDetail 是 Job 的包装器,包含了 Job 的类信息和任务的额外信息。
- 设计目的:解耦任务逻辑和任务元信息,提高灵活性和可扩展性。
- 原理图:

- Job、JobDetail和Trigger的关系
Trigger:
- 一个 Trigger 只能绑定一个 JobDetail。
JobDetail:
- 一个 JobDetail 可以被多个 Trigger 绑定。相当于是一个Job信息可以被多次使用。
Job:
- 一个 Job 类可以创建多个 JobDetail 对象。相当于是可以允许多个JobDetail使用一个Job的execute逻辑。
- 默认情况下,Quartz 每次触发器触发时都会创建一个新的 Job 实例,再去执行execute方法。(这样可以线程安全)
例子:
java
package com.example;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.*;
import static org.quartz.JobBuilder.newJob;
public class SimpleTriggerMisfireExample {
public static void main(String[] args) throws SchedulerException {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail job = newJob(MyJob.class)
.withIdentity("myJob", "group1")
.storeDurably()
.build();
Trigger trigger1 = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5)
.repeatForever())
.build();
Trigger trigger2 = TriggerBuilder.newTrigger()
.withIdentity("trigger2", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
Set<Trigger> triggers = new HashSet<>();
triggers.add(trigger1);
triggers.add(trigger2);
Map<JobDetail, Set<? extends Trigger>> triggersAndJobs = new HashMap<>();
triggersAndJobs.put(job, triggers);
scheduler.scheduleJobs(triggersAndJobs, false);
System.out.println("调度器启动时间: " + new Date());
scheduler.start();
try {
Thread.sleep(60000); // 等待 60 秒
} catch (InterruptedException e) {
e.printStackTrace();
}
scheduler.shutdown();
}
public static class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) {
Trigger trigger = context.getTrigger();
System.out.println("任务执行开始: " + new Date() +
", Trigger: " + trigger.getKey() +
", 触发时间: " + trigger.getPreviousFireTime());
try {
Thread.sleep(3000); // 模拟任务执行耗时 3 秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务执行结束: " + new Date());
}
}
}
执行结果:
java
D:\jdk1.8.0_172\bin\java.exe "-javaagent:D:\IDEA\IntelliJ IDEA 2021.3.2\lib\idea_rt.jar=55136:D:\IDEA\IntelliJ IDEA 2021.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk1.8.0_172\jre\lib\charsets.jar;D:\jdk1.8.0_172\jre\lib\deploy.jar;D:\jdk1.8.0_172\jre\lib\ext\access-bridge-64.jar;D:\jdk1.8.0_172\jre\lib\ext\cldrdata.jar;D:\jdk1.8.0_172\jre\lib\ext\dnsns.jar;D:\jdk1.8.0_172\jre\lib\ext\jaccess.jar;D:\jdk1.8.0_172\jre\lib\ext\jfxrt.jar;D:\jdk1.8.0_172\jre\lib\ext\localedata.jar;D:\jdk1.8.0_172\jre\lib\ext\nashorn.jar;D:\jdk1.8.0_172\jre\lib\ext\sunec.jar;D:\jdk1.8.0_172\jre\lib\ext\sunjce_provider.jar;D:\jdk1.8.0_172\jre\lib\ext\sunmscapi.jar;D:\jdk1.8.0_172\jre\lib\ext\sunpkcs11.jar;D:\jdk1.8.0_172\jre\lib\ext\zipfs.jar;D:\jdk1.8.0_172\jre\lib\javaws.jar;D:\jdk1.8.0_172\jre\lib\jce.jar;D:\jdk1.8.0_172\jre\lib\jfr.jar;D:\jdk1.8.0_172\jre\lib\jfxswt.jar;D:\jdk1.8.0_172\jre\lib\jsse.jar;D:\jdk1.8.0_172\jre\lib\management-agent.jar;D:\jdk1.8.0_172\jre\lib\plugin.jar;D:\jdk1.8.0_172\jre\lib\resources.jar;D:\jdk1.8.0_172\jre\lib\rt.jar;E:\develop\spring\target\classes;C:\Users\Administrator\.m2\repository\org\quartz-scheduler\quartz\2.3.2\quartz-2.3.2.jar;C:\Users\Administrator\.m2\repository\com\mchange\c3p0\0.9.5.4\c3p0-0.9.5.4.jar;C:\Users\Administrator\.m2\repository\com\mchange\mchange-commons-java\0.2.15\mchange-commons-java-0.2.15.jar;C:\Users\Administrator\.m2\repository\com\zaxxer\HikariCP-java7\2.4.13\HikariCP-java7-2.4.13.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\1.7.36\slf4j-api-1.7.36.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-simple\1.7.36\slf4j-simple-1.7.36.jar com.example.SimpleTriggerMisfireExample
[main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
[main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main
[main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
[main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.
[main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
[main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
[main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
[main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
调度器启动时间: Wed Mar 19 19:29:33 CST 2025
任务执行开始: Wed Mar 19 19:29:33 CST 2025, Trigger: group1.trigger1, 触发时间: Wed Mar 19 19:29:33 CST 2025
[main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
任务执行开始: Wed Mar 19 19:29:33 CST 2025, Trigger: group1.trigger2, 触发时间: Wed Mar 19 19:29:33 CST 2025
任务执行开始: Wed Mar 19 19:29:36 CST 2025, Trigger: group1.trigger2, 触发时间: Wed Mar 19 19:29:36 CST 2025
任务执行结束: Wed Mar 19 19:29:36 CST 2025
任务执行结束: Wed Mar 19 19:29:36 CST 2025
任务执行开始: Wed Mar 19 19:29:38 CST 2025, Trigger: group1.trigger1, 触发时间: Wed Mar 19 19:29:38 CST 2025
任务执行开始: Wed Mar 19 19:29:39 CST 2025, Trigger: group1.trigger2, 触发时间: Wed Mar 19 19:29:39 CST 2025
任务执行结束: Wed Mar 19 19:29:39 CST 2025
任务执行结束: Wed Mar 19 19:29:41 CST 2025
任务执行开始: Wed Mar 19 19:29:42 CST 2025, Trigger: group1.trigger2, 触发时间: Wed Mar 19 19:29:42 CST 2025
任务执行结束: Wed Mar 19 19:29:42 CST 2025
任务执行开始: Wed Mar 19 19:29:43 CST 2025, Trigger: group1.trigger1, 触发时间: Wed Mar 19 19:29:43 CST 2025
任务执行结束: Wed Mar 19 19:29:45 CST 2025
任务执行开始: Wed Mar 19 19:29:45 CST 2025, Trigger: group1.trigger2, 触发时间: Wed Mar 19 19:29:45 CST 2025
任务执行结束: Wed Mar 19 19:29:46 CST 2025
进程已结束,退出代码130
Scheduler
只要知道Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();就行
Trigger
Trigger 的触发器中主要是下面两种比较常用:
- SimpleTrigger:适用于单次或固定间隔重复执行的任务。
- CronTrigger:适用于复杂调度规则的任务。他也能和SimpleTrigger一样,指定开始执行和结束执行的时间。
SimpleTrigger适合场景:
- 定时任务:比如指定晚上12点去导出订单
- 延时任务:比如,当用户提交订单后,启动任务,并且延时30秒钟后检测订单状态,如果没有支付就取消订单并释放库存
- 循环任务:可以无限循环、可以指定循环次数、可以指定循环到某个时间停止
具体使用可以让GBT给出简单使用案例。
CronTrigger适合场景:
- 适合需要复杂调度规则的任务(例如每天、每周、每月执行、每个星期几执行、第几周执行)。
- 需要精确控制执行时间的任务。
Cron 表达式由 6 或 7 个字段组成(各字段使用空格分割,最后一个年份是可选的),格式如下:
java
秒 分 时 日 月 周 年(可选)
cron表达式的规则:
- 秒(0-59):表示每分钟的秒数。
- 分(0-59):表示每小时的分钟数。
- 时(0-23):表示每天的小时数。
- 日(1-31):表示每月的日期。
- 月(1-12 或 JAN-DEC):表示每年的月份。使用月份的前3个字母也是可以表示月份的。
- 周(1-7 或 SUN-SAT):表示每周的星期几(1 表示周日,7 表示周六)。
- 年(可选,1970-2099):表示年份(可以省略)。省略表示对年份没有要求,任何年份都可以执行,如果指定年份,那么久只能在对应的年份才能执行。
Cron 表达式中可以使用以下符号来定义时间的范围和规则:
- 星号(*)
含义:表示"每"或"任意"。
示例:
0 * * * * ?:每分钟的第 0 秒执行(即每分钟执行一次)。
0 0 * * * ?:每小时的 0 分 0 秒执行(即每小时执行一次)。
- 问号(?)
含义:表示"无特定值",它通常用于 "日"字段 或 "周"字段,以避免这两个字段之间的冲突。因为"日"和"周"字段是互斥的,不能同时指定具体的值,所以需要用 ? 来表示其中一个字段不指定具体值。
示例:
0 0 12 ? * MON:每周一的 12:00:00 执行。
0 0 0 1 * ?:每月 1 日的 00:00:00 执行。
- 逗号(,)
含义:表示"或",用于列举多个值。
示例:
0 0 12,18 * * ?:每天的 12:00:00 和 18:00:00 执行。
0 0 0 1,15 * ?:每月 1 日和 15 日的 00:00:00 执行。
- 连字符(-)
含义:表示"范围",用于定义一个连续的时间范围。
示例:
0 0 9-17 * * ?:每天 9:00:00 到 17:00:00,每小时执行一次。
0 0 0 1-5 * ?:每月 1 日到 5 日的 00:00:00 执行。
- 斜杠(/)
含义:表示"间隔",用于定义时间间隔。
示例:
0 0/5 * * * ?:每 5 分钟执行一次(从 0 分钟开始)。
0 0 0/2 * * ?:每 2 小时执行一次(从 0 点开始)。
0 0 10/7 * * ?:从 10 点开始,每 7 个小时执行一次。过了范围后还是一样从10点开始的。
执行的时间如下:
- 2025-03-13 10:00:00
- 2025-03-13 17:00:00
- 2025-03-14 10:00:00
- 2025-03-14 17:00:00
- 2025-03-15 10:00:00
- 井号(#)
含义:表示"第几个",用于指定某月的第几个星期几。
示例:
0 0 12 ? * 6#3:每月的第 3 个星期五的 12:00:00 执行。
0 0 12 ? * 2#1:每月的第 1 个星期一的 12:00:00 执行。
- 字母(L、W)
L:L加在时间上面,就是代表最后一个这个时间才会执行。比如,加在日上,就是最后一个满足条件的日才会执行。加在周上,就是最后一个满足条件的周才执行。允许使用在月、日、星期几上。如果在月、日、周上面写L,不写其他的,分别就是指满足条件的最后一个月、满足条件的最后一天、满足条件的星期六。
示例:
0 0 0 L * ?:每月的最后一天的 00:00:00 执行。
0 0 12 ? * 5L:每月的最后一个星期五的 12:00:00 执行。
0 0 12 L * ?:表示每月最后一天的12:00:00执行。
W:表示"工作日"(Weekday),用于"日"字段,指定离给定日期最近的工作日。
示例:
0 0 0 15W * ?:每月 15 日最近的工作日的 00:00:00 执行。
比如:当前是2025年3月12日
- 2025-03-14 00:00:00(周五)
- 2025-04-15 00:00:00(周二)
- 2025-05-15 00:00:00(周四)
- 2025-06-16 00:00:00(周一)
- 2025-07-15 00:00:00(周二)
例如:
- 0 0 12 * * ?:每天中午 12 点执行。
- 0 0/5 14 * * ?:每天下午 2 点到 2:55,每 5 分钟执行一次。
使用案例:创建一个任务在每天的 12:00:00 执行。
java
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class SimpleJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("SimpleJob is executing at: " + new java.util.Date());
}
}
java
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class CronTriggerExample {
public static void main(String[] args) {
try {
// 1. 获取任务调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2. 创建任务实例
JobDetail job = JobBuilder.newJob(SimpleJob.class)
.withIdentity("cronJob", "group1")
.build();
// 3. 创建 CronTrigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("cronTrigger", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 12 * * ?")) // 每天中午 12:00:00 执行
.build();
// 4. 把任务和触发器告诉任务调度器
scheduler.scheduleJob(job, trigger);
// 5. 使用任务调度器启动任务
scheduler.start();
// 让任务运行一段时间
Thread.sleep(60000); // 60秒
// 6. 使用任务调度器关闭任务
scheduler.shutdown();
} catch (SchedulerException | InterruptedException e) {
e.printStackTrace();
}
}
}
触发器的Misfire策略
misfire解释
Misfire(错过触发) 发生在触发器的计划时间已经过去,但由于某些原因(例如,调度器未启动、任务执行时间过长、系统负荷等),未能按预定时间执行触发器。这种情况通常是由于调度器(Scheduler)没有运行(可能是没有启动、暂停、关闭等原因)、线程池没有空闲的线程供调度器调度而产生的(调度器去执行任务的时候,其实是使用线程池中的线程去执行的)。如下图:

你可以理解为,Trigger创建时,你没有指定start就是指计划开始执行的时间是Trigger创建的时间,如果你指定了某个时间,就是计划了从你指定的开始时间开始什么时候去执行什么任务(注意,如果你创建trigger的时候,设置开始时间是过去的时间,那么你start这个Scheduler的时候,就可能就直接就会直接补偿了)。相当于是有一个计划表,如果当前时间超过了那个任务指定要执行的时间,但是又没有执行,那么就是misfire了,如果超过了,但是执行了那就是正常的。相当于在时间表上,打了✓,但是如果是misfire,那么就是相当于打×。Trigger会有补偿机制,会去补偿执行错过的任务,补偿机制是什么样的取决于你设置的misfire处理策略。
misfire发生的场景:
①:所有的工作线程都在忙碌,导致某些trigger得不到触发.(如:simplethreadpool 默认是10个工作线程,但我有15个trigger同时触发, 恰巧这10个trigger关联的job耗时都很长,剩下的5个trigger超过了等待时间仍然没有得到触发)
②:调度器(sheduler)中途挂了,某个时间又恢复了
③:设置的trigger的开始时间早于当前时间
Misfire 补偿
Quartz 为不同类型的触发器(如 SimpleTrigger 和 CronTrigger)提供了不同的 Misfire 处理策略。
我们主要使用的是SimpleTrigger 和 CronTrigger,所以我们只介绍这两个触发器的Misfire处理策略。
SimpleTrigger
略。这个我测试发现结果和很多博客、GBT的结果不一样,并且因为这个用的也没有很多,这里不记录了。主要了解CronTrigger 就行了。
CronTrigger
CronTrigger 提供了以下几种处理 Misfire 策略:
- withMisfireHandlingInstructionDoNothing:错过的不进行补偿,然后正常调度。
- withMisfireHandlingInstructionFireAndProceed:错过的全部合成一次,并且立即进行补偿(即时任务终止时间已经到达),然后正常调度。默认是这个。
- withMisfireHandlingInstructionIgnoreMisfires:错过的全部立即进行补偿(即时任务终止时间已经到达),然后正常调度
代码:
java
package com.example.springbootdemo.config;
import com.example.springbootdemo.job.MyJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzConfiguration {
private String jobName="myjob";
@Bean("myJobDetail")
public JobDetail jobDetail(){
return JobBuilder.newJob(MyJob.class)
.storeDurably()
.withIdentity(jobName)
.build();
}
@Bean
public Trigger trigger(){
String cronExpression="0 0/2 * * * ?";
CronScheduleBuilder cronScheduleBuilder=
CronScheduleBuilder.cronSchedule(cronExpression)
.withMisfireHandlingInstructionDoNothing();
return TriggerBuilder.newTrigger()
.startAt(DateBuilder.todayAt(10,50,0))
.withIdentity(jobName+"_trigger")
.withSchedule(cronScheduleBuilder)
.build();
}
@Bean
public Scheduler scheduler(Trigger trigger, JobDetail jobDetail) throws SchedulerException {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(jobDetail, trigger);
return scheduler;
}
}
java
package com.example.springbootdemo.job;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 获取任务关联的jobDetail
JobDetail jobDetail = context.getJobDetail();
// 获取任务的名字
String jobName = jobDetail.getKey().getName();
// 获取任务的本次执行时间
Date fireTime = context.getFireTime();
String executeTime = new SimpleDateFormat("yyyy-HW-dd HH:mm:ss").format(fireTime);
// 输出任务的相关信息
System.out.println("|---"+jobName+"---!");
System.out.println("\t本次执行时间:"+executeTime);
}
}
java
package com.example.springbootdemo.run;
import org.quartz.Scheduler;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
// 在 Spring Boot 应用启动时,QuartzStartupRunner 的 run 方法会被自动调用。
// 在 run 方法中,scheduler.start() 会启动 Quartz 调度器。
// 一旦调度器启动,所有配置好的任务(Job)和触发器(Trigger)就会开始按照预定的时间表执行。
@Component
public class QuartzStartupRunner implements CommandLineRunner {
private final Scheduler scheduler;
public QuartzStartupRunner(Scheduler scheduler) {
this.scheduler = scheduler;
}
@Override
public void run(String... args) throws Exception {
scheduler.start();
}
}
java
package com.example.springbootdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
Job、JobDetail和Scheduler
quartz中提供了一个Job接口,如果你想要有一个定时执行的任务,那么你就需要创建一个类,实现这个Job接口,并且实现其execute方法,execute方法中写要定时执行的逻辑。然后创建一个JobDetail,JobDetail创建的时候要指定Job实现类,你也可以指定一些其他的任务信息。然后创建一个Trigger,指定执行的时间。接着,把JobDetail和Trigger告诉调度器,让调度器去按你计划执行,这样就实现了一个定时执行的任务了。
Job只是记录做什么事情而已,不记录任何其他的,所以很单一。
Detail会绑定一个Job的class对象,相当于是把任务执行的逻辑告诉给JobDetail。当然JobDetail还会记录一些其他的任务信息,不只是记录任务的逻辑是什么,还记录其他的,比如任务叫什么名字,任务属于什么组,任务描述信息呀,等等。
JobDetail 只是一个描述 Job 的元数据对象,它不会直接调用 Job 的 execute 方法。也不会new一个Job实例。
真正会调用Job逻辑的东西其实是Scheduler,他会根据Trigger指定的时间去执行,即,到了该执行的时间,就会去创建Job,然后调用他的execute方法。默认情况下,Scheduler每次在Trigger指定的时间去执行Job的execute的时候,都是会创建一个新的Job对象去执行的。因为可能会有多个调度器会用一个Job的execute逻辑。
Job的状态
分类
Job 可以分为两种类型:有状态的 Job(Stateful Job) 和 无状态的 Job(Stateless Job)。它们的区别主要体现在任务执行过程中是否保留状态,以及是否支持并发执行。
- 无状态的 Job(Stateless Job)
无状态的 Job 是 Quartz 的默认行为。每次任务执行时,Quartz 都会创建一个新的 Job 实例,且不会保留任何状态。
特点:
- 每次执行都是独立的:每次触发器触发时,Quartz 都会创建一个新的 Job 实例,任务执行完成后,实例会被销毁。
- 支持并发执行:多个触发器可以同时触发同一个 Job,Quartz 会为每个触发器创建一个新的 Job 实例
- 不保留状态:JobDataMap 中的数据在任务执行后不会被保留。即使你在 execute 方法中修改了 JobDataMap,并且把修改后的值put进去,这些修改也不会影响下一次任务的执行。
适用场景:
- 任务是无状态的,不需要保留任何数据。
- 任务可以并发执行,且不需要考虑线程安全问题。
- 有状态的 Job(Stateful Job)
有状态的 Job 会在任务执行过程中保留状态,且不支持并发执行。Quartz 通过 @PersistJobDataAfterExecution 和 @DisallowConcurrentExecution 注解来实现有状态的 Job。
特点:
- 保留状态:任务执行过程中对 JobDataMap 的修改会被保留,并影响下一次任务的执行。
- 不支持并发执行:同一时间只能有一个触发器执行该 Job,其他触发器需要等待当前任务执行完成后才能执行。
- 单例行为:即使有多个触发器同时执行,Quartz 也会确保同一时间只有一个任务实例在执行。
适用场景:
- 任务需要保留状态(例如,记录任务的执行次数或中间结果)。
- 任务不能并发执行,需要确保线程安全。
例子:
java
@PersistJobDataAfterExecution // 保留 JobDataMap 的状态
@DisallowConcurrentExecution // 禁止并发执行
public class StatefulJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
int count = dataMap.getInt("count", 0);
count++;
dataMap.put("count", count); // 修改状态
System.out.println("Stateful Job Executed! Count: " + count);
}
}
如何选择无状态 Job 和有状态 Job?
- 如果你的任务是独立的,不需要保留任何状态,且可以并发执行,使用无状态 Job。
- 如果你的任务需要保留状态(例如,记录执行次数或中间结果),使用有状态 Job。
注意事项
- 有状态 Job 的性能:由于有状态 Job 不支持并发执行,可能会影响任务的执行效率。如果任务执行时间较长,可能会导致其他触发器等待。
- 线程安全:即使是有状态 Job,也需要确保 JobDataMap 中的数据类型是线程安全的(例如,使用 AtomicInteger 而不是普通的 int)。
- 状态持久化:如果使用 @PersistJobDataAfterExecution,JobDataMap 的状态会被持久化到数据库中(如果配置了持久化存储)。确保数据库连接和配置正确。
参数传递
Job的抽象方法execute的参数是JobExecutionContext类型的,他是一个非常重要的对象,它提供了任务执行时的上下文信息。很重要的一个作用就是,他可以接收Trigger和JobDetail传递的参数。
你在Trigger或者JobDetail中可以把一些数据保存到JobDataMap中,然后具体执行execute方法的时候,execute中可以通过JobExecutionContext去获取到对应的JobDataMap数据,进行你需要的逻辑。
例子:
java
package com.example;
import com.example.job.MyJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class QuartzExample {
public static void main(String[] args) throws SchedulerException {
// 创建调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 启动调度器
scheduler.start();
// 定义 JobDetail,并设置 JobDataMap
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("myJob", "group1")
.usingJobData("message", "Hello from JobDetail!") // JobDetail 中的参数
.usingJobData("count", 0) // JobDetail 中的参数
.build();
// 定义 Trigger,并设置 JobDataMap
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.usingJobData("message", "Hello from Trigger!") // Trigger 中的参数
.usingJobData("count", 100) // Trigger 中的参数
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5) // 每 5 秒执行一次
.repeatForever())
.build();
// 将 JobDetail 和 Trigger 注册到调度器中
scheduler.scheduleJob(jobDetail, trigger);
}
}
java
package com.example.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobDataMap;
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 获取 JobDetail 中的 JobDataMap
JobDataMap jobDetailDataMap = context.getJobDetail().getJobDataMap();
// 获取 Trigger 中的 JobDataMap
JobDataMap triggerDataMap = context.getTrigger().getJobDataMap();
// 获取合并后的 JobDataMap(Trigger 中的参数会覆盖 JobDetail 中的同名参数)
JobDataMap mergedDataMap = context.getMergedJobDataMap();
// 从 JobDetail 中获取参数
String jobDetailMessage = jobDetailDataMap.getString("message");
int jobDetailCount = jobDetailDataMap.getInt("count");
// 从 Trigger 中获取参数
String triggerMessage = triggerDataMap.getString("message");
int triggerCount = triggerDataMap.getInt("count");
// 从合并后的 JobDataMap 中获取参数
String mergedMessage = mergedDataMap.getString("message");
int mergedCount = mergedDataMap.getInt("count");
// 打印参数
System.out.println("JobDetail Message: " + jobDetailMessage);
System.out.println("JobDetail Count: " + jobDetailCount);
System.out.println("Trigger Message: " + triggerMessage);
System.out.println("Trigger Count: " + triggerCount);
System.out.println("Merged Message: " + mergedMessage);
System.out.println("Merged Count: " + mergedCount);
// 更新计数(如果需要)
mergedCount++;
mergedDataMap.put("count", mergedCount); // 更新合并后的 JobDataMap
}
}
输出:
java
"D:\1program file\Java\jdk1.8.0_231\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2021.3.3\lib\idea_rt.jar=10177:D:\Program Files\JetBrains\IntelliJ IDEA 2021.3.3\bin" -Dfile.encoding=UTF-8 -classpath "D:\1program file\Java\jdk1.8.0_231\jre\lib\charsets.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\deploy.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\access-bridge-64.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\cldrdata.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\dnsns.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\jaccess.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\jfxrt.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\localedata.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\nashorn.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\sunec.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\sunjce_provider.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\sunmscapi.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\sunpkcs11.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\zipfs.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\javaws.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\jce.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\jfr.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\jfxswt.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\jsse.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\management-agent.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\plugin.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\resources.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\rt.jar;E:\SpringBootDemo1\target\classes;E:\develop\maven_repository\maven_repository\org\springframework\boot\spring-boot-starter-web\2.5.0\spring-boot-starter-web-2.5.0.jar;E:\develop\maven_repository\maven_repository\org\springframework\boot\spring-boot-starter\2.5.0\spring-boot-starter-2.5.0.jar;E:\develop\maven_repository\maven_repository\org\springframework\boot\spring-boot\2.5.0\spring-boot-2.5.0.jar;E:\develop\maven_repository\maven_repository\org\springframework\boot\spring-boot-starter-logging\2.5.0\spring-boot-starter-logging-2.5.0.jar;E:\develop\maven_repository\maven_repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;E:\develop\maven_repository\maven_repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;E:\develop\maven_repository\maven_repository\org\apache\logging\log4j\log4j-to-slf4j\2.14.1\log4j-to-slf4j-2.14.1.jar;E:\develop\maven_repository\maven_repository\org\apache\logging\log4j\log4j-api\2.14.1\log4j-api-2.14.1.jar;E:\develop\maven_repository\maven_repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;E:\develop\maven_repository\maven_repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\develop\maven_repository\maven_repository\org\yaml\snakeyaml\1.28\snakeyaml-1.28.jar;E:\develop\maven_repository\maven_repository\org\springframework\boot\spring-boot-starter-json\2.5.0\spring-boot-starter-json-2.5.0.jar;E:\develop\maven_repository\maven_repository\com\fasterxml\jackson\core\jackson-databind\2.12.3\jackson-databind-2.12.3.jar;E:\develop\maven_repository\maven_repository\com\fasterxml\jackson\core\jackson-annotations\2.12.3\jackson-annotations-2.12.3.jar;E:\develop\maven_repository\maven_repository\com\fasterxml\jackson\core\jackson-core\2.12.3\jackson-core-2.12.3.jar;E:\develop\maven_repository\maven_repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.12.3\jackson-datatype-jdk8-2.12.3.jar;E:\develop\maven_repository\maven_repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.12.3\jackson-datatype-jsr310-2.12.3.jar;E:\develop\maven_repository\maven_repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.12.3\jackson-module-parameter-names-2.12.3.jar;E:\develop\maven_repository\maven_repository\org\springframework\boot\spring-boot-starter-tomcat\2.5.0\spring-boot-starter-tomcat-2.5.0.jar;E:\develop\maven_repository\maven_repository\org\apache\tomcat\embed\tomcat-embed-core\9.0.46\tomcat-embed-core-9.0.46.jar;E:\develop\maven_repository\maven_repository\org\apache\tomcat\embed\tomcat-embed-el\9.0.46\tomcat-embed-el-9.0.46.jar;E:\develop\maven_repository\maven_repository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.46\tomcat-embed-websocket-9.0.46.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-web\5.3.7\spring-web-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-beans\5.3.7\spring-beans-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-webmvc\5.3.7\spring-webmvc-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-aop\5.3.7\spring-aop-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-context\5.3.7\spring-context-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-expression\5.3.7\spring-expression-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\quartz-scheduler\quartz\2.3.2\quartz-2.3.2.jar;E:\develop\maven_repository\maven_repository\com\mchange\mchange-commons-java\0.2.15\mchange-commons-java-0.2.15.jar;E:\develop\maven_repository\maven_repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-core\5.3.7\spring-core-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-jcl\5.3.7\spring-jcl-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\projectlombok\lombok\1.18.20\lombok-1.18.20.jar;E:\develop\maven_repository\maven_repository\com\baomidou\mybatis-plus-boot-starter\3.3.1\mybatis-plus-boot-starter-3.3.1.jar;E:\develop\maven_repository\maven_repository\com\baomidou\mybatis-plus\3.3.1\mybatis-plus-3.3.1.jar;E:\develop\maven_repository\maven_repository\com\baomidou\mybatis-plus-extension\3.3.1\mybatis-plus-extension-3.3.1.jar;E:\develop\maven_repository\maven_repository\com\baomidou\mybatis-plus-core\3.3.1\mybatis-plus-core-3.3.1.jar;E:\develop\maven_repository\maven_repository\com\baomidou\mybatis-plus-annotation\3.3.1\mybatis-plus-annotation-3.3.1.jar;E:\develop\maven_repository\maven_repository\com\github\jsqlparser\jsqlparser\3.1\jsqlparser-3.1.jar;E:\develop\maven_repository\maven_repository\org\mybatis\mybatis\3.5.3\mybatis-3.5.3.jar;E:\develop\maven_repository\maven_repository\org\mybatis\mybatis-spring\2.0.3\mybatis-spring-2.0.3.jar;E:\develop\maven_repository\maven_repository\org\springframework\boot\spring-boot-autoconfigure\2.5.0\spring-boot-autoconfigure-2.5.0.jar;E:\develop\maven_repository\maven_repository\org\springframework\boot\spring-boot-starter-jdbc\2.5.0\spring-boot-starter-jdbc-2.5.0.jar;E:\develop\maven_repository\maven_repository\com\zaxxer\HikariCP\4.0.3\HikariCP-4.0.3.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-jdbc\5.3.7\spring-jdbc-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-tx\5.3.7\spring-tx-5.3.7.jar" com.example.QuartzExample
22:29:49.528 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
22:29:49.539 [main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main
22:29:49.559 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
22:29:49.559 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.
22:29:49.559 [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
22:29:49.559 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
22:29:49.559 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
22:29:49.559 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
22:29:49.561 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
22:29:49.561 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 0 triggers
22:29:49.570 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
22:29:49.571 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.myJob', class=com.example.job.MyJob
22:29:49.571 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
22:29:49.571 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.myJob
JobDetail Message: Hello from JobDetail!
JobDetail Count: 0
Trigger Message: Hello from Trigger!
Trigger Count: 100
Merged Message: Hello from Trigger!
Merged Count: 100
22:29:54.570 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.myJob', class=com.example.job.MyJob
22:29:54.570 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
22:29:54.570 [DefaultQuartzScheduler_Worker-2] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.myJob
JobDetail Message: Hello from JobDetail!
JobDetail Count: 0
Trigger Message: Hello from Trigger!
Trigger Count: 100
Merged Message: Hello from Trigger!
Merged Count: 100
22:29:59.579 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.myJob', class=com.example.job.MyJob
22:29:59.579 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
22:29:59.579 [DefaultQuartzScheduler_Worker-3] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.myJob
JobDetail Message: Hello from JobDetail!
JobDetail Count: 0
Trigger Message: Hello from Trigger!
Trigger Count: 100
Merged Message: Hello from Trigger!
Merged Count: 100
22:30:04.563 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.myJob', class=com.example.job.MyJob
22:30:04.563 [DefaultQuartzScheduler_Worker-4] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.myJob
JobDetail Message: Hello from JobDetail!
JobDetail Count: 0
Trigger Message: Hello from Trigger!
Trigger Count: 100
Merged Message: Hello from Trigger!
Merged Count: 100
22:30:04.563 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
22:30:09.565 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.myJob', class=com.example.job.MyJob
22:30:09.566 [DefaultQuartzScheduler_Worker-5] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.myJob
JobDetail Message: Hello from JobDetail!
JobDetail Count: 0
Trigger Message: Hello from Trigger!
Trigger Count: 100
Merged Message: Hello from Trigger!
Merged Count: 100
22:30:09.566 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
22:30:14.572 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.myJob', class=com.example.job.MyJob
22:30:14.573 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
22:30:14.573 [DefaultQuartzScheduler_Worker-6] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.myJob
JobDetail Message: Hello from JobDetail!
JobDetail Count: 0
Trigger Message: Hello from Trigger!
Trigger Count: 100
Merged Message: Hello from Trigger!
Merged Count: 100
......
可以看到一些其他信息:
quartz默认使用的是SimpleThreadPool,并且线程为10个。RAMJobStore 是 Quartz 默认的 Job 存储实现,它将所有的调度数据(如 JobDetail、Trigger、调度状态等)存储在内存中。RAMJobStore 不会将调度数据持久化到磁盘或数据库中。所有的数据都存储在内存中,一旦应用程序停止或崩溃,数据就会丢失。存在内存的好处是快。存在内存容易丢失,断电或者重启程序就丢失job、trigger等信息了,并且,内存存储的方式无法做集群部署的定时任务,只能做简单的单机应用的定时任务。如果需要quartz支持持久化或者集群,那么需要将 JobStore 切换为 JDBCJobStore。

从输出结果可以看到,Merged Count 的值始终是 100,没有递增。这是因为:默认情况下,Quartz 的 Job 是无状态的,每次执行时都会创建一个新的 Job 实例。对 JobDataMap 的修改不会影响下一次任务的执行。

监听器
监听器可以用于监控任务的执行、触发器的触发、调度器的启动和关闭等事件。Quartz 提供了三种监听器JobListener(任务监听器)、TriggerListener(触发器监听器)、 SchedulerListener(调度器监听器)
JobListener
用于监听与 Job 相关的事件,例如:
- 任务执行前触发(jobToBeExecuted)。
- 任务执行完成时触发(jobWasExecuted)。
- 任务执行失效时触发(jobExecutionVetoed)。
使用例子:
java
package com.example.listeners;
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 context) {
System.out.println("Job is about to be executed: " + context.getJobDetail().getKey());
}
@Override
public void jobExecutionVetoed(JobExecutionContext context) {
System.out.println("Job execution was vetoed: " + context.getJobDetail().getKey());
}
@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
System.out.println("Job was executed: " + context.getJobDetail().getKey());
if (jobException != null) {
System.out.println("Job execution failed with exception: " + jobException.getMessage());
}
}
}
java
package com.example;
import com.example.job.SimpleJob;
import com.example.listeners.MyJobListener;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.KeyMatcher;
public class CronTriggerExample {
public static void main(String[] args) {
try {
// 1. 获取任务调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2. 创建任务实例
JobDetail job = JobBuilder.newJob(SimpleJob.class)
.withIdentity("cronJob", "group1")
.build();
// 3. 创建 CronTrigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("cronTrigger", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 11 23 * * ?"))
.build();
// 4. 把任务和触发器告诉任务调度器
scheduler.scheduleJob(job, trigger);
// 5. 注册任务监听器
// scheduler.getListenerManager().addJobListener(new MyJobListener());// (监听当前调度器上的所有任务)
// scheduler.getListenerManager().addJobListener(new MyJobListener(), GroupMatcher.jobGroupEquals("group1"));// (只监听group1组的Job任务)
scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(new JobKey("cronJob", "group1")));// (只监听group1组中叫cronJob的Job任务)
// 6. 使用任务调度器启动任务
scheduler.start();
// 让任务运行一段时间
Thread.sleep(60000); // 60秒
// 7. 使用任务调度器关闭任务
scheduler.shutdown();
} catch (SchedulerException | InterruptedException e) {
e.printStackTrace();
}
}
}
注意:一个任务调度器Scheduler是可以绑定多个任务的,所以,当我们给Scheduler中注册监听器的时候,要说明是监听这个调度器所有的任务,还是只监听某个组的任务,还是说只监听某个key的任务。
TriggerListener
用于监听与 Trigger 相关的事件,例如:
- 触发器触发时触发(triggerFired)。
- 触发器触发完成时触发(triggerComplete)。
- 触发器错过触发时触发(triggerMisfired)。
- 触发器执行失效时触发(vetoJobExecution)。
例子:
java
package com.example.listeners;
import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.TriggerListener;
public class MyTriggerListener implements TriggerListener {
@Override
public String getName() {
return "MyTriggerListener"; // 监听器的名称
}
@Override
public void triggerFired(Trigger trigger, JobExecutionContext context) {
System.out.println("Trigger fired: " + trigger.getKey());
}
@Override
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
System.out.println("Checking if job execution should be vetoed for trigger: " + trigger.getKey());
return false; // 返回 true 表示否决任务执行
}
@Override
public void triggerMisfired(Trigger trigger) {
System.out.println("Trigger misfired: " + trigger.getKey());
}
@Override
public void triggerComplete(Trigger trigger, JobExecutionContext jobExecutionContext, Trigger.CompletedExecutionInstruction completedExecutionInstruction) {
System.out.println("Trigger completed: " + trigger.getKey());
}
}
java
package com.example;
import com.example.job.SimpleJob;
import com.example.listeners.MyJobListener;
import com.example.listeners.MyTriggerListener;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.KeyMatcher;
public class CronTriggerExample {
public static void main(String[] args) {
try {
// 1. 获取任务调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2. 创建任务实例
JobDetail job = JobBuilder.newJob(SimpleJob.class)
.withIdentity("cronJob", "group1")
.build();
// 3. 创建 CronTrigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("cronTrigger", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 11 23 * * ?"))
.build();
// 4. 把任务和触发器告诉任务调度器
scheduler.scheduleJob(job, trigger);
// 5. 注册任务监听器
// scheduler.getListenerManager().addJobListener(new MyJobListener());// (监听当前调度器上的所有任务)
// scheduler.getListenerManager().addJobListener(new MyJobListener(), GroupMatcher.jobGroupEquals("group1"));// (只监听group1组的Job任务)
// scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(new JobKey("cronJob", "group1")));// (只监听group1组中叫cronJob的Job任务)
// 5. 注册触发器监听器
scheduler.getListenerManager().addTriggerListener(new MyTriggerListener());// 注册触发器监听器
// 6. 使用任务调度器启动任务
scheduler.start();
// 让任务运行一段时间
Thread.sleep(60000); // 60秒
// 7. 使用任务调度器关闭任务
scheduler.shutdown();
} catch (SchedulerException | InterruptedException e) {
e.printStackTrace();
}
}
}
SchedulerListener
用于监听与 Scheduler 相关的事件,例如:
- 调度器启动时(schedulerStarted)。
- 调度器关闭时(schedulerShutdown)。
- 任务或触发器被添加、删除、暂停、恢复时。
例子:
java
package com.example.listeners;
import org.quartz.*;
public class MySchedulerListener implements SchedulerListener {
@Override
public void jobScheduled(Trigger trigger) {
System.out.println("Job scheduled: " + trigger.getJobKey());
}
@Override
public void jobUnscheduled(TriggerKey triggerKey) {
System.out.println("Job unscheduled: " + triggerKey);
}
@Override
public void triggerFinalized(Trigger trigger) {
System.out.println("Trigger finalized: " + trigger.getKey());
}
@Override
public void triggerPaused(TriggerKey triggerKey) {
System.out.println("Trigger paused: " + triggerKey);
}
@Override
public void triggersPaused(String triggerGroup) {
System.out.println("Triggers paused in group: " + triggerGroup);
}
@Override
public void triggerResumed(TriggerKey triggerKey) {
System.out.println("Trigger resumed: " + triggerKey);
}
@Override
public void triggersResumed(String triggerGroup) {
System.out.println("Triggers resumed in group: " + triggerGroup);
}
@Override
public void jobAdded(JobDetail jobDetail) {
System.out.println("Job added: " + jobDetail.getKey());
}
@Override
public void jobDeleted(JobKey jobKey) {
System.out.println("Job deleted: " + jobKey);
}
@Override
public void jobPaused(JobKey jobKey) {
System.out.println("Job paused: " + jobKey);
}
@Override
public void jobsPaused(String jobGroup) {
System.out.println("Jobs paused in group: " + jobGroup);
}
@Override
public void jobResumed(JobKey jobKey) {
System.out.println("Job resumed: " + jobKey);
}
@Override
public void jobsResumed(String jobGroup) {
System.out.println("Jobs resumed in group: " + jobGroup);
}
@Override
public void schedulerError(String msg, SchedulerException cause) {
System.out.println("Scheduler error: " + msg);
cause.printStackTrace();
}
@Override
public void schedulerInStandbyMode() {
System.out.println("Scheduler in standby mode");
}
@Override
public void schedulerStarted() {
System.out.println("Scheduler started");
}
@Override
public void schedulerStarting() {
System.out.println("Scheduler starting");
}
@Override
public void schedulerShutdown() {
System.out.println("Scheduler shutdown");
}
@Override
public void schedulerShuttingdown() {
System.out.println("Scheduler shutting down");
}
@Override
public void schedulingDataCleared() {
System.out.println("Scheduling data cleared");
}
}
java
package com.example;
import com.example.job.SimpleJob;
import com.example.listeners.MyJobListener;
import com.example.listeners.MySchedulerListener;
import com.example.listeners.MyTriggerListener;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.KeyMatcher;
public class CronTriggerExample {
public static void main(String[] args) {
try {
// 1. 获取任务调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 2. 创建任务实例
JobDetail job = JobBuilder.newJob(SimpleJob.class)
.withIdentity("cronJob", "group1")
.build();
// 3. 创建 CronTrigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("cronTrigger", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 20 23 * * ?"))
.build();
// 4. 把任务和触发器告诉任务调度器
scheduler.scheduleJob(job, trigger);
// 5. 注册任务监听器
// scheduler.getListenerManager().addJobListener(new MyJobListener());// (监听当前调度器上的所有任务)
// scheduler.getListenerManager().addJobListener(new MyJobListener(), GroupMatcher.jobGroupEquals("group1"));// (只监听group1组的Job任务)
// scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(new JobKey("cronJob", "group1")));// (只监听group1组中叫cronJob的Job任务)
// 5. 注册触发器监听器
// scheduler.getListenerManager().addTriggerListener(new MyTriggerListener());// 注册触发器监听器
// 5. 注册调度器监听器
scheduler.getListenerManager().addSchedulerListener(new MySchedulerListener());// 注册调度器监听器
// 6. 使用任务调度器启动任务
scheduler.start();
// 让任务运行一段时间
Thread.sleep(60000); // 60秒
// 7. 使用任务调度器关闭任务
scheduler.shutdown();
} catch (SchedulerException | InterruptedException e) {
e.printStackTrace();
}
}
}
其实也是和前面两个类似的。
Quartz存储及持久化
JobStore
JobStore负责存储调度器的工作数据(Job、Trigger、JobDataMap),通俗地讲,就是JobStore中存什么,调度器就会执行什么。
其实,Quartz是先把数据存到JobStore中,然后再使用Scheduler去JobStore中拿信息进行调度的。即,Scheduler 接收到 JobDetail 和 Trigger 后,会将它们的相关数据存储到 JobStore 中。scheduler.scheduleJob(job, trigger);这个语句会把相关数据存储到 JobStore 中。Scheduler 启动后(scheduler.start()),它会从 JobStore 中读取任务和触发器的数据,按预定计划进行执行。
Quartz默认是存储到内存的,所以可能会出现下面这个问题:比如我们要执行100次任务,当执行了40次时系统崩溃了,系统重启时任务的执行计数器默认会从0开始。虽然这种策略能够满足多数业务场景需求。但是在某个特定的场景中我们需要继续之前的任务执行,我们可以通过对Jobstore进行持久化来实现。

Quartz提供了两种作业存储方式:
- RAMJobStore:基于内存,重启服务器会丢失数据,但是速度很快。不适合集群部署的情况。
- JDBCJobStore:基于数据库,重启服务器不会丢失数据,速度会比内存慢一点。适合集群部署的情况。
注意:如果集群部署应用,那么你就必须使用JDBCJobStore了,因为,如果多个地方部署Quartz的应用,那么就会有多个调度器了,并且如果都使用内存保存JobStore信息,那么,一个任务可能会被多次执行。但是,如果你多个应用的多个调度器使用一个DB,保存JobStore信息,那么执行就不会重复了,因为执行的信息保存到一个地方,A调度器执行了,告诉数据库,B调度器去执行前,先看数据库,就知道这个任务被A执行了,所以B不会再执行了,这样就不会进行多次执行同一个任务了。

要Quartz使用JDBCJobStore,那么需要自己进行额外的配置的,因为默认是使用RAMJobStore的。集群部署时,需要在多个应用的调度器中配置JDBCJobStore时,使用同一个数据库就行了。
Job主要有volatility、durability两个属性。其中,volatility表示任务是否被持久化,durability表示再没有trigger关联的时候,任务是否被保留。