前言
现在智能手表、手环,相信很多人都使用过,其中有一个功能,就是会有各种的提醒,如喝水提醒、运动提醒、远眺提醒,本质上根据用户的设置,间隔一定时间执行一个调度任务,提醒用户做某件事情。这篇文章将以这样的场景为便,和大家分享一下如何使用SprIngboot对定时调度任务进行动态管理。
1. 表结构设计
创建提醒任务数据表(remind_task ),用来存储提醒任务的基本信息,如任务的名称、cron表达式、任务状态、提醒任务的实现类。
-
create
table if
not
exists remind_task
-
(
-
id
int auto_increment,
-
task_name
varchar(
30)
null comment
'任务名称',
-
cron
varchar(
100)
null comment
'cron表达式',
-
task_status
int
null comment
'任务状态,1、启动;0、停止;',
-
bean_clazz
varchar(
255)
null comment
'提醒任务的实现类',
-
constraint remind_task_id_uindex
-
unique (id)
-
)
-
comment
'提醒任务数据表';
-
alter
table remind_task
add
primary key (id);
-
INSERT
INTO happy_home.remind_task (id, task_name, cron, task_status, bean_clazz)
-
VALUES (
1,
'喝水提醒',
'0/5 * * * * ?',
1,
'com.fanfu.task.remind.task.DrinkTask');
-
INSERT
INTO happy_home.remind_task (id, task_name, cron, task_status, bean_clazz)
-
VALUES (
2,
'运动提醒',
'0/5 * * * * ?',
1,
'com.fanfu.task.remind.task.SportTask');
-
INSERT
INTO happy_home.remind_task (id, task_name, cron, task_status, bean_clazz)
-
VALUES (
3,
'吃饭提醒',
'0/5 * * * * ?',
0,
'com.fanfu.task.remind.task.EatTask');
2. 实现方法
2.1 调度任务的抽象封装
面向接口编程,把提醒任务抽象封装为一个接口RemindTask,定义execute()方法;
-
public
interface
RemindTask {
-
/**
-
* 提醒任务执行逻辑
-
*/
-
void execute();
-
}
具体的提醒任务,如吃饭、喝水、运动,则实现RemindTask,在execute()方法中编写具体的提醒业务逻辑
-
public
class
DrinkTask
implements
RemindTask {
-
@Override
-
public
void
execute(
) {
-
System.
out.
println(
"[喝水提醒]主人,记得喝水哦"+
LocalDateTime.
now());
-
}
-
}
-
public
class
SportTask
implements
RemindTask {
-
@Override
-
public
void
execute(
) {
-
System.
out.
println(
"[运动提醒]主人,站起来活动一下吧"+
LocalDateTime.
now());
-
}
-
}
-
public
class
EatTask
implements
RemindTask {
-
@Override
-
public
void
execute(
) {
-
System.
out.
println(
"[吃饭提醒]主人,吃点东西吧"+
LocalDateTime.
now());
-
}
-
}
2.2 调度任务的注册
完成调度任务的抽象封装后,就需要对这些提醒调度任务进行注册,具体的方法也很简单,在前面的文章中也已经分享过,主要逻辑是实现org.springframework.scheduling.annotation.SchedulingConfigurer接口,重写com.fanfu.task.DynamicScheduleTask#configureTasks方法,在重写方法内完成提醒任务的注册;重写方法的具体业务逻辑是:
- 从提醒任务数据表(remind_task)中查询出任务状态为启动的任务列表信息;
- 遍历提醒任务列表 ,根据提醒任务信息中提醒任务具体实现类、cron表达式构建一个触发器任务并进行注册
- 注册调度任务成功后,把返回结果进行缓存;
- 把Spring的调度任务注册管理中心注入到本类的属性中,方便后续使用它对调度任务进行动态管理
其中configureTasks()方法会在Spring容器启动完成时被调用,因此在项目启动后会完成调度任务的初次注册并开始执行。
-
@Configuration
-
public
class
DynamicScheduleTask
implements
SchedulingConfigurer {
-
@Autowired
-
private
RemindTaskDao remindTaskDao;
-
private
ScheduledTaskRegistrar scheduledTaskRegistrar;
-
//用于存储已注册过的调度任务
-
private
Map<
String,
ScheduledTask> triggerTaskMap =
new
ConcurrentHashMap<>();
-
/**
-
* spring容器启动完成时会执行这个方法,完成初次的调度任务注册
-
* @param scheduledTaskRegistrar
-
*/
-
@Override
-
public
void
configureTasks(
ScheduledTaskRegistrar scheduledTaskRegistrar) {
-
//查询出任务状态为启动状态的调度任务
-
List<
RemindTaskBean> remindTaskBeans = remindTaskDao.
queryAll(
null);
-
for (
RemindTaskBean remindTaskBean : remindTaskBeans) {
-
//定义一个触发器类类型的调度任务
-
TriggerTask triggerTask =
new
TriggerTask(
new
Runnable() {
-
@SneakyThrows
-
@Override
-
public
void
run(
) {
-
String beanClazz = remindTaskBean.
getBeanClazz();
-
Class<?> aClass =
Class.
forName(beanClazz);
-
Method execute = aClass.
getMethod(
"execute");
-
execute.
invoke(aClass.
newInstance(),
null);
-
}
-
},
new
CronTrigger(remindTaskBean.
getCron()));
-
//注册并立即开始调度任务
-
ScheduledTask scheduledTask = scheduledTaskRegistrar.
scheduleTriggerTask(triggerTask);
-
//注册过的调度任务存储在自定义的容器里,方便后续对注册的调度任务进行动态管理
-
triggerTaskMap.
put(remindTaskBean.
getTaskName(), scheduledTask);
-
}
-
//把Spring的调度任务注册管理中心注入到本类的属性中,方便后续使用它对调度任务进行动态管理
-
this.
scheduledTaskRegistrar = scheduledTaskRegistrar;
-
}
-
}
2.3 调度任务的动态管理
这里我通过定义三个接口(任务列表、启动接口、停止接口)来分享一下如何对调度任务进行动态管理,在实际开发中,可以做成一个单独的管理页面,前端页面通过调用后台接口完成调度任务的启动、停止、编辑操作。
在SchedulingConfigurer接口的实现类DynamicScheduleTask中定义三个方法,1、查询调度任务列表 ;2、启动调度任务;3、停止调度任务
-
/**
-
* 调度任务列表
-
* @return
-
*/
-
public
List<
RemindTaskBean>
taskList(
){
-
List<
RemindTaskBean> remindTaskBeans =
this.
remindTaskDao.
queryAll(
null);
-
return remindTaskBeans;
-
}
-
/**
-
* 启动调度任务
-
* @param taskName
-
*/
-
public
synchronized
void
addTask
(String taskName) {
-
//启动任务前,需要先判断启动任务的状态,如果是已启动,就不能重复启动,抛出异常提示
-
RemindTaskBean
remindTaskBean
=
this.remindTaskDao.queryByTaskName(taskName);
-
if (
1== remindTaskBean.getTaskStatus()) {
-
throw
new
RuntimeException(
"调度任务已启动,不能重复添加已启动的任务");
-
}
-
//如果待启动的调度任务处于停止状态,则定义一个触发器任务
-
TriggerTask
triggerTask
=
new
TriggerTask(
new
Runnable() {
-
@SneakyThrows
-
@Override
-
public
void
run
() {
-
String
beanClazz
= remindTaskBean.getBeanClazz();
-
Class<?> aClass = Class.forName(beanClazz);
-
Method
execute
= aClass.getMethod(
"execute");
-
execute.invoke(aClass.newInstance(),
null);
-
}
-
},
new
CronTrigger(remindTaskBean.getCron()));
-
//注册并开始执行触发器调度任务
-
ScheduledTask
scheduledTask
=
this.scheduledTaskRegistrar.scheduleTriggerTask(triggerTask);
-
//注册调度任务成功后,把执行结果缓存起来,用于调度任务的动态管理
-
this.triggerTaskMap.put(taskName, scheduledTask);
-
//调度任务注册成功后,更新调度任务状态为启动状态
-
remindTaskBean.setTaskStatus(
1);
-
this.remindTaskDao.updateByTaskName(remindTaskBean);
-
}
-
/**
-
* 停止调度任务
-
* @param taskName
-
*/
-
public
synchronized
void
cancel
(String taskName) {
-
//这个地方其实也需要加一些判断,比如只有启动状态的调度任务才能取消,不能取消处于停止状态的任务
-
//取缓存结果中取出要取消的调度任务,然后取消执行
-
ScheduledTask
scheduledTask
=
this.triggerTaskMap.get(taskName);
-
scheduledTask.cancel();
-
//调度任务取消后,更新调度任务的状态为停止状态
-
RemindTaskBean
remindTaskBean
=
new
RemindTaskBean();
-
remindTaskBean.setTaskName(taskName);
-
remindTaskBean.setTaskStatus(
0);
-
this.remindTaskDao.updateByTaskName(remindTaskBean);
-
}
定义一个提醒任务控制器,并定义三个接口(调度任务列表查询接口、停止调度任务接口、添加并启动调度任务接口),供前端调用;
-
@RestController
-
@RequestMapping(
"/remindTask")
-
public
class
RemindTaskController {
-
@Autowired
-
private
DynamicScheduleTask dynamicScheduleTask;
-
-
@GetMapping(
"/list")
-
public
List<
RemindTaskBean>
list(
){
-
List<
RemindTaskBean> remindTasks = dynamicScheduleTask.
taskList();
-
return remindTasks;
-
}
-
/**
-
* 停止调度任务
-
* @param taskName
-
*/
-
@GetMapping(
"/cancel")
-
public
void
cancel(
String taskName){
-
dynamicScheduleTask.
cancel(taskName);
-
}
-
-
/**
-
* 添加并启动调度任务
-
* @param taskName
-
*/
-
@GetMapping(
"/add")
-
public
void
add(
String taskName){
-
dynamicScheduleTask.
addTask(taskName);
-
}
-
}
3. 基本实现原理
这里回顾一下Springboot实现定时调度任务的动态管理的基本原理:
- 创建一个提醒任务数据表,用于存储提醒调度任务的基本信息,如cron表达式、具体的实现类;
- 通过实现接口SchedulingConfigurer并重写configureTasks(),在spring容器启动完成时,完成提醒调度任务的初次注册并开始执行;
- 对前端暴露接口(任务列表接口、启动调度任务接口、停止调度任务接口),实现调度任务的动态管理;
4. 总结
总体来说,实现原理还是比较简单、且容易理解的。但是这个实现方案也是有缺点的,实际开发过程中应该根据具体情况作出适当调整。具体的缺点就是初次完成调度任务注册后,会把执行结果缓存起来,以方便对调度任务进行动态管理,这里使用的是虚拟机级别的缓存(ConcurrentHashMap),所以如果实际业务场景中是分布式部署,就考虑使用分布式缓存。
文章内的源码示例可以从这里下载:https://download.csdn.net/download/fox9916/87354573
转载:https://blog.csdn.net/fox9916/article/details/128494957