杰瑞科技汇

Quartz如何高效实现Java定时任务?

目录

  1. Quartz 是什么?
  2. 核心概念
  3. 快速入门:第一个 Quartz 程序
  4. 进阶:使用 Cron 表达式
  5. 高级:Job 与 JobDataMap
  6. 任务管理与持久化
  7. Spring/Spring Boot 集成 (推荐方式)
  8. Quartz 的优缺点与适用场景

Quartz 是什么?

Quartz 是一个功能强大、开源的作业调度库,完全由 Java 编写,它可以集成到任何 Java 应用中,也可以独立运行。

Quartz如何高效实现Java定时任务?-图1
(图片来源网络,侵删)

它的核心功能是:在指定的时间点执行指定的任务(Job)

想象一下你需要实现以下功能:

  • 每天凌晨 2 点生成一份销售报表。
  • 每隔 30 分钟检查一次订单状态。
  • 每个月的 1 号自动清理过期数据。

手动用 Thread.sleep()Timer 来实现这些功能会非常复杂且不可靠,Quartz 就是为了解决这类问题而生的,它提供了稳定、灵活、可管理的任务调度解决方案。


核心概念

要使用 Quartz,必须先理解它的三个核心组件:

Quartz如何高效实现Java定时任务?-图2
(图片来源网络,侵删)

Job (任务)

Job 是一个接口,你只需要实现这个接口并重写 execute() 方法。execute() 方法中的就是你希望定时执行的具体业务逻辑

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

Trigger (触发器)

Trigger 用于定义 Job 的执行时间规则,它决定了一个 Job 何时以及以怎样的频率被执行。 Quartz 提供了多种类型的 Trigger,最常用的是:

  • SimpleTrigger: 用于简单的调度,在指定的时间点执行一次,或者重复执行 N 次,间隔固定时间。
  • CronTrigger: 功能最强大的触发器,它基于 Cron 表达式,可以定义非常复杂的调度规则,每周一、周三、周五的上午 9:30”。

Scheduler (调度器)

Scheduler 是 Quartz 的总指挥,它负责将 JobTrigger 绑定在一起,并按照 Trigger 设定的规则来执行 Job。 你可以把它想象成一个“调度中心”,所有任务的注册、启动、暂停、恢复、删除等操作都通过它来完成。

它们三者的关系: SchedulerJobTrigger 关联起来,Trigger 驱动 Scheduler 在特定时间去执行 Job

Quartz如何高效实现Java定时任务?-图3
(图片来源网络,侵删)
[Scheduler] -- (调度) --> [Job]
    ^
    |
    | (关联)
    |
[Trigger] -- (定义何时执行) --

快速入门:第一个 Quartz 程序

下面我们通过一个最简单的例子,来感受一下 Quartz 的基本用法,这个任务的功能是:每隔 5 秒打印一次 "Hello, Quartz!"。

步骤 1: 添加 Maven 依赖

在你的 pom.xml 文件中添加 Quartz 的核心依赖。

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.2</version> <!-- 建议使用较新稳定版本 -->
</dependency>
<!-- 如果不需要连接数据库,可以排除这个依赖 -->
<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.5</version>
    <exclusions>
        <exclusion>
            <groupId>com.mchange</groupId>
            <artifactId>mchange-commons-java</artifactId>
        </exclusion>
    </exclusions>
</dependency>

步骤 2: 创建一个 Job 类

创建一个实现 Job 接口的类。

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.Date;
public class MyFirstJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 这里是你要执行的业务逻辑
        System.out.println("Hello, Quartz! " + new Date());
    }
}

