Springboot定时调度任务动态管理

news2024/11/17 5:41:49

前言

        现在智能手表、手环,相信很多人都使用过,其中有一个功能,就是会有各种的提醒,如喝水提醒、运动提醒、远眺提醒,本质上根据用户的设置,间隔一定时间执行一个调度任务,提醒用户做某件事情。这篇文章将以这样的场景为便,和大家分享一下如何使用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方法,在重写方法内完成提醒任务的注册;重写方法的具体业务逻辑是:

  1. 从提醒任务数据表(remind_task)中查询出任务状态为启动的任务列表信息;
  2. 遍历提醒任务列表 ,根据提醒任务信息中提醒任务具体实现类、cron表达式构建一个触发器任务并进行注册
  3. 注册调度任务成功后,把返回结果进行缓存;
  4. 把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实现定时调度任务的动态管理的基本原理:

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

4. 总结

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/128976.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

myBaits Expert Human Affinities Kit ——捕获人类样本中的 SNPs 信息,化石、法医样本和降解 DNA 样本均适用

myBaits Expert Human Affinities Kit用于人类基因组SNPs富集&#xff0c;该试剂盒与古人类种群遗传学领域的专家合作设计和开发&#xff0c;针对古代和现代人类种群中已知的2M以上多态位点设计&#xff0c;特异性捕获人类基因组文库中标志性的有效信息&#xff0c;提高人类种群…

ardupilot EKF3核心算法《可以使用的传感器》

目录 文章目录 目录摘要1.可以使用的传感器2.可以使用的传感器如果从代码中提供原始数据2.1 IMU传感器提供的有用数据2.2地磁传感器提供的有用数据2.3 GPS传感器提供的有用数据2.4 气压计传感器提供的有用数据摘要 本节主要讲解Ardupilot EKF3核心算法《可以使用的传感器》。 …

java租房app房东直租系统租房网站

简介 本系统是前后端分离的项目&#xff0c;前端使用mui开发科打包成为安卓的apk。后端采用springboot开发。主要是房东登录注册后&#xff0c;可以发布房源信息&#xff0c;可以上架下架编辑删除房源信息等&#xff0c;租房者可以搜索自己需要的房子&#xff0c;然后进入详情…

Maven基础知识

第1章 Maven 介绍 1.1 什么是 Maven 1.1.1 什么是 Maven Maven 的正确发音是[ˈmevən]&#xff0c;而不是“马瘟”以及其他什么瘟。Maven 在美国是一个口语化的词 语&#xff0c;代表专家、内行的意思。 一个对 Maven 比较正式的定义是这么说的&#xff1a;Maven 是一个项…

代码随想录算法训练营第二天 java : 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II ,

文章目录Leecode977.有序数组的平方题目链接题目暴力解法双指针解法小结Leecode209.长度最小的子数组。题目连接题目难点 &#xff1a;滑动窗口Leecode59.螺旋矩阵II题目链接题目思路用到的变量代码总结今日收获Leecode977.有序数组的平方 题目链接 题目 给你一个按 非递减顺…

攻防世界-shrine

题目 访问题目场景 这个界面很乱奥&#xff0c;我们访问网页源代码&#xff0c;我们就能看见我们需要审计的源码了 我们把python源码复制下来&#xff0c;进行分析 import flask import osapp flask.Flask(__name__)app.config[FLAG] os.environ.pop(FLAG)app.route(/) def…

如何对企业内部进行知识内容有效性管理

企业内部文档管理版本的场景&#xff0c;如果日常没有比较好的工具进行内容控制&#xff0c;经常出现意外&#xff0c;有些意外很小、但是有些意外可能就伤筋动骨&#xff0c;例如标书里边的文件放错了&#xff0c;如果定性为串标&#xff0c;那么就非常严重了。JVS 企业文档管…

细刨进程状态,浅谈僵尸进程,孤儿进程,进程切换

文章目录进程状态宏观上操作系统层面上理解进程状态具体Linux操作系统的状态kill -19 pid &#xff1a;停止进程kill -18 pid &#xff1a;继续进程D&#xff08;disk sleep&#xff09;深度睡眠$和 $^僵尸状态和死亡状态僵尸进程孤儿进程进程优先级由此衍生出另外几个概念进程…

目标检测指标IOU和mAP

IOU(交并比) 交并比&#xff08;loU&#xff09;函数做的是计算两个边界框交集和并集之比。可以用来判断定位算法的好坏。 一般来说&#xff0c;IoU大于等于0.5&#xff0c;那么结果是可以接受的&#xff0c;就说检测正确。如果预测器和实际边界框完美重叠&#xff0c;loU就是…

