Quartz定时任务框架(一):概述和Job、JobDetail详解

article/2025/9/24 0:31:54

文章目录

  • Quartz定时任务框架
    • 核心概念
    • Hello World 入门
  • 深入学习
    • 原理概述
    • Job和JobDetail
    • JobDataMap
    • Job实例
    • Job的数据状态和并发
    • Job的其它特性

Quartz定时任务框架

核心概念

  • Job
  • JobDetail
  • Trigger
  • Scheduler

Job:

既然是定时任务,任务任务对吧,job就是你要定时干的事。比如就在控制台打印Hello World

JobDetail:

Job只是定义你要干什么,可以理解为模板不能直接用。需要创建一个Job实例,这个实例就是JobDetail。

Trigger:

触发器,规定怎么执行,5s一次还是10s一次。

Scheduler:

调度器、执行器。它可以根据触发器Trigger去执行Job

Hello World 入门

我们用结果驱动学习,先来一个Hello World玩玩。
依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

从上面的概念可以理解到:我们要输出Hello World,这不就是一个任务(Job)吗?来:

Job是一个接口,我们写一个类继承即可:

public class MyJob implements Job {@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {System.out.println("Hello World!");}
}

我滴任务完成啦~~~!!

任务就是这么简单。


任务有了,接下来就是触发器Trigger、调度器Scheduler。

写一个main方法就能搞定了:

