SpringMVC 集成 Quartz 框架 完成动态定时任务
前言
随着需求的多样化,我们在制作一些项目的时候会对一些任务进行定时执行的操作(例如,定时获取实时新闻、天气、疫情趋势…),为了更加灵活的对任务进行操作,这时候我们就需要引用到其他框架的配合,此文章主要介绍 Quartz 框架的使用和对任务的操作做简单介绍。
详情查看:https://www.w3cschool.cn/quartz_doc/quartz_doc-2put2clm.html
一、前期准备
1.设计数据库
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for sys_cron
-- ----------------------------
DROP TABLE IF EXISTS `sys_cron`;
CREATE TABLE `sys_cron` (
`cron_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'id',
`cron_class` varchar(255) DEFAULT NULL COMMENT '类名',
`cron` varchar(255) DEFAULT NULL COMMENT 'cron表达式',
`cron_param` varchar(255) DEFAULT NULL COMMENT '参数',
`cron_remark` varchar(255) DEFAULT NULL COMMENT '描述',
`cron_status` varchar(255) DEFAULT NULL COMMENT '启停标识(1:表示启用)',
PRIMARY KEY (`cron_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.导入依赖
<!-- 导入Quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.2</version>
</dependency>
3.所需实体类
public class SysCron implements Serializable {
/**
* id
*/
private String cronId;
/**
* 类名
*/
private String cronClass;
/**
* cron表达式
*/
private String cron;
/**
* 参数
*/
private String cronParam;
/**
* 描述
*/
private String cronRemark;
/**
* 启停标识(1:表示启用)
*/
private String cronStatus;
public SysCron(){}
public SysCron(String cronId, String cronClass, String cron) {
this.cronId = cronId;
this.cronClass = cronClass;
this.cron = cron;
}
}
4.创建任务工具类
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;
/**
* 2022/6/21 20:45
* 动态定时任务
*
* @author
*/
@Slf4j
@Component
@EnableScheduling
public class TaskInfo {
}
5.测试类
public class T {
public void m1(){
System.out.println("这是一个 3s 测试任务 (*^▽^*)");
}
public void m2(){
System.out.println("这是一个 5s 测试任务 (*^▽^*)==");
}
public void m3(){
System.out.println("这是一个 7s 测试任务 (*^▽^*)=====");
}
}
6.job 实现类
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.lang.reflect.Method;
/**
* 2022/6/23 9:28
*
* @author
*/
public class StartJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
String clazzName = (String) context.getTrigger().getJobDataMap().get("clazz");
String methodName = (String) context.getTrigger().getJobDataMap().get("method");
try {
System.out.print(clazzName + " 任务启动 ... ");
//反射调用方法
Class<?> clazz = Class.forName(clazzName);
Object instance = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getDeclaredMethod(methodName);
method.invoke(instance);
}catch (Exception e) {
e.printStackTrace();
}
}
}
二、使用任务工具类操作任务
很多操作任务的方法都可以在 Scheduler 接口中找到对应的 API,按需自取即可
1.设置触发器方法
/**
* 根据任务信息设置触发器并返回
* @param cron
* @return
*/
private static CronTrigger getCronTrigger(SysCron cron) {
int index = cron.getCronClass().lastIndexOf(".");
return TriggerBuilder.newTrigger()
//设置触发器名称和分组(组名需要与后续的增删改操作对应,不设置会采用默认的分组名 DEFAULT_GROUP)
.withIdentity(cron.getCronId(), "group1")
//设置内容(作业数据)
.usingJobData("clazz", cron.getCronClass().substring(0, index)) //设置需要调用方法所在类的全类名
.usingJobData("method", cron.getCronClass().substring(index + 1)) //设置调用方法的方法名
//设置定时执行规则
//SimpleScheduleBuilder:以 单位秒(s) 为执行规则
//CronScheduleBuilder:以 cron 表达式为执行规则
.withSchedule(CronScheduleBuilder.cronSchedule(cron.getCron())).build();
}
2.添加执行任务
/**
* 执行定时任务
*
* @param cronList 任务集合
*/
public static void timedTask(List<SysCron> cronList) {
try {
//创建一个 scheduler (调度器)
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
//创建一个 Trigger 集合
/*
* 同时执行多个任务时需要将要值行的任务封装成一个 触发器(Trigger) 集合,每个任务对应一个触发器
* 触发器的名称可以作为该触发器在其 分组内 的唯一标识 (可以理解为 一个键)
*/
Set<Trigger> triggerSet = cronList.stream()
.map(TaskInfo::getCronTrigger).collect(Collectors.toSet());
//创建一个job (StartJob 是一个 Job 的实现类,用来执行任务)
JobDetail job = JobBuilder.newJob(StartJob.class)
.withIdentity("myJob", "group1").build();
//注册trigger并启动scheduler
scheduler.scheduleJob(job, triggerSet, true);
//启动任务(start() 方法执行后才会调用 触发器(Trigger))
scheduler.start();
} catch (SchedulerException se) {
se.printStackTrace();
}
}
3.修改任务
/**
* 修改任务
*
* @param cron 任务信息
*/
public static void updateTask(SysCron cron) {
try {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
//根据任务信息从调度器中获取对应触发器的键
TriggerKey triggerKey = new TriggerKey(cron.getCronId(), "group1");
//创建新的触发器
CronTrigger newTrigger = getCronTrigger(cron);
//使用给定键删除触发器,并存储新的给定键
scheduler.rescheduleJob(triggerKey, newTrigger);
} catch (Exception e) {
e.printStackTrace();
}
}
4.结束任务
/**
* 结束任务
*
* @param cronList
*/
public static void endTask(List<SysCron> cronList) {
if (Objects.isNull(cronList) || cronList.size() < 1) return;
try {
//创建一个 scheduler
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
final String GROUP_NAME = "group1";
List<TriggerKey> triggerKeys = cronList.stream()
//创建触发器对象
.map(cron -> new TriggerKey(cron.getCronId(), GROUP_NAME))
//筛选出调度器中已存在的触发器
.filter(triggerKey -> {
try {
return scheduler.checkExists(triggerKey);
} catch (SchedulerException e) {
e.printStackTrace();
return false;
}
}).collect(Collectors.toList());
//调用 API 批量结束任务
boolean del = scheduler.unscheduleJobs(triggerKeys);
//提示信息
if (del) {
System.err.println("任务结束");
} else {
System.err.println("结束任务执行失败!!!");
}
//获取任务分组中的 触发器 集合,若集合中触发器个数为0,表示已无需要执行的任务
Set<TriggerKey> group1 = scheduler.getTriggerKeys(GroupMatcher.groupEquals("group1"));
if (group1.size() <= 0) {
//本轮任务执行完之后,结束所有任务,并清除所有调度数据 - 所有作业、触发器日历
scheduler.shutdown(true);
}
} catch (SchedulerException se) {
System.err.println("结束任务时出现异常 o(╥﹏╥)o");
se.printStackTrace();
}
}
5.测试
//测试方法
public static void main(String[] args) {
//全类名一定要与你要执行的方法相对应
SysCron sysCron1 = new SysCron("1","xxx.xxxx.T.m1", "0/3 * * * * ? ");
SysCron sysCron2 = new SysCron("2","xxx.xxxx.T.m2", "0/5 * * * * ?");
SysCron sysCron3 = new SysCron("3","xxx.xxxx.T.m3", "0/7 * * * * ?");
List<SysCron> list = new ArrayList<>();
list.add(sysCron1);
list.add(sysCron2);
list.add(sysCron3);
timedTask(list);
}
6.设置项目启动时自动执行已开启的任务
@Resource
private ISysCronService cronService;
/**
* 项目启动默认执行的任务
*/
@PostConstruct
public void init() {
//获取任务信息
List<SysCron> cronList = cronService
.list(new LambdaQueryWrapper<SysCron>().eq(SysCron::getCronStatus, "1"));
timedTask(cronList);
}
解决 Quartz 无法调用 Spring 容器中的方法的 空指针 问题
原因:
Job 是在 quartz 的框架中实例化的,service 是在 spring 容器中创建出来的,所以 Job 实现类不受 spring 管理,在 quartz 使用 spring 容器中的方法时导致注入失败
解决:
由于本人找到的方法都未解决此问题,于是我采用了一个比较笨的方法:在 Quartz 调用容器方法前 从Web 应用程序上下文集中初始化 service。
这里是写了一个统一管理 service 的类,让所有控制器继承该类。
public class BaseController {
{
WebApplicationContext applicationContext= ContextLoader.getCurrentWebApplicationContext();
if (Objects.nonNull(applicationContext)) {
this.newsService = applicationContext.getBean(ISysNewsService.class);
this.cronService = applicationContext.getBean(ISysCronService.class);
}
}
@Resource
public ISysNewsService newsService;
@Resource
public ISysCronService cronService;
}
新手上路,不足之处,望多指教
版权声明:本文为m0_51985332原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。