Quartz知识点总结

简单说明

简单的定时任务使用Timer或者ScheduledExecutorService

quartz支持复杂的定时执行功能。支持ram存储(内存存储)和持久化存储。quartz有分布式和集群能力

简单使用

  1. 获取任务调度器Schedule。任务调度器可以管理任务。
  2. 创建任务实例。使用JobBuilder(任务类.class)创建,会返回一个JobDetail。任务是一个实现了Job,并实现execute方法的类而已,execute方法记录要执行的事情。
  3. 创建触发器Trigger。Trigger是用来指定触发策略的,包括什么时候开始执行、循环多少次、多久执行一次呀,等等。支持链式编程。
  4. 把任务和触发器告诉任务调度器,使用scheduleJob方法来完成。
  5. 使用任务调度器启动任务
  6. 使用任务调度器关闭任务

例子:

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;public class SimpleJob implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("SimpleJob is executing at: " + new java.util.Date());}
}
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 模式(建造者模式):

JobDetail job = JobBuilder.newJob(SimpleJob.class)
.withIdentity("simpleJob", "group1")
.build();
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("simpleTrigger", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever()).build();

Quartz基本的实现原理

  1. job和jobdetail的关系是什么?

Job(任务)是一个接口,表示一个具体的任务。你需要实现这个接口,并在 execute 方法中定义任务的具体逻辑。Job 只关注任务的执行逻辑,即 做什么。

JobDetail(任务详情)JobDetail 是一个类,用于描述任务的详细信息。它包含了任务的元数据,例如任务类、任务名称、任务组、任务数据等。JobDetail 关注任务的 元信息,即 谁来做 和 怎么做。

JobDetail job = JobBuilder.newJob(SimpleJob.class).withIdentity("simpleJob", "group1").build();

这里 JobDetail 描述了任务的详细信息:

  • 任务类是 SimpleJob.class。
  • 任务名称是 “simpleJob”。
  • 任务组是 “group1”。

总结:

  • Job:定义任务的具体逻辑(做什么)。
  • JobDetail:描述任务的元信息(谁来做、怎么做)。
  • 关系:JobDetail 是 Job 的包装器,包含了 Job 的类信息和任务的额外信息。
  • 设计目的:解耦任务逻辑和任务元信息,提高灵活性和可扩展性。
  1. 原理图:

  1. Job、JobDetail和Trigger的关系

Trigger:

  • 一个 Trigger 只能绑定一个 JobDetail。

JobDetail:

  • 一个 JobDetail 可以被多个 Trigger 绑定。相当于是一个Job信息可以被多次使用。

Job:

  • 一个 Job 类可以创建多个 JobDetail 对象。相当于是可以允许多个JobDetail使用一个Job的execute逻辑。
  • 默认情况下,Quartz 每次触发器触发时都会创建一个新的 Job 实例,再去执行execute方法。(这样可以线程安全)

例子:

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 {@Overridepublic 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());}}
}

执行结果:

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: 0Using 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 的触发器中主要是下面两种比较常用:

  1. SimpleTrigger:适用于单次或固定间隔重复执行的任务。
  2. CronTrigger:适用于复杂调度规则的任务。他也能和SimpleTrigger一样,指定开始执行和结束执行的时间。

SimpleTrigger适合场景:

  1. 定时任务:比如指定晚上12点去导出订单
  2. 延时任务:比如,当用户提交订单后,启动任务,并且延时30秒钟后检测订单状态,如果没有支付就取消订单并释放库存
  3. 循环任务:可以无限循环、可以指定循环次数、可以指定循环到某个时间停止

具体使用可以让GBT给出简单使用案例。

CronTrigger适合场景:

  1. 适合需要复杂调度规则的任务(例如每天、每周、每月执行、每个星期几执行、第几周执行)。
  2. 需要精确控制执行时间的任务。

Cron 表达式由 6 或 7 个字段组成(各字段使用空格分割,最后一个年份是可选的),格式如下:

秒 分 时 日 月 周 年(可选)

cron表达式的规则:

  • 秒(0-59):表示每分钟的秒数。
  • 分(0-59):表示每小时的分钟数。
  • 时(0-23):表示每天的小时数。
  • 日(1-31):表示每月的日期。
  • 月(1-12 或 JAN-DEC):表示每年的月份。使用月份的前3个字母也是可以表示月份的。
  • 周(1-7 或 SUN-SAT):表示每周的星期几(1 表示周日,7 表示周六)。
  • 年(可选,1970-2099):表示年份(可以省略)。省略表示对年份没有要求,任何年份都可以执行,如果指定年份,那么久只能在对应的年份才能执行。