public class TestScheduler {public static void main(String[] args) throws SchedulerException {// 创建调度器Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// 创建任务实例(传入job类)JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("hello", "job").build();// 创建触发器SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger", "trigger").startNow().withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)).build();// 调度器接管任务和触发器scheduler.scheduleJob(jobDetail, trigger);// 开始执行scheduler.start();}
}

我们现在只需要知道,现在每5s钟,控制台就会打印Hello World

我们现在不要去纠结那些方法是干嘛的(让结果驱动学习)。

可以看见触发器Trigger和任务Job都是有一个xxxBuilder的构建对象!!!!

调度器则是有一个工厂类获取的。

一切就是这么简单。

动手操作一遍,运行main方法:

image-20220427145533412

接下来,我们慢慢深入

深入学习

原理概述

Job为一个接口,只有一个方法:

public interface Job {void execute(JobExecutionContext var1) throws JobExecutionException;
}

属实很朴素。

job的一个 trigger 被触发后(稍后会讲到),execute() 方法会被 scheduler 的一个工作线程调用;传递给 execute() 方法的 JobExecutionContext 对象中保存着该 job 运行时的一些信息 ,执行 job 的 scheduler 的引用,触发 job 的 trigger 的引用,JobDetail 对象引用,以及一些其它信息。(当job的触发器被触发时,scheduler调度器的一个线程会执行job的execute方法,同时会传递一些参数)

JobDetail 对象是在将 job 加入 scheduler 时,由客户端程序(你的程序)创建的。它包含 job 的各种属性设置,以及用于存储 job 实例状态信息的 JobDataMap

Trigger 用于触发 Job 的执行。当你准备调度一个 job 时,你创建一个 Trigger 的实例,然后设置调度相关的属性。Trigger 也有一个相关联的 JobDataMap,用于给 Job 传递一些触发相关的参数。Quartz 自带了各种不同类型的 Trigger,最常用的主要是 SimpleTriggerCronTrigger

SimpleTrigger 主要用于一次性执行的 Job(只在某个特定的时间点执行一次),或者 Job 在特定的时间点执行,重复执行 N 次,每次执行间隔T个时间单位。CronTrigger 在基于日历的调度上非常有用,如“每个星期五的正午”,或者“每月的第十天的上午 10:15”等。

为什么既有 Job,又有 Trigger 呢?很多任务调度器并不区分 Job 和 Trigger。有些调度器只是简单地通过一个执行时间和一些 job 标识符来定义一个 Job;其它的一些调度器将 Quartz 的 Job 和 Trigger 对象合二为一。在开发 Quartz 的时候,我们认为将调度和要调度的任务分离是合理的。在我们看来,这可以带来很多好处。

例如,Job 被创建后,可以保存在 Scheduler 中,与 Trigger 是独立的,同一个 Job可以有多个 Trigger;这种松耦合的另一个好处是,当与 Scheduler 中的 Job 关联的 trigger 都过期时,可以配置 Job 稍后被重新调度,而不用重新定义 Job;还有,可以修改或者替换 Trigger,而不用重新定义与之关联的 Job。


将 Job 和 Trigger 注册到 Scheduler 时,可以为它们设置 key,配置其身份属性。

image-20220427151613343

Job 和 Trigger 的 key(JobKey 和 TriggerKey)可以用于将 Job 和 Trigger 放到不同的分组(group)里,然后基于分组进行操作。同一个分组下的 Job 或 Trigger 的名称必须唯一,即一个 Job 或 Trigger 的 key 由名称(name)和分组(group)组成。

Job和JobDetail

当你定义了一个实现Job接口的类,这个类仅仅表明该job需要完成什么任务。

除此之外,Quartz还需要知道该Job实例所包含的属性而这将由JobDetail类来完成。

不妨再看看HelloWorld中的代码:

// 创建任务实例(传入job类)
JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("hello", "job").build();// 创建触发器
SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger", "trigger").startNow().withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)).build();// 调度器接管任务和触发器
scheduler.scheduleJob(jobDetail, trigger);

因为我们在创建JobDetail对象的时候,把要执行的Job类型传递了进来。

而JobDetail对象又和触发器一起交给了调度器Scheduler。

所以,调度器Scheduler知道将要执行的任务类型。

只不过,调度器每次执行这个任务的时候,每次都会重新创建任务对象。

这个时候我们思考一个问题:如何给Job对象添加属性来追踪job的状态呢?

答案是:,因为每次执行都会创建一个新的job任务对象,所以无法直接在任务中添加什么属性。

但是,你是否还记得前文原理概述时说过:

public interface Job {void execute(JobExecutionContext var1) throws JobExecutionException;
}

JobExecutionContext这个参数:Scheduler执行任务execute方法时,会传递很多的参数,包括JobDetail的对象。

都在这个参数中。

不知道此时你有没有恍然大悟。

既然无法直接在任务上动手脚,就用一个外部对象来,每次执行任务的时候,都传给你这个任务。这个外部对象就是JobDetail,而需要增加的属性就放在JobDetail对象的JobDataMap

我们在execute方法中打个断点,看看JobExecutionContext都有些啥:

image-20220427154246130

JobDataMap

JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据。JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法

我们就来试试,稍微改动下HelloWorld代码:

// 创建任务实例
JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("hello", "job").usingJobData("xp","好帅好帅").build();

只需要加上.usingJobData("xp","好帅好帅")

然后我们去任务的execute方法中取这个参数:

@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();String xp = jobDataMap.getString("xp");System.out.println("Hello World!" + xp);
}

效果:

image-20220427155017683

在决定JobDataMap中存放什么数据的时候需要小心,因为JobDataMap中存储的对象都会被序列化,因此很可能会导致类的版本不一致的问题;Java的标准类型都很安全,如果你已经有了一个类的序列化后的实例,某个时候,别人修改了该类的定义,此时你需要确保对类的修改没有破坏兼容性

当然,你也可以做一些配置。使得map中仅允许存储基本类型和String类型的数据,这样可以避免后续的序列化问题。


JobExecutionContext中的JobDataMap为我们提供了很多的便利。它是JobDetail中的JobDataMap和Trigger中的JobDataMap的并集,但是如果存在相同的数据,则后者会覆盖前者的值。(打上断点,细心的你可能会发现JobExecutionContext中有好几个jobDataMap)

在execute方法中,可以合并这几个jobDataMap为一个。

@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {JobDataMap mergedJobDataMap = jobExecutionContext.getMergedJobDataMap();String xp = mergedJobDataMap.getString("xp");System.out.println("Hello World!" + xp);
}

