基于 Quartz 组件在 Spring Boot 框架下的周期任务调度实验

一、实验概述

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 框架下的基本使用方法,理解周期任务调度的核心流程,并能够根据不同业务场景选择合适的任务调度实现方式。

通过本实验,需要达到以下目标:

  1. 理解周期任务调度在后端系统中的应用场景和实际意义。

  2. 掌握 Spring Boot 项目中 Quartz 相关依赖的引入方式。

  3. 掌握 Quartz 在 application.yml 配置文件中的基础配置方法。

  4. 理解 Quartz 中 JobJobDetailTriggerScheduler 等核心组件的作用。

  5. 掌握静态任务调度的实现方式,能够在项目启动后自动执行固定周期任务。

  6. 掌握动态任务调度的基本实现思路,能够通过接口实现任务的新增、修改、暂停、恢复和删除。

  7. 理解静态任务调度和动态任务调度的区别,并能够根据实际业务需求进行选择。

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 的任务调度主要由 JobJobDetailTriggerScheduler 等核心组件组成。这些组件分工明确,共同完成任务的定义、触发和执行。

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 的调度器,也是整个任务调度的核心。它负责管理 JobDetailTrigger,并根据触发器规则在合适的时间执行对应的任务。

开发人员通常会将任务和触发器注册到 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 表示周一到周五
复杂日期用 LW# 分别表示最后、工作日、第几个星期几

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 进行任务调度的基础运行环境。后续只需要编写具体任务类,并配置对应的 JobDetailTrigger,即可实现周期性任务调度。

四、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,用于配置任务的触发规则。

第四,将 JobDetailTrigger 注册到 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 完整静态任务配置类

为了使代码结构更加清晰,可以将 JobDetailTrigger 写在同一个配置类中。这样项目启动时,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 会自动识别 JobDetailTrigger 类型的 Bean,并将其注册到 Quartz 调度器中。Quartz 调度器启动后,会按照 Cron 表达式每 10 秒执行一次 StaticTaskJob 中的任务逻辑。

5.6 静态任务执行流程

静态任务调度的执行流程可以概括为以下步骤:

第一步,Spring Boot 应用启动,并加载 Quartz 相关配置。

第二步,Spring 容器扫描到 QuartzStaticConfig 配置类。

第三步,Spring 创建 JobDetail Bean,用于描述任务信息。

第四步,Spring 创建 Trigger Bean,用于描述任务触发规则。

第五步,Quartz 调度器启动,并将 JobDetailTrigger 注册到调度器中。

第六步,当 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_namejob_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 在运行时创建 JobDetailTrigger,然后调用 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 用于唯一标识一个触发器。

执行新增任务时,系统会先判断任务是否已经存在。如果任务不存在,则创建对应的 JobDetailCronTrigger,最后通过 scheduler.scheduleJob(jobDetail, trigger) 注册任务。

需要注意的是,动态加载任务类时,传入的 jobClass 必须是实现了 Job 接口的类,否则任务无法正常创建。

6.4 JobDetail 与 Trigger 的绑定方式说明

在 Quartz 中,JobDetail 用于定义任务本身,Trigger 用于定义任务的触发规则。一个任务能否按照指定时间执行,关键在于 JobDetailTrigger 是否建立了正确的绑定关系。

在实际开发中,JobDetailTrigger 的绑定常见有两种写法。

6.4.1 第一种方式

通过调度器同时绑定并注册

第一种方式是先分别创建 JobDetailTrigger,然后通过调度器的 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,也就是同一个任务需要按照多个不同的时间规则执行。

第三,动态任务封装时,为了让 TriggerJobDetail 的绑定关系更加清晰,可以显式写出 .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);

在上述代码中,morningTriggereveningTrigger 都通过 .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) 必须写 同一个任务需要多个触发规则
动态任务封装 两种写法都可以 建议写 代码语义更清晰,避免绑定关系不明确

综上,JobDetailTrigger 的绑定既可以通过 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 调度器中。

可以通过实现 CommandLineRunnerApplicationRunner,在项目启动完成后加载数据库任务。

示例代码如下:

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 调度器中执行。

