前言

现在智能手表、手环,相信很多人都使用过,其中有一个功能,就是会有各种的提醒,如喝水提醒、运动提醒、远眺提醒,本质上根据用户的设置,间隔一定时间执行一个调度任务,提醒用户做某件事情。这篇文章将以这样的场景为便,和大家分享一下如何使用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_uindexunique (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 {@Overridepublic void execute() {System.out.println("[喝水提醒]主人,记得喝水哦"+ LocalDateTime.now());}
}
public class SportTask implements RemindTask {@Overridepublic void execute() {System.out.println("[运动提醒]主人,站起来活动一下吧"+ LocalDateTime.now());}
}
public class EatTask implements RemindTask {@Overridepublic void execute() {System.out.println("[吃饭提醒]主人,吃点东西吧"+ LocalDateTime.now());}
}

2.2 调度任务的注册

完成调度任务的抽象封装后,就需要对这些提醒调度任务进行注册,具体的方法也很简单,在前面的文章中也已经分享过,主要逻辑是实现org.springframework.scheduling.annotation.SchedulingConfigurer接口,重写com.fanfu.task.DynamicScheduleTask#configureTasks方法,在重写方法内完成提醒任务的注册;重写方法的具体业务逻辑是:

  1. 从提醒任务数据表(remind_task)中查询出任务状态为启动的任务列表信息;
  2. 遍历提醒任务列表 ,根据提醒任务信息中提醒任务具体实现类、cron表达式构建一个触发器任务并进行注册
  3. 注册调度任务成功后,把返回结果进行缓存;
  4. 把Spring的调度任务注册管理中心注入到本类的属性中,方便后续使用它对调度任务进行动态管理

其中configureTasks()方法会在Spring容器启动完成时被调用,因此在项目启动后会完成调度任务的初次注册并开始执行。

@Configuration
public class DynamicScheduleTask implements SchedulingConfigurer {@Autowiredprivate RemindTaskDao remindTaskDao;private ScheduledTaskRegistrar scheduledTaskRegistrar;//用于存储已注册过的调度任务private Map<String, ScheduledTask> triggerTaskMap = new ConcurrentHashMap<>();/*** spring容器启动完成时会执行这个方法,完成初次的调度任务注册* @param scheduledTaskRegistrar*/@Overridepublic void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {//查询出任务状态为启动状态的调度任务List<RemindTaskBean> remindTaskBeans = remindTaskDao.queryAll(null);for (RemindTaskBean remindTaskBean : remindTaskBeans) {//定义一个触发器类类型的调度任务TriggerTask triggerTask = new TriggerTask(new Runnable() {@SneakyThrows@Overridepublic 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@Overridepublic 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 {@Autowiredprivate 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实现定时调度任务的动态管理的基本原理:

  1. 创建一个提醒任务数据表,用于存储提醒调度任务的基本信息,如cron表达式、具体的实现类;
  2. 通过实现接口SchedulingConfigurer并重写configureTasks(),在spring容器启动完成时,完成提醒调度任务的初次注册并开始执行;
  3. 对前端暴露接口(任务列表接口、启动调度任务接口、停止调度任务接口),实现调度任务的动态管理;

4. 总结

总体来说,实现原理还是比较简单、且容易理解的。但是这个实现方案也是有缺点的,实际开发过程中应该根据具体情况作出适当调整。具体的缺点就是初次完成调度任务注册后,会把执行结果缓存起来,以方便对调度任务进行动态管理,这里使用的是虚拟机级别的缓存(ConcurrentHashMap),所以如果实际业务场景中是分布式部署,就考虑使用分布式缓存。

文章内的源码示例可以从这里下载:https://download.csdn.net/download/fox9916/87354573

Springboot扩展点系列实现方式、工作原理集合:

Springboot扩展点之ApplicationContextInitializer

Springboot扩展点之BeanDefinitionRegistryPostProcessor

Springboot扩展点之BeanFactoryPostProcessor

Springboot扩展点之BeanPostProcessor

Springboot扩展点之InstantiationAwareBeanPostProcessor

Springboot扩展点之SmartInstantiationAwareBeanPostProcessor

Springboot扩展点之ApplicationContextAwareProcessor

Springboot扩展点之@PostConstruct

Springboot扩展点之InitializingBean

Springboot扩展点之DisposableBean

Springboot扩展点之SmartInitializingSingleton

Springboot核心功能工作原理:

Springboot实现调度任务的工作原理

Springboot事件监听机制的工作原理

Springboot调度任务:动态管理相关推荐

  1. springBoot+springSecurity 数据库动态管理用户、角色、权限(二)

    序:  本文使用springboot+mybatis+SpringSecurity 实现数据库动态的管理用户.角色.权限管理 本文细分角色和权限,并将用户.角色.权限和资源均采用数据库存储,并且自定义 ...

  2. 定时任务动态管理-Scheduled

    文章目录 前言 一.架构流程图 二.代码实现流程 1.引入库 2.代码流程 前言 定时任务动态管理分为两种方式: 方式一:Web前台配置Trigger触发器(关联Cron).ThreadPoolTas ...

  3. SpringBoot 定时任务动态管理通用解决方案

    欢迎关注方志朋的博客,回复"666"获面试宝典 一.功能说明 SpringBoot的定时任务的加强工具,实现对SpringBoot原生的定时任务进行动态管理,完全兼容原生@Sche ...

  4. springBoot+security+mybatis 实现用户权限的数据库动态管理

    [b][size=large]一.Spring Security 应用的概述[/size][/b] [size=medium] 鉴于目前微服务的兴起,Spring周边方案的普及,以及 Spring S ...

  5. quartz 动态添加job_SpringBoot+Quartz实现动态管理定时任务

    前言: 最近在做Java开发对接微信公众平台之类的工作,在开发过程中遇到一个需求是定时任务这块的,但是普通的定时任务还远远不能满足:最后要实现的效果是每个任务都是不同的定时时间来执行而且是在前台页面上 ...

  6. ThreadPoolTaskScheduler实现动态管理定时任务

    最近,有个项目有需要用到定时任务,所以做了一个动态管理定时任务的模块.本文将从项目背景.需求.选型.思路.具体实现等方面展开介绍. 背景:有个支付类的项目,中间会产生一些中间态的订单,需要有个定时任务 ...

  7. SpringCloud的Archaius - 动态管理属性配置

    参考链接:http://www.th7.cn/Program/java/201608/919853.shtml 一.Archaius是什么? Archaius用于动态管理属性配置文件. 参考自Gett ...

  8. “一文读懂”系列:AMS是如何动态管理进程的?

    前言 前面一篇文章介绍了关于WMS在整个Android体系中的作用,主要可以划分为四类职责: 1.窗口管理 2.窗口动画 3.Surface管理 4.输入事件中转站. 如果把WMS比作古代将军,那么这 ...

  9. java quartz 动态执行,浅谈SpringBoot集成Quartz动态定时任务

    SpringBoot自带schedule 沿用的springboot少xml配置的优良传统,本身支持表达式等多种定时任务 注意在程序启动的时候加上@EnableScheduling @Schedule ...

最新文章

  1. hadoop集群运维碰到的问题汇总
  2. JAVA 创建线程池
  3. mysql 1243_MySQL#1243给予EXECUTE的未知预处理语句处理程序(stmt)
  4. [转] 三层开发中的层次划分
  5. 如何建立应付暂估明细查询
  6. MongoDB-JAVA-Driver 3.2版本常用代码全整理(2) - 查询
  7. 大数据与web开发整合的最佳实践-思考
  8. 终于弄明白了 Singleton,Transient,Scoped 的作用域是如何实现的
  9. usb hub区分端口_树莓派上 USB 子系统拓扑浅析
  10. 只需两步手把手教你玩转图像识别
  11. Python数据可视化1.5 可视化图像
  12. Android 常用adb shell 命令
  13. Spring-data ElasticSearch的使用
  14. speedoffice如何根据身份证号码提取出性别
  15. 【产品】 产品设计:工业设计之外观设计详解(形态设计和CMF设计)
  16. pycharm正则表达式检索
  17. python中with open as f什么意思_Python中 with open(file_abs,'r') as f: 的用法以及意义
  18. 一个合格的项目经理都需要做哪些事情?
  19. React中文文档之Forms
  20. 数据结构学习——RBT(红黑树)以及实现Map和Set

热门文章

  1. 什么是新零售 新零售对电商的影响是什么?
  2. 商务英语口语考试准备
  3. 关于计算机求职英语作文,计算机专业英文面试自我介绍范文
  4. 随笔散文:关于北京的重度雾霾想到的
  5. [转载]每周问问你的团队这10个问题
  6. android接入支持海外的支付,visa,mastercard
  7. Matlab:实现杨氏双缝干涉仿真
  8. uniapp换行符号_uni-app 跨端开发注意事项
  9. MySQL登录时出现Access denied for user ‘root‘@‘localhost‘ (using password: YES)
  10. 【Unity】5.X灯光烘焙与4.X在实际应用中的区别和注意事项