同样也能拿到值,而且这样很方便。不用一会从这个jobDataMap拿数据,一会从另一个拿。


如果你在job类中,为JobDataMap中存储的数据的key增加set方法,那么Quartz的默认JobFactory实现在job被实例化的时候会自动调用这些set方法,这样你就不需要在execute()方法中显式地从map中取数据了。

不明白?上代码,就拿我保存的xp这个值来说

public class MyJob implements Job {private String xp;/** set方法 */public void setXp(String value) {this.xp = value;}@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {System.out.println("Hello World!" + xp);}}

运行效果:

image-20220427161257714

可以看到,只需要在job类中定义这个属性,添加set方法。就直接帮我们赋值了。

直接使用,简直不要太方便。

Job实例

你可以只创建一个job类,然后创建多个与该job关联的JobDetail实例,每一个实例都有自己的属性集和JobDataMap,最后,将所有的实例都加到scheduler中。

比如,你创建了一个实现Job接口的类“SalesReportJob”。该job需要一个参数(通过JobdataMap传入),表示负责该销售报告的销售员的名字。因此,你可以创建该job的多个实例(JobDetail),比如“SalesReportForJoe”、“SalesReportForMike”,将“joe”和“mike”作为JobDataMap的数据传给对应的job实例。

当一个trigger被触发时,与之关联的JobDetail实例会被加载,JobDetail引用的job类通过配置在Scheduler上的JobFactory进行初始化。默认的JobFactory实现,仅仅是调用job类的newInstance()方法,然后尝试调用JobDataMap中的key的setter方法。你也可以创建自己的JobFactory实现,比如让你的IOC或DI容器可以创建/初始化job实例。


一个任务(Job类)可以有多个任务实例(JobDetail),即:

// 创建任务实例
JobDetail jobDetailA = JobBuilder.newJob(MyJob.class).withIdentity("helloA", "job").usingJobData("xpA","好帅好帅").build();
JobDetail jobDetailB = JobBuilder.newJob(MyJob.class).withIdentity("helloB", "job").usingJobData("xpB","你小子真流弊").build();

但是一个任务实例(JobDetail)只能和一个Trigger对应!

Job的数据状态和并发

关于job的状态数据(即JobDataMap)和并发性,还有一些地方需要注意。

@DisallowConcurrentExecution:将该注解加到job类上,告诉Quartz不要并发地执行同一个job定义(这里指特定的job类)的多个实例

@DisallowConcurrentExecution
public class MyJob implements Job {@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {System.out.println("Hello World!");}}

该注解限制是针对JobDetail的,而不是job类

@PersistJobDataAfterExecution:将该注解加在job类上,告诉Quartz在成功执行了job类的execute方法后(没有发生任何异常),更新JobDetail中JobDataMap的数据,使得该job(即JobDetail)在下一次执行的时候,JobDataMap中是更新后的数据,而不是更新前的旧数据。和 @DisallowConcurrentExecution注解一样,尽管注解是加在job类上的,但其限制作用是针对job实例的,而不是job类的。

如果你使用了@PersistJobDataAfterExecution注解,我们强烈建议你同时使用@DisallowConcurrentExecution注解,因为当同一个job类的两个实例被并发执行时,由于竞争,JobDataMap中存储的数据很可能是不确定的。

Job的其它特性

通过JobDetail对象,可以给job实例配置的其它属性有:

  • Durability:如果一个job是非持久的,当没有活跃的trigger与之关联的时候,会被自动地从scheduler中删除。也就是说,非持久的job的生命期是由trigger的存在与否决定的;
  • RequestsRecovery:如果一个job是可恢复的,并且在其执行的时候,scheduler发生硬关闭(hard shutdown)(比如运行的进程崩溃了,或者关机了),则当scheduler重新启动的时候,该job会被重新执行。此时,该job的JobExecutionContext.isRecovering() 返回true。
