本文主要是介绍SpringBoot整合 Quartz实现定时推送实战指南,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
《SpringBoot整合Quartz实现定时推送实战指南》文章介绍了SpringBoot中使用Quartz动态定时任务和任务持久化实现多条不确定结束时间并提前N分钟推送的方案,本文结合实例代码给大...
前言
根据需求(多条不确定的结束时间 + 提前 N 分钟推送),Spring Boot 中最优方案是结合 Quartz 动态定时任务 + 任务持久化,支持动态添加 / 删除结束时间、自动计算提前 N 分钟的触发点,且能应对服务重启后任务不丢失的问题。
一、Quartz 是什么?

Quartz 是一个功能强大、开源的任务调度框架,用于实现定时、周期性或基于特定规则的任务执行。简单来说,它就是 Java 生态中 “定时任务” 的标准解决方案,支持复杂的调度逻辑,广泛应用于后台系统的定时任务场景(如定时备份、数据同步、定时推送、报表生成等)。
1、核心定位:解决什么问题?
日常开发中,我们常需要 “在特定时间执行某段代码”,比如:
- 每天凌晨 2 点执行数据库备份;
- 每隔 30 分钟同步一次第三方数据;
- 每月 1 号生成上月报表;
- 某个固定时间点触发短信推送。
Java 原生提供了 Timer/TimerTask,但存在明显缺陷(如单线程执行、不支持复杂表达式、异常会导致线程终止),无法满足企业级需求。而 Quartz 弥补了这些不足,提供了:
- 复杂调度规则(支持 Cron 表达式,覆盖几乎所有时间场景);
- 任务与调度解耦(任务逻辑和执行规则分离);
- 高可用(支持集群部署,避免单点故障);
- 可持久化(任务和调度状态可存储到数据库,重启后不丢失);
- 并发控制(支持任务并发执行或串行执行)。
2、Quartz 核心组件
Quartz 的架构设计清晰,核心组件分为 3 类,需理解它们的职责和关系:
| 组件 | 作用 |
|---|---|
| Job(任务) | 具体要执行的 “业务逻辑”,需实现 org.quartz.Job 接口(重写 execute() 方法)。 |
| JobDetail(任务详情) | 描述 Job 的元数据(如任务名称、分组、是否持久化等),是 Quartz 内部管理 Job 的载体(Job 本身是业务逻辑,JobDetail 是管理配置)。 |
| Trigger(触发器) | 定义 Job 的 “执行规则”(何时执行、执行频率),是触发 Job 执行的 “开关”。常见实现:SimpleTrigger:简单规则(如延迟 5 秒执行、每隔 10 秒执行 3 次);CronTrigger:复杂规则(基于 Cron 表达式,如每天 02:00 执行)。 |
| Scheduler(调度器) | Quartz 的核心 “大脑”,负责管理 JobDetail 和 Trigger,根据 Trigger 规则触发 Job 执行。需通过 SchedulerFactory 创建,是任务调度的入口。 |
二、使用步骤
1javascript. 引入依赖(Spring Boot + Quartz + 数据库)
需要 Quartz 核心依赖、Spring 整合包,以及数据库依赖(用于任务持久化):
<!-- Spring Boot 整合 Quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- 数据库依赖(以 mysql 为例,用于任务持久化) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>2. 配置文件(application.yml)
配置数据库连接和 Quartz 持久化(关键:禁用内存存储,启用数据库存储):
yaml
spring:
# 数据库配置(Quartz 任务将存储到该库)
datasource:
url: jdbc:mysql://localhost:3306/quartz_db?useSSL=false&serverTimezone=UTC&allowpublicKeyRetrieval=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# JPA 配置(可选,用于存储业务结束时间,方便管理)
jpa:
hibernate:
ddl-auto: update
show-sql: true
# Quartz 配置(核心:数据库持久化)
quartz:
job-store-type: jdbc # 任务存储类型:jdbc(数据库),默认是 memory(内存)
jdbc:
initialize-schema: always # 自动创建 Quartz 所需表(首次启动用 always,后续改为 never)
properties:
org:
quartz:
scheduler:
instanceName: pushScheduler
instanceId: AUTO # 集群模式下自动分配实例ID
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
tablePrefix: QRTZ_ # Quartz 表前缀(自动创建的表会带此前缀)
isClustered: false # 单节点用 false,集群部署改为 true
clusterCheckinInterval: 20000
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10 # 线程池大小3. 定义业务实体(存储结束时间,可选但推荐)
用于记录用户添加的 “结束时间”,方便后续查询、修改、删除任务(关联 Quartz 任务 ID):
import jakarta.persistence.*; import lombok.Data; import java.time.LocalDateTime; @Data @Entity @Table(name = "push_task") public class Pushtask { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 自增ID @Column(name = "end_time", nullable = false) private LocalDateTime endTime; // 原始结束时间(如明天3点) @Column(name = "trigger_time", nullable = false) private LocalDateTime triggerTime; // 推送触发时间(endTime -5分钟) @Column(name = "quartz_job_id", unique = true, nullable = false) private String quartzJobId; // 关联 Quartz 的 JobDetail ID @Column(name = "task_desc") private String taskDesc; // 任务描述(如“订单123超时提醒”) @Column(name = "status") private Integer status; // 状态:0-未触发 1-已触发 2-已取消 }
4. 实现消息推送 Job(Quartz 任务逻辑)
Quartz 任务的核心逻辑:触发时执行消息推送,同时更新业务任务状态:
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 消息推送 Job(Quartz 任务)
*/
@Component
public class PushMessageJob implements Job {
private static final Logger logger = LoggerFactory.getLogger(PushMessageJob.class);
@Autowired
private PushTaskRepository pushTaskRepository; // 后续定义的 DAO 接口
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 1. 从 Job 上下文获取参数( quartzJobId + 任务描述 )
String quartzJobId = context.getJobDetail().getKey().getName();
String taskDesc = (String) context.getJobDetail().getJobDataMap().get("taskDesc");
String triggerTimeDate = (String) context.getJobDetail().getJobDataMap().get("triggerTime");
LocalDateTime triggerTime = null;
if (null != triggerTimeDate) {
triggerTime = LocalDateTime.parse(triggerTimeDate);
}
try {
// 2. 核心:执行消息推送(替换为你的实际逻辑:短信、APP推送、邮件等)
doPush(taskDesc, triggerTime);
// 3. 更新业务任务状态为“已触发”
pushTaskRepository.updateStatusByQuartzJobId(1, quartzJobId);
logger.info("消息推送成功!quartzJobId:{},任务描述:{},触发时间:{}",
quartzJobId, taskDesc, triggerTime);
} catch (Exception e) {
logger.error("消息推送失败!quartzJobId:{}", quartzJobId, e);
// 可选:抛出异常触发 Quartz 重试(需配置重试策略)
throw new JobExecutionException("推送失败,触发重试", e, true);
China编程 }
}
/**
* 实际推送逻辑(根据业务需求替换)
*/
private void doPush(String taskDesc, LocalDateTime triggerTime) {
// 示例1:打印日志(实际场景替换为第三方推送接口)
System.out.printf("[推送通知] 任务描述:%s,触发时间:%s,内容:即将到达结束时间(提前5分钟提醒)%n",
taskDesc, triggerTime);
// 示例2:调用 HTTP 推送接口(如极光推送)
// RestTemplate restTemplate = new RestTemplate();
// String pushUrl = "https://api.jpush.cn/v3/push";
// PushRequest request = new PushRequest();
// request.setContent("即将到达结束时间:" + taskDesc);
// restTemplate.postForObject(pushUrl, request, String.class);
}
}5. 定义 DAO 接口(操作业务任务表)
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
public interface PushTaskRepository extends JpaRepository<PushTask, Long> {
// 根据 QuartzJobId 查询任务
PushTask findByQuartzJobId(String quartzJobId);
// 更新任务状态
@Modifying
@Transactional
@Query("update PushTask t set t.status = :status where t.quartzJobId = :quartzJobId")
int updateStatusByQuartzJobId(Integer status, String quartzJobId);
// 根据状态和触发时间查询未执行的任务(服务重启后恢复任务用)
@Query("select t from PushTask t where t.status = 0 and t.triggerTime > :now")
List<PushTask> findUnTriggeredTasks(LocalDateTime now);
}6. 动态定时任务服务(核心:添加 / 删除 / 恢复任务)
封装 Quartz API,实现动态添加任务(自动计算提前 5 分钟)、删除任务、服务重启后恢复未触发任务:
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
@Service
public class DynamicQuartzService {
@Autowired
private Scheduler scheduler;
@Autowired
private PushTaskRepository pushTaskRepository;
// 任务组名(统一分组,方便管理)
private static final String JOB_GROUP = "PUSH_JOB_GROUP";
private static final String TRIGGER_GROUP = "PUSH_TRIGGER_GROUP";
/**
* 核心方法:添加推送任务(自动计算提前5分钟触发)
*
* @params endTime 原始结束时间(如明天3点)
* @params triggerTime 触发时间(如明天2点)
* @params taskDesc 任务描述(如“订单123超时提醒”)
* @return 保存的业务任务
*/
public PushTask addPushTask(LocalDateTime endTime, LocalDateTime triggerTime,String taskDesc) throws SchedulerException {
PaAssert.notNull(endTime, "结束时间不能为空!");
PaAssert.notNull(triggerTime, "触发时间不能为空!");
// 校验结束时间
LocalDateTime now = LocalDateTime.now();
if (endTime.isBefore(now) || endTime.isEqual(now)) {
throw new IllegalArgumentException("结束时间必须晚于当前时间!");
}
if (triggerTime.isBefore(now) || triggerTime.isEqual(now)) {
throw new IllegalArgumentException("触发时间必须晚于当前时间!");
}
// 3. 生成唯一的 QuartzJobId(避免重复)
String quartzJobId = "PUSH_JOB_" + UUID.randomUUID().toString().replace("-", "");
// 4. 构建 Quartz JobDetail(绑定 PushMessageJob)
JobDetail jobDetail = JobBuilder.newJob(PushMessageJob.class)
.withIdentity(quartzJobId, JOB_GROUP) // 任务ID:quartzJobId,组名:JOB_GROUP
.usingJobData("taskDesc", taskDesc)
.usingJobData("triggerTime", String.valueOf(triggerTime))
.storeDurably() // 持久化(无触发器时也保留)
.build();
// 5. 构建 CronTrigger(根据 triggerTime 生成 Cron 表达式)
String cronExpression = buildCronExpression(triggerTime);
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(quartzJobId, TRIGGER_GROUP)
.forJob(jobDetail)
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression)
.withMisfireHandlingInstructionFireAndProceed()) // 错过触发时立即执行
.startNow()
// 关键:触发时间+1分钟,确保仅执行一次(避免循环或重复触发)
.endAt(Date.from(triggerTime.plusMinutes(1).atZone(ZoneId.systemDefault()).toInstant()))
.build();
// 6. 注册任务到 Quartz 调度器
scheduler.scheduleJob(jobDetail, trigger);
// 7. 保存业务任务到数据库(关联 QuartzJobId)
PushTask pushTask = new PushTask();
pushTask.setEndTime(endTime);
pushTask.setTriggerTime(triggerTime);
pushTask.setQuartzJobId(quartzJobId);
pushTask.setTaskDesc(taskDesc);
pushTask.setStatus(0); // 0-未触发
return pushTaskRepository.save(pushTask);
}
/**
* 删除推送任务(同时删除 Quartz 任务和业务任务)
* @param quartzJobId 任务ID(添加任务时返回的 quartzJobId)
*/
public void deletePushTask(String quartzJobId) throws SchedulerException {
// 1. 停止并删除 Quartz 任务
JobKey jobKepythony = JobKey.jobKey(quartzJobId, JOB_GROUP);
TriggerKey triggerKey = TriggerKey.triggerKey(quartzJobId, TRIGGER_GROUP);
scheduler.pauseTrigger(triggerKey); // 暂停触发器
scheduler.unscheduleJob(triggerKey); // 移除触发器
scheduler.deleteJob(jobKey); // 删除任务
// 2. 更新业务任务状态为“已取消”
pushTaskRepository.updateStatusByQuartzJobId(2, quartzJobId);
}
/**
* 服务重启后恢复未触发的任务(关键:避免任务丢失)
*/
public void restoreUnTriggeredTasks() throws SchedulerException {
// 1. 查询数据库中“未触发”且“触发时间未到”的任务
List<PushTask> unTriggeredTasks = pushTaskRepository.findUnTriggeredTasks(LocalDateTime.now());
if (unTriggeredTasks.isEmpty()) {
return;
}
// 2. 重新注册这些任务到 Quartz
for (PushTask task : unTriggeredTasks) {
String quartzJobId = task.getQuartzJobId();
LocalDateTime triggerTime = task.getTriggerTime();
String taskDesc = task.getTaskDesc();
// 构建 JobDetail
JobDetail jobDetail = JobBuilder.newJob(PushMessageJob.class)
.withIdentity(quartzJobId, JOB_GROUP)
.usingJobData("taskDesc", taskDesc)
.usingJobData("triggerTime", String.valueOf(triggerTime))
.storeDurably()
.build();
// 构建 Trigger
String cronExpression = buildCronExpression(triggerTime);
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(quartzJobId, TRIGGER_GROUP)
.forJob(jobDetail)
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.startNow()
.build();
// 注册任务
scheduler.scheduleJob(jobDetail, trigger);
}
System.out.printf("恢复未触发的推送任务数量:%d%n", unTriggeredTasks.size());
}
/**
* 工具方法:将 LocalDateTime 转换为 Cron 表达式(精准到秒)
* Cron 格式:秒 分 时 日 月 周 年(年可选)
* 例:2025-11-01 02:55:00 → 0 55 2 1 11 ? 2025
*/
private Stringpython buildCronExpression(LocalDateTime triggerTime) {
int second = triggerTime.getSecond(); // 秒(0)
int minute = triggerTime.getMinute(); // 分(0)
int hour = triggerTime.getHour(); // 时(16)
int day = triggerTime.getDayOfMonth();// 日(21)
int month = triggerTime.getMonthValue();// 月(11,LocalDateTime 直接是 1-12,无需+1)
int year = triggerTime.getYear(); // 年(2025)
// Cron 格式:秒 分 时 日 月 ? 年 → 周字段用 ?,避免与日冲突
return String.format("%d %d %d %d %d ? %d",
second, minute, hour, day, month, year);
}
}7. 启动时恢复任务(监听服务启动)
服务重启后,自动恢复数据库中 “未触发” 的任务,避免任务丢失:
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class TaskRestoreRunner implements ApplicationRunner {
@Autowired
private DynamicQuartzService dynamicQuartzService;
@Override
public void run(ApplicationArguments args) throws SchedulerException {
// 服务启动后,恢复未触发的任务
dynamicQuartzService.restoreUnTriggeredTasks();
}
}8. 测试接口(对外提供添加 / 删除任务的入口)
通过 HTTP 接口测试动态添加、删除任务:
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
@RestController
@RequestMapping("/push")
public class PushTaskController {
@Autowired
private DynamicQuartzService dynamicQuartzService;
/**
* 添加推送任务
* @param endTime 结束时间(格式:yyyy-MM-dd HH:mm:ss,如 2025-11-01 03:00:00)
* @param taskDesc 任务描述
*/
@PostMapping("/add")
public PushTask addTask(
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
@RequestParam String taskDesc) throws SchedulerException {
return dynamicQuartzService.addPushTask(endTime, taskDesc);
}
/**
* 删除推送任务
* @param quartzJobId 任务Iwww.chinasem.cnD(添加任务时返回的 quartzJobId)
*/
@PostMapping("/delete")
public String deleteTask(@RequestParam String quartzJobId) throws SchedulerException {
dynamicQuartzService.deletePushTask(quartzJobId);
return "任务删除成功!quartzJobId:" + quartzJobId;
}
}三、核心功能测试
1. 添加任务测试
发送 POST 请求:
plaintext http://localhost:8080/push/add?endTime=2025-11-01
03:00:00&taskDesc=订单123超时提醒
- 后台会自动计算触发时间:2025-11-01 02:55:00;
- 数据库 push_task 表会新增一条记录,状态为 0(未触发);
- Quartz 会创建对应的 Job 和 Trigger,到 02:55 自动触发推送。
2. 触发效果
到触发时间后,控制台会输出:
[推送通知] 任务描述:订单123超时提醒,触发时间:2025-11-01T02:55,内容:即将到达结束时间(提前5分钟提醒)
同时 push_task 表中该任务的状态会更新为 1(已触发)。
3. 删除任务测试
发送 POST 请求:
http://localhost:8080/push/delete?quartzJobId=PUSH_JOB_xxx(添加任务时返回的
quartzJobId)
- Quartz 会删除对应的 Job 和 Trigger;
- 数据库中任务状态更新为 2(已取消)。
四、关键特性说明
1. 动态性
- 支持任意多条结束时间:调用 addPushTask 方法可添加多个任务,每个任务独立触发;
- 结束时间不确定:无需提前配置 Cron 表达式,传入 LocalDateTime 即可自动生成触发规则。
2. 可靠性
- 持久化:任务信息存储在数据库,服务重启后自动恢复未触发的任务;
- 失败重试:推送失败时抛出异常,Quartz 会根据配置的策略重试(默认重试 3 次);
- 集群支持:修改 application.yml 中 quartz.properties.org.quartz.jobStore.isClustered=true,即可支持集群部署(避免重复触发)。
3. 灵活性
- 提前时间可配置:将 minusMinutes(5) 改为参数(如 minusMinutes(提前分钟数)),支- - 持动态调整提前提醒时间;
- 推送逻辑可扩展:修改 doPush 方法,整合短信、APP 推送(极光 / 个推)、邮件等任意渠道。
4. 时间校验
- 避免添加已过期的任务(结束时间早于当前时间);
- 避免提前 5 分钟后仍过期的任务(如结束时间为当前时间 + 3 分钟,提前 5 分钟则已过期)。
五、生产环境优化建议
1.任务监控:通过 Quartz 提供的 API 监控任务状态(如查询所有任务、触发次数、失败次数),或集成 Prometheus + Grafana 可视化监控;
2.过期任务清理:定时清理数据库中 “已触发” 或 “已取消” 的任务(避免表数据过大);
3.推送异步化:如果推送逻辑耗时较长(如调用第三方接口),可在 doPush 中发送 MQ 消息,由消费者异步处理推送(解耦定时任务和推送逻辑);
4.日志增强:记录推送结果(成功 / 失败)到日志文件或数据库,方便问题排查;
5.权限控制:对 /push/add 和 /push/delete 接口添加权限校验(如 Token 验证),避免恶意操作。
总结
本方案通过 Quartz 动态任务 + 数据库持久化,完美满足 “多条不确定结束时间 + 提前 5 分钟推送” 的需求,核心优势:
- 动态灵活:支持任意结束时间,无需提前配置;
- 可靠稳定:任务持久化、服务重启恢复、失败重试;
- 易于扩展:支持多推送渠道、集群部署、监控告警。
如果需要轻量级方案(无需持久化,服务重启后任务可丢失),也可使用 Spring Scheduler + 内存缓存,但生产环境建议优先选择本方案(可靠性更高)
到此这篇关于SpringBoot整合 Quartz实现定时推送实战指南的文章就介绍到这了,更多相关SpringBoot Quartz定时推送内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!
这篇关于SpringBoot整合 Quartz实现定时推送实战指南的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!