模块深度解析 · 定时任务(ruoyi-quartz)
📍 核心源码在
ruoyi-quartz/src/main/java/com/ruoyi/quartz/下的util/、service/、controller/、domain/,以及common/constant/ScheduleConstants.java。
1. 模块概述
若依内置了 Quartz 动态定时任务能力,意义在于:普通管理系统不需要下线重启,就能在页面上在线新增/编辑/暂停/删除定时任务,并支持立即执行、查看执行日志。
核心链路:
| 环节 | 类 | 说明 |
|---|---|---|
| 任务实体 | SysJob / SysJobLog |
任务定义 + 执行日志 |
| 任务持久化 | SysJobMapper |
Quartz 的任务存到 sys_job 业务表 |
| 任务编排 | ScheduleUtils |
把 SysJob 转成 Quartz JobDetail + CronTrigger |
| 任务执行 | AbstractQuartzJob / QuartzJobExecution |
Quartz 到业务方法的桥接 |
| 反射调用 | JobInvokeUtil |
解析 invokeTarget 字符串并反射执行方法 |
| Cron 校验 | CronUtils |
校验表达式 / 算下次执行时间 |
| 服务层 | SysJobServiceImpl |
生命周期管理(增删改暂停恢复) |
graph TB
UI[SysJobController 页面操作] --> SVC[SysJobServiceImpl]
SVC --> SCHED[ScheduleUtils.createScheduleJob]
SCHED --> TRIG[Quartz Scheduler
JobDetail + CronTrigger]
TRIG --> ABS[AbstractQuartzJob.execute]
ABS --> INVOKE[JobInvokeUtil 反射调用]
ABS --> LOG[SysJobLog 记录执行]
2. 核心概念:invokeTarget(任务目标字符串)
Quartz 调用什么方法,由 SysJob.invokeTarget 字符串决定。例如:ryTask.ryParams('ry') 或 com.ruoyi.quartz.task.RyTask.ryParams('ry')。
- Bean 名. 方法(点号 ≤1):
ryTask.ryParams→ 从容器取 bean 反射调用。 - 完整类名. 方法(点号 >1):
com.ruoyi.quartz.task.RyTask.ryParams→Class.forName反射调用。
JobInvokeUtil 解析参数:
'xxx'/"xxx"→Stringtrue/false→Boolean123L→Long1.2D→Double- 其它 →
Integer
安全校验:
ScheduleUtils.whiteList白名单只允许com.ruoyi.quartz.task包下的类被调度,杜绝任意类实例化。
3. API Signatures
1 | public class ScheduleUtils |
1 | public abstract class AbstractQuartzJob implements Job |
1 | public class JobInvokeUtil |
1 |
|
4. 数据结构深度解析
SysJob(任务定义)
1 | stateDiagram-v2 |
字段:jobId jobName jobGroup invokeTarget cronExpression misfirePolicy concurrent status createBy createTime remark。
Quartz 调度时序
sequenceDiagram
participant UI as 页面
participant S as SysJobServiceImpl
participant Q as Quartz Scheduler
participant J as AbstractQuartzJob
UI->>S: addJob(job)
S->>Q: createScheduleJob (add JobDetail+Trigger)
Q-->>J: 到点 execute(context)
J->>J: 取出 jobDataMap 中的 SysJob
J->>J: doExecute -> JobInvokeUtil.invokeMethod
J->>J: after 组装 SysJobLog 入库
5. 函数逐行精讲
5.a + 5.b:AbstractQuartzJob.execute(Quartz 桥接)
函数:
execute
- 调用时机:Quartz cron 到点触发
- 前置条件:任务已通过
createScheduleJob注册- 目的:从上下文取出 SysJob,执行并记录日志
1 |
|
5.a + 5.b:ScheduleUtils.createScheduleJob(注册任务)
函数:
createScheduleJob
- 调用时机:任务新增/修改/启动
- 目的:把 SysJob 注册进 Quartz 调度器
1 | public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException |
6. 关键算法剖析
6.1 并发与错失触发
concurrent = '1'允许并发(QuartzJobExecution);'0'则用QuartzDisallowConcurrentExecution(Quartz 的@DisallowConcurrentExecution特性保证同 key 任务不并发)。misfirePolicy(错失触发):1忽略 /2立即补执行一次 /3放弃丢失。由handleCronScheduleMisfirePolicy转发给 Quartz。
6.2 启动恢复
SysJobServiceImpl.init() 用 @PostConstruct:启动即 scheduler.clear() 清空 Quartz,然后从 sys_job 表全量重建所有任务。保证 DB 与调度器一致,防止手动改库导致的脏调度。
⚠️ 注释明确警告:不能手动修改数据库的
job_id和job_group,否则会产生脏数据。
7. 设计决策分析
7.1 为什么任务目标用字符串(invokeTarget)而非 Java 代码配置?
让任务的定义可落库、可由管理员在页面自由填写,无需改代码即可新增任务调用已有 bean 的方法。代价是必须做白名单安全校验,防止任意类实例化(whiteList)。
7.2 为什么 SysJob 不直接持久化进 Quartz,而分离业务表 sys_job?
Quartz 自带的表结构(qrtz_*)复杂、与业务耦合。若依用独立 sys_job 业务表保存任务,启动时再同步到 Quartz,这样:管理界面操作简单、可与系统其它功能(角色/日志)统一、支持 sql/quartz.sql 里那套 Quartz 表做高级特性(集群/持久化)。
8. 学习检查点
📝 本章小结
- 定时任务 = 业务表
sys_job定义 + Quartz 调度器执行,启动时全量重建保证一致性。 invokeTarget字符串 + 反射(JobInvokeUtil)动态决定执行哪个 bean 方法,配白名单防注入。AbstractQuartzJob是 Quartz → 业务的统一入口,负责记录执行日志。- 支持暂停/恢复/立即执行/改 cron(重建 trigger)/错失触发策略/并发控制。
🤔 思考题
为什么
init()要scheduler.clear()后全量重建而非增量同步?参考答案
增量同步需要对比 DB 与 Quartz 状态,复杂且易漏(比如有人手动操作了 Quartz)。全量重建(SysJobServiceImpl.java:37-46)以 DB 为准,一次 clear 加 create 幂等恢复,简单且可靠——代价是任务多时启动稍慢。
getQuartzJobClass根据concurrent返回不同 Job 类,二者的区别是什么?参考答案
QuartzJobExecution无@DisallowConcurrentExecution,同一 job 可并发重入;QuartzDisallowConcurrentExecution加了该注解(ScheduleUtils.java:35-39),Quartz 会保证同一 key 的任务在上次未结束时不会再次触发,避免资源竞争。