之前接触过基于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', '开始学习', '', 'method1', '0/10 * * * * ?');
代码中对应的Job模型
package ;
import ;
/**
* @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 ;
import ;
import ;
import ;
import ;
import .;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* @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();
(10);
return threadPoolTaskScheduler;
}
/**
* @Description: 增加任务
* @Author: DoNg
*/
@Override
public String add(Job job) {
//设置随机字符串作为id
((16));
int result = (job);
if (result > 0) {
return "增加任务成功!";
} else {
return "增加任务失败!";
}
}
/**
* @Description: 删除任务
* @Author: DoNg
*/
@Override
public String delete(String id) {
//先尝试暂停任务
pause(id);
//从数据库中删除
int result = (id);
if (result > 0) {
return "删除成功!";
} else {
return "删除失败!";
}
}
/**
* @Description: 修改任务配置
* @Author: DoNg
*/
@Override
public String edit(Job job) {
int result = (job);
if (result > 0) {
//如果该任务正在调度中运行,则重启
ScheduledFuture future = (());
if (future != null) {
pause(());
start(());
}
}
return "修改任务配置成功!";
}
/**
* @Description: 查询任务列表
* @Author: DoNg
*/
@Override
public List<Job> list(String status) {
if ("0".equals(status)) {
//查询数据库中全部任务列表
return ();
} else {
//查询调度集合中正在运行的任务列表
List<Job> jobRunnings = new ArrayList<>();
((k, v) -> {
((k));
});
return jobRunnings;
}
}
/**
* @Description: 启动任务
* @Author: DoNg
*/
@Override
public String start(String id) {
//获取job实体
Job job = (id);
if (job == null) {
return "任务不存在,无法启动!";
}
if ((()) != null) {
return "任务已经在运行,无法重复启动!";
}
//回调
ScheduledFuture future = (new MyJobRunnable(job), new CronTrigger(()));
//放入任务调度集合中
((), future);
return "任务启动成功!";
}
/**
* @Description: 暂停任务
* @Author: DoNg
*/
@Override
public String pause(String id) {
//检测该任务是否在任务调度集合中
ScheduledFuture future = (id);
if (future == null) {
return "该任务不在运行行列中,无法暂停!";
} else {
//从调度集合中移除
(true);
(id);
return "任务暂停成功!";
}
}
}
回调中的第一个参数需要一个Runnable类型的线程类,这里我们写一个类去实现Runnable,在run方法中通过job的属性得到具体要执行的方法并调用,即可实现任务的执行。
package ;
import ;
import ;
import ;
/**
* @author: DoNg
* @create: 2020-12-24 21:27
**/
public class MyJobRunnable implements Runnable{
private Job job;
public MyJobRunnable(Job job){
= job;
}
@Override
public void run() {
//计时器
StopWatch stopWatch = new StopWatch();
();
Class<?> clazz;
try {
//加载类
clazz = (());
//获取方法
Method method = ((), );
//调用方法
((), job);
} catch (Exception e) {
();
}
();
("任务:【" + () + "】执行结束,耗时:" + ());
}
}
最后可以创建一个类用于存储各种定时任务执行的方法,以后每次要写一个新的定时任务时,我们只需在该类中添加一个新的方法,把任务要执行的逻辑写在里面,添加定时任务的时候配置好类名、方法、cron等参数就可以了。
package ;
import ;
/**
* @Description: 存放每个定时任务需要执行的方法体
* @Param:
* @return:
* @Author: DoNg
*/
public class ScheduledMethodsConfig {
/**
* @Description: 任务一
* @Param:
* @return:
* @Author: DoNg
*/
public void method1(Job job) throws InterruptedException {
("任务:【" + () + "】正在执行中。。。。。");
//模拟任务耗时
(5000);
}
/**
* @Description: 任务二
* @Param:
* @return:
* @Author: DoNg
*/
public void method2(Job job) throws InterruptedException {
("任务:【" + () + "】正在执行中。。。。。");
//模拟任务耗时
(3000);
}
}
最终一个简单的任务调度中心就手写完成了,本文只给出了核心的代码段。项目启动后,我们可以手动开启任意的定时任务,根据需要进行任务的配置修改而不需要重启项目,并且可以随时暂停任务及增添任务内容,这些操作都可以通过前端界面操作完成。