SpringBoot 动态操作定时任务(启动、停止、修改执行周期)增强版

前段时间编写了一篇博客SpringBoot 动态操作定时任务(启动、停止、修改执行周期,该篇博客还是帮助了很多同学。
但是该篇博客中的方法有些不足的地方:

  1. 只能通过前端控制器controller手动注册任务。【具体的应该是我们提前配置好我们的任务,配置完成后让springboot应用帮我们加载并注册任务】
  2. 无法打印任务的启动时间、下次执行时间及任务耗时等情况。

所以针对以上的不足我对该方案进行了整改。

新方案涉及4个类:

  1. TaskSchedulerConfig 任务调度器及任务注册器的配置
  2. ScheduledTaskRegistrar 任务注册器,用于将定时任务注册到调度器中
  3. ScheduledTaskHolder 任务的包装类
  4. ITask 任务抽象类

提醒:该定时任务只能实现单机环境下使用,无法在分布式环境下使用。

一、核心实现如下:

1、配置类TaskSchedulerConfig

该配置类往spring容器中注册了两个bean,第一个bean为org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler,该bean为spring提供的基于线程池的任务调度器,其本质是持有一个java.util.concurrent.ScheduledThreadPoolExecutor,这里需要注意初始化ScheduledThreadPoolExecutor的时候最大线程数为Integer.MAX_VALU。
setRemoveOnCancelPolicy方法如果设置为true,则在中断当前执行的任务后会将其从任务等待队列(如果队列中有该任务)中移除。
第二个bean为ScheduledTaskRegistrar即我们的任务注册器,该bean需要持有一个任务调度器并且需要配置任务列表【目前任务列表需要手动配置如果同学们想增强的话可以自己实现注解扫描配置或者包扫描配置】。

package com.bbs.config.scheduled;

import com.bbs.task.MoniterTask;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

import java.util.Arrays;

/**
 * @Author whh
 * @Date: 2023/03/15/ 20:15
 * @description
 */
@Configuration
public class TaskSchedulerConfig {


    /**
     *
     * 本质是ScheduledThreadPoolExecutor的包装,其中初始化ScheduledThreadPoolExecutor的构造函数如下
     * 特别的要注意最大线程数为 Integer.MAX_VALUE
     *
     *  public ScheduledThreadPoolExecutor(int corePoolSize,
     *                                        ThreadFactory threadFactory,
     *                                        RejectedExecutionHandler handler) {
     *         super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
     *               new DelayedWorkQueue(), threadFactory, handler);
     *     }
     * @return
     */
    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler(){
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(3);
        threadPoolTaskScheduler.setRemoveOnCancelPolicy(true);
        return threadPoolTaskScheduler;
    }

    @Bean
    public ScheduledTaskRegistrar taskRegistrar(ThreadPoolTaskScheduler scheduler){
        ScheduledTaskRegistrar taskRegistrar = new ScheduledTaskRegistrar(scheduler);
        MoniterTask moniterTask = new MoniterTask("*/30 * * * * ?");
        moniterTask.setTaskName("监控任务");
        moniterTask.setTaskDescription("每隔30s对机器进行监控");
        taskRegistrar.setTaskes(Arrays.asList(moniterTask));
        return taskRegistrar;
    }
}

2、任务注册器ScheduledTaskRegistrar

该任务注册器实现了org.springframework.beans.factory.InitializingBean接口,所以可以达到配置完成后让springboot应用帮我们加载并注册任务。该bean主要有5个方法,包括注册任务、查询所有任务、立即执行任务、暂停任务、任务恢复。其中立即执行任务并未使用调度器的线程执行而是使用用户线程执行

package com.bbs.config.scheduled;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.util.StringUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

/**
 * @Author whh
 * @Date: 2023/03/15/ 19:44
 * @description 任务注册
 */
public class ScheduledTaskRegistrar implements InitializingBean {

    private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTaskRegistrar.class);

    /**
     * 任务调度器
     */
    private ThreadPoolTaskScheduler scheduler;


    /**
     * 任务列表
     */
    private List<ITask> taskes;

    private final Map<String, ScheduledTaskHolder> register = new ConcurrentHashMap<>();


    public ScheduledTaskRegistrar(ThreadPoolTaskScheduler scheduler) {
        this.scheduler = scheduler;
    }


    /**
     * 注册任务
     */
    public void register() {
        for (ITask task : taskes) {
            ScheduledFuture<?> future = this.scheduler.schedule(task, new CronTrigger(task.getCron()));
            ScheduledTaskHolder holder = new ScheduledTaskHolder();
            holder.setScheduledFuture(future);
            holder.setTask(task);
            register.put(task.getClass().getName(), holder);
        }

    }


    /**
     * 查询所有任务
     *
     * @return
     */
    public Collection<ScheduledTaskHolder> list() {
        return register.values();
    }


    /**
     * 立即执行任务
     *
     * @param className
     */
    public void start(String className) {
        ScheduledTaskHolder holder = register.get(className);
        if (holder != null) {
            holder.getTask().run();
        }
    }


    /**
     * 暂停任务
     *
     * @param className
     */
    public void pause(String className) {
        ScheduledTaskHolder holder = register.get(className);
        if (holder != null) {
        if(holder.terminate()){
                return;
            }
            ScheduledFuture<?> future = holder.getScheduledFuture();
            future.cancel(true);
        }
    }


    /**
     *重启任务
     * @param className
     * @param cron
     */
    public void restart(String className,String cron){
        ScheduledTaskHolder holder = register.get(className);
        if (holder != null) {
         if(!holder.terminate()){
               //暂停原任务
            holder.getScheduledFuture().cancel(true);
            }
            ITask task = holder.getTask();
            if(!StringUtils.isEmpty(cron)){
               task.setCron(cron);
            }
            ScheduledFuture<?> future = this.scheduler.schedule(task, new CronTrigger(task.getCron()));
            holder.setScheduledFuture(future);

        }
    }

    public void setTaskes(List<ITask> taskes) {
        this.taskes = taskes;
    }

    private void log(){
        register.forEach((k,v)->{
            LOGGER.info("register {} complete,cron {}",k,v.getTask().getCron());

        });

    }

    @Override
    public void afterPropertiesSet(){
        register();
        log();
    }
}

