背景:定时任务其实已经用过多套代码了,比如quartz或者是直接注解的@Scheduled,还有之前CronTrigger加载cron函数来配合库表进行操作的定时任务管理。但是这些总少了一些调度的灵活性,刚好本次也要做一个调度框架,那对之前用过的在完善下,意在使用更加灵活方便。
框架:springboot+mybatisplus组合
直接开始完整步骤----->
1.启动类加上支持定时任务注解
2.建表以及实体类
CREATE TABLE `index_schedule` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(64) DEFAULT NULL COMMENT '调度名称',
`active` TINYINT DEFAULT '1' COMMENT '是否启动 0否 1是',
`cron_expression` VARCHAR(100) DEFAULT NULL COMMENT 'cron表达式',
`class_name` VARCHAR(256) DEFAULT NULL COMMENT '类名(路径)',
`method_name` VARCHAR(128) DEFAULT NULL COMMENT '方法名',
`remark` VARCHAR(256) DEFAULT NULL,
`create_time` DATETIME DEFAULT NULL,
`create_by` VARCHAR(32) DEFAULT NULL,
`update_by` VARCHAR(32) DEFAULT NULL,
`update_time` DATETIME DEFAULT NULL,
`status` TINYINT DEFAULT '1',
`del_flag` VARCHAR(2) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=INNODB comment '调度表'
关键字段说明:
1)class_name类名、method_name方法名:某个类中的方法作为定时任务来调度执行,类名为绝对路径,方法名则为单名称。
2)cron_expression(cron函数):定时任务最基本使用的时间表达式,自行了解。
3)active:定时任务是否启动字段,通过该字段来控制定时任务是否使用
实体类:略
3.调度框架主要配置类
@Slf4j
@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
@Resource
private TaskScheduler taskScheduler;
//调度任务注册器
private ScheduledTaskRegistrar taskRegistrar;
// 调度任务
private final ConcurrentHashMap<Long, ScheduledFuture<?>> scheduledFutures = new ConcurrentHashMap<>(10);
// 线程任务
private final ConcurrentHashMap<Long, ScheduleTask> tasks = new ConcurrentHashMap<>(10);
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler);
this.taskRegistrar = taskRegistrar;
}
// 1.刷新任务
public void refreshTask(ScheduleTask cronTask) {
Long id = cronTask.getId();
if (cronTask.getActive() == CommonConstrant.INTEGER_ZREO) {
// 删除任务
if (scheduledFutures.containsKey(cronTask.getId())) {
scheduledFutures.get(id).cancel(false);
scheduledFutures.remove(id);
tasks.remove(id);
}
return;
}
// 调度任务已存在且无变化
if (scheduledFutures.containsKey(id) && StringUtils.equals(tasks.get(id).getCronExpression(),cronTask.getCronExpression())) &&
StringUtils.equals(tasks.get(id).getClassName(),cronTask.getClassName()) &&
StringUtils.equals(tasks.get(id).getMethodName(),cronTask.getMethodName())
)
{
return;
}
// 任务发生了变化
if (scheduledFutures.containsKey(id) && (StringUtils.equals(tasks.get(id).getCronExpression(),cronTask.getCronExpression())) ||
StringUtils.equals(tasks.get(id).getClassName(),cronTask.getClassName()) ||
StringUtils.equals(tasks.get(id).getMethodName(),cronTask.getMethodName()))
)
{
scheduledFutures.get(id).cancel(false);
scheduledFutures.remove(id);
tasks.remove(id);
}
// 添加任务
Trigger trigger = getTrigger(cronTask);
taskRegistrar.setScheduler(taskScheduler);
TaskScheduler scheduler = taskRegistrar.getScheduler();
ScheduledFuture schedule = scheduler.schedule(getRunable(cronTask),trigger);
scheduledFutures.put(id,schedule);
tasks.put(id, cronTask);
log.info("成功刷新任务--->");
}
// 2.设置定时器
private Trigger getTrigger(ScheduleTask cronTask) {
if (cronTask.getCronExpression().equals(CommonConstrant.ACTIVE_ONCE)){
return null;
}
return new Trigger() {
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
CronTrigger trigger = new CronTrigger(cronTask.getCronExpression());
Date date = trigger.nextExecutionTime(triggerContext);
return date;
}
};
}
/**
* 3.执行任务(异步且带检测步骤)
* @param cronTask
*/
private Runnable getRunable(ScheduleTask cronTask) {
return new Runnable() {
/**
* 验证一条定时任务
* @param cronTask
* @return
*/
private boolean checkOneTask(ScheduleTask cronTask) {
if (cronTask.getActive()==0) {
log.error(cronTask.getClassName() + "的" + cronTask.getMethodName() + ": 该项任务已停止运行");
return false;
}
Class clazz;
try {
// 1.测试加载类
clazz = Class.forName(cronTask.getClassName());
Object bean = SpringContextUtils.getBean(clazz);
// 2.测试加载方法
if (!StringUtils.equals(CommonConstrant.CRON_TASK_METHOD_RUNSCHEDULE,cronTask.getMethodName())){//不带参数
bean.getClass().getMethod(cronTask.getMethodName());
}else{// 带参数
Long[] paramsNum = new Long[]{cronTask.getId()};
Class[] classes = new Class[paramsNum.length];
for(int i = 0; i < paramsNum.length;i++){
classes[i] = Long.class;
}
bean.getClass().getMethod(cronTask.getMethodName(),classes);
}
String cronExpression = cronTask.getCronExpression();
if (StringUtils.isBlank(cronExpression)) {
log.error("该方法未添加定时表达式");
return false;
}
} catch (ClassNotFoundException | NoSuchMethodException e) {
e.printStackTrace();
log.error("找不到类名或方法名", e.getMessage());
}
return true;
}
@Override
public void run() {
// 1.验证任务信息
if (!this.checkOneTask(cronTask)) {
return;
}
// 2.加载类和方法并运行
try {
Class aClass = Class.forName(cronTask.getClassName());
Object bean = SpringContextUtils.getBean(aClass);
if (!StringUtils.equals(CommonConstrant.CRON_TASK_METHOD_RUNSCHEDULE,cronTask.getMethodName())){//不带参数
Method method = bean.getClass().getMethod(cronTask.getMethodName());
method.invoke(bean);
}else{//带参数
Long[] paramsNum = new Long[]{cronTask.getId()};
Class[] classes = new Class[paramsNum.length];
for(int i = 0; i < paramsNum.length;i++){
classes[i] = Long.class;
}
Method method = bean.getClass().getMethod(cronTask.getMethodName(),classes);
method.invoke(bean,cronTask.getId());
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
log.error(cronTask.getClassName() + ": 读取任务接口异常", e.getMessage());
}
}
};
}
/**
* 新增任务时方法调用-刷新全部任务
* 实现调度任务的灵活性
*/
public void refreshAllTask(List<ScheduleTask> list) {
// 已删除任务
Set<Long> ids = scheduledFutures.keySet();
for (Long id : ids) {
if (notExist(id, list)) {
scheduledFutures.get(id).cancel(false);
scheduledFutures.remove(id);
tasks.remove(id);
}
}
list.forEach(e -> {refreshTask(e);});
}
// 判断任务是否存在
private boolean isExist(Long id, List<ScheduleTask> list) {
List<ScheduleTask> tasks = list.stream().filter(timedTask -> timedTask.getId() == id).collect(Collectors.toList());
if (CollectionUtils.isEmpty(tasks)){
return true;
}else {
return false;
}
}
}
4.调度线程池类
@Configuration
public class ScheduleThreadPool {
/**
* 核心线程5
*/
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.initialize();
taskScheduler.setPoolSize(5);
taskScheduler.setRemoveOnCancelPolicy(true);
taskScheduler.setThreadNamePrefix("thread-->");
return taskScheduler;
}
}
5.启动初始化加载
@Configuration
@Slf4j
public class JustApplicationRunner implements ApplicationRunner {
@Resource
private ScheduledConfig scheduledConfig;
@Resource
private ScheduleService scheduleService;
@Override
public void run(ApplicationArguments args) {
ScheduleTask task = new ScheduleTask();
task.setActive(CommonConstrant.INTEGER_ONE);
// 当即执行,该标识在代码中处理
task.setCronExpression(CommonConstrant.CRON_NOW); scheduledConfig.refreshAllTask(scheduleService.getPage(task).getData().getRecords());
}
}
6.数据库存储字段案例展示
总结:以上只是简单的概述下本次使用的调度框架并记录下,也防止自己之后忘记这些。该调度框架可玩性还是挺高的,我之后把其直接抽出增加其他代码,比如异步任务和中间件等框架工具,整合成独立的项目降低耦合性,对其他项目进行调度开发,不失为一个好用的调度工具。