杰瑞科技汇

Java中如何实现Quartz定时任务?

Quartz是一个功能强大、开源的作业调度库,它可以集成到几乎任何Java应用程序中——从最小的独立应用程序到最大的电子商务系统。

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

本教程将涵盖以下内容:

  1. Quartz核心概念
  2. 快速入门:一个简单的 "Hello World" 示例
  3. 进阶:使用 JobDataMap 传递数据
  4. 进阶:使用 Cron 表达式设置复杂调度
  5. 进阶:Job 依赖管理与 JobBuilderTriggerBuilder
  6. 如何优雅地关闭调度器
  7. Quartz 与 Spring/Spring Boot 集成
  8. 总结与最佳实践

Quartz 核心概念

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

  • Job (作业): 这是一个接口,你只需要实现 execute(JobExecutionContext context) 方法,这个接口代表一个“可执行的工作单元”,即你想要定时执行的具体任务逻辑,发送邮件、生成报表等。
  • JobDetail (作业详情): 这是一个 Job 的详细配置信息,它包含了 Job 的实现类名、以及一个 JobDataMap(用于传递数据给 Job)。JobDetailJob 的实例配置,而不是 Job 本身,每次调度器执行一个 Job,它都会根据 JobDetail 创建一个新的 Job 实例。
  • Trigger (触发器): 这个组件定义了 Job 的执行计划,它决定了 Job 何时以及如何执行,可以设置它在某个特定时间点执行,或者每隔5秒执行一次。
    • SimpleTrigger: 用于简单的调度场景,比如在指定时间执行一次,或重复执行N次。
    • CronTrigger: 用于更复杂的调度场景,基于 Cron 表达式来定义执行时间(每天凌晨1点执行)。
  • Scheduler (调度器): 这是Quartz的核心控制器,它负责将 JobTrigger 绑定在一起,并根据 Trigger 的定义来执行 Job,你可以通过 Scheduler 来启动、停止、暂停或恢复任务。

简单比喻

  • Job:是你想要执行的“工作内容”(打扫卫生”)。
  • JobDetail:是这份工作的“工作单”,上面写着“打扫卫生”这个任务,以及需要传递给清洁工的“数据”(使用绿色拖把”)。
  • Trigger:是“工作时间表”,上面写着“每周一、周三、周五下午5点”。
  • Scheduler:是“项目经理”,他拿着工作单和时间表,确保清洁工在正确的时间和正确的方式下完成工作。

快速入门:一个简单的 "Hello World" 示例

下面是一个最简单的Quartz程序,它会每隔5秒打印一次 "Hello, Quartz World!"。

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

1 添加依赖

在你的 pom.xml (Maven) 或 build.gradle (Gradle) 中添加Quartz的核心依赖。

Maven (pom.xml)

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.2</version> <!-- 建议使用较新稳定版本 -->
</dependency>
<!-- 为了方便日志输出,可以添加一个日志实现,如SLF4J + Logback -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.36</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.11</version>
</dependency>

2 编写 Job

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

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloJob implements Job {
    private static final Logger logger = LoggerFactory.getLogger(HelloJob.class);
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 这就是你的定时任务逻辑
        logger.info("Hello, Quartz World! - Current time: {}", System.currentTimeMillis());
    }
}