// 创建任务实例
JobDetail jobDetailA = JobBuilder.newJob(MyJob.class).requestRecovery(false) // 是否可恢复.storeDurably(false) // 是否持久.withIdentity("helloA", "job").usingJobData("xpA","好帅好帅").build();

由于篇幅原因,打算分开些。不然一篇得很长了。这里只是说完了Job和JobDetail,后续更精彩。


http://chatgpt.dhexx.cn/article/DcKu1i1o.shtml

相关文章

struts2+quartz定时任务

最近有需求要写一个定时任务 目的是更新一些员工/人员与部门之间的关系 项目用的是struts2 当我加了spring的jar包之后写了一个定时任务 项目经理不让用spring 就修改一下 这次贴个全的 下面是任务类 package com.timetask.action; import java.io.BufferedWriter; import…

Quartz定时任务框架使用教程详解

Quartz定时任务框架学习 什么是QuartzQuartz依赖引入使用 QuartzQuartz API&#xff0c;Jobs和TriggersJob 和 Trigger为什么既有 Job&#xff0c;又有 Trigger 呢&#xff1f;Key Job与JobDetail介绍为什么设计成JobDetail Job&#xff0c;不直接使用JobJobDataMapJob实例Job…

quartz定时任务不执行

quartz定时任务执行一段时间不执行的原因 数据库表QRTZ_TRIGGERS 里的TRIGGER_STATE 字段的值自动修改为ERROR了 ,quartz定时任务是不扫描这种ERROR情况. 之所以出现ERROR 原因在于job里有bug,导致quartz自动将状态改变,然后quartz不执行ERROR状态的job. 1.查找原因 原因如下图…

SpringBoot结合Quartz实现定时任务

《从零打造项目》系列文章 工具 比MyBatis Generator更强大的代码生成器 ORM框架选型 SpringBoot项目基础设施搭建SpringBoot集成Mybatis项目实操SpringBoot集成MybatisPlus项目实操SpringBoot集成Spring Data JPA项目实操 数据库变更管理 数据库变更管理&#xff1a;Liquibase…

Quartz定时任务不定时执行

请移步&#xff1a; https://editor.csdn.net/md/?articleId126933515 手动执行才执行。 cron表达式是 1分钟执行3次&#xff0c;一直正常的。 cron "0 */20 * * * ?"依次报错&#xff1a; 后定时任务不再执行了。 手动触发可以正常执行。 Job taskJob (Job…

Springboot 使用quartz 定时任务 增删改查

前段时间公司项目用到了 定时任务 所以写了一篇定时任务的文章 &#xff0c;浏览量还不错 &#xff0c; Springboot 整合定时任务 ) 所以就准备写第二篇&#xff0c; 如果你是一名Java工程师&#xff0c;你也可以会看到如下的页面 &#xff0c;去添加定时任务 定时任务展示 :…

JAVA - Quartz 定时任务_启动原理

JAVA - Quartz 定时任务_启动原理 前言 在开发过程中&#xff0c;我们会用定时任务来执行一些操作&#xff0c;例如定时去捞取流水重试业务、定时去消息中间件获取消息等等相关需求 简单的定时任务实现可以借助Spring提供的 Scheduled 注解 详细看 Spring 原理之 Scheduled …

如何应用quartz定时任务?

Quartz可以用来做什么&#xff1f; Quartz是一个强大任务调度框架&#xff0c;我工作时候会在这些情况下使用到quartz框架&#xff0c;当然还有很多的应用场景&#xff0c;在这里只列举2个实际用到的 餐厅系统会在每周四晚上的22点自动审核并生成报表人事系统会在每天早晨8点…

Quartz定时任务基础学习

Quartz基础笔记 前言 1、什么是Quartz Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目。 2、Quartz的运行环境是什么&#xff1f; Quartz可以运行嵌入在一个独立式应用程序Quartz可以在应用服务器或者Servlet容器实例化&#xff0c;并且参与事务Quartz可…

Quartz定时任务

