若依框架 Quartz :定时任务初始化源码全拆解(附图解)
一、前置知识 Quartz
1.1核心定位
Quartz 是 Java 生态中成熟、开源的定时任务调度框架(简单说就是专门帮你实现 “在指定时间做某件事”“每隔一段时间重复做某件事” 的工具),几乎是 Java 定时任务的标配方案。
1.2. 核心价值
- 支持复杂的定时规则(不仅是固定间隔,还支持类似 Cron 表达式的 “每月 1 号凌晨 3 点执行” 等灵活配置)
- 支持任务的持久化(任务信息可存到数据库,避免应用重启后定时任务丢失)
- 支持任务的暂停、恢复、删除、动态添加等精细化管控
- 支持集群部署(避免多实例部署时任务重复执行)
1.3 三个核心概念
1.3.1 Job(任务)
- 定义:你要执行的具体业务逻辑载体,比如发送邮件、数据备份、清理日志。
- 关键类与接口
org.quartz.Job:所有任务类必须实现的接口,只需重写execute(JobExecutionContext context)方法,业务逻辑就写在这里。org.quartz.JobExecutionContext:任务执行时的上下文对象,可获取JobDetail、Trigger、Scheduler等信息,还能通过getMergedJobDataMap()读取任务参数。org.quartz.JobDetail:任务的元数据描述,包含任务名称、分组、持久化配置等,由JobBuilder创建。org.quartz.JobBuilder:构建JobDetail的工具类,通过链式调用设置任务标识、分组、描述等。org.quartz.JobDataMap:任务参数的存储容器,可在创建JobDetail时存入参数,任务执行时通过JobExecutionContext取出。
1.3.2 Trigger(触发器)
- 定义:定义任务的执行时间规则,决定 Job 什么时候执行、执行多少次。
- 关键类与接口
org.quartz.Trigger:所有触发器的顶级接口,核心实现是CronTrigger和SimpleTrigger。org.quartz.CronTrigger:基于 Cron 表达式的触发器,支持复杂时间规则,是最常用的类型。org.quartz.SimpleTrigger:支持固定间隔、固定延迟的简单触发器,适合周期性重复的场景。org.quartz.TriggerBuilder:构建Trigger的工具类,用于设置触发器标识、分组、调度规则。org.quartz.CronScheduleBuilder:构建 Cron 调度规则的工具类,通过cronSchedule(String cronExpression)生成 Cron 表达式对应的调度规则。org.quartz.SimpleScheduleBuilder:构建简单调度规则的工具类,支持withIntervalInSeconds(int interval)等方法。org.quartz.impl.matchers.GroupMatcher:用于按分组匹配任务或触发器,常用于批量操作。
1.3.3 Scheduler(调度器)
- 定义:Quartz 的核心大脑,负责将
Job和Trigger绑定,统一管理和调度所有任务。 - 关键类与接口
org.quartz.Scheduler:调度器的核心接口,提供任务注册、启动、暂停、删除等所有调度操作。org.quartz.SchedulerFactory:用于创建Scheduler实例,常用实现是StdSchedulerFactory。org.quartz.impl.StdSchedulerFactory:默认的调度器工厂,通过读取配置文件(quartz.properties)初始化调度器。org.quartz.JobKey/org.quartz.TriggerKey:任务和触发器的唯一标识,由名称(name)和分组(group)组成,用于精准定位任务或触发器。
1.4 简单工作流程
- 初始化调度器:通过
SchedulerFactory创建Scheduler实例,并启动。 - 构建 JobDetail:用
JobBuilder创建JobDetail,指定任务实现类和唯一标识。 - 构建 Trigger:用
TriggerBuilder+CronScheduleBuilder/SimpleScheduleBuilder创建触发器,设置时间规则和唯一标识。 - 绑定并调度:调用
scheduler.scheduleJob(jobDetail, trigger)将任务与触发器绑定到调度器。 - 触发执行:当触发器的时间规则满足时,调度器会触发
Job的execute()方法执行业务逻辑。
1.5 和Spring Task对比
| 对比维度 | Quartz(独立定时任务框架) | Spring Task(Spring 内置定时功能) |
|---|---|---|
| 核心定位 | 成熟、功能全面的独立开源定时任务调度框架 | Spring 生态内置的轻量级定时工具 |
| 学习成本 | 较高(有专属概念、配置复杂,支持集群 / 持久化) | 极低(注解式开发,配置简单,适合初学者) |
| 功能丰富度 | 非常全面(复杂 Cron、任务暂停 / 恢复 / 动态添加、失败重试、集群、持久化) | 基础够用(简单 Cron、固定间隔,无原生集群 / 持久化 / 动态任务功能) |
| 依赖情况 | 需额外引入 Quartz 相关依赖(与 Spring 整合需额外配置) | 无额外依赖(Spring Core 内置,引入 Spring 即可使用) |
| 与 Spring 生态整合度 | 支持整合,但需手动配置(或使用 Spring Boot 起步依赖简化) | 无缝整合(Spring/Spring Boot 原生支持,注解一键启用) |
| 持久化支持 | 支持(可将任务信息存入数据库,应用重启任务不丢失) | 不支持(任务仅存于内存,应用重启任务失效) |
| 集群部署支持 | 支持(原生解决集群下任务重复执行问题) | 不支持(原生无集群能力,需借助外部工具实现) |
| 适用场景 | 复杂定时需求、生产环境核心任务、集群部署、需要任务精细化管控 | 简单定时需求、非核心任务、单机部署、快速开发验证 |
| 性能表现 | 性能稳定,针对复杂任务做了优化,开销略高 | 轻量高效,开销极小,适合简单任务 |
二、若依定时任务初始化
定位到quartz模块

