layout: post
title: 【SpringBoot整合系列】SpringBoot整合定时任务【Quartz】
date: 24-04-01 21:51:09 修改
author: 'zhangtao'
header-img: 'img/post-bg-2015.jpg'
catalog: false
tags:
- Java
- SpringBoot
- spring boot
- 后端
- java
目录
- 关于定时任务
- java 定时任务调度的实现方式
- Quartz
- Quartz框架中MisFire的理解
- springboot整合Quartz
-
- 1.引入依赖
- 2.ScheduleConstants常量类
- 3.SysJob实体类
- 4.SpringUtils工具类
- 5.核心配置类
- 测试一下吧
{#_1}关于定时任务
{#1_126}1.引入依赖
SpringBoot版本:2.7.16
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--quartz依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>3.0.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!--常用工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
{#2ScheduleConstants_169}2.ScheduleConstants常量类
package com.zjl.constant;
/**
* @author: zjl
* @datetime: 2024/4/1
* @desc: Misfire定义的五种常量
*/
public class ScheduleConstants {
//参数
public static final String TASK_PARAMS = "PARAMS";
//默认
public static final String MISFIRE_DEFAULT = "0";
//忽略错过的执行,按新 Cron 继续运行。
public static final String MISFIRE_IGNORE_MISFIRES = "1";
//补偿错过的执行,然后继续运行。
public static final String MISFIRE_FIRE_AND_PROCEED = "2";
//错过的执行不做任何处理,等待下一次 Cron 触发。
public static final String MISFIRE_DO_NOTHING = "3";
}
{#3SysJob_192}3.SysJob实体类
这边我只列举了一些必须会用到的字段,其他字段可以根据实际需求来进行扩展。
package com.zjl.domain;
import lombok.Data;
import com.zjl.constant.ScheduleConstants;
/**
* @author: zjl
* @datetime: 2024/4/1
* @desc:
*/
@Data
public class SysJob {
//定时任务ID
private String jobId;
//定时任务名称
private String jobName;
//定时任务组
private String jobGroup;
//目标bean名
private String beanTarget;
//目标bean的方法名
private String beanMethodTarget;
//执行表达式
private String cronExpression;
//是否并发 0:代表允许并发执行 1:代表不允许并发执行
private String concurrent;
//计划策略
private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT;
}
{#4SpringUtils_226}4.SpringUtils工具类
- 实现BeanFactoryPostProcessor接口,重写postProcessBeanFactory()方法,在所有Bean初始化之前就会被执行,因此可以在此处获取到最主要的BeanFactory。
- 通过beanFactory对象提供的API实现获取 Bean功能
package com.zjl.utils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
/**
* @author: zjl
* @datetime: 2024/4/1
* @desc:
*/
@Component
public final class SpringUtils implements BeanFactoryPostProcessor {
//Spring应用上下文环境
private static ConfigurableListableBeanFactory beanFactory;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
SpringUtils.beanFactory = beanFactory;
}
/**
* 获取对象
* @param name
* @return Object 一个以所给名字注册的bean的实例
* @throws BeansException
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) throws BeansException {
return (T) beanFactory.getBean(name);
}
}
{#5_263}5.核心配置类
{#_264}先简单梳理一些每个实体类的大致作用,以及它们之间的联系和执行逻辑。
| 实体类类 | 作用 |
|:---|
| JobExecuteUtils | 任务执行的工具类,通过反射调用目标Bean的方法。 |
| AbstractQuartzJob | 抽象QuartzJob类,实现了execute方法,在执行任务前后做了trycatch。 |
| QuartzJobExecution | 任务执行类,继承AbstractQuartzJob,在doExecute方法中调用JobExecuteUtils、executeMethod执行目标方法。 |
| QuartzDisallowConcurrentExecution | 禁止并发执行的任务执行类,也继承自AbstractQuartzJob,执行逻辑同QuartzJobExecution。 |
| ScheduleUtils | 任务调度的工具类,根据SysJob创建JobDetail和CronTrigger,调度任务。 |
| SysJobService | 任务管理业务接口,定义了初始化、增加、修改、删除任务等方法。 |
| SysJobServiceImpl | 任务管理业务接口实现类。 |
{#_275}执行逻辑:
- 初始化时,SysJobServiceImpl从数据库加载定时任务,调度到调度器。
- 调度器根据CronTrigger触发时间启动JobDetail。
- JobDetail执行时调用对应Job类的execute方法。
- AbstractQuartzJob实现了execute方法,在其中调用子类doExecute。
- 子类QuartzJobExecution的doExecute通过JobExecuteUtils反射执行目标方法。
- 这样就完成了定时任务的执行。
{#51_JobExecuteUtils_282}5.1 JobExecuteUtils执行定时任务方法类
- 我们通过jobExecutionContext.getMergedJobDataMap().get(TASK_PARAMS);这个方法去获取咱们的SysJob参数信息。
- 默认获取的是Object类型,所以我们需要通过BeanUtils.copyProperties(param,sysJob);这个方法进行实体类的转换和拷贝,注意参数顺序别写反了。
- 然后我们通过SpringUtils封装好的getBean方法,通过拷贝好的实体类中的参数去动态进行获取被Spring托管的Bean。
- 获取到Bean之后通过bean.getClass().getMethod(sysJob.getBeanMethodTarget());这个代码去获取Bean中需要执行的方法。
- 最后通过method.invoke(bean);进行执行Bean中对应的方法内容。
package com.zjl.utils;
import com.zjl.domain.SysJob;
import org.quartz.JobExecutionContext;
import org.springframework.beans.BeanUtils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import static com.zjl.constant.ScheduleConstants.TASK_PARAMS;
/**
* @author: zjl
* @datetime: 2024/4/1
* @desc:
*/
public class JobExecuteUtils {
/**
* 获取bean并执行对应的方法
* @param jobExecutionContext
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
public static void executeMethod(JobExecutionContext jobExecutionContext) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Object param = jobExecutionContext.getMergedJobDataMap().get(TASK_PARAMS);
SysJob sysJob = new SysJob();
BeanUtils.copyProperties(param,sysJob);
Object bean = SpringUtils.getBean(sysJob.getBeanTarget());
Method method = bean.getClass().getMethod(sysJob.getBeanMethodTarget());
method.invoke(bean);
}
}
{#52_AbstractQuartzJob_324}5.2 AbstractQuartzJob抽象类
定义了一个抽象类并实现了Job接口,同时重写execute方法。这个方法是定时任务执行的核心方法,其次在这个抽象类当中我们还定义了一个doExecute方法,主要就是为了写我们定时任务的执行逻辑。
package com.zjl.domain;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.lang.reflect.InvocationTargetException;
/**
* @author: zjl
* @datetime: 2024/4/1
* @desc: 抽象类 Quartz调用
*/
public abstract class AbstractQuartzJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
try {
doExecute(jobExecutionContext);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected abstract void doExecute(JobExecutionContext jobExecutionContext) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException;
}
{#53_QuartzJobExecutionQuartzDisallowConcurrentExecution_353}5.3 QuartzJobExecution/QuartzDisallowConcurrentExecution开启/禁止并发执行任务类
继承上述的抽象类,通过@DisallowConcurrentExecution这个注解来禁止咱们的定时任务的并发执行,如果不加则是默认允许并发执行,最后我们重写了doExecute方法去执行我们定时任务的处理逻辑。
例如有个定时任务每隔5秒执行一次,不过这个任务做完可能需要20秒。
{#_356}允许并发执行:
在这处理的20秒钟,肯定需要处理下一个任务了,如果设置了允许并发执行,不会等到上一个任务执行完毕才会执行下一个任务,只要每隔5s就会执行下一个任务。
package com.zjl.domain;
import org.quartz.JobExecutionContext;
import java.lang.reflect.InvocationTargetException;
import static com.zjl.utils.JobExecuteUtils.executeMethod;
/**
* @author: zjl
* @datetime: 2024/4/1
* @desc:
*/
public class QuartzJobExecution extends AbstractQuartzJob {
@Override
protected void doExecute(JobExecutionContext jobExecutionContext) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
executeMethod(jobExecutionContext);
}
}
允许并发执行体现在任务一中
{#DisallowConcurrentExecution_379}不允许并发执行(@DisallowConcurrentExecution):
在这处理的20秒钟,肯定需要处理下一个任务了,如果设置了不允许并发执行,会必须等到上一个任务执行完毕才会执行下一个任务
package com.zjl.domain;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import java.lang.reflect.InvocationTargetException;
import static com.zjl.utils.JobExecuteUtils.executeMethod;
/**
* @author: zjl
* @datetime: 2024/4/1
* @desc: 定时任务处理(禁止并发执行)
*/
@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob
{
@Override
protected void doExecute(JobExecutionContext jobExecutionContext) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
executeMethod(jobExecutionContext);
}
}
不允许并发执行体现在任务二中
{#54_ScheduleUtils_406}5.4 ScheduleUtils定时任务工具类
一共封装三个方法
{#getQuartzJobClass_408}getQuartzJobClass
这个方法是主要通过SysJob实体类当中会通过动态参数获取任务类并决定当前任务是否并发执行(0代表并发执行,1代表禁止并发执行)
{#handleCronScheduleMisfirePolicy_410}handleCronScheduleMisfirePolicy
这个方法主要设置待执行任务的执行策略。
{#createScheduleJob_412}createScheduleJob
这个方法是最主要的方法,它的逻辑总共7个步骤如下:
- 得到任务类(是否并发执行)
- 构建job信息
- 构件表达式调度构建器
- 配置执行策略
- 按新的cronExpression表达式构建一个新的trigger
- 放入参数,运行时的方法可以获取
- 执行调度任务
{#_421}代码
package com.zjl.utils;
import com.zjl.constant.ScheduleConstants;
import com.zjl.domain.QuartzDisallowConcurrentExecution;
import com.zjl.domain.QuartzJobExecution;
import com.zjl.domain.SysJob;
import org.quartz.*;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationTargetException;
import static com.zjl.constant.ScheduleConstants.TASK_PARAMS;
/**
* @author: zjl
* @datetime: 2024/4/1
* @desc: 定时任务工具类
*/
@Component
public class ScheduleUtils {
/**
* 得到quartz任务类
* @param sysJob 执行计划
* @return 具体执行任务类
*/
private static Class<? extends Job> getQuartzJobClass(SysJob sysJob) {
boolean isConcurrent = "0".equals(sysJob.getConcurrent());
return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
}
/**
* 创建定时任务
*/
public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//获取任务类型
Class<? extends Job> jobClass = getQuartzJobClass(job);
// 构建job信息
String cornExpression = job.getCronExpression();
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(job.getJobId(),job.getJobGroup()).build();
// 表达式调度构建器
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cornExpression);
//配置执行策略
cronScheduleBuilder = handleCronScheduleMisfirePolicy(job,cronScheduleBuilder);
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(job.getJobId(),job.getJobGroup())
.withSchedule(cronScheduleBuilder).build();
// 放入参数,运行时的方法可以获取
jobDetail.getJobDataMap().put(TASK_PARAMS, job);
// 执行调度任务
scheduler.scheduleJob(jobDetail, trigger);
}
/**
* 设置定时任务策略
*/
public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb) {
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 RuntimeException("策略异常");
}
}
}
{#55_SysJobService_492}5.5 SysJobService定时任务调度信息接口
撰写定时任务调度的接口,主要分为初始化所有任务、新增任务,立即执行任务、更新任务、暂停任务,恢复任务和删除任务,代码如下,这个不多说,主要详细讲解一下它的实现类。
package com.zjl.service;
import org.quartz.SchedulerException;
import java.lang.reflect.InvocationTargetException;
import com.zjl.domain.SysJob;
/**
* @author: zjl
* @datetime: 2024/4/1
* @desc: 定时任务调度信息
*/
public interface SysJobService {
/**
* 项目启动时,初始化定时器
*/
void init() throws SchedulerException, NoSuchMethodException, InvocationTargetException, IllegalAccessException;
/**
* 新增任务
* @param job 调度信息
* @return 结果
*/
public int insertJob(SysJob job) throws SchedulerException, InvocationTargetException, NoSuchMethodException, IllegalAccessException;
/**
* 立即运行任务
* @param job 调度信息
* @return 结果
*/
public void run(SysJob job) throws SchedulerException;
/**
* 更新任务
* @param job 调度信息
* @return 结果
*/
public int updateJob(SysJob job) throws SchedulerException, InvocationTargetException, NoSuchMethodException, IllegalAccessException;
/**
* 暂停任务
* @param job 调度信息
* @return 结果
*/
public int pauseJob(SysJob job) throws SchedulerException;
/**
* 恢复任务
* @param job 调度信息
* @return 结果
*/
public int resumeJob(SysJob job) throws SchedulerException;
/**
* 删除任务后,所对应的trigger也将被删除
* @param job 调度信息
* @return 结果
*/
public int deleteJob(SysJob job) throws SchedulerException;
}
{#56_SysJobServiceImpl_548}5.6 SysJobServiceImpl定时任务调度信息接口实现类
- 首先initTaskList这个方法我这边做了偷懒,直接写了一个List集合在项目中,正确的配置应该是从数据库去查询一共有哪些定时任务需要程序去执行的,以及对应的配置信息应该如何去执行。
- 其次在init方法中,@PostConstruct这个注解会在项目启动的时候帮我们进行初始化,其次我调用了clear方法进行先清空定时任务,使用循环获取到需要所有定时任务集合,通过createScheduleJob方法去创建。
package com.zjl.service;
import com.zjl.utils.ScheduleUtils;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import com.zjl.domain.SysJob;
import static com.zjl.constant.ScheduleConstants.MISFIRE_IGNORE_MISFIRES;
/**
* @author: zjl
* @datetime: 2024/4/1
* @desc:
*/
@Service
public class SysJobServiceImpl implements SysJobService {
@Resource
private Scheduler scheduler;
/**
* 模拟从数据库获取数据
* @return
*/
public List<SysJob> initTaskList(){
List<SysJob> list = new ArrayList<>();
SysJob job = new SysJob();
job.setJobId(UUID.randomUUID().toString());
job.setJobGroup("system");
job.setConcurrent("0");
job.setCronExpression("0/5 * * * * ?");
job.setBeanTarget("task1");
job.setBeanMethodTarget("handle");
list.add(job);
job = new SysJob();
job.setJobId(UUID.randomUUID().toString());
job.setJobGroup("system");
job.setConcurrent("1");
job.setCronExpression("0/50 * * * * ?");
job.setBeanTarget("task2");
job.setBeanMethodTarget("handle");
job.setMisfirePolicy(MISFIRE_IGNORE_MISFIRES);
list.add(job);
return list;
}
/**
* 初始化定时任务
* @throws SchedulerException
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
@PostConstruct
@Override
public void init() throws SchedulerException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
scheduler.clear();
List<SysJob> list = initTaskList();
for (int i = 0 ; i < list.size() ; i ++){
SysJob job = list.get(i);
ScheduleUtils.createScheduleJob(scheduler, job);
}
}
@Override
public int insertJob(SysJob job) throws SchedulerException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
ScheduleUtils.createScheduleJob(scheduler, job);
return 1;
}
@Override
public void run(SysJob job) throws SchedulerException {
scheduler.triggerJob(JobKey.jobKey(job.getJobId(),job.getJobGroup()));
}
@Override
public int updateJob(SysJob job) throws SchedulerException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
// 判断是否存在
JobKey jobKey = JobKey.jobKey(job.getJobId(),job.getJobGroup());
if (scheduler.checkExists(jobKey)) {
scheduler.deleteJob(jobKey);
}
ScheduleUtils.createScheduleJob(scheduler, job);
return 1;
}
@Override
public int pauseJob(SysJob job) throws SchedulerException {
scheduler.pauseJob(JobKey.jobKey(job.getJobId(),job.getJobGroup()));
return 1;
}
@Override
public int resumeJob(SysJob job) throws SchedulerException {
scheduler.resumeJob(JobKey.jobKey(job.getJobId(),job.getJobGroup()));
return 1;
}
@Override
public int deleteJob(SysJob job) throws SchedulerException {
scheduler.deleteJob(JobKey.jobKey(job.getJobId(),job.getJobGroup()));
return 1;
}
}
{#_665}测试一下吧
{#_666}任务一:
package com.zjl.task;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component("task1")
public class Task1 {
public void handle() throws InterruptedException {
String dateStr = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date());
System.out.println("task1"+ dateStr +"开始");
Thread.sleep(10000);
System.out.println("task1"+ dateStr +"结束");
}
}
{#_684}任务二:
package com.zjl.task;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component("task2")
public class Task2 {
public void handle(){
String dateStr = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date());
System.out.println("task2"+ dateStr +"开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("task2"+ dateStr +"结束");
}
}
可以分别采用并发或者非并发方式测试,反正参数都可以动态进行配置
原文链接: https://zhoujl.blog.csdn.net//article/details/137227630