这种方式最大的特点是实现简单,结构清晰。开发人员只需要编写任务类,并在配置类中定义 JobDetailTrigger,项目启动后任务就可以按照指定周期自动执行。对于一些业务规则固定、执行周期稳定的任务,静态任务调度非常适合。

例如,系统每天凌晨清理一次历史日志、每天定时生成统计报表、每隔 5 分钟采集一次固定指标、每小时同步一次固定接口数据等,都可以使用静态任务调度实现。

静态任务调度的优点主要包括:

  1. 实现简单,代码结构清晰,适合初学者理解 Quartz 的基本使用流程。

  2. 任务规则固定,系统启动后自动注册并执行,不需要额外设计管理接口。

  3. 适合固定周期、固定业务逻辑的内部系统任务。

  4. 任务定义直接写在代码中,便于版本管理和开发维护。

但是,静态任务调度也存在一定局限。如果任务执行周期需要经常修改,或者需要在系统运行过程中新增、暂停、恢复、删除任务,静态任务就不够灵活。通常需要修改代码或配置文件,并重新启动应用才能生效。

因此,静态任务调度适合任务数量较少、调度规则稳定、不需要动态管理的场景。

7.2 动态任务调度特点

动态任务调度是指任务信息不再完全写死在代码中,而是可以通过数据库、接口或后台页面进行管理。系统在运行过程中,可以动态新增任务、修改任务 Cron 表达式、暂停任务、恢复任务和删除任务。

这种方式最大的特点是灵活性高。任务的执行周期、任务状态和任务参数可以根据业务需要动态调整,不需要重新发布或重启系统。对于任务数量较多、任务规则经常变化、需要后台页面统一维护的系统,动态任务调度更加适合。

例如,在自动化运维系统中,不同服务器可能需要配置不同的巡检周期;在设备管理系统中,不同设备可能需要按照不同时间采集指标;在消息通知系统中,不同类型的通知任务可能需要根据业务情况临时启用或停用。这些场景都适合使用动态任务调度。

动态任务调度的优点主要包括:

  1. 灵活性强,可以在系统运行过程中动态新增、修改、暂停、恢复和删除任务。

  2. 适合后台页面管理,可以将任务信息保存到数据库中统一维护。

  3. 支持任务配置持久化,应用重启后可以重新加载任务配置。

  4. 适合任务数量较多、执行周期复杂、业务规则经常变化的系统。

但是,动态任务调度的实现复杂度也更高。它不仅需要操作 Quartz 的 Scheduler API,还需要设计任务配置表、管理接口、参数校验、异常处理和启动加载逻辑。如果允许通过接口传入任务类路径,还需要注意安全控制,避免加载非法任务类。

因此,动态任务调度适合任务规则变化频繁、需要运行时管理、需要页面化配置和数据库持久化的场景。

7.3 静态任务与动态任务对比表

静态任务调度和动态任务调度的对比如下:

对比项 静态任务调度 动态任务调度
任务定义方式 写在代码中 通过接口、数据库或页面配置
执行周期配置 代码或配置文件中固定配置 可以运行时动态修改
是否需要重启 修改任务通常需要重启应用 修改任务不需要重启应用
实现复杂度 较低 较高
灵活性 较弱 较强
是否适合页面管理 不太适合 适合
是否适合数据库持久化 一般不需要 通常需要
任务数量 适合少量固定任务 适合较多可配置任务
典型应用场景 固定日志清理、固定报表生成、固定数据同步 动态巡检任务、动态采集任务、后台任务管理、自动化运维任务
维护方式 开发人员维护代码 管理员或用户通过页面/接口维护

通过上表可以看出,静态任务调度更偏向于代码级固定任务,适合实现简单、周期稳定的任务;动态任务调度更偏向于平台化任务管理,适合需要灵活配置和运行时调整的复杂业务系统。

7.4 适用场景分析

在实际项目中,选择静态任务调度还是动态任务调度,需要根据业务复杂度和任务变化频率来决定。

