一、实验概述
1.1 实验背景
在实际的软件系统开发中,周期性任务调度是一类非常常见的功能需求。例如,系统需要每天定时生成统计报表、定期清理历史日志、按固定时间发送通知消息、周期性同步第三方接口数据,或者定时采集服务器、设备、网卡、网口等运行指标。如果这些任务全部依靠人工执行,不仅效率较低,而且容易出现遗漏、重复执行或执行时间不统一等问题。因此,在后端系统中引入自动化的任务调度机制,可以有效提高系统运行效率和业务处理的稳定性。
Spring Boot 自身提供了 @Scheduled 注解,可以较方便地实现简单的定时任务。但是在一些较复杂的业务场景中,仅使用 @Scheduled 可能存在一定局限。例如,任务执行周期需要动态调整,任务需要支持暂停、恢复、删除,任务信息需要持久化保存,或者系统中存在多个任务需要统一管理时,就需要使用功能更完善的任务调度框架。
Quartz 是 Java 生态中较为成熟的开源任务调度框架,支持简单周期任务、Cron 表达式任务、任务动态管理、任务持久化以及多任务并发执行等功能。Spring Boot 对 Quartz 提供了良好的集成支持,通过引入 spring-boot-starter-quartz 依赖,可以在 Spring Boot 项目中快速完成 Quartz 的配置和使用。本实验以 Spring Boot 框架为基础,集成 Quartz 组件,实现周期任务的静态调度和动态调度功能。
1.2 实验目的
本实验的主要目的是掌握 Quartz 组件在 Spring Boot 框架下的基本使用方法,理解周期任务调度的核心流程,并能够根据不同业务场景选择合适的任务调度实现方式。
通过本实验,需要达到以下目标:
-
理解周期任务调度在后端系统中的应用场景和实际意义。
-
掌握 Spring Boot 项目中 Quartz 相关依赖的引入方式。
-
掌握 Quartz 在
application.yml配置文件中的基础配置方法。 -
理解 Quartz 中
Job、JobDetail、Trigger、Scheduler等核心组件的作用。 -
掌握静态任务调度的实现方式,能够在项目启动后自动执行固定周期任务。
-
掌握动态任务调度的基本实现思路,能够通过接口实现任务的新增、修改、暂停、恢复和删除。
-
理解静态任务调度和动态任务调度的区别,并能够根据实际业务需求进行选择。
1.3 实验内容
本实验主要围绕 Spring Boot 集成 Quartz 实现周期任务调度展开,实验内容包括 Quartz 基础配置、静态任务调度实现和动态任务调度实现三个部分。
首先,在 Spring Boot 项目中引入 Quartz 相关依赖,并在配置文件中完成 Quartz 调度器、线程池和任务存储方式的基础配置。通过这些配置,使 Spring Boot 应用在启动后能够自动创建并启动 Quartz 调度器,为后续任务执行提供运行环境。
其次,实现静态任务调度。静态任务是指任务类、任务名称、执行周期和触发规则都在代码中提前定义,系统启动后自动注册到 Quartz 调度器中,并按照指定的 Cron 表达式周期性执行。该方式适合业务逻辑固定、执行周期较稳定的任务场景。
最后,实现动态任务调度。动态任务是指任务的名称、分组、执行周期、Cron 表达式和任务状态等信息可以通过接口或数据库进行管理。系统运行过程中,可以动态新增任务、修改任务执行周期、暂停任务、恢复任务和删除任务。该方式适合需要页面化配置、接口化管理和运行时调整的复杂任务调度场景。
通过本实验,可以较完整地理解 Quartz 在 Spring Boot 项目中的使用流程,为后续实现定时数据采集、自动化运维任务下发、周期性报表生成、定时消息通知等功能奠定基础。

二、Quartz 任务调度基础介绍
2.1 Quartz 简介
Quartz 是 Java 生态中一个功能较为完善的开源任务调度框架,主要用于在应用程序中执行周期性任务或指定时间点任务。它可以按照简单的时间间隔执行任务,也可以通过 Cron 表达式描述更加复杂的执行规则,例如每天凌晨执行、每周一执行、每月指定日期执行等。
在实际项目中,Quartz 常用于定时报表生成、日志清理、数据同步、接口轮询、邮件发送、设备状态检测、服务器指标采集等场景。相比普通的线程或定时器方式,Quartz 提供了更加规范的任务定义、触发规则和调度管理能力,可以帮助开发人员更加稳定、灵活地管理周期任务。
Spring Boot 对 Quartz 提供了良好的集成支持。通过引入 spring-boot-starter-quartz 依赖,开发人员可以在 Spring Boot 项目中快速使用 Quartz,并结合 Spring 的依赖注入、配置管理和生命周期管理能力,实现更加工程化的任务调度功能。
2.2 Quartz 核心组件
Quartz 的任务调度主要由 Job、JobDetail、Trigger 和 Scheduler 等核心组件组成。这些组件分工明确,共同完成任务的定义、触发和执行。
2.2.1 Job
Job 表示具体要执行的任务逻辑。开发人员需要定义一个任务类,并实现 Quartz 提供的 Job 接口,在 execute 方法中编写具体的业务代码。
例如,定时打印日志、清理数据库历史数据、发送通知消息等操作,都可以写在 execute 方法中。Quartz 在任务触发时,会自动调用该方法完成任务执行。
2.2.2 JobDetail
JobDetail 用于描述一个任务的详细信息。它并不直接执行任务,而是保存任务的定义信息,例如任务名称、任务分组、任务类以及任务携带的参数等。
在 Quartz 中,Job 更像是任务执行逻辑,而 JobDetail 是对这个任务逻辑的具体描述。一个 Job 类可以通过不同的 JobDetail 创建出多个任务实例,从而实现相同任务逻辑在不同场景下的复用。
2.2.3 Trigger
Trigger 表示触发器,用于定义任务什么时候执行、多久执行一次、按照什么规则执行。Quartz 中常用的触发器包括简单触发器和 Cron 触发器。
简单触发器适合固定间隔执行的任务,例如每隔 10 秒执行一次。Cron 触发器适合更加复杂的周期规则,例如每天凌晨 2 点执行、每周一上午 9 点执行、每月 1 号执行等。
2.2.4 Scheduler
Scheduler 是 Quartz 的调度器,也是整个任务调度的核心。它负责管理 JobDetail 和 Trigger,并根据触发器规则在合适的时间执行对应的任务。
开发人员通常会将任务和触发器注册到 Scheduler 中,由 Scheduler 统一完成任务的启动、暂停、恢复、删除和执行管理。

