官方文档:https://www.quartz-scheduler.org/documentation/
一、Quartz 简介
Quartz 是一个功能强大且灵活的任务调度框架,由 Java 编写,用于在应用程序中调度任务(如定时任务)。它支持简单和复杂的调度需求,广泛应用于各种 Java 项目中,包括 Web 应用和企业应用。
二、Quartz 的核心概念
-
Scheduler(调度器)
- Quartz 的核心组件,用于管理和执行任务。它控制任务的添加、启动、暂停和删除。
- 关键方法:
start()
:启动调度器。shutdown()
:关闭调度器。scheduleJob()
:调度一个任务。pauseJob()
/resumeJob()
:暂停或恢复任务。
-
Job(任务)
- 一个实现了
Job
接口的类,封装了任务的具体逻辑。 - Job接口:
public interface Job { void execute(JobExecutionContext context) throws JobExecutionException; }
- 关键点:
- 每个任务的执行逻辑写在
execute()
方法中。 - 任务执行时,Quartz 会创建一个新的
Job
实例。
- 每个任务的执行逻辑写在
- 一个实现了
-
JobDetail(任务详情)
- 描述一个任务的具体信息,包括任务的名称、分组、执行类等。
- 创建方式:
JobDetail jobDetail = JobBuilder.newJob(MyJob.class) .withIdentity("myJob", "group1") .build();
-
Trigger(触发器)
- 定义任务的调度规则(如执行时间、频率等)。
- 两种主要触发器:
- SimpleTrigger:简单触发器,基于固定时间间隔。
- CronTrigger:复杂触发器,使用 Cron 表达式定义调度规则。
- 创建方式:
Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .startNow() .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?")) .build();
-
JobStore(任务存储)
- Quartz 用于存储任务和调度信息的机制。支持两种存储方式:
- RAMJobStore:将任务存储在内存中(默认)。
- JDBCJobStore:将任务存储在数据库中,适合需要持久化任务的场景。
- Quartz 用于存储任务和调度信息的机制。支持两种存储方式:
三、Quartz在若依框架中的工作流程
1. 管理台添加自定义定时任务
在定时任务部分新增两个自定义方法,一个是无参的,一个是有参的。
2. 添加对应Task类
管理平台创建的任务是需要和后端的task类对应的,所以需要添加ZyTask任务类,在具体的任务类中,可以写定时任务具体的内容。代码如下:
package com.ruoyi.quartz.task;
import org.springframework.stereotype.Component;
@Component("zyTask")
public class ZyTask {
public void noParamTest() {
System.out.println("执行无参test方法");
}
public void paramTest(String param) {
System.out.println("执行有参test方法,参数为:"+ param);
}
}
3. 任务持久化到数据库
在管理台创建任务后,任务会被保存在sys_job表中。
invoke_target
:任务调用的目标(Spring Bean 名称和方法)。
misfire_policy
:错过执行时的处理策略。
concurrent
:是否允许并发,1不允许,0允许。
4. 加载数据库任务到Quartz调度器
加载sys_job中的任务到Quartz调度器中,具体代码在SysJobServiceImpl中的 init() 方法中。
@PostConstruct
public void init() throws SchedulerException, TaskException
{
//清除调度器中的所有任务,确保不会出现重复任务。
scheduler.clear();
List<SysJob> jobList = jobMapper.selectJobAll();
//将每个任务添加到调度器
for (SysJob job : jobList)
{
ScheduleUtils.createScheduleJob(scheduler, job);
}
}
@PostConstruct
是 Java 中的一种注解,用于标注在一个方法上,表示在 依赖注入完成后(即对象创建并完成依赖注入后)自动调用该方法进行初始化操作。
具体的创建逻辑在ScheduleUtils中的 createScheduleJob(Scheduler scheduler, SysJob job) 方法中。代码如下:
/**
* 创建定时任务
*/
public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException
{
//1.确定任务类型,根据job对象动态获取对应的Job类 根据concurrent属性获取
// 若依中按照是否允许并发定义了两类Job,分别是 QuartzJobExecution 和 QuartzDisallowConcurrentExecution
Class<? extends Job> jobClass = getQuartzJobClass(job);
//2. 构建job信息 获取jobId 和 group
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
//3. 创建JobDetail对象
//.newJob(jobClass) 是 JobBuilder 的一个静态方法,用来指定该 JobDetail 对应的具体 Job 类。
//.withIdentity() 方法用于为任务设置一个唯一标识符。任务的身份通常由 jobId 和 jobGroup 来唯一确定。
//.build() 方法是 JobBuilder 的最后一步,它构建并返回一个 JobDetail 对象。
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
//4. 表达式调度构建器
//cronSchedule(String cronExpression) 是一个静态方法,它接受一个 Cron 表达式(字符串)并返回一个 CronScheduleBuilder 对象。
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
//5. 处理错过触发策略
//Quartz 的错过触发(Misfire)指的是当一个任务错过了预定的触发时间(可能因为系统停机、任务阻塞等原因),Quartz 需要决定如何处理这种情况。
//handleCronScheduleMisfirePolicy方法根据创建任务时的错过触发策略来执行
cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
//6. 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
.withSchedule(cronScheduleBuilder).build();
//7. 添加参数,运行时的方法可以获取
//将任务对象 job 放入 Quartz 的 JobDataMap 中,为任务执行时提供参数支持。
jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
//8. 判断任务是否存在 若存在,先删除
if (scheduler.checkExists(getJobKey(jobId, jobGroup)))
{
// 防止创建时存在数据问题 先移除,然后在执行创建操作
scheduler.deleteJob(getJobKey(jobId, jobGroup));
}
//9. 判断任务是否过期
if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression())))
{
//10. 执行调度任务 将一个任务(jobDetail)和触发器(trigger)绑定到 Quartz 的调度器(scheduler)中,并启动任务调度。
scheduler.scheduleJob(jobDetail, trigger);
}
// 暂停任务
if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
{
scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
}
JobDetail 是 Quartz 中表示一个具体任务(Job)的详细信息的对象,包含任务的类、任务的标识等。
通过上面代码,最终将jobDetail和trigger绑定到了调度器中。
5. 按定时规则启动任务
5.1 Quartz 调度器触发任务
- Quartz 调度器在运行时,根据任务注册时定义的触发器(
CronTrigger
)监控当前时间。 - 当触发器条件与当前时间匹配时,Quartz 调用任务的执行逻辑。也就是说,会执行Job对应任务类的execute(JobExecutionContext context)方法。
传入 JobExecutionContext
对象,该对象包含任务运行时的上下文信息,包括:
- 任务的元信息(如任务名称、分组等)。
- 数据参数(通过
JobDataMap
传递)。 - 当前触发器的状态。
5.2 动态调用任务逻辑
- Quartz 根据Job对应的任务类QuartzJobExecution
或者
QuartzDisallowConcurrentExecution。去动态调用的任务执行类。我们创建的任务是允许并发执行的,因此调用的任务类是QuartzJobExecution,然后执行该类中的execute()方法。 - QuartzJobExecution
和
QuartzDisallowConcurrentExecution这两个类是若依中定义的,他们继承自AbstractQuartzJob抽象类,AbstractQuartzJob类实现了Job接口。
在它们的父类AbstractQuartzJob类中重写了execute()方法,定义任务执行的通用逻辑。
//定义任务执行的通用逻辑 Quartz 定时任务的入口方法,负责执行任务。
@Override
public void execute(JobExecutionContext context) throws JobExecutionException
{
SysJob sysJob = new SysJob();
//从 JobExecutionContext 的 MergedJobDataMap 中提取任务参数,并将其转换为 SysJob 对象。
//从 Quartz 提供的 JobExecutionContext 中获取任务执行时所携带的参数数据
Object o = context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES);
//Bean属性复制工具方法。
BeanUtils.copyBeanProp(sysJob, o);
try
{
//执行前:调用 before 方法,记录任务开始时间。
before(context, sysJob);
if (sysJob != null)
{
//实际执行:调用抽象方法 doExecute,由子类实现具体的任务逻辑。
doExecute(context, sysJob);
}
//执行后:调用 after 方法,记录任务执行的状态、耗时、并保存日志。
after(context, sysJob, null);
}
catch (Exception e)
{
log.error("任务执行异常 - :", e);
after(context, sysJob, e);
}
}
- QuartzJobExecution 在执行时,会先执行execute()方法,在execute()方法中,又会去执行doexecute()方法。doexecute()中会通过
JobInvokeUtil
动态调用用户配置的目标方法。
//该类没有 @DisallowConcurrentExecution 注解,表示默认允许并发执行。
//如果任务被频繁触发,Quartz 会同时运行多个任务实例,彼此之间独立运行。
public class QuartzJobExecution extends AbstractQuartzJob
{
@Override
protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
{
//即通过 JobInvokeUtil.invokeMethod(sysJob) 调用任务的具体方法。
JobInvokeUtil.invokeMethod(sysJob);
}
}
5.3 JobInvokeUtil
动态调用机制
JobInvokeUtil
是任务动态调用的核心,它通过反射机制解析用户配置的 invokeTarget
并执行目标方法。invokeMethod()方法代码如下:
//动态调用任务目标方法,根据任务的 invokeTarget 执行相应逻辑。
public static void invokeMethod(SysJob sysJob) throws Exception
{
String invokeTarget = sysJob.getInvokeTarget();
String beanName = getBeanName(invokeTarget);
String methodName = getMethodName(invokeTarget);
List<Object[]> methodParams = getMethodParams(invokeTarget);
//判断是bean还是普通类
//如果 beanName 不是一个合法的类名,认为它是 Spring 容器中的 Bean。
if (!isValidClassName(beanName))
{
//调用 SpringUtils.getBean(beanName) 从 Spring 容器中动态获取 Bean。
Object bean = SpringUtils.getBean(beanName);
//动态调用目标方法,核心逻辑在 invokeMethod(重载方法)中完成。
invokeMethod(bean, methodName, methodParams);
}
else
// 如果是合法的类名,则通过反射直接加载。
{
//通过反射机制加载目标类,并实例化对象。
//这里传入的 beanName 是一个完整的类名
Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();
//动态调用目标方法,核心逻辑在 invokeMethod(重载方法)中完成。
invokeMethod(bean, methodName, methodParams);
}
}
通过 invokeMethod(Object bean, String methodName, List<Object[]> methodParams)动态调用目标方法,这个方法是 Java 反射机制 的一个典型使用,它动态调用指定对象上的方法,并可以支持传递参数。
参数
Object bean
:
- 被调用方法所属的对象实例。
- 如果调用静态方法,这个对象可以是
null
。
String methodName
:
- 需要调用的方法的名字。
List<Object[]> methodParams
:
- 方法的参数列表。
- 每个元素是一个数组,数组的第一个值是参数类型,第二个值是参数值。
方法内容如下:
//执行目标对象 bean 上的方法。
private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
//判断是否有参数
if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0)
{
//通过 getMethod 动态获取方法对象。参数类型:通过 getMethodParamsType(methodParams) 动态生成。
Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));
//调用 invoke 执行该方法:
//参数值:通过 getMethodParamsValue(methodParams) 动态生成。
method.invoke(bean, getMethodParamsValue(methodParams));
}
else
{
Method method = bean.getClass().getMethod(methodName);
method.invoke(bean);
}
}
invoke
方法是 Java 反射机制 提供的功能,用于在运行时调用指定对象的某个方法。这样最终就执行了ZyTask类中的有参和无参的test方法。