3、任务包装类ScheduledTaskHolder

任务包装类包含具体任务的实例、任务执行的ScheduledFuture以及任务的运行状态。

package com.bbs.config.scheduled;

import java.util.concurrent.ScheduledFuture;

/**
 * @Author whh
 * @Date: 2023/03/15/ 19:45
 * @description
 */
public class ScheduledTaskHolder {

    /**
     * 具体任务
     */
    private ITask task;
    /**
     *result of scheduling
     */
    private ScheduledFuture<?> scheduledFuture;


    public ITask getTask() {
        return task;
    }

    public void setTask(ITask task) {
        this.task = task;
    }

    public ScheduledFuture<?> getScheduledFuture() {
        return scheduledFuture;
    }

    public void setScheduledFuture(ScheduledFuture<?> scheduledFuture) {
        this.scheduledFuture = scheduledFuture;
    }

    public boolean terminate() {
        return scheduledFuture.isCancelled();
    }
}

4、 任务抽象类ITask

任务抽象类ITask实现了Runnable接口同时提供了抽象方法execute用于自定义任务实现,同时对任务进行了增强,增加了任务执行信息的打印。

package com.bbs.config.scheduled;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.SimpleTriggerContext;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Date;

/**
 * @Author whh
 * @Date: 2023/03/15/ 19:46
 * @description
 * 任务的抽象类
 * 用于做切面打印任务执行的时间,耗时等信息
 */
public abstract class ITask implements Runnable{


    private static final Logger LOGGER = LoggerFactory.getLogger("ITask");
    private String cron;
    private String taskName;
    private String taskDescription;

    public ITask(String cron) {
        this.cron = cron;
    }

    @Override
    public void run() {
        LocalDateTime start = LocalDateTime.now();
        execute();
        LocalDateTime end = LocalDateTime.now();
        Duration duration = Duration.between(start, end);
        long millis = duration.toMillis();
        Date date = new CronTrigger(this.cron).nextExecutionTime(new SimpleTriggerContext());
        LOGGER.info("任务:[{}]执行完毕,开始时间:{},结束时间:{},耗时:{}ms,下次执行时间{}",this.taskName, start,end,millis,date);
    }


	/**
     * 用户的任务实现
     */
    public abstract void execute();


    public String getCron() {
        return cron;
    }

    public void setCron(String cron) {
        this.cron = cron;
    }

    public String getTaskName() {
        return taskName;
    }

    public void setTaskName(String taskName) {
        this.taskName = taskName;
    }

    public String getTaskDescription() {
        return taskDescription;
    }

    public void setTaskDescription(String taskDescription) {
        this.taskDescription = taskDescription;
    }
}

二、前端控制器TaskController

package com.bbs.task.controller;

import com.bbs.config.scheduled.ITask;
import com.bbs.config.scheduled.ScheduledTaskRegistrar;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.SimpleTriggerContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @Author whh
 * @Date: 2023/03/15/ 21:18
 * @description
 */
@RestController
@RequestMapping("/task")
public class TaskController {