3 编写主程序来调度任务

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
public class QuartzDemo {
    private static final Logger logger = LoggerFactory.getLogger(QuartzDemo.class);
    public static void main(String[] args) throws SchedulerException {
        // 1. 创建一个 SchedulerFactory(调度器工厂)
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        // 2. 从工厂中获取一个 Scheduler 实例
        Scheduler scheduler = schedulerFactory.getScheduler();
        // 3. 创建 JobDetail 实例,并与 HelloJob 类绑定
        JobDetail jobDetail = newJob(HelloJob.class)
                .withIdentity("myJob", "group1") // 设置 name 和 group
                .build();
        // 4. 创建 Trigger 实例,定义立即执行,并且每5秒重复一次
        Trigger trigger = newTrigger()
                .withIdentity("myTrigger", "group1") // 设置 name 和 group
                .startNow() // 立即开始
                .withSchedule(simpleSchedule()
                        .withIntervalInSeconds(5) // 间隔5秒
                        .repeatForever()) // 永远重复
                .build();
        // 5. 将 Job 和 Trigger 绑定到 Scheduler 上,并启动调度器
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
        logger.info("Scheduler started. Job will run every 5 seconds.");
        // 保持主线程存活,让定时任务有机会运行
        try {
            Thread.sleep(60000); // 睡眠60秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 关闭调度器
        logger.info("Shutting down scheduler...");
        scheduler.shutdown();
    }
}

运行这个程序,你会看到控制台每隔5秒打印一次 "Hello, Quartz World!"。


进阶:使用 JobDataMap 传递数据

有时你需要向 Job 传递一些参数,比如用户ID、查询条件等,这时可以使用 JobDataMap

1 修改 Job 以接收数据

import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DataPassingJob implements Job {
    private static final Logger logger = LoggerFactory.getLogger(DataPassingJob.class);
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 从 JobExecutionContext 中获取 JobDataMap
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        // 从 map 中获取数据
        String jobSays = dataMap.getString("jobSays");
        float myFloatValue = dataMap.getFloat("myFloatValue");
        logger.info("Job says: {}", jobSays);
        logger.info("MyFloatValue is: {}", myFloatValue);
    }
}

2 在调度时设置数据

// ... (SchedulerFactory 和 Scheduler 获取代码)
// 1. 创建 JobDetail
JobDetail jobDetail = newJob(DataPassingJob.class)
        .withIdentity("dataJob", "group1")
        .usingJobData("jobSays", "Hello from the JobDataMap!") // 设置字符串数据
        .usingJobData("myFloatValue", 3.14f) // 设置浮点数数据
        .build();
// 2. 创建 Trigger (也可以在 Trigger 中设置数据,如果同名,JobDetail中的数据会被覆盖)
// Trigger trigger = newTrigger()...
//    .usingJobData("jobSays", "This value will override the one in JobDetail") // 覆盖数据
//    .build();
// 3. 调度
scheduler.scheduleJob(jobDetail, trigger);
// ...

进阶:使用 Cron 表达式设置复杂调度

CronTrigger 非常强大,它使用 Cron 表达式来定义执行时间。

1 常用 Cron 表达式示例

表达式 说明
0/5 * * * * ? 每隔5秒执行一次
0 0/1 * * * ? 每隔1分钟执行一次
0 0 0/1 * * ? 每隔1小时执行一次
0 0 12 * * ? 每天12点(中午)执行一次
0 15 10 ? * * 每天10:15执行一次
0 15 10 * * ? 2025 在2025年的每天10:15执行一次
0 0/30 9-17 * * ? 工作日的上午9点到下午5点,每30分钟执行一次
0 0 0 L * ? 每天最后一天(月底)的0点执行一次

2 使用 CronTrigger

只需要修改 Trigger 的构建方式即可。

import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
// ...
// 创建一个 CronTrigger,每天凌晨1点执行
Trigger cronTrigger = newTrigger()
        .withIdentity("cronTrigger", "group1")
        .startNow()
        .withSchedule(cronSchedule("0 0 1 * * ?")) // Cron 表达式
        .build();
// ...
scheduler.scheduleJob(jobDetail, cronTrigger);

进阶:Job 依赖管理与 JobBuilder, TriggerBuilder

从上面的例子中,你已经看到了 JobBuilderTriggerBuilder,它们是 Quartz 2.x 引入的建造者模式API,比旧版本的 new JobDetail(...) 构造函数更安全、更易读,是目前推荐的用法。

Job 依赖管理: Quartz 默认使用 无状态Job,这意味着每次执行 Job 时,Quartz 都会创建一个新的 Job 实例,执行完毕后这个实例就会被丢弃,你不能在 Job 类中定义成员变量来保存上次执行的状态。

如果你需要管理状态(记录任务执行的次数),有以下几种方式:

  1. 使用 JobDataMap:将状态数据存入 JobDataMap,每次执行后更新它,Quartz 会将 JobDataMap 持久化到数据库(如果使用JDBC JobStore),下次执行时可以取到。
  2. 使用 @PersistJobDataAfterExecution 注解:在你的 Job 类上添加这个注解,Quartz 会在 Job 执行成功后自动将 JobDataMap 的内容持久化。
  3. 使用有状态的 Job (StatefulJob)StatefulJob 是一个已废弃的接口(不推荐使用),它会禁止同一个 Job 的多个实例并发执行,JobDataMap 会被保留,但现代更推荐使用注解方式。