Cron 表达式中可以使用以下符号来定义时间的范围和规则:

  1. 星号(*)
    含义:表示“每”或“任意”。

示例:

0 * * * * ?:每分钟的第 0 秒执行(即每分钟执行一次)。

0 0 * * * ?:每小时的 0 分 0 秒执行(即每小时执行一次)。

  1. 问号(?)
    含义:表示“无特定值”,它通常用于 “日”字段 或 “周”字段,以避免这两个字段之间的冲突。因为“日”和“周”字段是互斥的,不能同时指定具体的值,所以需要用 ? 来表示其中一个字段不指定具体值。

示例:

0 0 12 ? * MON:每周一的 12:00:00 执行。

0 0 0 1 * ?:每月 1 日的 00:00:00 执行。

  1. 逗号(,)
    含义:表示“或”,用于列举多个值。

示例:

0 0 12,18 * * ?:每天的 12:00:00 和 18:00:00 执行。

0 0 0 1,15 * ?:每月 1 日和 15 日的 00:00:00 执行。

  1. 连字符(-)
    含义:表示“范围”,用于定义一个连续的时间范围。

示例:

0 0 9-17 * * ?:每天 9:00:00 到 17:00:00,每小时执行一次。

0 0 0 1-5 * ?:每月 1 日到 5 日的 00:00:00 执行。

  1. 斜杠(/)
    含义:表示“间隔”,用于定义时间间隔。

示例:

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
  1. 井号(#)
    含义:表示“第几个”,用于指定某月的第几个星期几。

示例:

0 0 12 ? * 6#3:每月的第 3 个星期五的 12:00:00 执行。

0 0 12 ? * 2#1:每月的第 1 个星期一的 12:00:00 执行。

  1. 字母(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(周二)

例如:

  1. 0 0 12 * * ?:每天中午 12 点执行。
  2. 0 0/5 14 * * ?:每天下午 2 点到 2:55,每 5 分钟执行一次。

使用案例:创建一个任务在每天的 12:00:00 执行。

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;public class SimpleJob implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("SimpleJob is executing at: " + new java.util.Date());}
}
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. 创建 CronTriggerTrigger 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:错过的全部立即进行补偿(即时任务终止时间已经到达),然后正常调度

代码:

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();}@Beanpublic 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();}@Beanpublic Scheduler scheduler(Trigger trigger, JobDetail jobDetail) throws SchedulerException {Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();scheduler.scheduleJob(jobDetail, trigger);return scheduler;}
}
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 {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {// 获取任务关联的jobDetailJobDetail 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);}
}
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;}@Overridepublic void run(String... args) throws Exception {scheduler.start();}
}
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)。它们的区别主要体现在任务执行过程中是否保留状态,以及是否支持并发执行。

  1. 无状态的 Job(Stateless Job)

无状态的 Job 是 Quartz 的默认行为。每次任务执行时,Quartz 都会创建一个新的 Job 实例,且不会保留任何状态。

特点:

  • 每次执行都是独立的:每次触发器触发时,Quartz 都会创建一个新的 Job 实例,任务执行完成后,实例会被销毁。
  • 支持并发执行:多个触发器可以同时触发同一个 Job,Quartz 会为每个触发器创建一个新的 Job 实例
  • 不保留状态:JobDataMap 中的数据在任务执行后不会被保留。即使你在 execute 方法中修改了 JobDataMap,并且把修改后的值put进去,这些修改也不会影响下一次任务的执行。

适用场景:

  • 任务是无状态的,不需要保留任何数据。
  • 任务可以并发执行,且不需要考虑线程安全问题。
  1. 有状态的 Job(Stateful Job)

有状态的 Job 会在任务执行过程中保留状态,且不支持并发执行。Quartz 通过 @PersistJobDataAfterExecution 和 @DisallowConcurrentExecution 注解来实现有状态的 Job。

特点:

  • 保留状态:任务执行过程中对 JobDataMap 的修改会被保留,并影响下一次任务的执行。
  • 不支持并发执行:同一时间只能有一个触发器执行该 Job,其他触发器需要等待当前任务执行完成后才能执行。
  • 单例行为:即使有多个触发器同时执行,Quartz 也会确保同一时间只有一个任务实例在执行。