2.3 Cron 表达式介绍
Cron 表达式是一种用于描述时间规则的表达式,在 Quartz 中常用于配置复杂的周期任务执行时间。通过 Cron 表达式,可以灵活地描述秒、分钟、小时、日期、月份、星期等时间条件。
Quartz 中常见的 Cron 表达式格式如下:
秒 分 时 日 月 星期 年
其中"年"字段可以省略,因此常用格式也可以写成:
秒 分 时 日 月 星期
例如:
0/10 * * * * ?
表示每 10 秒执行一次。
0 0 2 * * ?
表示每天凌晨 2 点执行一次。
0 0 9 ? * MON
表示每周一上午 9 点执行一次。
0 0 0 1 * ?
表示每月 1 号零点执行一次。
Cron 表达式可以满足大多数复杂的周期任务需求,是 Quartz 实现灵活任务调度的重要基础。
| 规律 | 说明 |
|---|---|
| 年字段可以省略 | 一般不写,表示每年都执行 |
| "日"和"星期"一般互斥 | 两个字段通常必须有一个写 ? |
* 表示所有 |
例如日字段写 * 表示每天 |
| 数字表示具体值 | 例如小时字段写 9 表示上午 9 点 |
/ 表示步长 |
例如 0/10 表示从 0 开始每隔 10 个单位执行 |
, 表示多个值 |
例如 1,15 表示 1 号和 15 号 |
- 表示范围 |
例如 MON-FRI 表示周一到周五 |
复杂日期用 L、W、# |
分别表示最后、工作日、第几个星期几 |
2.4 Quartz 与 Spring Boot 定时任务对比
Spring Boot 自身提供了 @Scheduled 注解,可以较方便地实现简单定时任务。例如,在方法上添加 @Scheduled(cron = "0/10 * * * * ?"),即可实现每 10 秒执行一次任务。这种方式配置简单,适合任务数量少、执行周期固定、业务逻辑不需要动态调整的场景。
但是,当系统中存在较多任务,并且任务需要支持动态新增、修改、暂停、恢复、删除时,@Scheduled 的灵活性就相对不足。它的任务规则通常写在代码或配置文件中,系统运行过程中不方便动态修改。如果要修改任务周期,往往需要修改代码或配置并重新启动服务。
Quartz 相比 @Scheduled 具有更强的任务管理能力。它不仅支持 Cron 表达式和多任务并发执行,还支持通过 Scheduler 动态管理任务,能够在系统运行过程中完成任务的新增、修改、暂停、恢复和删除。同时,Quartz 还可以结合数据库实现任务持久化,保证应用重启后任务配置仍然可以恢复。
因此,在简单定时任务场景下,可以优先选择 Spring Boot 自带的 @Scheduled;在任务数量较多、调度规则复杂、需要动态管理或任务持久化的场景下,更适合使用 Quartz 组件实现任务调度。
2.5 本实验中的任务调度方式
本实验将分别实现静态任务调度和动态任务调度两种方式。
静态任务调度是指任务类、任务名称、触发规则和执行周期都提前写在代码中,项目启动后自动注册并执行。这种方式实现简单,适合执行周期固定、业务逻辑稳定的任务。
动态任务调度是指任务信息可以通过接口或数据库进行管理,系统运行过程中可以动态新增任务、修改任务执行周期、暂停任务、恢复任务和删除任务。这种方式灵活性更高,适合需要后台页面管理或接口管理的业务系统。
通过对这两种方式的实现和对比,可以更加清晰地理解 Quartz 在 Spring Boot 项目中的使用方式,也能够根据实际业务需求选择合适的任务调度方案。
三、实验环境与依赖引入
3.1 实验环境
本实验基于 Spring Boot 框架集成 Quartz 组件,实现周期任务调度功能。实验主要使用 Java 后端开发环境,通过 Maven 进行项目依赖管理,并使用 application.yml 配置 Quartz 调度器相关参数。
本实验使用的主要环境如下:
| 环境项 | 版本或说明 |
|---|---|
| 开发语言 | Java |
| JDK 版本 | JDK 17 |
| 开发框架 | Spring Boot 3.x |
| 构建工具 | Maven |
| 任务调度组件 | Quartz |
| 开发工具 | IntelliJ IDEA / VS Code |
| 配置文件 | application.yml |
在实际项目中,如果只实现静态任务调度,可以使用 Quartz 的内存存储方式,不需要额外创建任务表。如果需要实现动态任务调度,并且要求应用重启后任务信息不丢失,则可以结合数据库保存任务配置,例如使用 MySQL 保存任务名称、任务分组、任务类路径、Cron 表达式和任务状态等信息。
3.2 Maven 依赖引入
Spring Boot 对 Quartz 提供了官方集成支持,在项目中只需要引入 spring-boot-starter-quartz 依赖,即可使用 Quartz 相关功能。
在 pom.xml 文件中添加如下依赖:
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
该依赖会自动引入 Quartz 核心组件,并与 Spring Boot 的自动配置机制结合,使开发人员能够更加方便地在 Spring Boot 项目中创建和管理周期任务。
如果后续动态任务需要将任务信息保存到数据库中,还可以根据项目实际情况引入数据库驱动和持久层框架。例如,使用 MySQL 和 MyBatis-Plus 时,可以补充如下依赖:
XML
<!-- MySQL 数据库驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- MyBatis-Plus 持久层框架,可用于保存动态任务配置 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>
其中,spring-boot-starter-quartz 是实现 Quartz 调度功能的核心依赖;MySQL 驱动和 MyBatis-Plus 主要用于动态任务配置的数据库保存,不是静态任务调度的必需依赖。
3.3 Quartz 基础配置
在 Spring Boot 项目中,可以通过 application.yml 对 Quartz 进行基础配置。配置内容主要包括任务存储方式、调度器是否自动启动、是否覆盖已有任务、线程池大小以及 JobStore 类型等。
示例配置如下:
python
spring:
quartz:
# Quartz 任务存储方式
# memory 表示任务信息存储在内存中,应用重启后任务会丢失
# 如果希望任务持久化到数据库,可改为 jdbc
job-store-type: memory
# 应用启动时是否自动启动 Quartz 调度器
auto-startup: true
# 启动时是否覆盖已有的 Job 配置
# true 表示如果同名 Job 或 Trigger 已存在,会使用当前配置覆盖
overwrite-existing-jobs: true
properties:
org:
quartz:
scheduler:
# 调度器名称,用于区分不同的 Quartz Scheduler 实例
instanceName: rasQuartzScheduler
# 调度器实例 ID
# AUTO 表示自动生成,常用于多实例或集群场景
instanceId: AUTO
threadPool:
# Quartz 使用的线程池实现类
# SimpleThreadPool 是 Quartz 自带的简单线程池
class: org.quartz.simpl.SimpleThreadPool
# 线程池线程数量
# 表示最多可以同时执行 10 个任务
threadCount: 10
# 线程优先级,范围通常是 1 到 10
# 5 表示普通优先级
threadPriority: 5
jobStore:
# JobStore 实现类
# RAMJobStore 表示将 Job、Trigger 等调度信息存储在内存中
# 优点是简单、速度快;缺点是应用重启后任务会丢失
class: org.quartz.simpl.RAMJobStore
该配置使用的是内存模式,即 RAMJobStore。这种方式配置简单,适合教学实验、功能验证和固定任务调度场景。应用启动后,Quartz 会自动创建调度器,并根据代码中注册的任务和触发器进行调度执行。
如果项目需要将任务信息持久化到数据库中,可以将 job-store-type 修改为 jdbc,并配置 Quartz 对应的数据源和数据库表结构。数据库持久化方式适合生产环境中的动态任务管理,例如任务配置来自后台页面,应用重启后仍需要恢复原有任务。
3.4 配置项说明
本实验中使用的主要 Quartz 配置项说明如下:
| 配置项 | 说明 |
|---|---|
spring.quartz.job-store-type |
指定 Quartz 任务存储方式,memory 表示内存存储,jdbc 表示数据库存储 |
spring.quartz.auto-startup |
是否在 Spring Boot 应用启动时自动启动 Quartz 调度器 |
spring.quartz.overwrite-existing-jobs |
启动时是否覆盖已有的 Job 和 Trigger 配置 |
org.quartz.scheduler.instanceName |
Quartz 调度器名称 |
org.quartz.scheduler.instanceId |
Quartz 调度器实例 ID,AUTO 表示自动生成 |
org.quartz.threadPool.class |
Quartz 线程池实现类 |
org.quartz.threadPool.threadCount |
线程池线程数量,决定可同时执行的任务数量 |
org.quartz.threadPool.threadPriority |
Quartz 工作线程优先级 |
org.quartz.jobStore.class |
Quartz 任务存储实现类,RAMJobStore 表示内存存储 |
通过以上配置,Spring Boot 应用就具备了使用 Quartz 进行任务调度的基础运行环境。后续只需要编写具体任务类,并配置对应的 JobDetail 和 Trigger,即可实现周期性任务调度。
四、Quartz 基础配置实现
4.1 配置文件编写
在 Spring Boot 项目中,Quartz 的基础配置可以统一写在 application.yml 文件中。通过配置文件,可以指定 Quartz 调度器的启动方式、任务存储方式、线程池大小以及任务覆盖策略等内容。
本实验采用内存存储方式配置 Quartz,适合教学实验和静态任务调度场景。配置内容如下:
python
spring:
quartz:
# Quartz 任务存储方式
# memory 表示任务信息存储在内存中,应用重启后任务会丢失
# 如果希望任务持久化到数据库,可改为 jdbc
job-store-type: memory
# 应用启动时是否自动启动 Quartz 调度器
auto-startup: true
# 启动时是否覆盖已有的 Job 配置
# true 表示如果同名 Job 或 Trigger 已存在,会使用当前配置覆盖
overwrite-existing-jobs: true
properties:
org:
quartz:
scheduler:
# 调度器名称,用于区分不同的 Quartz Scheduler 实例
instanceName: rasQuartzScheduler
# 调度器实例 ID
# AUTO 表示自动生成,常用于多实例或集群场景
instanceId: AUTO
threadPool:
# Quartz 使用的线程池实现类
# SimpleThreadPool 是 Quartz 自带的简单线程池
class: org.quartz.simpl.SimpleThreadPool
# 线程池线程数量
# 表示最多可以同时执行 10 个任务
threadCount: 10
# 线程优先级,范围通常是 1 到 10
# 5 表示普通优先级
threadPriority: 5
jobStore:
# JobStore 实现类
# RAMJobStore 表示将 Job、Trigger 等调度信息存储在内存中
# 优点是简单、速度快;缺点是应用重启后任务会丢失
class: org.quartz.simpl.RAMJobStore
通过以上配置,Spring Boot 应用启动时会自动创建并启动 Quartz 调度器。后续定义的任务和触发器会被注册到调度器中,由 Quartz 按照指定的时间规则统一调度执行。
4.2 调度器配置说明
Quartz 调度器由 Scheduler 负责管理,它是整个任务调度系统的核心。调度器负责接收任务定义、管理触发器、判断任务执行时间,并在满足触发条件时调用对应的任务类执行具体业务逻辑。
在本实验配置中,调度器名称配置如下:
scheduler:
instanceName: rasQuartzScheduler
instanceId: AUTO
其中,instanceName 表示调度器名称,用于区分不同的 Quartz 调度器实例。在单体应用中,该名称主要用于标识当前调度器;在多实例或集群场景中,可以通过不同的调度器名称区分不同服务实例。
instanceId 表示调度器实例 ID。配置为 AUTO 时,Quartz 会自动生成实例 ID。该配置在集群或多实例部署场景中比较常见,可以避免不同调度器实例之间出现标识冲突。
4.3 线程池配置说明
Quartz 执行任务时需要使用线程池。线程池用于控制任务的并发执行数量,避免系统中同时执行过多任务,造成服务器资源压力过大。
本实验中的线程池配置如下:
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10
threadPriority: 5
其中,class 表示 Quartz 使用的线程池实现类。org.quartz.simpl.SimpleThreadPool 是 Quartz 自带的简单线程池实现,适合大多数普通任务调度场景。
threadCount 表示线程池中的线程数量。本实验设置为 10,表示 Quartz 最多可以同时执行 10 个任务。如果系统中的周期任务数量较多,或者存在多个任务在同一时间触发,就需要合理调整该值。如果线程数量过少,可能会导致部分任务排队等待;如果线程数量过多,则可能增加服务器 CPU 和内存压力。
threadPriority 表示线程优先级,通常取值范围为 1 到 10。本实验设置为 5,表示使用普通优先级。一般情况下保持默认或普通优先级即可,不建议随意设置过高。
4.4 任务存储方式说明
Quartz 支持多种任务存储方式,常见的有内存存储和数据库存储两种。
本实验使用的是内存存储方式,配置如下:
job-store-type: memory
jobStore:
class: org.quartz.simpl.RAMJobStore
memory 表示 Quartz 的任务信息存储在内存中,对应的 JobStore 实现类为 org.quartz.simpl.RAMJobStore。使用这种方式时,任务注册后会保存在当前应用进程的内存中,任务执行速度较快,配置也比较简单。
但是,内存存储方式有一个明显缺点:当应用重启后,内存中的任务信息会丢失。因此,如果任务是写在代码中的静态任务,应用启动后重新注册即可,不会影响使用;但如果任务是用户通过页面或接口动态新增的,并且需要在服务重启后继续保留,就不适合只使用内存方式。
在生产环境中,如果需要实现任务持久化和集群调度,通常会使用数据库存储方式。此时可以将配置改为:
spring:
quartz:
job-store-type: jdbc
并配合 Quartz 官方提供的数据库表结构,将 Job、Trigger 等信息保存到数据库中。这样即使服务重启,Quartz 也可以从数据库中恢复任务调度信息。
4.5 基础配置的作用
完成 Quartz 基础配置后,Spring Boot 项目就具备了任务调度的运行环境。配置文件主要完成以下几方面作用:
第一,指定 Quartz 调度器在应用启动时自动启动,使任务能够随着系统启动而开始调度。
第二,配置任务存储方式,决定任务信息是保存在内存中还是数据库中。
第三,配置线程池参数,控制任务并发执行能力,避免任务过多时造成资源竞争。
第四,配置任务覆盖策略,避免项目启动时因重复注册任务导致异常。
在本实验中,Quartz 基础配置主要为后续静态任务调度和动态任务调度提供运行基础。后续章节将在此配置基础上,分别实现固定任务的静态注册,以及通过接口动态管理任务的功能。
五、静态任务调度实现
5.1 静态任务调度概述
静态任务调度是指任务的执行逻辑、任务名称、任务分组以及执行周期都提前写在代码中,系统启动后自动将任务注册到 Quartz 调度器中,并按照指定的时间规则周期性执行。
这种方式实现简单、结构清晰,适合执行规则比较固定的任务场景。例如,系统每天凌晨清理一次历史日志、每隔一段时间采集一次设备指标、每天固定时间生成统计报表等。如果任务执行周期不需要频繁修改,也不需要通过页面或接口动态管理,那么使用静态任务调度就可以满足需求。
在 Quartz 中,实现一个静态任务通常需要完成以下几个步骤:
第一,编写具体的任务类,实现 Job 接口。
第二,创建 JobDetail,用于描述任务的基本信息。
第三,创建 Trigger,用于配置任务的触发规则。
第四,将 JobDetail 和 Trigger 注册到 Scheduler 调度器中。
完成以上步骤后,Spring Boot 项目启动时,Quartz 就会根据触发器规则自动执行对应的任务。
5.2 编写 Job 任务类
在 Quartz 中,具体的任务逻辑需要写在 Job 类中。任务类需要实现 Quartz 提供的 Job 接口,并重写 execute 方法。当任务被触发时,Quartz 会自动调用该方法执行任务逻辑。
示例代码如下:
java
package com.example.quartz.job;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.time.LocalDateTime;
@Slf4j
public class StaticTaskJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
log.info("静态任务开始执行,当前时间:{}", LocalDateTime.now());
// 这里可以编写具体的业务逻辑
// 例如:清理日志、同步数据、采集指标、发送通知等
log.info("静态任务执行结束");
}
}
在上述代码中,StaticTaskJob 是一个静态任务类。该类实现了 Job 接口,并在 execute 方法中打印当前任务执行时间。实际项目中,可以将定时执行的业务逻辑写在该方法中。
需要注意的是,Quartz 默认会自己创建 Job 实例。如果任务类中需要使用 Spring 容器中的 Service 对象,可以结合 Spring 的自动装配方式进行处理,或者通过自定义 JobFactory 让 Quartz 创建的任务对象支持 Spring Bean 注入。
5.3 配置 JobDetail
JobDetail 用于描述任务的详细信息,包括任务名称、任务分组、任务类以及任务是否持久保存等内容。Quartz 在调度任务时,并不是直接注册一个 Job 类,而是通过 JobDetail 来保存任务定义。
示例代码如下:
java
package com.example.quartz.config;
import com.example.quartz.job.StaticTaskJob;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzStaticConfig {
@Bean
public JobDetail staticTaskJobDetail() {
return JobBuilder.newJob(StaticTaskJob.class)
// 设置任务名称和任务分组
.withIdentity("staticTaskJob", "staticTaskGroup")
// 即使没有 Trigger 关联,也保留该 JobDetail
.storeDurably()
.build();
}
}
在上述代码中,JobBuilder.newJob(StaticTaskJob.class) 表示创建一个任务定义,并指定具体执行的任务类为 StaticTaskJob。
withIdentity("staticTaskJob", "staticTaskGroup") 用于设置任务名称和任务分组。Quartz 通过任务名称和任务分组唯一确定一个任务,因此在同一个分组下,任务名称不能重复。
storeDurably() 表示即使当前任务暂时没有触发器关联,也会将该任务定义保留下来。在静态任务配置中,通常可以保留该配置。
5.4 配置 Trigger 触发器
Trigger 用于定义任务的触发规则,也就是任务什么时候执行、多久执行一次。Quartz 支持多种触发器,其中最常用的是 Cron 触发器。
示例代码如下:
java
package com.example.quartz.config;
import org.quartz.CronScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzStaticTriggerConfig {
@Bean
public Trigger staticTaskTrigger(JobDetail staticTaskJobDetail) {
return TriggerBuilder.newTrigger()
// 绑定对应的 JobDetail
.forJob(staticTaskJobDetail)
// 设置触发器名称和分组
.withIdentity("staticTaskTrigger", "staticTaskGroup")
// 设置 Cron 表达式,这里表示每 10 秒执行一次
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
.build();
}
}
在上述代码中,forJob(staticTaskJobDetail) 表示该触发器绑定的是前面定义的 staticTaskJobDetail 任务。
withIdentity("staticTaskTrigger", "staticTaskGroup") 用于设置触发器名称和分组。
CronScheduleBuilder.cronSchedule("0/10 * * * * ?") 表示使用 Cron 表达式配置任务执行周期。这里的表达式表示每 10 秒执行一次任务。
常见 Cron 表达式示例如下:
| Cron 表达式 | 含义 |
|---|---|
0/10 * * * * ? |
每 10 秒执行一次 |
0 0/1 * * * ? |
每 1 分钟执行一次 |
0 0 2 * * ? |
每天凌晨 2 点执行一次 |
0 0 9 ? * MON |
每周一上午 9 点执行一次 |
0 0 0 1 * ? |
每月 1 号零点执行一次 |
通过 Cron 表达式,可以灵活配置任务的执行时间,满足每天、每周、每月等不同周期任务需求。
5.5 完整静态任务配置类
为了使代码结构更加清晰,可以将 JobDetail 和 Trigger 写在同一个配置类中。这样项目启动时,Spring Boot 会自动加载该配置类,并将任务和触发器交给 Quartz 调度器管理。
完整示例代码如下:
java
package com.example.quartz.config;
import com.example.quartz.job.StaticTaskJob;
import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzStaticConfig {
@Bean
public JobDetail staticTaskJobDetail() {
return JobBuilder.newJob(StaticTaskJob.class)
.withIdentity("staticTaskJob", "staticTaskGroup")
.storeDurably()
.build();
}
@Bean
public Trigger staticTaskTrigger(JobDetail staticTaskJobDetail) {
return TriggerBuilder.newTrigger()
.forJob(staticTaskJobDetail)
.withIdentity("staticTaskTrigger", "staticTaskGroup")
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
.build();
}
}
该配置类中定义了一个任务和一个触发器。项目启动后,Spring Boot 会自动识别 JobDetail 和 Trigger 类型的 Bean,并将其注册到 Quartz 调度器中。Quartz 调度器启动后,会按照 Cron 表达式每 10 秒执行一次 StaticTaskJob 中的任务逻辑。
5.6 静态任务执行流程
静态任务调度的执行流程可以概括为以下步骤:
第一步,Spring Boot 应用启动,并加载 Quartz 相关配置。
第二步,Spring 容器扫描到 QuartzStaticConfig 配置类。
第三步,Spring 创建 JobDetail Bean,用于描述任务信息。
第四步,Spring 创建 Trigger Bean,用于描述任务触发规则。
第五步,Quartz 调度器启动,并将 JobDetail 和 Trigger 注册到调度器中。
第六步,当 Cron 表达式满足触发条件时,Quartz 调用对应 Job 类的 execute 方法。
第七步,任务执行完成后,Quartz 等待下一次触发时间,继续周期性执行。
通过这个流程可以看出,静态任务调度的核心是提前在代码中定义任务和触发规则,然后由 Quartz 调度器自动完成周期执行。
5.7 静态任务测试
完成任务类和配置类编写后,启动 Spring Boot 项目。项目启动成功后,可以观察控制台日志,验证任务是否按照指定周期执行。
如果 Cron 表达式配置为:
0/10 * * * * ?
则控制台每隔 10 秒会输出一次类似日志:
静态任务开始执行,当前时间:2026-06-02T10:00:10
静态任务执行结束
静态任务开始执行,当前时间:2026-06-02T10:00:20
静态任务执行结束
静态任务开始执行,当前时间:2026-06-02T10:00:30
静态任务执行结束
如果日志能够按照固定周期持续输出,说明静态任务已经成功注册到 Quartz 调度器中,并能够按照 Cron 表达式周期性执行。
5.8 静态任务调度特点
静态任务调度具有以下特点:
第一,实现简单。任务类、任务详情和触发器都直接写在代码中,结构清晰,便于理解和维护。
第二,启动自动执行。只要项目启动成功,Quartz 就会自动加载并执行已经配置好的任务。
第三,适合固定任务。对于执行周期稳定、业务逻辑固定的任务,静态调度方式非常适合。
第四,灵活性较弱。如果需要修改任务执行周期,通常需要修改代码或配置,然后重新启动应用。
第五,不适合复杂任务管理。如果系统需要通过页面动态新增、修改、暂停、恢复或删除任务,单纯使用静态任务调度就不够灵活。
因此,静态任务调度适合教学实验、系统内部固定任务、简单周期任务等场景。如果项目中任务执行规则需要频繁变化,或者需要后台页面统一管理任务,则应进一步使用动态任务调度方式。
六、动态任务调度实现
6.1 动态任务调度概述
动态任务调度是指任务的名称、分组、执行周期、Cron 表达式、任务状态等信息不再完全固定写死在代码中,而是可以通过接口、数据库或后台页面进行管理。系统在运行过程中,可以根据用户提交的任务配置动态创建任务,并将任务注册到 Quartz 调度器中执行。
相比静态任务调度,动态任务调度具有更高的灵活性。它可以在不重启系统的情况下完成任务新增、修改、暂停、恢复和删除,适合任务数量较多、执行周期经常变化、需要后台统一管理的业务场景。
例如,在自动化运维系统中,不同设备可能需要在不同时间执行巡检任务;在数据采集系统中,不同指标可能需要配置不同的采集周期;在消息通知系统中,不同类型的通知任务可能需要按照不同规则执行。这些场景都比较适合使用 Quartz 动态任务调度。
动态任务调度通常包含以下内容:
第一,设计任务配置表,用于保存任务名称、分组、任务类、Cron 表达式和状态等信息。
第二,编写动态任务管理接口,用于新增、修改、暂停、恢复和删除任务。
第三,调用 Quartz 的 Scheduler API,在运行时动态操作任务。
第四,系统启动时从数据库加载启用状态的任务,并重新注册到 Quartz 调度器中。
6.2 动态任务表设计
为了实现动态任务调度,需要先设计一张任务配置表,用于保存任务的基本信息。表中可以包含任务名称、任务分组、任务类路径、Cron 表达式、任务状态、任务描述、创建时间和更新时间等字段。
示例表结构如下:
sql
CREATE TABLE quartz_job (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
job_name VARCHAR(100) NOT NULL COMMENT '任务名称',
job_group VARCHAR(100) NOT NULL COMMENT '任务分组',
job_class VARCHAR(255) NOT NULL COMMENT '任务类全路径',
cron_expression VARCHAR(100) NOT NULL COMMENT 'Cron表达式',
status TINYINT NOT NULL DEFAULT 1 COMMENT '任务状态:1启用,0暂停',
description VARCHAR(500) DEFAULT NULL COMMENT '任务描述',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY uk_job_name_group (job_name, job_group)
) COMMENT='Quartz动态任务配置表';
各字段说明如下:
| 字段名 | 说明 |
|---|---|
id |
主键 ID |
job_name |
任务名称,用于标识一个任务 |
job_group |
任务分组,用于对任务进行分类 |
job_class |
任务类全路径,例如 com.example.quartz.job.DynamicTaskJob |
cron_expression |
Cron 表达式,用于定义任务执行周期 |
status |
任务状态,1 表示启用,0 表示暂停 |
description |
任务描述 |
create_time |
创建时间 |
update_time |
更新时间 |
其中,job_name 和 job_group 需要保证唯一。Quartz 中通常通过任务名称和任务分组共同确定一个任务,因此可以为这两个字段建立联合唯一索引,避免重复创建同一个任务。
6.3 编写动态任务类
动态任务同样需要编写具体的 Job 任务类。为了方便演示,可以先定义一个通用动态任务类,在任务执行时从 JobDataMap 中获取任务参数并打印日志。
示例代码如下:
java
package com.example.quartz.job;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.time.LocalDateTime;
@Slf4j
public class DynamicTaskJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap jobDataMap = context.getMergedJobDataMap();
String jobName = jobDataMap.getString("jobName");
String jobGroup = jobDataMap.getString("jobGroup");
log.info("动态任务开始执行,任务名称:{},任务分组:{},当前时间:{}",
jobName, jobGroup, LocalDateTime.now());
// 这里可以根据任务参数执行不同业务逻辑
// 例如:采集指标、发送消息、执行远程命令、同步数据等
log.info("动态任务执行结束,任务名称:{},任务分组:{}", jobName, jobGroup);
}
}
在上述代码中,DynamicTaskJob 是一个动态任务类。任务执行时,可以通过 JobDataMap 获取任务参数,例如任务名称、任务分组、业务参数等。实际项目中,可以根据不同任务类型调用不同的业务服务完成具体逻辑。
6.4 动态新增任务
动态新增任务的核心是通过 Scheduler 在运行时创建 JobDetail 和 Trigger,然后调用 scheduler.scheduleJob() 将任务注册到 Quartz 调度器中。
示例代码如下:
java
package com.example.quartz.service;
import org.quartz.*;
import org.springframework.stereotype.Service;
@Service
public class QuartzJobService {
private final Scheduler scheduler;
public QuartzJobService(Scheduler scheduler) {
this.scheduler = scheduler;
}
public void addJob(String jobName, String jobGroup, String jobClass, String cronExpression) {
try {
Class<? extends Job> clazz =
(Class<? extends Job>) Class.forName(jobClass);
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
if (scheduler.checkExists(jobKey)) {
throw new RuntimeException("任务已存在,不能重复添加");
}
JobDetail jobDetail = JobBuilder.newJob(clazz)
.withIdentity(jobKey)
.usingJobData("jobName", jobName)
.usingJobData("jobGroup", jobGroup)
.build();
TriggerKey triggerKey = TriggerKey.triggerKey(jobName + "Trigger", jobGroup);
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.forJob(jobDetail)
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
} catch (Exception e) {
throw new RuntimeException("新增动态任务失败", e);
}
}
}
在上述代码中,Class.forName(jobClass) 用于根据数据库或接口传入的任务类路径加载任务类。JobKey 用于唯一标识一个任务,TriggerKey 用于唯一标识一个触发器。
执行新增任务时,系统会先判断任务是否已经存在。如果任务不存在,则创建对应的 JobDetail 和 CronTrigger,最后通过 scheduler.scheduleJob(jobDetail, trigger) 注册任务。
需要注意的是,动态加载任务类时,传入的 jobClass 必须是实现了 Job 接口的类,否则任务无法正常创建。
6.4 JobDetail 与 Trigger 的绑定方式说明
在 Quartz 中,JobDetail 用于定义任务本身,Trigger 用于定义任务的触发规则。一个任务能否按照指定时间执行,关键在于 JobDetail 和 Trigger 是否建立了正确的绑定关系。
在实际开发中,JobDetail 和 Trigger 的绑定常见有两种写法。
6.4.1 第一种方式
通过调度器同时绑定并注册
第一种方式是先分别创建 JobDetail 和 Trigger,然后通过调度器的 scheduleJob(jobDetail, trigger) 方法将二者绑定,并同时注册到 Quartz 调度器中。
示例代码如下:
java
JobKey jobKey = JobKey.jobKey("taskJob", "taskGroup");
TriggerKey triggerKey = TriggerKey.triggerKey("taskTrigger", "taskGroup");
JobDetail jobDetail = JobBuilder.newJob(TaskJob.class)
.withIdentity(jobKey)
.usingJobData("taskId", 1L)
.build();
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
.build();
// 通过调度器将 JobDetail 和 Trigger 进行绑定,并同时注册到 Scheduler 中
scheduler.scheduleJob(jobDetail, trigger);
在这种写法中,Trigger 创建时没有显式调用 .forJob(jobKey),但是在调用 scheduler.scheduleJob(jobDetail, trigger) 时,Quartz 会将当前的 trigger 绑定到当前的 jobDetail 上,并一起注册到调度器中。因此,对于这种"任务和触发器同时创建、同时注册"的场景,可以不写 .forJob(jobKey)。
这种方式适合新增一个完整任务的场景,即任务定义和触发规则一起创建,然后一次性注册到 Quartz 中。
6.4.2 第二种方式
Trigger 中显式指定绑定的 JobDetail
第二种方式是在创建 Trigger 时,使用 .forJob(jobKey) 显式指定该触发器要绑定的任务。
这种方式适合以下场景:
第一,JobDetail 已经提前注册到调度器中,后续只是单独为这个任务添加新的触发器。
第二,一个 JobDetail 需要绑定多个 Trigger,也就是同一个任务需要按照多个不同的时间规则执行。
第三,动态任务封装时,为了让 Trigger 和 JobDetail 的绑定关系更加清晰,可以显式写出 .forJob(jobKey)。
示例代码如下:
java
JobKey jobKey = JobKey.jobKey("taskJob", "taskGroup");
JobDetail jobDetail = JobBuilder.newJob(TaskJob.class)
.withIdentity(jobKey)
.usingJobData("taskId", 1L)
.storeDurably()
.build();
// 先单独注册 JobDetail
scheduler.addJob(jobDetail, true);
上面代码表示先将 JobDetail 注册到 Quartz 调度器中。此时任务定义已经存在,但是还没有具体的触发规则。
后续如果要为该任务单独添加触发器,就需要在 Trigger 中通过 .forJob(jobKey) 指定绑定关系:
java
TriggerKey triggerKey = TriggerKey.triggerKey("taskTrigger", "taskGroup");
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.forJob(jobKey)
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
.build();
// 此时只注册 Trigger,Trigger 通过 forJob(jobKey) 绑定到已经存在的 JobDetail
scheduler.scheduleJob(trigger);
在这种写法中,调用的是:
java
scheduler.scheduleJob(trigger);
由于这里只传入了 Trigger,没有同时传入 JobDetail,所以 Trigger 必须提前知道自己要绑定哪个任务。因此,需要使用 .forJob(jobKey) 明确指定绑定的 JobDetail。
多个 Trigger 绑定同一个 JobDetail 示例
在一些业务场景中,同一个任务可能需要按照多个时间规则执行。例如,一个数据同步任务既需要每天上午 9 点执行一次,也需要每天下午 6 点执行一次。这时可以让多个 Trigger 绑定到同一个 JobDetail。
示例代码如下:
java
JobKey jobKey = JobKey.jobKey("syncDataJob", "syncGroup");
JobDetail jobDetail = JobBuilder.newJob(SyncDataJob.class)
.withIdentity(jobKey)
.storeDurably()
.build();
// 先注册 JobDetail
scheduler.addJob(jobDetail, true);
// 上午 9 点执行的 Trigger
CronTrigger morningTrigger = TriggerBuilder.newTrigger()
.withIdentity("morningTrigger", "syncGroup")
.forJob(jobKey)
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 9 * * ?"))
.build();
// 下午 6 点执行的 Trigger
CronTrigger eveningTrigger = TriggerBuilder.newTrigger()
.withIdentity("eveningTrigger", "syncGroup")
.forJob(jobKey)
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 18 * * ?"))
.build();
// 分别注册两个 Trigger
scheduler.scheduleJob(morningTrigger);
scheduler.scheduleJob(eveningTrigger);
在上述代码中,morningTrigger 和 eveningTrigger 都通过 .forJob(jobKey) 绑定到了同一个 syncDataJob 任务上。这样,同一个任务就可以根据多个不同的 Cron 表达式,在不同时间点被触发执行。
6.4.3 两种绑定方式对比
| 绑定方式 | 示例方法 | 是否需要 .forJob(jobKey) |
适用场景 |
|---|---|---|---|
| 同时注册 JobDetail 和 Trigger | scheduler.scheduleJob(jobDetail, trigger) |
可以不写 | 新增完整任务,任务和触发器一起注册 |
| 先注册 JobDetail,再单独注册 Trigger | scheduler.addJob(jobDetail, true) + scheduler.scheduleJob(trigger) |
必须写 | JobDetail 已存在,后续单独添加 Trigger |
| 一个 JobDetail 绑定多个 Trigger | 多次调用 scheduler.scheduleJob(trigger) |
必须写 | 同一个任务需要多个触发规则 |
| 动态任务封装 | 两种写法都可以 | 建议写 | 代码语义更清晰,避免绑定关系不明确 |
综上,JobDetail 和 Trigger 的绑定既可以通过 scheduler.scheduleJob(jobDetail, trigger) 在注册时完成,也可以通过 TriggerBuilder.forJob(jobKey) 在创建触发器时显式指定。对于任务和触发器一起创建的场景,.forJob(jobKey) 不是必须的;对于 JobDetail 已经提前存在、后续单独添加 Trigger 或多个 Trigger 绑定同一个 JobDetail 的场景,.forJob(jobKey) 是必要的。
6.5 动态修改任务
动态修改任务主要用于修改任务的 Cron 表达式。例如,原本任务每 10 秒执行一次,现在需要改为每 1 分钟执行一次,就可以通过重新设置 Trigger 的方式实现。
示例代码如下:
java
public void updateJob(String jobName, String jobGroup, String cronExpression) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobName + "Trigger", jobGroup);
CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (oldTrigger == null) {
throw new RuntimeException("任务触发器不存在,无法修改");
}
CronTrigger newTrigger = oldTrigger.getTriggerBuilder()
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
scheduler.rescheduleJob(triggerKey, newTrigger);
} catch (Exception e) {
throw new RuntimeException("修改动态任务失败", e);
}
}
该方法首先根据 TriggerKey 获取原有触发器,然后基于原触发器重新构建新的触发器,并替换原来的执行规则。scheduler.rescheduleJob() 方法会让新的 Cron 表达式立即生效,后续任务会按照新的周期执行。
6.6 动态暂停任务
动态暂停任务是指暂时停止某个任务的执行,但不删除任务定义。暂停后,任务仍然存在于 Quartz 调度器中,只是不会继续触发执行。
示例代码如下:
java
public void pauseJob(String jobName, String jobGroup) {
try {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
scheduler.pauseJob(jobKey);
} catch (Exception e) {
throw new RuntimeException("暂停动态任务失败", e);
}
}
pauseJob() 方法适合任务临时停用的场景。例如,某个设备暂时不需要采集数据,可以先暂停对应任务,后续需要时再恢复执行。
6.7 动态恢复任务
动态恢复任务是指将已经暂停的任务重新恢复执行。恢复后,任务会继续按照原有的 Cron 表达式进行周期调度。
示例代码如下:
java
public void resumeJob(String jobName, String jobGroup) {
try {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
scheduler.resumeJob(jobKey);
} catch (Exception e) {
throw new RuntimeException("恢复动态任务失败", e);
}
}
当调用 scheduler.resumeJob(jobKey) 后,被暂停的任务会重新进入可调度状态。只要后续时间满足触发器规则,Quartz 就会继续执行该任务。
6.8 动态删除任务
动态删除任务是指将任务从 Quartz 调度器中移除。删除后,该任务不会再执行。如果后续还需要该任务,需要重新新增。
示例代码如下:
java
public void deleteJob(String jobName, String jobGroup) {
try {
JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
if (scheduler.checkExists(jobKey)) {
scheduler.deleteJob(jobKey);
}
} catch (Exception e) {
throw new RuntimeException("删除动态任务失败", e);
}
}
deleteJob() 方法会删除指定的任务,同时与该任务关联的触发器也会被移除。该方法适合任务不再需要执行的场景。
6.9 动态任务接口设计
为了方便通过接口管理动态任务,可以设计一组任务管理接口。接口可以包括新增任务、修改任务、暂停任务、恢复任务和删除任务等功能。
示例接口设计如下:
| 请求方式 | 接口地址 | 功能说明 |
|---|---|---|
POST |
/quartz/job/add |
新增动态任务 |
PUT |
/quartz/job/update |
修改任务 Cron 表达式 |
PUT |
/quartz/job/pause |
暂停任务 |
PUT |
/quartz/job/resume |
恢复任务 |
DELETE |
/quartz/job/delete |
删除任务 |
GET |
/quartz/job/list |
查询任务列表 |
控制层示例代码如下:
java
package com.example.quartz.controller;
import com.example.quartz.service.QuartzJobService;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/quartz/job")
public class QuartzJobController {
private final QuartzJobService quartzJobService;
public QuartzJobController(QuartzJobService quartzJobService) {
this.quartzJobService = quartzJobService;
}
@PostMapping("/add")
public String addJob(@RequestParam String jobName,
@RequestParam String jobGroup,
@RequestParam String jobClass,
@RequestParam String cronExpression) {
quartzJobService.addJob(jobName, jobGroup, jobClass, cronExpression);
return "新增任务成功";
}
@PutMapping("/update")
public String updateJob(@RequestParam String jobName,
@RequestParam String jobGroup,
@RequestParam String cronExpression) {
quartzJobService.updateJob(jobName, jobGroup, cronExpression);
return "修改任务成功";
}
@PutMapping("/pause")
public String pauseJob(@RequestParam String jobName,
@RequestParam String jobGroup) {
quartzJobService.pauseJob(jobName, jobGroup);
return "暂停任务成功";
}
@PutMapping("/resume")
public String resumeJob(@RequestParam String jobName,
@RequestParam String jobGroup) {
quartzJobService.resumeJob(jobName, jobGroup);
return "恢复任务成功";
}
@DeleteMapping("/delete")
public String deleteJob(@RequestParam String jobName,
@RequestParam String jobGroup) {
quartzJobService.deleteJob(jobName, jobGroup);
return "删除任务成功";
}
}
通过上述接口,可以使用 Postman、Apipost 或前端页面对 Quartz 动态任务进行管理。实际项目中,建议将请求参数封装成 DTO 对象,并增加参数校验、异常处理和统一返回结果。
6.10 系统启动时加载数据库任务
如果动态任务信息保存到了数据库中,那么应用重启后,内存中的 Quartz 任务会丢失。因此,需要在系统启动时查询数据库中处于启用状态的任务,并重新注册到 Quartz 调度器中。
可以通过实现 CommandLineRunner 或 ApplicationRunner,在项目启动完成后加载数据库任务。
示例代码如下:
java
package com.example.quartz.runner;
import com.example.quartz.entity.QuartzJobEntity;
import com.example.quartz.mapper.QuartzJobMapper;
import com.example.quartz.service.QuartzJobService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class QuartzJobInitRunner implements CommandLineRunner {
private final QuartzJobMapper quartzJobMapper;
private final QuartzJobService quartzJobService;
public QuartzJobInitRunner(QuartzJobMapper quartzJobMapper,
QuartzJobService quartzJobService) {
this.quartzJobMapper = quartzJobMapper;
this.quartzJobService = quartzJobService;
}
@Override
public void run(String... args) {
List<QuartzJobEntity> jobList = quartzJobMapper.selectEnableJobs();
for (QuartzJobEntity job : jobList) {
quartzJobService.addJob(
job.getJobName(),
job.getJobGroup(),
job.getJobClass(),
job.getCronExpression()
);
}
}
}
在上述代码中,系统启动后会查询数据库中启用状态的任务,然后逐个调用 addJob() 方法重新注册任务。这样即使使用的是内存存储方式,也可以通过数据库保存任务配置,并在应用启动时恢复任务。
6.11 动态任务测试
动态任务实现完成后,可以通过接口进行测试。
例如,新增一个每 10 秒执行一次的动态任务,请求参数可以设置为:
jobName=dynamicTaskJob
jobGroup=dynamicTaskGroup
jobClass=com.example.quartz.job.DynamicTaskJob
cronExpression=0/10 * * * * ?
新增成功后,观察控制台日志,如果每 10 秒输出一次动态任务执行日志,说明任务新增成功。
然后可以继续测试修改任务周期,例如将 Cron 表达式修改为:
0/30 * * * * ?
表示每 30 秒执行一次。如果修改后任务按照新的时间间隔执行,说明动态修改任务成功。
接着可以测试暂停任务。暂停后,观察控制台日志,如果任务不再继续执行,说明暂停成功。
最后测试恢复任务和删除任务。恢复任务后,任务会继续按照 Cron 表达式执行;删除任务后,任务会从 Quartz 调度器中移除,不再执行。
6.12 动态任务调度特点
动态任务调度具有以下特点:
第一,灵活性高。任务可以在系统运行过程中动态新增、修改、暂停、恢复和删除,不需要重启服务。
第二,适合页面管理。任务信息可以保存到数据库中,并通过后台页面进行统一维护。
第三,适合复杂业务。对于任务数量较多、执行周期不固定、需要根据业务配置变化的系统,动态调度更加适合。
第四,实现复杂度较高。相比静态任务,动态任务需要额外设计任务表、管理接口、参数校验、异常处理和启动加载逻辑。
第五,需要注意任务安全性。由于动态任务可能涉及通过类路径加载任务类,因此实际项目中应限制可执行任务范围,避免任意类被加载执行。
综上,动态任务调度适合对任务管理灵活性要求较高的系统,例如自动化运维平台、设备巡检系统、数据采集系统、定时通知系统和后台任务管理平台等。
七、静态任务与动态任务对比
7.1 静态任务调度特点
静态任务调度是指任务类、任务名称、任务分组、触发器以及 Cron 表达式等信息都提前写在代码中,系统启动后由 Spring Boot 自动加载配置,并将任务注册到 Quartz 调度器中执行。
这种方式最大的特点是实现简单,结构清晰。开发人员只需要编写任务类,并在配置类中定义 JobDetail 和 Trigger,项目启动后任务就可以按照指定周期自动执行。对于一些业务规则固定、执行周期稳定的任务,静态任务调度非常适合。
例如,系统每天凌晨清理一次历史日志、每天定时生成统计报表、每隔 5 分钟采集一次固定指标、每小时同步一次固定接口数据等,都可以使用静态任务调度实现。
静态任务调度的优点主要包括:
-
实现简单,代码结构清晰,适合初学者理解 Quartz 的基本使用流程。
-
任务规则固定,系统启动后自动注册并执行,不需要额外设计管理接口。
-
适合固定周期、固定业务逻辑的内部系统任务。
-
任务定义直接写在代码中,便于版本管理和开发维护。
但是,静态任务调度也存在一定局限。如果任务执行周期需要经常修改,或者需要在系统运行过程中新增、暂停、恢复、删除任务,静态任务就不够灵活。通常需要修改代码或配置文件,并重新启动应用才能生效。
因此,静态任务调度适合任务数量较少、调度规则稳定、不需要动态管理的场景。
7.2 动态任务调度特点
动态任务调度是指任务信息不再完全写死在代码中,而是可以通过数据库、接口或后台页面进行管理。系统在运行过程中,可以动态新增任务、修改任务 Cron 表达式、暂停任务、恢复任务和删除任务。
这种方式最大的特点是灵活性高。任务的执行周期、任务状态和任务参数可以根据业务需要动态调整,不需要重新发布或重启系统。对于任务数量较多、任务规则经常变化、需要后台页面统一维护的系统,动态任务调度更加适合。
例如,在自动化运维系统中,不同服务器可能需要配置不同的巡检周期;在设备管理系统中,不同设备可能需要按照不同时间采集指标;在消息通知系统中,不同类型的通知任务可能需要根据业务情况临时启用或停用。这些场景都适合使用动态任务调度。
动态任务调度的优点主要包括:
-
灵活性强,可以在系统运行过程中动态新增、修改、暂停、恢复和删除任务。
-
适合后台页面管理,可以将任务信息保存到数据库中统一维护。
-
支持任务配置持久化,应用重启后可以重新加载任务配置。
-
适合任务数量较多、执行周期复杂、业务规则经常变化的系统。
但是,动态任务调度的实现复杂度也更高。它不仅需要操作 Quartz 的 Scheduler API,还需要设计任务配置表、管理接口、参数校验、异常处理和启动加载逻辑。如果允许通过接口传入任务类路径,还需要注意安全控制,避免加载非法任务类。
因此,动态任务调度适合任务规则变化频繁、需要运行时管理、需要页面化配置和数据库持久化的场景。
7.3 静态任务与动态任务对比表
静态任务调度和动态任务调度的对比如下:
| 对比项 | 静态任务调度 | 动态任务调度 |
|---|---|---|
| 任务定义方式 | 写在代码中 | 通过接口、数据库或页面配置 |
| 执行周期配置 | 代码或配置文件中固定配置 | 可以运行时动态修改 |
| 是否需要重启 | 修改任务通常需要重启应用 | 修改任务不需要重启应用 |
| 实现复杂度 | 较低 | 较高 |
| 灵活性 | 较弱 | 较强 |
| 是否适合页面管理 | 不太适合 | 适合 |
| 是否适合数据库持久化 | 一般不需要 | 通常需要 |
| 任务数量 | 适合少量固定任务 | 适合较多可配置任务 |
| 典型应用场景 | 固定日志清理、固定报表生成、固定数据同步 | 动态巡检任务、动态采集任务、后台任务管理、自动化运维任务 |
| 维护方式 | 开发人员维护代码 | 管理员或用户通过页面/接口维护 |
通过上表可以看出,静态任务调度更偏向于代码级固定任务,适合实现简单、周期稳定的任务;动态任务调度更偏向于平台化任务管理,适合需要灵活配置和运行时调整的复杂业务系统。
7.4 适用场景分析
在实际项目中,选择静态任务调度还是动态任务调度,需要根据业务复杂度和任务变化频率来决定。
如果系统中的任务数量较少,任务执行周期基本固定,并且不需要通过页面或接口进行管理,那么可以优先选择静态任务调度。例如,每天凌晨清理一次日志、每天定时统计一次数据、每隔固定时间执行一次系统检查等。这类任务通常由开发人员提前定义好,随着系统启动自动执行即可。
如果系统中的任务数量较多,任务执行周期经常变化,或者不同用户、不同设备、不同业务需要配置不同的执行规则,那么更适合选择动态任务调度。例如,自动化运维系统中的远程命令定时下发、设备管理系统中的指标采集任务、消息系统中的定时通知任务等。这类任务往往需要通过后台页面或接口进行管理,因此动态任务调度更加合适。
对于一些中小型系统,也可以采用静态任务和动态任务结合的方式。系统内部固定任务使用静态任务实现,例如日志清理、缓存刷新、基础数据同步等;需要用户配置或业务动态调整的任务使用动态任务实现,例如设备巡检、报表生成、消息通知等。这样既可以保证固定任务的稳定性,又可以满足复杂业务的灵活性需求。
7.5 本实验中的选择建议
在本实验中,静态任务调度主要用于演示 Quartz 的基本使用流程,帮助理解 Job、JobDetail、Trigger 和 Scheduler 之间的关系。通过静态任务,可以快速掌握 Quartz 任务创建、触发器配置和周期执行的基本过程。
动态任务调度则用于演示 Quartz 在实际项目中的扩展能力。通过动态任务,可以理解如何使用 Scheduler API 在运行时管理任务,并掌握任务新增、修改、暂停、恢复和删除的实现方式。
因此,本实验同时实现静态任务调度和动态任务调度两种方式。静态任务用于理解基础原理,动态任务用于贴近实际业务应用。通过两种方式的对比,可以更加全面地掌握 Quartz 在 Spring Boot 框架下的周期任务调度实现方法。
八、实验结果与分析
8.1 实验运行结果
完成 Quartz 依赖引入、基础配置、静态任务和动态任务代码编写后,启动 Spring Boot 项目。项目启动过程中,Quartz 调度器会随着 Spring Boot 应用一起启动,并根据配置加载对应的任务信息。
在静态任务调度测试中,系统启动后会自动注册提前定义好的 JobDetail 和 Trigger。当 Cron 表达式满足触发条件时,Quartz 会自动调用对应任务类中的 execute 方法,控制台能够按照固定时间间隔输出任务执行日志。
例如,将静态任务的 Cron 表达式配置为:
0/10 * * * * ?
表示任务每 10 秒执行一次。项目运行后,控制台可以看到类似如下日志:
静态任务开始执行,当前时间:2026-06-02T10:00:10
静态任务执行结束
静态任务开始执行,当前时间:2026-06-02T10:00:20
静态任务执行结束
静态任务开始执行,当前时间:2026-06-02T10:00:30
静态任务执行结束
从日志结果可以看出,静态任务能够按照 Cron 表达式配置的时间规则周期性执行,说明 Quartz 基础配置和静态任务调度功能运行正常。
在动态任务调度测试中,通过接口新增任务后,Quartz 会在系统运行过程中动态创建 JobDetail 和 Trigger,并将其注册到 Scheduler 调度器中。新增任务成功后,控制台可以看到动态任务按照指定 Cron 表达式周期执行。
例如,动态新增一个每 10 秒执行一次的任务,参数如下:
jobName=dynamicTaskJob
jobGroup=dynamicTaskGroup
jobClass=com.example.quartz.job.DynamicTaskJob
cronExpression=0/10 * * * * ?
任务新增成功后,控制台可以看到类似如下日志:
动态任务开始执行,任务名称:dynamicTaskJob,任务分组:dynamicTaskGroup,当前时间:2026-06-02T10:01:10
动态任务执行结束,任务名称:dynamicTaskJob,任务分组:dynamicTaskGroup
动态任务开始执行,任务名称:dynamicTaskJob,任务分组:dynamicTaskGroup,当前时间:2026-06-02T10:01:20
动态任务执行结束,任务名称:dynamicTaskJob,任务分组:dynamicTaskGroup
从运行结果可以看出,动态任务能够在系统不重启的情况下完成新增并开始执行,说明动态任务注册功能正常。
8.2 功能验证结果
本实验分别对静态任务调度和动态任务调度进行了功能验证。验证结果如下:
| 验证内容 | 验证方式 | 验证结果 |
|---|---|---|
| Quartz 调度器启动 | 启动 Spring Boot 项目,观察启动日志 | 调度器能够正常启动 |
| 静态任务执行 | 配置静态任务 Cron 表达式,观察控制台日志 | 任务能够按照固定周期执行 |
| 动态新增任务 | 调用新增任务接口,传入任务名称、分组、类路径和 Cron 表达式 | 任务能够动态注册并执行 |
| 动态修改任务 | 调用修改任务接口,更新 Cron 表达式 | 任务能够按照新的周期执行 |
| 动态暂停任务 | 调用暂停任务接口 | 任务停止继续触发 |
| 动态恢复任务 | 调用恢复任务接口 | 任务恢复周期执行 |
| 动态删除任务 | 调用删除任务接口 | 任务被移除,不再执行 |
| 启动加载任务 | 项目启动时读取数据库中启用状态任务 | 启用任务能够重新注册到调度器 |
从验证结果可以看出,本实验实现了 Quartz 在 Spring Boot 框架下的基本任务调度能力。静态任务适合固定周期任务,动态任务可以满足运行时任务管理需求。
8.3 静态任务结果分析
静态任务调度的运行结果比较稳定。由于任务信息提前写在代码中,项目启动时由 Spring Boot 自动加载对应的 JobDetail 和 Trigger,因此只要配置正确,任务就可以随着系统启动自动执行。
静态任务的优点是实现简单、结构清晰、启动后自动运行,适合业务规则固定的任务。通过本实验可以看出,静态任务不需要额外设计数据库表和管理接口,只需要编写任务类和配置类即可完成周期调度。
但是,静态任务也存在一定不足。如果需要修改任务执行周期,需要修改 Cron 表达式对应的代码或配置,并重新启动项目后才能生效。对于任务规则经常变化的业务场景,静态任务的灵活性不够。
因此,静态任务更适合用于系统内部固定任务,例如日志清理、缓存刷新、固定数据同步、定时报表生成等。
8.4 动态任务结果分析
动态任务调度的运行结果表明,Quartz 可以在系统运行过程中通过 Scheduler API 动态管理任务。通过调用新增任务接口,可以在不重启系统的情况下创建新的周期任务;通过修改任务接口,可以调整任务执行周期;通过暂停、恢复和删除接口,可以控制任务的运行状态。
动态任务的优点是灵活性高,适合任务数量较多、执行周期经常变化、需要后台页面管理的业务系统。通过数据库保存任务配置后,系统还可以在启动时重新加载启用状态的任务,从而解决内存任务重启后丢失的问题。
但是,动态任务实现复杂度更高。它不仅需要设计任务配置表,还需要编写任务管理接口、任务服务类、参数校验逻辑和异常处理逻辑。如果系统允许通过接口传入任务类路径,还需要限制可执行任务范围,避免非法类被加载执行。
因此,动态任务更适合用于自动化运维、设备巡检、指标采集、定时通知、后台任务管理等需要灵活配置任务的场景。
8.5 常见问题与解决方法
在实验过程中,可能会遇到一些常见问题,需要根据具体情况进行排查。
8.5.1 任务没有执行
如果任务没有执行,首先需要检查 Quartz 调度器是否成功启动,其次检查 JobDetail 和 Trigger 是否已经注册到调度器中。还需要确认 Cron 表达式是否正确,任务类是否实现了 Job 接口。
常见原因包括:
-
Cron 表达式书写错误。
-
任务类没有实现
Job接口。 -
Trigger没有正确绑定JobDetail。 -
Quartz 配置未生效。
-
Spring Boot 没有扫描到任务配置类。
解决方法是检查启动日志、确认配置类是否添加 @Configuration 注解,并在任务执行方法中添加日志输出,方便观察任务是否触发。
8.5.2 Cron 表达式错误
Quartz 使用的 Cron 表达式与 Linux 系统中的 Cron 表达式略有区别。Quartz Cron 通常包含秒字段,因此常见格式为:
秒 分 时 日 月 星期
例如,每 10 秒执行一次应写为:
0/10 * * * * ?
如果误写为 Linux 风格的五位 Cron 表达式,可能会导致任务无法正常创建或执行。因此,在配置 Cron 表达式时,需要注意 Quartz 的表达式格式。
8.5.3 任务重复注册
如果在系统启动或动态新增任务时重复注册相同名称和分组的任务,可能会出现任务已存在的问题。Quartz 中 JobKey 由任务名称和任务分组组成,同一个分组下任务名称不能重复。
解决方法是在新增任务前先调用:
scheduler.checkExists(jobKey)
判断任务是否已经存在。如果已经存在,可以选择不重复添加,或者先删除旧任务后再重新注册。
8.5.4 任务修改后未生效
动态修改任务执行周期时,如果只是修改了数据库中的 Cron 表达式,而没有调用 Quartz 的 rescheduleJob() 方法,则调度器中的任务周期不会立即变化。
正确做法是在修改数据库记录后,同时调用:
scheduler.rescheduleJob(triggerKey, newTrigger);
重新设置任务触发器,使新的 Cron 表达式立即生效。
8.5.5 应用重启后动态任务丢失
如果 Quartz 使用的是内存存储方式,即 RAMJobStore,那么任务信息只保存在当前应用进程内存中。应用重启后,内存中的任务会丢失。
解决方法有两种:
第一,将动态任务配置保存到业务数据库中,应用启动时查询数据库中启用状态的任务并重新注册到 Quartz。
第二,将 Quartz 的任务存储方式改为 JDBC 模式,即使用 Quartz 官方表结构保存 Job 和 Trigger 信息,实现任务持久化。
本实验为了简化配置,采用第一种方式,即业务表保存任务配置,系统启动时重新加载任务。
8.6 实验结果总结
通过本实验验证,Quartz 可以很好地集成到 Spring Boot 项目中,并实现周期任务调度功能。静态任务调度能够满足固定周期任务的执行需求,动态任务调度能够满足系统运行过程中对任务进行灵活管理的需求。
实验结果表明,Quartz 的 Job、JobDetail、Trigger 和 Scheduler 各自承担不同职责,其中 Job 负责具体任务逻辑,JobDetail 负责任务定义,Trigger 负责触发规则,Scheduler 负责统一调度和管理任务。
通过静态任务和动态任务两种方式的实现,可以更加全面地理解 Quartz 在 Spring Boot 项目中的使用方法,也为后续实现定时数据采集、自动化运维任务下发、周期性报表生成和定时消息通知等功能提供了技术基础。
九、实验总结
通过本次实验,完成了 Quartz 组件在 Spring Boot 框架下的集成与使用,并分别实现了静态任务调度和动态任务调度两种方式。实验从 Quartz 的基础概念入手,介绍了 Job、JobDetail、Trigger、Scheduler 等核心组件的作用,并通过具体代码实现了周期任务的创建、注册、执行和管理。
在静态任务调度部分,通过编写固定的 Job 任务类,并在配置类中定义 JobDetail 和 Trigger,实现了项目启动后自动注册任务并按 Cron 表达式周期执行的功能。静态任务调度实现简单、结构清晰,适合任务执行周期固定、业务逻辑稳定的场景,例如定时清理日志、定时生成报表、定时同步数据等。
在动态任务调度部分,通过调用 Quartz 提供的 Scheduler API,实现了任务的动态新增、修改、暂停、恢复和删除。动态任务调度相比静态任务更加灵活,可以在系统运行过程中调整任务执行规则,不需要重新启动应用。结合数据库保存任务配置后,还可以实现任务信息的持久化管理,适合自动化运维、设备巡检、指标采集、消息通知等复杂业务场景。
通过实验可以看出,Quartz 在 Spring Boot 项目中具有较强的实用价值。它不仅能够满足简单的周期任务执行需求,还可以支持复杂的任务管理场景。相比 Spring Boot 自带的 @Scheduled 定时任务,Quartz 在任务动态管理、任务状态控制、复杂 Cron 表达式支持、任务持久化和多任务调度方面具有更强的扩展能力。
本实验也进一步加深了对任务调度机制的理解。在实际开发中,选择静态任务还是动态任务,需要根据业务需求决定。如果任务数量较少、执行周期固定,可以优先使用静态任务调度;如果任务数量较多、执行周期经常变化,或者需要通过后台页面统一管理,则更适合使用动态任务调度。
总体来说,本实验较完整地展示了 Quartz 在 Spring Boot 框架下实现周期任务调度的基本流程。通过本实验,可以掌握 Quartz 的核心使用方法,并为后续开发定时数据采集、周期性报表生成、自动化运维命令下发、定时通知推送等功能奠定基础。