    @Autowired
    private ScheduledTaskRegistrar scheduledTaskRegistrar;

    @GetMapping
    public List<Map<String,Object>> listTask(){
        return scheduledTaskRegistrar.list().stream().map(e->{
            Map<String,Object> temp = new HashMap<>();
            ITask task = e.getTask();
            temp.put("任务名",task.getTaskName());
            temp.put("任务描述",task.getTaskDescription());
            temp.put("cron表达式",task.getCron());
            temp.put("任务状态",e.terminate()?"已停止":"运行中");
            temp.put("任务类名",task.getClass().getName());
            temp.put("下次执行时间",new CronTrigger(task.getCron()).nextExecutionTime(new SimpleTriggerContext()));
            return temp;
        }).collect(Collectors.toList());
    }



    @PostMapping("/pause")
    public void pause(String className){
        scheduledTaskRegistrar.pause(className);
    }


    @PostMapping("/start")
    public void start(String className){
        scheduledTaskRegistrar.start(className);
    }

    @PostMapping("/restart")
    public void restart(String className,String cron){
        scheduledTaskRegistrar.restart(className,cron);
    }
}

三、自定义任务实现

package com.bbs.task;

import com.bbs.config.scheduled.ITask;

/**
 * @Author whh
 * @Date: 2023/03/15/ 21:16
 * @description
 */
public class MonitorTask extends ITask {

    public MonitorTask(String cron) {
        super(cron);
    }

    @Override
    public void execute() {
        System.out.println("hello world ....");
    }
}

四、测试

1、应用启动时控制台会打印我们注册的任务信息
在这里插入图片描述
2、任务执行情况
在这里插入图片描述
3、前端控制器查询任务
在这里插入图片描述
调用暂停API后任务停止执行,重启任务后任务又重新开始执行。
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

selenium(4)-------自动化测试脚本(python)

webdriverAPI 一)定位元素的方式&#xff0c;必问 1.1)id来定位元素&#xff0c;前提是元素必须具有id属性&#xff0c;因为有的元素是没有id的 1.2)name&#xff0c;元素必须有name&#xff0c;并且必须全局唯一 1.3)tagname&#xff0c;元素是一定有的&#xff0c;但是必须全…

HTTP 缓存的工作原理

缓存是解决http1.1当中的性能问题主要手段。缓存可能存在于客户端浏览器上&#xff0c;也可以存在服务器上面&#xff0c;当使用过期缓存可能给用户展示的是错误的信息而导致一些bug。 HTTP 缓存&#xff1a;为当前请求复用前请求的响应 • 目标&#xff1a;减少时延&#xff1…

Python+Yolov8目标识别特征检测

Yolov8目标识别特征检测如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01;前言这篇博客针对<<Yolov8目标识别特征检测>>编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读。 学习与应用推荐…

3分钟看完-丄-Python自动化测试【项目实战解析】经验分享

目录&#xff1a;导读 引言 自动化测试 背景 测试团队 测试体系发展 测试平台 自动化测试现状 现状一&#xff1a; 现状二&#xff1a; 现状三&#xff1a; 现状四&#xff1a; 现状五&#xff1a; 现状六&#xff1a; 失败的背景 失败的经历 失败总结 引言 内…

Java多线程系列--synchronized的原理

原文网址&#xff1a;Java多线程系列--synchronized的原理_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Java的synchronized的原理。 反编译出字节码 Test.java public class Test {private static Object LOCK new Object();public static int main(String[] args) {synchro…

动态矢量瓦片缓存库方案

目录 前言 二、实现步骤 1.将数据写入postgis数据库 2.将矢量瓦片数据写入缓存库 3.瓦片接口实现 4.瓦片局部更新接口实现 总结 前言 矢量瓦片作为webgis目前最优秀的数据格式&#xff0c;其主要特点就是解决了大批量数据在前端渲染时出现加载缓慢、卡顿的问题&#xff0…

LeetCode 112. 路径总和

LeetCode 112. 路径总和 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 叶…

Python笔记 -- 文件和异常

文章目录1、文件1.1、with关键字1.2、逐行读取1.3、写入模式1.4、多行写入2、异常2.1、try-except-else2.2、pass1、文件 1.1、with关键字 with关键字用于自动管理资源 使用with可以让python在合适的时候释放资源 python会将文本解读为字符串 # -*- encoding:utf-8 -*- # 如…

Linux操作系统基础的常用命令

1&#xff0c;Linux简介Linux是一种自由和开放源码的操作系统&#xff0c;存在着许多不同的Linux版本&#xff0c;但它们都使用了Linux内核。Linux可安装在各种计算机硬件设备中&#xff0c;比如手机、平板电脑、路由器、台式计算机。1.1Linux介绍Linux出现于1991年&#xff0c…