如果系统中的任务数量较少,任务执行周期基本固定,并且不需要通过页面或接口进行管理,那么可以优先选择静态任务调度。例如,每天凌晨清理一次日志、每天定时统计一次数据、每隔固定时间执行一次系统检查等。这类任务通常由开发人员提前定义好,随着系统启动自动执行即可。

如果系统中的任务数量较多,任务执行周期经常变化,或者不同用户、不同设备、不同业务需要配置不同的执行规则,那么更适合选择动态任务调度。例如,自动化运维系统中的远程命令定时下发、设备管理系统中的指标采集任务、消息系统中的定时通知任务等。这类任务往往需要通过后台页面或接口进行管理,因此动态任务调度更加合适。

对于一些中小型系统,也可以采用静态任务和动态任务结合的方式。系统内部固定任务使用静态任务实现,例如日志清理、缓存刷新、基础数据同步等;需要用户配置或业务动态调整的任务使用动态任务实现,例如设备巡检、报表生成、消息通知等。这样既可以保证固定任务的稳定性,又可以满足复杂业务的灵活性需求。

7.5 本实验中的选择建议

在本实验中,静态任务调度主要用于演示 Quartz 的基本使用流程,帮助理解 JobJobDetailTriggerScheduler 之间的关系。通过静态任务,可以快速掌握 Quartz 任务创建、触发器配置和周期执行的基本过程。

动态任务调度则用于演示 Quartz 在实际项目中的扩展能力。通过动态任务,可以理解如何使用 Scheduler API 在运行时管理任务,并掌握任务新增、修改、暂停、恢复和删除的实现方式。

因此,本实验同时实现静态任务调度和动态任务调度两种方式。静态任务用于理解基础原理,动态任务用于贴近实际业务应用。通过两种方式的对比,可以更加全面地掌握 Quartz 在 Spring Boot 框架下的周期任务调度实现方法。

八、实验结果与分析

8.1 实验运行结果

完成 Quartz 依赖引入、基础配置、静态任务和动态任务代码编写后,启动 Spring Boot 项目。项目启动过程中,Quartz 调度器会随着 Spring Boot 应用一起启动,并根据配置加载对应的任务信息。

在静态任务调度测试中,系统启动后会自动注册提前定义好的 JobDetailTrigger。当 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 会在系统运行过程中动态创建 JobDetailTrigger,并将其注册到 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 自动加载对应的 JobDetailTrigger,因此只要配置正确,任务就可以随着系统启动自动执行。

静态任务的优点是实现简单、结构清晰、启动后自动运行,适合业务规则固定的任务。通过本实验可以看出,静态任务不需要额外设计数据库表和管理接口,只需要编写任务类和配置类即可完成周期调度。

但是,静态任务也存在一定不足。如果需要修改任务执行周期,需要修改 Cron 表达式对应的代码或配置,并重新启动项目后才能生效。对于任务规则经常变化的业务场景,静态任务的灵活性不够。

因此,静态任务更适合用于系统内部固定任务,例如日志清理、缓存刷新、固定数据同步、定时报表生成等。

8.4 动态任务结果分析

动态任务调度的运行结果表明,Quartz 可以在系统运行过程中通过 Scheduler API 动态管理任务。通过调用新增任务接口,可以在不重启系统的情况下创建新的周期任务;通过修改任务接口,可以调整任务执行周期;通过暂停、恢复和删除接口,可以控制任务的运行状态。

动态任务的优点是灵活性高,适合任务数量较多、执行周期经常变化、需要后台页面管理的业务系统。通过数据库保存任务配置后,系统还可以在启动时重新加载启用状态的任务,从而解决内存任务重启后丢失的问题。

但是,动态任务实现复杂度更高。它不仅需要设计任务配置表,还需要编写任务管理接口、任务服务类、参数校验逻辑和异常处理逻辑。如果系统允许通过接口传入任务类路径,还需要限制可执行任务范围,避免非法类被加载执行。

因此,动态任务更适合用于自动化运维、设备巡检、指标采集、定时通知、后台任务管理等需要灵活配置任务的场景。

8.5 常见问题与解决方法

在实验过程中,可能会遇到一些常见问题,需要根据具体情况进行排查。

8.5.1 任务没有执行