HTML5中的FileReader对象

FileReader FileReader主要用于将文件内容读入内存&#xff0c;通过一系列异步接口&#xff0c;可以在主线程中访问本地文件。 方法 readAsArrayBuffer(file):void : 异步按字节读取文件内容&#xff0c;结果用ArrayBuffer对象表示readAsBinaryString(file):void : 异步按字…

重磅!中国电信物联网用户超4亿

近日&#xff0c;中国电信物联网发展规模取得新突破&#xff0c;物联网用户超4亿&#xff0c;物联网用户数超过移动电话用户数&#xff0c;全面迈入“物超人”时代。 “物超人”&#xff0c;即代表“物”的移动物联网终端用户数超出代表“人”的移动电话用户数。工信部公布的数…

591页22万字城市智慧应急指挥中心信息化设计方案

【版权声明】本资料来源网络&#xff0c;仅用于行业知识分享&#xff0c;供个人学习参考&#xff0c;请勿商用。【侵删致歉】如有侵权请联系小编&#xff0c;将在收到信息后第一时间进行删除&#xff01;完整资料领取见文末&#xff0c;部分资料内容&#xff1a; 目 录 第1章 项…

为有状态应用而生,云原生本地存储Carina正式进入CNCF沙箱

12月14日&#xff0c;云原生本地存储开源项目 Carina 通过了全球顶级开源基金会 CNCF 技术监督委员会&#xff08;TOC&#xff09;的评定&#xff0c;正式成为 CNCF 沙箱级项目&#xff08;Sandbox Projects&#xff09;。 Carina是由博云于2021年10月主导发起的云原生本地存储…

FISCO BCOS的PBFT共识算法流程详解

原文&#xff1a; https://fisco-bcos-doc.readthedocs.io/zh_CN/latest/docs/design/consensus/pbft.html PBFT模块主要包括PrepareReq、SignReq、CommitReq和ViewChangeReq四种共识消息&#xff1a; PrepareReqPacket: 包含区块的请求包&#xff0c;由leader产生并向所有Repl…

nacos mac

一.nacos的下载 进入官网&#xff1a;home 朝下翻 下载一点几版本的&#xff0c;2点几的已经停止更新了。 将下载好的文件放入一个没有中文名称的目录下 nacos目录&#xff1a; 二.nacos的启动与终止 打开终端&#xff0c;进入bin目录下 我的是&#xff1a; cd /Volumes/…

办公高效、生活便捷!海尔科创生态园启用

理想的工作环境应该是什么样子&#xff1f; 是高效的智慧办公&#xff0c;流畅的商务洽谈&#xff0c;快速的停车及通行…… 仅仅这些就够了吗&#xff1f;高效的数字化体验之外&#xff0c;还有浓浓的人性化关爱&#xff0c;健身设备齐全的员工服务中心、给哺乳妈妈们专设的…

LinkedList和ArrayList对比各有什么优势?

一、LinkedList的概述 1. LinkedList是双向链表实现的List 2. LinkedList是非线程安全的 3. LinkedList元素允许为null&#xff0c;允许重复元素 4. LinkedList是基于链表实现的&#xff0c;因此插入删除效率高&#xff0c;查找效率低(虽然有一个加速动作) 5. LinkedList是…

宜明昂科上市申请“失效”:融资数据“打架”,田文志持股约20%

12月29日&#xff0c;贝多财经从港交所披露易了解到&#xff0c;宜明昂科生物医药技术&#xff08;上海&#xff09;股份有限公司&#xff08;下称“宜明昂科”&#xff09;在港交所递交的上市申请材料已经“失效”&#xff0c;目前已正常无法查看或下载。 其中&#xff0c;招股…

C++图论 最小生成树和二分图问题总结

目录 一、最小生成树 &#xff08;一&#xff09;Prim朴素版 思路 练习题 代码 &#xff08;二&#xff09;kruskal算法 练习题 代码 二、二分图 &#xff08;一&#xff09;染色法判定二分图 练习题 代码 &#xff08;二&#xff09;匈牙利算法 练习题 代码 一、…

CSDN官方开发工具利器猿如意实测

目录前言一.ChatGPT二.效率工具三.开发工具四.教程文档五.一行代码六.总结前言 猿如意 是CSDN官方2022年推出的面向程序员效率工具集合&#xff0c;里面包含非常实用的小工具&#xff08;如Linux命令查询、语音合成、Json格式化、Host切换、文字转图片、Postman、图片处理等&a…