Java实现定时任务的方式 一、线程等待&#xff08;不建议使用&#xff0c;任务复杂时存在内存泄露风险&#xff09; Thread myThread new Thread(new Runnable() {Overridepublic void run() {while (true) {System.out.println("TestThreadWait is called!");try…

java定时任务Quartz整理

目录 一、Quartz介绍 二、Quartz的组成 三、使用java实现一个简单的Quartz例子 四、使用Springboot整合Quartz定时任务框架 五、使用Springbootmybatis整合Quartz定时任务框架实现定时向数据库插入一条数据 六、总结 七、参考资料 一、Quartz介绍 quartz是一种基于java…

Quartz之定时任务

一 基础概念 1.什么是Quartz? Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目&#xff0c;完全由Java开发&#xff0c;可以用来执行定时任务&#xff0c;类似于 java.util.Timer。但是相较于Timer&#xff0c; Quartz增加了很多功能&#xff1a;(1).持久性…

定时任务框架Quartz

目录 一、Quartzh介绍 1、Quartz的定义&#xff1a; 2、Quartz完成定时任务的原理&#xff1a; 二、cron表达式 1、cron表达式定义 2、cron表达式规范 三、spring自带调度器 四、Quartz内存版集成基本使用 1、关于Quartz内存版集成的步骤 1、先到启动类中写好集成的步骤 2、…

定时任务:Quartz 详解

定时任务&#xff1a;Quartz 详解 文章目录 定时任务&#xff1a;Quartz 详解1 Quartz是什么&#xff1f;2 Quartz核心组成3 Quartz核心模块理解3.1 用工厂模式理解 Quartz 的设计机制&#xff1a;3.2 用流程图理解 Quartz 的核心模块关系&#xff1a; 4 Quartz详解4.1 Quartz的…

Linux 环境下的 for循环嵌套学习

题目&#xff1a;输出下面的 4*5 的矩阵&#xff1a; 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 解: 首先打开Xfce终端&#xff0c;创建c文件&#xff0c; 并用gedit记事本打开它。 touch zhanglong.c gedit zhanglong.c之后输入代码&#xff1a; #include<std…

for循环嵌套编程练习

1 编程要求:求出用50元,20元和10元换算100元有几种方式? 思路:用穷举法,将所有可能的情况都列举出来,用for循环可以实现穷举 分析:100元单用50换算,最多需要两张,用20元最多换算5张,用10元最多换算10张 #include<stdio.h> int main(void) {int w,e,s; // w代表…

c语言99乘法表循环嵌套写法,99乘法表(for循环嵌套)

计算乘法表 两个数相乘,外层循环代表被乘数,控制行数;内层代表乘数,控制列数。 循环嵌套,变量名不可以重复。 使用 break 语句让输出的乘法表更简洁。 使用 String 变量,做 String 的加法。 public class MultiTabble {public static void main(String[] args) {for (int…

关于For循环嵌套的简单理解

一&#xff1a;循环语句的嵌套 一个循环结构内可以含有另一个循环&#xff0c;称为循环嵌套&#xff0c;又称多重循环。常用的循环嵌套是二重循环&#xff0c;外层循环称为 外循环&#xff0c;内层循环称为内循环。 二&#xff1a;双重循环&#xff1a; 1.双重循环结构 for (循…

for循环嵌套的三种用法

目前本人了解到的常用的三种for循环嵌套&#xff0c;以下是对这三种循环嵌套的理解&#xff1a; 一&#xff1a;内外循环联动 var arr [[10, 20, 30],[hello, hi, world],[a, b, c]]for (var i 0; i < arr.length;i){let innerArr arr[i];for(var j 0;j < innerArr…

多重for循环嵌套

for循环定义&#xff1a; 同过一个简单的表达式&#xff0c;来完成多次重复性的代码功能&#xff1b;格式如下&#xff1a; for&#xff08;变量初始值&#xff1b;变量取值范围&#xff1b;变量自增/自减&#xff09;{ //代码块&#xff1b; } 一个复杂的for循环中可以嵌…