步骤 3: 编写主程序,启动调度

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class QuartzDemo {
    public static void main(String[] args) throws SchedulerException {
        // 1. 创建一个 SchedulerFactory(调度器工厂)
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        // 2. 从工厂中获取一个 Scheduler 实例(调度器)
        Scheduler scheduler = schedulerFactory.getScheduler();
        // 3. 创建 JobDetail 实例,并与 MyFirstJob 类绑定
        // JobDetail 是 Job 的详细配置信息
        JobDetail jobDetail = JobBuilder.newJob(MyFirstJob.class)
                .withIdentity("job1", "group1") // 给 Job 一个名称和分组
                .build();
        // 4. 创建 Trigger 实例(触发器)
        // 设置立即开始,每隔 5 秒重复执行一次
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1") // 给 Trigger 一个名称和分组
                .startNow() // 立即生效
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(5) // 间隔 5 秒
                        .repeatForever()) // 永久重复
                .build();
        // 5. 将 Job 和 Trigger 绑定到 Scheduler 中,并启动调度器
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
        System.out.println("Scheduler started. Jobs are scheduled.");
        // 由于是后台线程,程序不会退出,你需要手动关闭或使用其他方式保持主线程存活
        // scheduler.shutdown(); // 关闭调度器
    }
}

运行结果: 你会看到控制台每隔 5 秒打印一次 "Hello, Quartz!"。


进阶:使用 Cron 表达式

SimpleTrigger 虽然简单,但不够灵活,在实际开发中,我们更常用 CronTrigger

Cron 表达式是什么?

Cron 表达式是一个字符串,由 6-7 个子表达式组成,描述了详细的日期和时间信息。

格式:[秒] [分] [小时] [日] [月] [周] [年] (年可选)

常用符号:

  • 代表任意值。 在“小时”字段表示每小时。
  • 代表“不指定”,通常用于“日”和“周”字段,因为这两个字段会相互冲突。
  • 列出多个值。MON,WED,FRI 表示周一、周三、周五。
  • 表示一个范围。1-5 表示 1 到 5。
  • 表示起始时间和间隔时间。0/15 表示从 0 秒开始,每隔 15 秒;10/5 表示从 10 秒开始,每隔 5 秒。
  • L:表示“。L 在“日”字段表示当月的最后一天;L 在“周”字段表示周六(周日是 1,周六是 7)。
  • W:表示“工作日”(周一到周五)。
  • 表示“第几个”。6#3 表示“每个月的第 3 个周五”(周五是 6)。

示例:

  • 0 0 12 * * ?:每天中午 12 点触发。
  • 0 15 10 ? * *:每天上午 10:15 触发。
  • 0 0/5 14 * * ?:每天下午 2 点到 2 点 55 分之间,每隔 5 分钟触发一次。
  • 0 0 18 ? * FRI:每周五下午 6 点触发。
  • 0 0 0 1 * ?:每月 1 号午夜 0 点触发。

修改上面的例子,使用 Cron 表达式

我们只需要修改创建 Trigger 的部分。

// ... (前面的代码不变)
// 4. 创建 CronTrigger 实例
Trigger trigger = TriggerBuilder.newTrigger()
        .withIdentity("cronTrigger1", "group1")
        .startNow()
        .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?")) // 每隔 5 秒执行一次
        .build();
// ... (后面的代码不变)

运行效果和之前一样,但配置方式更加灵活和强大。


高级:Job 与 JobDataMap

我们需要在执行 Job 时传递一些参数,Quartz 提供了 JobDataMap 来实现这个功能。

JobDataMap 是一个 Map 接口的实现,可以存储任何可序列化的对象。

如何传递和使用数据?

通过 JobDetail 传递

这种方式传递的数据,在 Job 的整个生命周期内(对于 StatefulJob)是共享的。

// 在主程序中
JobDetail jobDetail = JobBuilder.newJob(MyDataJob.class)
        .withIdentity("dataJob", "group1")
        .usingJobData("message", "Hello from JobDetail!") // 存入数据
        .usingJobData("count", new Integer(10)) // 存入另一个数据
        .build();
// 在 Job 类中
public class MyDataJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 从 JobExecutionContext 中获取 JobDataMap
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        String message = dataMap.getString("message");
        int count = dataMap.getInt("count");
        System.out.println("Message: " + message);
        System.out.println("Count: " + count);
    }
}