如果任务没有执行,首先需要检查 Quartz 调度器是否成功启动,其次检查 JobDetailTrigger 是否已经注册到调度器中。还需要确认 Cron 表达式是否正确,任务类是否实现了 Job 接口。

常见原因包括:

  1. Cron 表达式书写错误。

  2. 任务类没有实现 Job 接口。

  3. Trigger 没有正确绑定 JobDetail

  4. Quartz 配置未生效。

  5. 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 的 JobJobDetailTriggerScheduler 各自承担不同职责,其中 Job 负责具体任务逻辑,JobDetail 负责任务定义,Trigger 负责触发规则,Scheduler 负责统一调度和管理任务。

通过静态任务和动态任务两种方式的实现,可以更加全面地理解 Quartz 在 Spring Boot 项目中的使用方法,也为后续实现定时数据采集、自动化运维任务下发、周期性报表生成和定时消息通知等功能提供了技术基础。

九、实验总结

通过本次实验,完成了 Quartz 组件在 Spring Boot 框架下的集成与使用,并分别实现了静态任务调度和动态任务调度两种方式。实验从 Quartz 的基础概念入手,介绍了 JobJobDetailTriggerScheduler 等核心组件的作用,并通过具体代码实现了周期任务的创建、注册、执行和管理。

在静态任务调度部分,通过编写固定的 Job 任务类,并在配置类中定义 JobDetailTrigger,实现了项目启动后自动注册任务并按 Cron 表达式周期执行的功能。静态任务调度实现简单、结构清晰,适合任务执行周期固定、业务逻辑稳定的场景,例如定时清理日志、定时生成报表、定时同步数据等。

在动态任务调度部分,通过调用 Quartz 提供的 Scheduler API,实现了任务的动态新增、修改、暂停、恢复和删除。动态任务调度相比静态任务更加灵活,可以在系统运行过程中调整任务执行规则,不需要重新启动应用。结合数据库保存任务配置后,还可以实现任务信息的持久化管理,适合自动化运维、设备巡检、指标采集、消息通知等复杂业务场景。

通过实验可以看出,Quartz 在 Spring Boot 项目中具有较强的实用价值。它不仅能够满足简单的周期任务执行需求,还可以支持复杂的任务管理场景。相比 Spring Boot 自带的 @Scheduled 定时任务,Quartz 在任务动态管理、任务状态控制、复杂 Cron 表达式支持、任务持久化和多任务调度方面具有更强的扩展能力。

本实验也进一步加深了对任务调度机制的理解。在实际开发中,选择静态任务还是动态任务,需要根据业务需求决定。如果任务数量较少、执行周期固定,可以优先使用静态任务调度;如果任务数量较多、执行周期经常变化,或者需要通过后台页面统一管理,则更适合使用动态任务调度。

总体来说,本实验较完整地展示了 Quartz 在 Spring Boot 框架下实现周期任务调度的基本流程。通过本实验,可以掌握 Quartz 的核心使用方法,并为后续开发定时数据采集、周期性报表生成、自动化运维命令下发、定时通知推送等功能奠定基础。

相关推荐
罗超驿1 小时前
14.LeetCode 438 题解:滑动窗口+哈希表找所有字母异位词
java·算法·leetcode
码不停蹄的玄黓1 小时前
Java线程池生命周期
java·开发语言
学习要积极1 小时前
Spring AI Alibaba-ChatClient
java·人工智能·spring
武子康1 小时前
Java-15 深入浅出MyBatis 分页与通用 Mapper 实战:PageHelper + tk.mybatis 从配置到分页查询
java·后端
z落落1 小时前
C# 虚方法(virtual)与抽象方法 +区别+new方法隐藏 & override方法重写
java·开发语言·c#
宋哥转AI1 小时前
Spring AI Graph:从0到Supervisor(二)并行执行+HITL实战
java·agent
plainGeekDev1 小时前
XML 布局 → Compose 声明式 UI
android·java·kotlin
浮游本尊2 小时前
项目全景 + 第一条完整后端链路
java·前端
jeffer_liu2 小时前
Spring AI 生产级实战:模型选择
java·人工智能·spring boot·后端·spring·语言模型·ai编程