2.1 初始化总流程
打开SysJobServiceImpl
/**
* 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
*/
@PostConstruct
public void init() throws SchedulerException, TaskException
{
scheduler.clear();
List jobList = jobMapper.selectJobAll();
for (SysJob job : jobList)
{
ScheduleUtils.createScheduleJob(scheduler, job);
}
}
-
@PostConstruct注解,作用是在当前类的依赖注入完成后、项目启动阶段自动执行被标记的方法。 -
清空旧任务首先调用
scheduler.clear()清除调度器中已存在的所有任务,避免项目重启后出现任务重复或残留的问题。 -
加载数据库任务通过
jobMapper.selectJobAll()从数据库中查询所有已配置的定时任务(SysJob)列表,确保任务配置与数据库保持一致。 -
重建所有任务遍历查询到的任务列表,调用工具类
ScheduleUtils.createScheduleJob()将每个任务重新注册到 Quartz 调度器中,完成任务的初始化。
三、任务创建
3.1 总流程
打开ScheduleUtils
/**
* 创建定时任务
*/
public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException
{
Class extends Job> jobClass = getQuartzJobClass(job);
// 构建job信息
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
// 表达式调度构建器
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
.withSchedule(cronScheduleBuilder).build();
// 放入参数,运行时的方法可以获取
jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
// 判断是否存在
if (scheduler.checkExists(getJobKey(jobId, jobGroup)))
{
// 防止创建时存在数据问题 先移除,然后在执行创建操作
scheduler.deleteJob(getJobKey(jobId, jobGroup));
}
// 判断任务是否过期
if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression())))
{
// 执行调度任务
scheduler.scheduleJob(jobDetail, trigger);
}
// 暂停任务
if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
{
scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
}
-
获取任务实现类通过
getQuartzJobClass()获取与当前任务对应的Job实现类,这是具体业务逻辑的载体。 -
构建 JobDetail用
JobBuilder创建JobDetail对象,并为其设置唯一标识(JobKey),这是任务的元数据载体。 -
构建 Cron 触发器用
CronScheduleBuilder基于任务的 Cron 表达式创建调度规则,并通过handleCronScheduleMisfirePolicy()配置任务错过触发后的处理策略。再用TriggerBuilder生成CronTrigger,并设置唯一标识(TriggerKey)。 -
传递任务参数将
SysJob对象存入JobDetail的JobDataMap,让任务执行时可以直接获取配置信息。 -
避免任务重复检查调度器中是否已存在该任务,如果存在则先删除旧任务,避免重复注册。
-
校验并注册任务通过
CronUtils校验 Cron 表达式的有效性,确认有合法的下次执行时间后,才将JobDetail和Trigger绑定到调度器。 -
处理暂停状态如果任务状态为 “暂停”,则在注册后立即暂停该任务,确保任务状态与数据库配置一致。
3.2 Job的实现类级获取
/**
* 抽象quartz调用
*
* @author ruoyi
*/
public abstract class AbstractQuartzJob implements Job
而这个类有两个子类,因为两个类都需要记录日志,有一些公共的方法,所以都继承AbstractQuartzJob
/**
* 定时任务处理(禁止并发执行)
*
* @author ruoyi
*
*/
@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob
/**
* 定时任务处理(允许并发执行)
*
* @author ruoyi
*
*/
public class QuartzJobExecution extends AbstractQuartzJob
我们在ScheduleUtils中通过getQuartzJobClass()获取对应的Job实现类
/**
* 得到quartz任务类
*
* @param sysJob 执行计划
* @return 具体执行任务类
*/
private static Class extends Job> getQuartzJobClass(SysJob sysJob)
{
boolean isConcurrent = "0".equals(sysJob.getConcurrent());
return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
}
3.3 定时任务的策略
这里指的是如果服务器发生了一些故障,没执行的任务在恢复后该进行什么操作
/** 默认 不执行*/
public static final String MISFIRE_DEFAULT = "0";
/** 立即触发执行 */
public static final String MISFIRE_IGNORE_MISFIRES = "1";
/** 触发一次执行 */
public static final String MISFIRE_FIRE_AND_PROCEED = "2";
/**
* 设置定时任务策略
*/
public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
throws TaskException
{
switch (job.getMisfirePolicy())
{
case ScheduleConstants.MISFIRE_DEFAULT:
return cb;
case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
return cb.withMisfireHandlingInstructionIgnoreMisfires();
case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
return cb.withMisfireHandlingInstructionFireAndProceed();
case ScheduleConstants.MISFIRE_DO_NOTHING:
return cb.withMisfireHandlingInstructionDoNothing();
default:
throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
+ "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR);
}
}
四、任务的执行
任务执行会调用job实现的execute方法
打开AbstractQuartzJob
@Override
public void execute(JobExecutionContext context)
{
SysJob sysJob = new SysJob();
BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
try
{
before(context, sysJob);
if (sysJob != null)
{
doExecute(context, sysJob);
}
after(context, sysJob, null);
}
catch (Exception e)
{
log.error("任务执行异常 - :", e);
after(context, sysJob, e);
}
}
- 从任务上下文取出预设参数,封装成业务任务对象
sysJob; - 按「前置处理 → 核心业务执行 → 后置收尾」的流程运行任务;
- 捕获执行异常,保证异常场景下收尾工作也能正常执行,同时记录错误信息。
4.1 取出JobDataMap中的job实例
JobDataMap:Quartz 定时任务框架提供的任务专属数据容器,用于在任务调度者(Scheduler)、任务实例(JobDetail)、任务执行器(Job)之间传递任务相关的业务数据。- 我们在ScheduleUtils中存入
// 放入参数,运行时的方法可以获取 jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job); - 现在在execute中取出
BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
4.2 JobDataMap与threadlocal
注意 JobDataMap并不是threadlocal
| 对比维度 | ThreadLocal(Java 原生) | JobDataMap(Quartz 框架) |
|---|---|---|
| 所属生态 | Java SE/EE 核心 API,无第三方依赖 | Quartz 定时任务框架专属,依赖 Quartz 包 |
| 核心用途 | 为单个线程维护专属变量副本,实现线程内数据共享、线程间数据隔离 | 存储与 Quartz 任务相关的配置 / 业务数据,实现任务上下游(调度者→任务)数据传递 |
| 数据作用域 | 绑定单个线程,线程生命周期内有效(线程销毁则数据回收) | 绑定单个 Quartz 任务(JobDetail/Trigger),任务生命周期内有效(任务未被移除则数据可复用) |
| 线程安全特性 | 本身是线程安全的(仅提供数据隔离能力),但存储的对象本身若为可变对象,仍可能存在线程安全问题(如共享对象引用) | 本身是线程安全的(内部基于 HashMap 封装,加了同步锁),支持任务执行时的数据安全存取 |
| 数据存储形式 | 键为 ThreadLocal 实例本身,值为任意 Object 类型(Java 8+ 支持泛型约束) | 键为 String 类型,值为可序列化的 Object 类型(推荐实现 Serializable,支持任务持久化) |
| 生命周期管理 | 手动管理为主:需调用 remove() 清理数据,否则线程池复用线程时可能导致内存泄漏;线程销毁时,对应的 ThreadLocalMap 会被回收 | 自动管理为主:跟随 JobDetail/Trigger 生命周期,Quartz 框架负责创建、存储和回收;也可手动调用 clear()/remove(String key) 清理 |
| 数据传递能力 | 仅支持同一线程内的方法间传递数据(无法跨线程、跨任务) | 支持 Quartz 框架内任务相关组件间传递(Scheduler→JobDetail→Job→Trigger),可跨线程(任务被不同线程执行时仍能获取数据) |
| 序列化支持 | 不支持序列化(仅存在于当前 JVM 内存的线程中),无法用于跨进程、持久化场景 | 支持序列化(内部封装了序列化逻辑),可配合 Quartz 的任务持久化(如存储到数据库),实现任务重启后数据不丢失 |
| 典型使用场景 | 1. 存储线程专属的用户会话(如 Web 中的用户登录信息)2. 避免多线程共享 SimpleDateFormat 等非线程安全对象3. 跨方法传递上下文数据(无需方法参数透传) | 1. 向 Quartz 任务传递执行参数(如任务执行的目标 ID、配置参数)2. 存储任务执行过程中的临时结果(供后续任务调度使用)3. 任务持久化时,存储需要持久化的任务配置信息 |
| 核心优缺点 | 优点:轻量、无依赖、线程内数据传递高效缺点:易引发内存泄漏(忘记 remove)、无法跨线程、不支持序列化 | 优点:线程安全、支持序列化 / 持久化、适配 Quartz 任务生态、数据传递场景明确缺点:依赖 Quartz 框架、仅适用于任务调度场景、相对重量级 |
4.3 任务执行前后
任务前
/**
* 执行前
*
* @param context 工作执行上下文对象
* @param sysJob 系统计划任务
*/
protected void before(JobExecutionContext context, SysJob sysJob)
{
threadLocal.set(new Date());
}
任务后
/**
* 执行后
*
* @param context 工作执行上下文对象
* @param sysJob 系统计划任务
*/
protected void after(JobExecutionContext context, SysJob sysJob, Exception e)
{
Date startTime = threadLocal.get();
threadLocal.remove();
final SysJobLog sysJobLog = new SysJobLog();
sysJobLog.setJobName(sysJob.getJobName());
sysJobLog.setJobGroup(sysJob.getJobGroup());
sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());
sysJobLog.setStartTime(startTime);
sysJobLog.setStopTime(new Date());
long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime();
sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
if (e != null)
{
sysJobLog.setStatus(Constants.FAIL);
String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
sysJobLog.setExceptionInfo(errorMsg);
}
else
{
sysJobLog.setStatus(Constants.SUCCESS);
}
// 写入数据库当中
SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
}
-
从
threadLocal中取出任务的开始时间,然后立刻清理掉这个ThreadLocal中的数据,避免内存泄漏。 -
构建任务日志对象,计算任务的运行耗时,写入日志的消息字段。
-
根据执行结果设置日志状态
-
最后通过
SpringUtils获取日志服务的实例,把这条日志记录写入数据库。
4.4 任务的执行
/**
* 执行方法,由子类重载
*
* @param context 工作执行上下文对象
* @param sysJob 系统计划任务
* @throws Exception 执行过程中的异常
*/
protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
此方法是抽象方法,由两个子类实现
@Override
protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
{
JobInvokeUtil.invokeMethod(sysJob);
}
两个类重载的方法体都一样,只不过QuartzDisallowConcurrentExecution类上加了@DisallowConcurrentExecution
打开JobInvokeUtil
/**
* 执行方法
*
* @param sysJob 系统任务
*/
public static void invokeMethod(SysJob sysJob) throws Exception
{
String invokeTarget = sysJob.getInvokeTarget();
String beanName = getBeanName(invokeTarget);
String methodName = getMethodName(invokeTarget);
List
这里主要是通过反射来调用对应方法。
五、图解