操作技巧 | 在Revit中借用CAD填充图案的方法

在建模过程中&#xff0c;有时需要达到多种填充效果&#xff0c;而CAD中大量的二维填充图案&#xff0c;便是最直接的资源之一。 使用 填充图案之前 使用 填充图案之后 其中要用到主要命令便是对表面填充图案的添加与编辑 简单效果 如下 模型填充与绘图填充 区别 模型填…

Java for循环嵌套for循环,你需要懂的代码性能优化技巧

前言 本篇分析的技巧点其实是比较常见的&#xff0c;但是最近的几次的代码评审还是发现有不少兄弟没注意到。 所以还是想拿出来说下。 正文 是个什么场景呢&#xff1f; 就是 for循环 里面还有 for循环&#xff0c; 然后做一些数据匹配、处理 这种场景。 我们结合实例代码来…

SpringBoot+WebSocket实时监控异常

# 写在前面此异常非彼异常&#xff0c;标题所说的异常是业务上的异常。最近做了一个需求&#xff0c;消防的设备巡检&#xff0c;如果巡检发现异常&#xff0c;通过手机端提交&#xff0c;后台的实时监控页面实时获取到该设备的信息及位置&#xff0c;然后安排员工去处理。因为…

Java实现调用第三方相关接口(附详细思路)

目录1.0.简单版2.0.升级版2-1.call.timeout()怎么传入新的超时值2-2.timeout(10, TimeUnit.SECONDS)两个参数的意思&#xff0c;具体含义3.0.进阶版3-1.java.net.SocketTimeoutException: 超时如何解决4.0.终极版1.0.简单版 以下是一个使用 Java 实际请求“第三方”的简单示例代…

一眼看破五花八门的链表结构

文章目录&#x1f4d5;一&#xff1a;五花八门的链表结构&#x1f4d6;链表与数组的简单对比&#x1f4d6;单链表&#x1f4d6;循环链表&#x1f4d6;双向链表&#x1f4d5;二&#xff1a;链表VS数组性能大比拼&#x1f47f;最后说一句&#x1f431;‍&#x1f409;作者简介&am…

数据挖掘(2.1)--数据预处理

一、基础知识 1.数据的基本概念 1.1基础知识 数据是数据对象(Data Objects)及其属性(Attributes)的集合。 数据对象(一条记录、一个实体、一个案例、一个样本等)是对一个事物或者物理对象的描述。 数据对象的属性则是这个对象的性质或特征&#xff0c;例如一个人的肤色、眼球…

GPT-4 性能炸天:10 秒做出一个网站,在考试中击败 90% 人类

一、GPT-4&#xff0c;吊打ChatGPT&#xff01; 一觉醒来&#xff0c;万众期待的 GPT-4&#xff0c;它来了&#xff01; OpenAI老板Sam Altman直接开门见山地介绍道&#xff1a;这是我们迄今为止功能最强大的模型&#xff01; 二、GPT-4&#xff0c;新功能一览 究竟有多强&am…

Python人脸识别

#头文件&#xff1a;import cv2 as cvimport numpy as npimport osfrom PIL import Imageimport xlsxwriterimport psutilimport time#人脸录入def get_image_name(name):name_map {f.split(.)[1]:int(f.split(.)[0]) for f in os.listdir("./picture")}if not name…

Java的jar包打包成exe应用

将springboot项目使用maven打出的jar包&#xff0c;打成windows平台下exe应用程序包&#xff08;自带jre环境&#xff09;。 工具&#xff1a;1、exe4j 2、Inno Setup 工具放到网盘&#xff0c;链接&#xff1a;https://pan.baidu.com/s/1ZHX8P7u-7GBxaC6uaIC8Ag 提取码&#x…

SpringBoot-核心技术篇

技术掌握导图 六个大标题↓ 配置文件web开发数据访问单元测试指标指控原理解析 配置文件 1.文件类型 1.1、properties 同以前的properties用法 1.2、yaml 1.2.1、简介 YAML是 “YAML Aint Markup Language”&#xff08;YAML不是一种标记语言&#xff09;的递归缩写。在…

76.qt qml-QianWindow开源炫酷界面框架(支持白色暗黑渐变自定义控件均以适配)

界面介绍界面支持: 透明 白色 黑色 渐变 单色 静态图 动态图侧边栏支持:抽屉、带折叠、多模式场景控件已集成: 暗黑风格 高亮风格、并附带个人自定义控件及开源demo白色场景如下所示:单色暗黑风格如下所示:用户自定义皮肤如下所示:皮肤预览如下所示:b站入口:https://www.bilibi…
最新文章