之前接触过基于SchedulingConfigurer接口的定时任务,发现其在动态管理方面并不是很方便。本文的内容是利用ThreadPoolTaskScheduler手写调度中心,实现定时任务的动态管理,但它也有一个缺点就是不支持分布式任务调度。
其实Java中实现定时任务的方式很多,现在的Quartz和xxl-job框架在定时任务方面其实已经做的非常优秀了,甚至支持分布式。但学习得有一个过程,所以打算通过一个简单的实践来学习一下ThreadPoolTaskScheduler的使用。
一、数据库表准备
在数据库中创建job_tb表,它的每条数据代表一个定时任务实例。
DROP TABLE IF EXISTS `job_tb`;
CREATE TABLE `job_tb` (
`id` varchar(255) NOT NULL,
`jobName` varchar(255) NOT NULL,
`className` varchar(255) NOT NULL,
`method` varchar(255) NOT NULL,
`cron` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of job_tb
-- ----------------------------
INSERT INTO `job_tb` VALUES ('w0o9pzCQAMJ5PQN5', '开始学习', 'com.hedong.configuration.ScheduledMethodsConfig', 'method1', '0/10 * * * * ?');
代码中对应的Job模型
package com.hedong.model;
import lombok.Data;
/**
* @author: DoNg
* @create: 2020-12-24 20:59
**/
@Data
public class Job {
//任务id
private String id;
//任务名称
private String jobName;
//类名
private String className;
//方法名
private String method;
//cron表达式
private String cron;
}
二、任务调度核心类
这里直接给出了任务调度的核心类,所有的核心操作都在这个类中,包括添加任务、删除任务、修改任务、查询任务、启动及暂停任务。
package com.hedong.service.impl;
import com.hedong.mapper.JobMapper;
import com.hedong.model.Job;
import com.hedong.service.JobService;
import com.hedong.utils.threads.MyJobRunnable;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
/**
* @author: DoNg
* @create: 2020-12-24 21:10
**/
@Service
public class JobServiceImpl implements JobService {
@Autowired
private JobMapper jobMapper;
@Autowired
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
//存放任务调度的容器,此容器中的任务都是正在运行的任务
private ConcurrentHashMap<String, ScheduledFuture> futureMap = new ConcurrentHashMap<>();
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler(){
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(10);
return threadPoolTaskScheduler;
}
/**
* @Description: 增加任务
* @Author: DoNg
*/
@Override
public String add(Job job) {
//设置随机字符串作为id
job.setId(RandomStringUtils.randomAlphanumeric(16));
int result = jobMapper.add(job);
if (result > 0) {
return "增加任务成功!";
} else {
return "增加任务失败!";
}
}
/**
* @Description: 删除任务
* @Author: DoNg
*/
@Override
public String delete(String id) {
//先尝试暂停任务
pause(id);
//从数据库中删除
int result = jobMapper.delete(id);
if (result > 0) {
return "删除成功!";
} else {
return "删除失败!";
}
}
/**
* @Description: 修改任务配置
* @Author: DoNg
*/
@Override
public String edit(Job job) {
int result = jobMapper.edit(job);
if (result > 0) {
//如果该任务正在调度中运行,则重启
ScheduledFuture future = futureMap.get(job.getId());
if (future != null) {
pause(job.getId());
start(job.getId());
}
}
return "修改任务配置成功!";
}
/**
* @Description: 查询任务列表
* @Author: DoNg
*/
@Override
public List<Job> list(String status) {
if ("0".equals(status)) {
//查询数据库中全部任务列表
return jobMapper.list();
} else {
//查询调度集合中正在运行的任务列表
List<Job> jobRunnings = new ArrayList<>();
futureMap.forEach((k, v) -> {
jobRunnings.add(jobMapper.findJobById(k));
});
return jobRunnings;
}
}
/**
* @Description: 启动任务
* @Author: DoNg
*/
@Override
public String start(String id) {
//获取job实体
Job job = jobMapper.findJobById(id);
if (job == null) {
return "任务不存在,无法启动!";
}
if (futureMap.get(job.getId()) != null) {
return "任务已经在运行,无法重复启动!";
}
//回调
ScheduledFuture future = threadPoolTaskScheduler.schedule(new MyJobRunnable(job), new CronTrigger(job.getCron()));
//放入任务调度集合中
futureMap.put(job.getId(), future);
return "任务启动成功!";
}
/**
* @Description: 暂停任务
* @Author: DoNg
*/
@Override
public String pause(String id) {
//检测该任务是否在任务调度集合中
ScheduledFuture future = futureMap.get(id);
if (future == null) {
return "该任务不在运行行列中,无法暂停!";
} else {
//从调度集合中移除
future.cancel(true);
futureMap.remove(id);
return "任务暂停成功!";
}
}
}
回调中的第一个参数需要一个Runnable类型的线程类,这里我们写一个类去实现Runnable,在run方法中通过job的属性得到具体要执行的方法并调用,即可实现任务的执行。
package com.hedong.utils.threads;
import com.hedong.model.Job;
import org.springframework.util.StopWatch;
import java.lang.reflect.Method;
/**
* @author: DoNg
* @create: 2020-12-24 21:27
**/
public class MyJobRunnable implements Runnable{
private Job job;
public MyJobRunnable(Job job){
this.job = job;
}
@Override
public void run() {
//计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Class<?> clazz;
try {
//加载类
clazz = Class.forName(job.getClassName());
//获取方法
Method method = clazz.getMethod(job.getMethod(), Job.class);
//调用方法
method.invoke(clazz.newInstance(), job);
} catch (Exception e) {
e.printStackTrace();
}
stopWatch.stop();
System.out.println("任务:【" + job.getJobName() + "】执行结束,耗时:" + stopWatch.getTotalTimeMillis());
}
}
最后可以创建一个类用于存储各种定时任务执行的方法,以后每次要写一个新的定时任务时,我们只需在该类中添加一个新的方法,把任务要执行的逻辑写在里面,添加定时任务的时候配置好类名、方法、cron等参数就可以了。
package com.hedong.configuration;
import com.hedong.model.Job;
/**
* @Description: 存放每个定时任务需要执行的方法体
* @Param:
* @return:
* @Author: DoNg
*/
public class ScheduledMethodsConfig {
/**
* @Description: 任务一
* @Param:
* @return:
* @Author: DoNg
*/
public void method1(Job job) throws InterruptedException {
System.out.println("任务:【" + job.getJobName() + "】正在执行中。。。。。");
//模拟任务耗时
Thread.sleep(5000);
}
/**
* @Description: 任务二
* @Param:
* @return:
* @Author: DoNg
*/
public void method2(Job job) throws InterruptedException {
System.out.println("任务:【" + job.getJobName() + "】正在执行中。。。。。");
//模拟任务耗时
Thread.sleep(3000);
}
}
最终一个简单的任务调度中心就手写完成了,本文只给出了核心的代码段。项目启动后,我们可以手动开启任意的定时任务,根据需要进行任务的配置修改而不需要重启项目,并且可以随时暂停任务及增添任务内容,这些操作都可以通过前端界面操作完成。