通过 Trigger 传递

JobDataMap 中同时存在 JobTrigger 的同名键,Trigger 中的值会覆盖 Job 中的值。

// 在主程序中
// ... 创建 jobDetail ...
Trigger trigger = TriggerBuilder.newTrigger()
        .withIdentity("dataTrigger", "group1")
        .usingJobData("message", "Hello from Trigger!") // 存入同名数据
        .build();

MyDataJob 中,execute 方法打印出的 message 将会是 "Hello from Trigger!"。


任务管理与持久化

默认情况下,Quartz 会将任务信息保存在内存中,这种方式虽然简单,但如果应用重启,所有已配置的任务都会丢失。

为了解决这个问题,Quartz 支持将任务信息持久化到数据库中,这样即使应用重启,调度器也能从数据库中恢复任务,继续执行。

如何实现持久化?

  1. 创建 Quartz 数据库表 Quartz 提供了 SQL 脚本来创建所需的表,你可以在 Quartz 的官方文档或其 JAR 包的 org/quartz/impl/jdbcjobstore 目录下找到这些脚本(如 tables_mysql.sql, tables_oracle.sql 等)。 你需要根据你使用的数据库类型,执行相应的 SQL 脚本。

  2. 配置 SchedulerFactory 在创建 Scheduler 之前,你需要配置 quartz.properties 文件,或者直接在代码中配置,告诉 Quartz 使用数据库作为 JobStore。

    quartz.properties 配置示例 (MySQL):

    # 实例名
    org.quartz.scheduler.instanceName = MyScheduler
    # 实例ID,AUTO表示自动生成
    org.quartz.scheduler.instanceId = AUTO
    # JobStore配置
    org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
    org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    org.quartz.jobStore.tablePrefix = QRTZ_
    org.quartz.jobStore.isClustered = false # 是否集群部署
    org.quartz.jobStore.dataSource = myDB
    # DataSource配置
    org.quartz.dataSource.myDB.driver = com.mysql.cj.jdbc.Driver
    org.quartz.dataSource.myDB.URL = jdbc:mysql://localhost:3306/quartz_db?useSSL=false&serverTimezone=UTC
    org.quartz.dataSource.myDB.user = root
    org.quartz.dataSource.myDB.password = your_password

    将此文件放在 src/main/resources 目录下,Quartz 会自动加载。

其他管理操作

  • 暂停任务: scheduler.pauseJob(jobKey);
  • 恢复任务: scheduler.resumeJob(jobKey);
  • 删除任务: scheduler.deleteJob(jobKey);
  • 立即触发任务: scheduler.triggerJob(jobKey);

Spring/Spring Boot 集成 (推荐方式)

虽然原生 Quartz API 很强大,但在企业级应用中,我们通常不会直接使用它。Spring/Spring Boot 提供了更简洁、更集成的 Quartz 使用方式,它会帮你管理 Scheduler 的生命周期,并且可以方便地将 Job 声明为 Spring Bean,从而注入其他 Service。