适用场景:

  • 任务需要保留状态(例如,记录任务的执行次数或中间结果)。
  • 任务不能并发执行,需要确保线程安全。

例子:

@PersistJobDataAfterExecution // 保留 JobDataMap 的状态
@DisallowConcurrentExecution  // 禁止并发执行
public class StatefulJob implements Job {@Overridepublic 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数据,进行你需要的逻辑。

例子:

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,并设置 JobDataMapJobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("myJob", "group1").usingJobData("message", "Hello from JobDetail!") // JobDetail 中的参数.usingJobData("count", 0) // JobDetail 中的参数.build();// 定义 Trigger,并设置 JobDataMapTrigger 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);}
}
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 {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {// 获取 JobDetail 中的 JobDataMapJobDataMap jobDetailDataMap = context.getJobDetail().getJobDataMap();// 获取 Trigger 中的 JobDataMapJobDataMap 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}
}

输出:

"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: 0Using 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)。

使用例子:

package com.example.listeners;import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;public class MyJobListener implements JobListener {@Overridepublic String getName() {return "MyJobListener"; // 监听器的名称}@Overridepublic void jobToBeExecuted(JobExecutionContext context) {System.out.println("Job is about to be executed: " + context.getJobDetail().getKey());}@Overridepublic void jobExecutionVetoed(JobExecutionContext context) {System.out.println("Job execution was vetoed: " + context.getJobDetail().getKey());}@Overridepublic 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());}}
}
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. 创建 CronTriggerTrigger 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)。

例子:

package com.example.listeners;import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.TriggerListener;public class MyTriggerListener implements TriggerListener {@Overridepublic String getName() {return "MyTriggerListener"; // 监听器的名称}@Overridepublic void triggerFired(Trigger trigger, JobExecutionContext context) {System.out.println("Trigger fired: " + trigger.getKey());}@Overridepublic boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {System.out.println("Checking if job execution should be vetoed for trigger: " + trigger.getKey());return false; // 返回 true 表示否决任务执行}@Overridepublic void triggerMisfired(Trigger trigger) {System.out.println("Trigger misfired: " + trigger.getKey());}@Overridepublic void triggerComplete(Trigger trigger, JobExecutionContext jobExecutionContext, Trigger.CompletedExecutionInstruction completedExecutionInstruction) {System.out.println("Trigger completed: " + trigger.getKey());}}
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. 创建 CronTriggerTrigger 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)。
  • 任务或触发器被添加、删除、暂停、恢复时。

例子:

package com.example.listeners;import org.quartz.*;public class MySchedulerListener implements SchedulerListener {@Overridepublic void jobScheduled(Trigger trigger) {System.out.println("Job scheduled: " + trigger.getJobKey());}@Overridepublic void jobUnscheduled(TriggerKey triggerKey) {System.out.println("Job unscheduled: " + triggerKey);}@Overridepublic void triggerFinalized(Trigger trigger) {System.out.println("Trigger finalized: " + trigger.getKey());}@Overridepublic void triggerPaused(TriggerKey triggerKey) {System.out.println("Trigger paused: " + triggerKey);}@Overridepublic void triggersPaused(String triggerGroup) {System.out.println("Triggers paused in group: " + triggerGroup);}@Overridepublic void triggerResumed(TriggerKey triggerKey) {System.out.println("Trigger resumed: " + triggerKey);}@Overridepublic void triggersResumed(String triggerGroup) {System.out.println("Triggers resumed in group: " + triggerGroup);}@Overridepublic void jobAdded(JobDetail jobDetail) {System.out.println("Job added: " + jobDetail.getKey());}@Overridepublic void jobDeleted(JobKey jobKey) {System.out.println("Job deleted: " + jobKey);}@Overridepublic void jobPaused(JobKey jobKey) {System.out.println("Job paused: " + jobKey);}@Overridepublic void jobsPaused(String jobGroup) {System.out.println("Jobs paused in group: " + jobGroup);}@Overridepublic void jobResumed(JobKey jobKey) {System.out.println("Job resumed: " + jobKey);}@Overridepublic void jobsResumed(String jobGroup) {System.out.println("Jobs resumed in group: " + jobGroup);}@Overridepublic void schedulerError(String msg, SchedulerException cause) {System.out.println("Scheduler error: " + msg);cause.printStackTrace();}@Overridepublic void schedulerInStandbyMode() {System.out.println("Scheduler in standby mode");}@Overridepublic void schedulerStarted() {System.out.println("Scheduler started");}@Overridepublic void schedulerStarting() {System.out.println("Scheduler starting");}@Overridepublic void schedulerShutdown() {System.out.println("Scheduler shutdown");}@Overridepublic void schedulerShuttingdown() {System.out.println("Scheduler shutting down");}@Overridepublic void schedulingDataCleared() {System.out.println("Scheduling data cleared");}
}
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. 创建 CronTriggerTrigger 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关联的时候,任务是否被保留。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/36623.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言每日一练——day_12(最后一天)

引言 针对初学者&#xff0c;每日练习几个题&#xff0c;快速上手C语言。第十二天。&#xff08;最后一天&#xff0c;完结散花啦&#xff09; 采用在线OJ的形式 什么是在线OJ&#xff1f; 在线判题系统&#xff08;英语&#xff1a;Online Judge&#xff0c;缩写OJ&#xff0…

【宇宙回响】从Canvas到MySQL:飞机大战的全栈交响曲【附演示视频与源码】

&#x1f31f; 这是星际大战系列的第三篇送福利文章&#xff0c;感谢一路以来支持和关注这个项目的每一位朋友&#xff01; &#x1f4a1; 文章力求严谨&#xff0c;但难免有疏漏之处&#xff0c;欢迎各位朋友指出&#xff0c;让我们一起在交流中进步。 &#x1f381; 项目代码…

数据结构知识点1

目录 一、时间复杂度和空间复杂度 1.1时间复杂度&#xff1a; 1.2空间复杂度&#xff1a; 二、装箱和拆箱 三、泛型 3.1泛型类的使用&#xff1a; 3.2泛型的上界&#xff1a; 3.3泛型方法&#xff1a; 一、时间复杂度和空间复杂度 1.1时间复杂度&#xff1a; 时间复杂…

华为ipd流程华为流程体系管理华为数字化转型流程数字化管理解决方案介绍81页精品PPT

华为流程体系最佳实践主要包括构建完善的流程框架&#xff0c;明确各层级流程要素与职责&#xff0c;梳理涵盖研发、采购、营销、服务、资产管理等多领域的流程&#xff0c;通过梳理业务场景和核心能力搭建差异化流程框架&#xff0c;采用自上而下与自下而上相结合的建模方法&a…

在大数据开发中ETL是指什么?

hello宝子们...我们是艾斯视觉擅长ui设计和前端数字孪生、大数据、三维建模、三维动画10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩! 在数字经济时代&#xff0c;数据已成为企业最核心的资产。然而&#xff0c;分散在业务系统、日志文件…

Collection系列集合的小结+集合并发修改异常问题

一、Collection系列集合的小结 二、补充知识&#xff1a;集合的并发修改异常问题 三、Collection的其他相关知识 1. 前置知识&#xff1a;可变参数 2. 集合的工具类&#xff1a;Collections 3. 综合案例&#xff1a;斗地主游戏 &#xff08;1&#xff09;创建Card类 public c…

QT Quick(C++)跨平台应用程序项目实战教程 2 — 环境搭建和项目创建

目录 引言 1. 安装Qt开发环境 1.1 下载Qt安装包 1.2 安装Qt 1.3 安装Visual Studio 2022 1.4 在Visual Studio 2022中安装Qt插件 1.5 在Visual Studio 2022中安装大模型编程助手 2. 创建Qt Quick项目 2.1 创建新项目 2.2 项目结构 2.3 运行项目 3. 理解项目代码 3…

免密登录远程服务器shell脚本

一、脚本代码 #!/bin/bash #提示用户输入用户i名和ip地址 read -p "请输入远程服务器的用户名: " hname read -p "请输入远程服务器的IP地址: " fip read -p "请输入远程服务器的远程端口:" sdk #检查是否配置了免密登录 function sfmm(){ …

repo init 错误 Permission denied (publickey)

一、已经生成ssh-key并设置到gerrit上 二、已经设置.gitconfig &#xff08;此步骤是公司要求&#xff0c;设置gerrit地址为一个别名之类的&#xff0c;有的公司不需要&#xff09; 然后出现下面的错误&#xff0c;最后发现忘记设置git的用户名和邮箱 1. git config --globa…

卷积神经网络 - 汇聚层

卷积神经网络一般由卷积层、汇聚层和全连接层构成&#xff0c;本文我们来学习汇聚层。 汇聚层(Pooling Layer)也叫子采样层(Subsampling Layer)&#xff0c;其作用是进 行特征选择&#xff0c;降低特征数量&#xff0c;从而减少参数数量。 卷积层虽然可以显著减少网络中连接的…

C++ 头文件说明

如果一个程序足够大&#xff0c;代码功能很多&#xff0c;可以想象&#xff0c;不可能把代码写在一个cpp文件里。我们需要模块化&#xff0c;这样的好处很多&#xff0c;方便分工合作&#xff0c;可读性提高&#xff0c;调用也方便。 这个要怎么做呢&#xff1f; 很简单直接当…

【蓝桥杯】省赛:分糖果(思维/模拟)

思路 数据很小&#xff0c;直接暴力模拟。 有意思的是一个列表如何当成循环队列写&#xff1f;可以arr[(i1)%n]让他右边超出时自动回到开头。 code import os import sysn int(input()) arr list(map(int,input().split()))ans 0 while 1:arr1 arr.copy()for i in range…

如何理解分布式光纤传感器?

关键词&#xff1a;OFDR、分布式光纤传感、光纤传感器 分布式光纤传感器是近年来备受关注的前沿技术&#xff0c;其核心在于将光纤本身作为传感介质和信号传输介质&#xff0c;通过解析光信号在光纤中的散射效应&#xff0c;实现对温度、应变、振动等物理量的连续、无盲区、高…

【java面型对象进阶】------继承实例

继承结构下的标准Javabean 代码如下&#xff1a; package demo10;//定义员工父类 public class Employee {private String id;private String name;private double salary;//构造方法public Employee(){}public Employee(String id,String name,double salary){this.idid;thi…

matrix-breakout-2-morpheus 靶机----练习攻略 【仅获取shell】

【此练习仅做到反弹shell】 1.靶机下载地址 https://download.vulnhub.com/matrix-breakout/matrix-breakout-2-morpheus.ova 2. 打开靶机&#xff0c;kali使用nmap扫描同C段的主机 找到靶机ip 确保靶机和kali网卡均为NAT模式 先查看kali的ip nmap 192.168.182.1/24 …

解锁MySQL 8.0.41源码调试:Mac 11.6+CLion 2024.3.4实战指南

文章目录 解锁MySQL 8.0.41源码调试&#xff1a;Mac 11.6CLion 2024.3.4实战指南前期准备环境搭建详细步骤安装 CLion安装 CMake 3.30.5准备 MySQL 8.0.41 源码配置 CMake 选项构建 MySQL 项目 调试环境配置与验证配置 LLDB 调试器启动调试验证调试环境 总结与拓展碰到的问题1.…

使用码云搭建CocoaPods远程私有库

一、创建远程私有索引库 用来存放私有框架的详细描述信息.podspec文件 1. 创建私有库 假设码云上创建的私有库为repo-spec 2. 查看本地已存在的索引库 pod repo list 3. 将远程私有索引库添加到本地 pod repo add [https://gitee.com/jingluoguo/repo-spec.git](https://gi…

Devops之AWS:如何安装AWS CLI

AWS 命令行界面&#xff08;AWS CLI&#xff09;是一种开源工具&#xff0c;让我们能够使用命令行 Shell 中的命令与 AWS 服务进行交互。 安装步骤&#xff1a; 下载并运行AWS CLI的MSI安装程序&#xff1a; 点击如下的链接&#xff0c;即可下载MSI安装程序&#xff1a; htt…

docker需要sudo才能使用

一种方法是添加当前用户到docker组里去&#xff0c;当时添加的时候貌似是没问题的&#xff0c;但是现在又不可以了 产生的报错 ❯ docker images Cannot connect to the Docker daemon at unix:///home/ying/.docker/desktop/docker.sock. Is the docker daemon running?解决…

jmeter将返回的数据写入csv文件

举例说明&#xff0c;我需要接口返回体中的exampleid与todoid的数据信息&#xff08;使用边界提取器先将其提取&#xff09;&#xff0c;并将其写入csv文件进行保存 使用后置处理器BeanShell 脚本实例如下 import java.io.*;// 设置要写入的文件路径 String filePath "…