实现目标
1.可以新建自定义调度任务,自定义调度能够配置cron表达式。
2.自定义调度任务可以禁用删除等操作,完成禁用删除操作需要停止自动调度任务。
3.可以查看调度任务每次执行任务情况。
4.自定义调度任务需要可以立即运行。
效果
技术选型
SpringBoot+Mybatis+org.quartz-scheduler
实现
首先自己实现一个调度任务这太麻烦了所以我找到了org.quartz-scheduler 他的功能刚好足够我实现上面的功能,现在我来整理一下为什么org.quartz-scheduler 能实现我的功能?
根据官网的文档我主要知道了 org.quartz-scheduler 主要有几个关键的API
Scheduler 调度器()
Job 由调度程序执行的组件实现的接口
JobDetail 定义Jobs的实例(也就是调度任务)
Trigger 触发器,定义执行给定作业时间表的组件(触发器又分为cron触发器和simple触发器,这两种触发器刚好可以满足我上述需求)
JobBuilder 构建JobDetail实例
TriggerBuilder 构建触发器实例
既然我知道了他几个关键的API能实现我的功能那我们怎么做了?首先我要知道他是怎么创建一个任务调度的并启动的
SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
sched.start();
// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1")
.build();
// Trigger the job to run now, and then every 40 seconds
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
注意:HelloJob.class是继承Job这个接口,所以我们可以理解为job实例执行都需要继承这个job
根据官方给的这个例子我们大概知道了他大概流程是先创建一个Scheduler 然后启动Scheduler,最后加入job实例(也就是我们这次需求的调度任务),而job detail 需要和触发器绑定官方绑定的是一个触发器simpleSchedule() 然后在将job和触发器绑定到调度器中,根据这个简单的调度代码我们知道如果我们需要创建一个job实例我们就需要创建一个公共的SchedulerBuilder类来创建触发器
SchedulerBuilder 主要的功能如下:
创建一个 Cron Job 实例,创建SimpleJob实例,停止调度任务,启动调度任务等功能
如下:
public final class SchedulerBuilder {
private static final Logger logger = LoggerFactory.getLogger(SchedulerBuilder.class);
private static Scheduler schedulerFactory;
static {
schedulerFactory = newFactory();
}
public static Scheduler newFactory() {
try {
return new StdSchedulerFactory().getDefaultScheduler();
} catch (SchedulerException e) {
e.printStackTrace();
}
return null;
}
/**
* 创建一个 Cron Scheduler
*
* @param name 任务名称
* @param groupName job分组名称
* @param cls 执行计划类
* @param cron 表达式
* @param data 参数
* @throws SchedulerException
*/
public static void newCronScheduler(String name, String groupName, Class cls, String cron, String triggerGroupName, String data) throws SchedulerException {
JobBuilder jobBuilder = newJob(cls)
.withIdentity(name, groupName);
//添加数据参数
JobDetail job = addData(jobBuilder, data).build();
// 构建一个触发器,规定触发的规则
Trigger trigger = TriggerBuilder.newTrigger()// 创建一个新的TriggerBuilder来规范一个触发器
.withIdentity(name, triggerGroupName)// 给触发器起一个名字和组名
.startNow()// 立即执行
.withSchedule(CronScheduleBuilder.cronSchedule(cron)) // 触发器的执行时间cors
.build();// 产生触发器
schedulerFactory.scheduleJob(job, trigger);
}
/**
* 创建一个Simple Scheduler
* 用于:特定时刻仅执行一次,或者在特定时刻然后以特定间隔重复执行
*
* @param name 任务名称
* @param groupName job分组名称
* @param cls 执行计划类
* @param triggerGroupName trigger分组名称
* @param data 参数
* @throws SchedulerException
*/
public static void newSimpleScheduler(String name, String groupName, Class cls, String triggerGroupName, String data) throws SchedulerException {
JobBuilder jobBuilder = newJob(cls)
.withIdentity(name, groupName);
//添加数据参数
JobDetail job = addData(jobBuilder, data).build();
// 构建一个触发器,规定触发的规则
Trigger trigger = TriggerBuilder.newTrigger()// 创建一个新的TriggerBuilder来规范一个触发器
.withIdentity(name, triggerGroupName)// 给触发器起一个名字和组名
.startNow()// 立即执行
.withSchedule(simpleSchedule()) // 触发器的执行时间cors
.build();// 产生触发器
schedulerFactory.scheduleJob(job, trigger);
}
/**
* 停止任务调度
*
* @param name 任务名称
* @param groupName 任务分组名称
* @param trigeerGroupName 触发器分组名称
* @throws SchedulerException
*/
public static void stopScheduler(String name, String groupName, String trigeerGroupName) throws SchedulerException {
JobKey jobKey = new JobKey(name, groupName);
schedulerFactory.pauseJob(jobKey); //停止任务
schedulerFactory.unscheduleJob(new TriggerKey(name, trigeerGroupName));// 移除触发器
schedulerFactory.deleteJob(jobKey); //删除job
}
/**
* 检验是否存在任务调度
*
* @param name 任务名称
* @param groupName 任务分组名称
*/
public static boolean checkExistScheduler(String name, String groupName) throws SchedulerException {
JobKey jobKey = new JobKey(name, groupName);
boolean exist = schedulerFactory.checkExists(jobKey);
return exist;
}
/**
* 启动所有任务
*
* @throws SchedulerException
*/
public static void startScheduler() throws SchedulerException {
schedulerFactory.start();
}
/**
* 关闭所有定时任务
*
* @throws SchedulerException
*/
public static void shutdownScheduler() throws SchedulerException {
if (!schedulerFactory.isShutdown()) {
schedulerFactory.shutdown();
}
}
/**
* 添加JobDetails数据
*
* @param builder jobBuilder
* @param text 数据源
*/
private static JobBuilder addData(JobBuilder builder, String text) {
if (StringUtils.isBlank(text)) {
return builder;
}
JSONObject object = JSONUtils.parseObject(text);
for (Map.Entry<String, Object> item : object.entrySet()) {
builder.usingJobData(item.getKey(), item.getValue().toString());
}
return builder;
}
}
现在我们已经可以创建调度任务了那我们怎么去创建调度任务了,我的想法是创建任务是动态的所以我们需要实时加载所以我们需要在前台写一个任务调度页面大致如下:
当我们创建了任务那他怎么去自动自定义调度的任务了所以我创建了两个默认的调度任务一个是系统cron启动调度任务和系统simple 启动调度任务,我只需要在服务启动时先运行这个两个系统调度任务,然后由这两个系统任务去创建其他的自定义调度任务,启动系统调度任务代码如下:
/**
* 运行系统定时任务
*/
public void RunSystemSchedulers() throws SchedulerException {
synchronized (runSystemSchedulersLock) {
SchedulersParamPo schedulersParam = new SchedulersParamPo(SchedulerTypeEnum.System.value());
//获取所有任务
List<SysScheduler> schedulers = schedulerMapper.selectSchedulers(schedulersParam);
for (SysScheduler sysScheduler : schedulers) {
String className = sysScheduler.getClassName();
Class cls = ClassUtils.getClassForName(className);
SchedulerBuilder.newCronScheduler(sysScheduler.getJobName(), SchedulerConstant.CRON_JOB_DEFAULT_GROUP_NAME, cls, sysScheduler.getCron(), SchedulerConstant.CRON_TRIGGER_DEFAULT_GROUP_NAME, sysScheduler.getJobData());
}
SchedulerBuilder.startScheduler();
}
}
系统cron启动调度任务
功能主要是将自定义调度任务启动起来,启动的时候根据状态判断是要启动还是要停止调度任务(停止调度任务会将此调度任务删除),运行和停止调度任务之前先判断是否存在过调度任务,然后将调度任务状态记录下来。代码如下
public Map<String, Integer> RunSchedulers() throws SchedulerException {
synchronized (runSchedulersLock) {
Map<String, Integer> result = new LinkedHashMap<>(8);
SchedulersParamPo schedulersParam = new SchedulersParamPo(SchedulerTypeEnum.Custom.value());
//获取所有任务
List<SysScheduler> schedulers = schedulerMapper.selectSchedulers(schedulersParam);
result.put("all", schedulers.size());
int buildCount, startCount, stopCount, errorCount, notFoundCount;
buildCount = startCount = stopCount = errorCount = notFoundCount = 0;
//循环创建job 但是不启动
for (SysScheduler sysScheduler : schedulers) {
String className = sysScheduler.getClassName();
Class cls = ClassUtils.getClassForName(className);
if (cls != null) {
//创建的时候报错不影响其他自动化任务
try {
String jobName = sysScheduler.getJobName();
boolean existScheduler = SchedulerBuilder.checkExistScheduler(jobName, SchedulerConstant.CRON_JOB_DEFAULT_GROUP_NAME);
//如果节点状态是停止状态,且存在任务则停止任务调度
if (sysScheduler.getStatus() == SchedulerStatusEnum.STOP.value()) {
if (existScheduler) {
SchedulerBuilder.stopScheduler(jobName, SchedulerConstant.CRON_JOB_DEFAULT_GROUP_NAME, SchedulerConstant.CRON_TRIGGER_DEFAULT_GROUP_NAME);
}
stopCount += 1;
} else {
if (!existScheduler) {
SchedulerBuilder.newCronScheduler(jobName, SchedulerConstant.CRON_JOB_DEFAULT_GROUP_NAME, cls, sysScheduler.getCron(), SchedulerConstant.CRON_TRIGGER_DEFAULT_GROUP_NAME, sysScheduler.getJobData());
buildCount += 1;
} else {
startCount += 1;
}
}
} catch (SchedulerException e) {
logger.error(e.getMessage());
errorCount += 1;
stopCount += 1;
}
} else {
logger.error(sysScheduler.getJobName() + " not fount class");
notFoundCount += 1;
stopCount += 1;
}
}
result.put("build", buildCount);
result.put("start", startCount);
result.put("stop", stopCount);
result.put("error", errorCount);
result.put("not found", notFoundCount);
if (buildCount > 0) {
SchedulerBuilder.startScheduler();
}
return result;
}
}
系统simple 启动调度任务
功能主要是将需要临时立即运行的任务运行,运行逻辑是将需要立即运行的任务存入另外一张表和系统调度任务表是区分开的,然后根据调度记录每次运行10条临时运行任务
/**
* 运行系统定时任务
*/
public Integer RunSimpleSchedulers() throws SchedulerException {
synchronized (runSimpleSchedulersLock) {
//获取所有任务
List<SchedulerSimplePageViewPo> schedulers = schedulerSimpleMapper.selectSchedulerSimplePage();
for (SchedulerSimplePageViewPo sysScheduler : schedulers) {
try {
String className = sysScheduler.getClassName();
Class cls = ClassUtils.getClassForName(className);
SchedulerBuilder.newSimpleScheduler(sysScheduler.getJobName(), SchedulerConstant.SIMPLE_JOB_DEFAULT_GROUP_NAME, cls, SchedulerConstant.SIMPLE_TRIGGER_DEFAULT_GROUP_NAME, sysScheduler.getJobData());
updateSchedulerSimpleStatus(sysScheduler.getSchedulerExecuteSimpleId(), SchedulerSimpleStatusEnum.SUCCESS);
} catch (SchedulerException e) {
updateSchedulerSimpleStatus(sysScheduler.getSchedulerExecuteSimpleId(), SchedulerSimpleStatusEnum.ERROR);
}
}
SchedulerBuilder.startScheduler();
return schedulers.size();
}
}
注意:临时调度任务都会生成一个唯一的job name 由调度任务名称+"_"+uuid组成
现在临时运行任务和自定义调度任务都能运行了,我们怎么去记录每个任务的执行时间了,因为所有运行的job实例都需要继承job接口所以我们实现了一个抽象类AbstractDefaultJob类来实现job接口,主要是实现excuse方法,然后提供一个run方法需要继承的类去实现,而我们在excute中实现相当于切面记录,代码如下:
public abstract class AbstractDefaultJob implements Job {
private static final Logger logger = LoggerFactory.getLogger(AbstractDefaultJob.class);
/**
* 执行异常
*/
public void execute(JobExecutionContext context) throws JobExecutionException {
long start = System.currentTimeMillis();
String name = "";
SchedulerSimpleJobTypeEnum schedulerSimpleJobTypeEnum = SchedulerSimpleJobTypeEnum.CRON;
try {
JobKey key = context.getTrigger().getJobKey();
name = key.getName();
if (SchedulerConstant.SIMPLE_JOB_DEFAULT_GROUP_NAME.equals(key.getGroup())) {
schedulerSimpleJobTypeEnum = SchedulerSimpleJobTypeEnum.SIMPLE;
}
JobDataMap dataMap = context.getMergedJobDataMap();
SchedulerExecuteResult result = run(dataMap);
handleExecute(name, start, true, schedulerSimpleJobTypeEnum, result.getMessage());
} catch (Exception e) {
logger.error(e.getMessage());
handleExecute(name, start, false, schedulerSimpleJobTypeEnum, "");
}
}
/**
* 执行处理
*
* @param schedulerName 任务名称
* @param start 开始时间
* @param succeess 是否成功
* @param remark 备注
*/
public void handleExecute(String schedulerName, long start, boolean succeess, SchedulerSimpleJobTypeEnum jobTypeEnum, String remark) {
long end = System.currentTimeMillis();
long executeInterval = end - start;
SchedulerExecuteService schedulerExecuteService = SpringContext.getBean("schedulerExecuteServiceImpl");
schedulerExecuteService.insertSchedulerExecute(schedulerName, start, end, executeInterval, succeess, jobTypeEnum, remark);
}
public <T> T getBean(Class cls) {
String name = cls.getSimpleName();
name = VariableNameConvert.firstLetterLower(name);
return SpringContext.getBean(name);
}
public abstract SchedulerExecuteResult run(JobDataMap dataMap) throws SchedulerException;
}
码云:youyue: 有岳,后台管理系统,springboot+mybatis+redis
https://gitee.com/ausions/youyue
文档地址:
http://www.quartz-scheduler.org/documentation/quartz-2.2.2/tutorials/