Spring Boot 集成示例

  1. 添加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
  2. 配置 application.yml Spring Boot 会自动配置 Quartz,如果你想连接数据库,可以添加如下配置:

    spring:
      quartz:
        job-store-type: jdbc # 使用 JDBC 持久化
        jdbc:
          initialize-schema: always # 启动时自动创建 Quartz 表 (首次建议使用,之后可改为 embedded 或 never)
        properties:
          org:
            quartz:
              scheduler:
                instanceName: clusteredScheduler
                instanceId: AUTO
              jobStore:
                class: org.quartz.impl.jdbcjobstore.JobStoreTX
                driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
                tablePrefix: QRTZ_
                isClustered: true
                clusterCheckinInterval: 10000
              threadPool:
                class: org.quartz.simpl.SimpleThreadPool
                threadCount: 10
                threadPriority: 5
  3. 创建一个 Job (作为 Spring Bean) 直接创建一个类,继承 QuartzJobBean 并重写 executeInternal 方法,Spring 会自动管理它。

    import org.quartz.*;
    import org.springframework.scheduling.quartz.QuartzJobBean;
    import org.springframework.stereotype.Component;
    @Component
    public class MySpringJob extends QuartzJobBean {
        // Spring 会自动注入这个 Service
        @Autowired
        private SomeBusinessService someBusinessService;
        @Override
        protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
            System.out.println("Executing Spring-managed Job at " + new Date());
            // 可以在这里调用业务逻辑
            someBusinessService.doSomething();
        }
    }
  4. 配置 Job 和 Trigger 创建一个配置类,使用 @Bean 来定义 JobDetailTrigger

    import org.quartz.*;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    @Configuration
    public class QuartzConfig {
        // 定义 JobDetail
        @Bean
        public JobDetail myJobDetail() {
            // 使用 JobBuilder 创建 JobDetail
            // 注意:这里传入的是 MySpringJob.class
            return JobBuilder.newJob(MySpringJob.class)
                    .withIdentity("mySpringJob", "myJobGroup")
                    .storeDurably() // 没有触发器关联时也持久化
                    .build();
        }
        // 定义 Trigger
        @Bean
        public Trigger myJobTrigger() {
            // 使用 CronTriggerBuilder 创建 Trigger
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ?");
            return TriggerBuilder.newTrigger()
                    .forJob(myJobDetail()) // 关联 JobDetail
                    .withIdentity("mySpringJobTrigger", "myTriggerGroup")
                    .withSchedule(cronScheduleBuilder)
                    .build();
        }
    }

启动 Spring Boot 应用,Quartz 任务就会自动运行,这种方式与 Spring 生态无缝集成,是当前最主流的用法。


Quartz 的优缺点与适用场景

优点

  1. 功能强大:支持复杂的 Cron 表达式,能满足绝大多数定时任务需求。
  2. 集群支持:通过数据库锁机制,可以实现 Quartz 集群,确保高可用和任务不重复执行。
  3. 持久化:支持将任务信息存入数据库,应用重启后任务不丢失。
  4. 灵活管理:提供了丰富的 API 来管理任务的生命周期(暂停、恢复、删除等)。
  5. 集成性好:可以方便地与 Spring、Spring Boot 等框架集成。

缺点

  1. 学习成本:概念相对较多(Job, Trigger, Scheduler, JobDataMap 等),初学者需要一定时间理解。
  2. 重量级:相比一些简单的轻量级定时方案(如 Spring 自带的 @Scheduled),Quartz 的配置和启动过程更重。
  3. 依赖数据库:如果使用持久化和集群,必须依赖一个数据库实例。

适用场景

  • 需要持久化的任务:任务需要在应用重启后恢复。
  • 复杂的调度规则:业务逻辑需要基于 Cron 表达式等复杂时间规则来执行。
  • 需要集群部署的应用:确保任务在分布式环境下能可靠执行,且不重复执行。
  • 需要动态管理任务:在运行时动态地添加、删除、修改任务。

特性 说明
核心组件 Job (任务), Trigger (触发器), Scheduler (调度器)
触发器类型 SimpleTrigger (简单), CronTrigger (强大,基于表达式)
数据传递 通过 JobDataMap,可在 JobDetailTrigger 中设置
持久化 通过配置 JobStoreJDBCJobStore,将任务信息存入数据库
集群 基于数据库锁机制,实现高可用和任务负载均衡
最佳实践 在 Spring/Spring Boot 项目中,优先使用其提供的集成方式,将 Job 声明为 Bean,管理更方便

选择 Quartz,意味着你选择了一个成熟、稳定、功能全面的调度解决方案,虽然它比 @Scheduled 复杂,但对于有复杂业务需求的系统来说,它的价值是巨大的。

分享:
扫描分享到社交APP
上一篇
下一篇