示例:使用 @PersistJobDataAfterExecution

import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@PersistJobDataAfterExecution // 标记此Job执行后会持久化JobDataMap
public class StatefulJob implements Job {
    private static final Logger logger = LoggerFactory.getLogger(StatefulJob.class);
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        int count = dataMap.getInt("count");
        count++;
        dataMap.put("count", count);
        logger.info("This job has been executed {} times.", count);
    }
}

在调度时,只需要初始化一次 count 即可。


如何优雅地关闭调度器

在应用程序关闭时(Web应用关闭时),必须正确地关闭 Scheduler,以确保所有正在执行的任务都能完成,并且资源被正确释放。

// ... 在程序关闭的地方,Spring 的 shutdown hook 或 Web 监听器中 ...
// 1. 等待已存在的任务执行完毕
scheduler.shutdown(true); // 参数 true 表示等待任务完成
// 或者 2. 立即关闭,中断正在执行的任务
// scheduler.shutdown(false); 

Quartz 与 Spring/Spring Boot 集成

在实际项目中,我们通常不会直接使用 StdSchedulerFactory,而是通过 Spring/Spring Boot 来管理 Scheduler,这样可以更好地利用 Spring 的依赖注入和生命周期管理。

1 Spring Boot 集成 (非常简单)

  1. 添加依赖:

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

    Spring Boot 会自动配置好一切。

  2. 创建 Job: 你的 Job 类可以直接作为 Spring Bean,如果它需要注入其他服务(如 UserService),直接使用 @Autowired 即可。

    import org.quartz.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    @Component
    public class SpringIntegratedJob implements Job {
        private static final Logger logger = LoggerFactory.getLogger(SpringIntegratedJob.class);
        @Autowired // Spring 会自动注入
        private SomeService someService;
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            logger.info("Executing Spring Integrated Job!");
            someService.doSomething();
        }
    }
  3. 配置调度: 你可以通过配置类来配置你的 JobTrigger

    import org.quartz.*;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    @Configuration
    public class QuartzConfig {
        // 1. 定义 JobDetail
        @Bean
        public JobDetail quartzJobDetail() {
            // 使用 JobBuilder 创建 JobDetail
            return JobBuilder.newJob(SpringIntegratedJob.class)
                    .withIdentity("springJob", "group1")
                    .storeDurably() // Trigger 不立即定义,需要设置为持久化
                    .build();
        }
        // 2. 定义 Trigger
        @Bean
        public Trigger cronJobTrigger() {
            // 使用 TriggerBuilder 创建 Trigger
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ?");
            return TriggerBuilder.newTrigger()
                    .forJob(quartzJobDetail()) // 关联 JobDetail
                    .withIdentity("cronTrigger", "group1")
                    .withSchedule(cronScheduleBuilder)
                    .build();
        }
    }

启动 Spring Boot 应用后,Quartz 会自动加载并启动这个定时任务。


总结与最佳实践

  1. 核心思想: Scheduler 调度 JobTrigger 决定何时调度。
  2. API选择: 优先使用 JobBuilderTriggerBuilder,它们更现代、更安全。
  3. 状态管理: 不要在 Job 中使用成员变量,如需保存状态,使用 JobDataMap@PersistJobDataAfterExecution 注解。
  4. 表达式: 熟练掌握 Cron 表达式,它是复杂调度的利器。
  5. 集成: 在企业级应用中,强烈推荐使用 Spring/Spring Boot 集成,以简化配置并利用 Spring 生态。
  6. 持久化: 对于需要高可用性的应用,配置 Quartz 使用数据库作为 JobStore(如 JobStoreTXJobStoreCMT),这样即使应用重启,任务也不会丢失。
  7. 关闭: 应用关闭时,务必调用 scheduler.shutdown(true) 优雅地关闭调度器。

Quartz 是一个非常成熟和强大的调度框架,掌握它对于任何Java开发者来说都是一项非常有用的技能,希望这份详细的指南能帮助你快速上手!

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