SpringBoot开发实战系列之动态定时任务

目录
  • 前言
  • 代码编写
  • 效果演示  
    • 启动
    • 修改
    • 停止
  • 后记

前言

定时器是我们项目中经常会用到的,SpringBoot使用@Scheduled注解可以快速启用一个简单的定时器(详情请看我们之前的博客《SpringBoot系列——定时器》),然而这种方式的定时器缺乏灵活性,如果需要对定时器进行调整,需要重启项目才生效,本文记录SpringBoot如何灵活配置动态定时任务

代码编写

首先先建表,重要字段:唯一表id、Runnable任务类、Cron表达式,其他的都是一些额外补充字段

DROP TABLE IF EXISTS `tb_task`;
CREATE TABLE `tb_task`  (
  `task_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '定时任务id',
  `task_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '定时任务名称',
  `task_desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '定时任务描述',
  `task_exp` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '定时任务Cron表达式',
  `task_status` int(1) NULL DEFAULT NULL COMMENT '定时任务状态,0停用 1启用',
  `task_class` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '定时任务的Runnable任务类完整路径',
  `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`task_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '动态定时任务表' ROW_FORMAT = Compact;

INSERT INTO `tb_task` VALUES ('1', 'task1', '测试动态定时任务1', '0/5 * * * * ?', 0, 'cn.huanzi.qch.springboottimer.task.MyRunnable1', '2021-08-06 17:39:23', '2021-08-06 17:39:25');
INSERT INTO `tb_task` VALUES ('2', 'task2', '测试动态定时任务2', '0/5 * * * * ?', 0, 'cn.huanzi.qch.springboottimer.task.MyRunnable2', '2021-08-06 17:39:23', '2021-08-06 17:39:25');

项目引入jpa、数据库驱动,用于数据库操作

<!--添加springdata-jpa依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!--添加MySQL驱动依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

数据库相关配置文件

spring:
    datasource: #数据库相关
      url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&characterEncoding=utf-8
      username: root
      password: 123456
      driver-class-name: com.mysql.jdbc.Driver
    mvc:
      date-format: yyyy-MM-dd HH:mm:ss #mvc接收参数时对日期进行格式化

    jackson:
      date-format: yyyy-MM-dd HH:mm:ss #jackson对响应回去的日期参数进行格式化
      time-zone: GMT+8
    jpa:
      show-sql: true

entity实体与数据表映射,以及与之对应的repository

/**
 * 动态定时任务表
 * 重要属性:唯一表id、Runnable任务类、Cron表达式,
 * 其他的都是一些额外补充说明属性
 */
@Entity
@Table(name = "tb_task")
@Data
public class TbTask {
    @Id
    private String taskId;//定时任务id
    private String taskName;//定时任务名称
    private String taskDesc;//定时任务描述
    private String taskExp;//定时任务Cron表达式
    private Integer taskStatus;//定时任务状态,0停用 1启用
    private String taskClass;//定时任务的Runnable任务类完整路径
    private Date updateTime;//更新时间
    private Date createTime;//创建时间
}
/**
 * TbTask动态定时任务Repository
 */
@Repository
public interface TbTaskRepository extends JpaRepository<TbTask,String>, JpaSpecificationExecutor<TbTask> {
}

测试动态定时器的配置类,主要作用:初始化线程池任务调度、读取/更新数据库任务、启动/停止定时器等

/**
 * 测试定时器2-动态定时器
 */
@Slf4j
@Component
public class TestScheduler2 {

    //数据库的任务
    public static ConcurrentHashMap<String, TbTask> tasks = new ConcurrentHashMap<>(10);

    //正在运行的任务
    public static ConcurrentHashMap<String,ScheduledFuture> runTasks = new ConcurrentHashMap<>(10);

    //线程池任务调度
    private ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();

    @Autowired
    private TbTaskRepository tbTaskRepository;

    /**
     * 初始化线程池任务调度
     */
    @Autowired
    public TestScheduler2(){
        this.threadPoolTaskScheduler.setPoolSize(10);
        this.threadPoolTaskScheduler.setThreadNamePrefix("task-thread-");
        this.threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        this.threadPoolTaskScheduler.initialize();
    }

    /**
     * 获取所有数据库里的定时任务
     */
    private void getAllTbTask(){
        //查询所有,并put到tasks
        TestScheduler2.tasks.clear();
        List<TbTask> list = tbTaskRepository.findAll();
        list.forEach((task)-> TestScheduler2.tasks.put(task.getTaskId(),task));
    }

    /**
     * 根据定时任务id,启动定时任务
     */
    void start(String taskId){
        try {
            //如果为空,重新获取
            if(TestScheduler2.tasks.size() <= 0){
                this.getAllTbTask();
            }
            TbTask tbTask = TestScheduler2.tasks.get(taskId);

            //获取并实例化Runnable任务类
            Class<?> clazz = Class.forName(tbTask.getTaskClass());
            Runnable runnable = (Runnable)clazz.newInstance();

            //Cron表达式
            CronTrigger cron = new CronTrigger(tbTask.getTaskExp());

            //执行,并put到runTasks
            TestScheduler2.runTasks.put(taskId, Objects.requireNonNull(this.threadPoolTaskScheduler.schedule(runnable, cron)));

            this.updateTaskStatus(taskId,1);

            log.info("{},任务启动!",taskId);
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            log.error("{},任务启动失败...",taskId);
            e.printStackTrace();
        }

    }

    /**
     * 根据定时任务id,停止定时任务
     */
    void stop(String taskId){
        TestScheduler2.runTasks.get(taskId).cancel(true);

        TestScheduler2.runTasks.remove(taskId);

        this.updateTaskStatus(taskId,0);

        log.info("{},任务停止...",taskId);
    }

    /**
     * 更新数据库动态定时任务状态
     */
    private void updateTaskStatus(String taskId,int status){
        TbTask task = tbTaskRepository.getOne(taskId);
        task.setTaskStatus(status);
        task.setUpdateTime(new Date());
        tbTaskRepository.save(task);
    }
}

接下来就是编写测试接口、测试Runnable类(3个Runnable类,这里就不贴那么多了,就贴个MyRunnable1)

/**
 * Runnable任务类1
 */
@Slf4j
public class MyRunnable1 implements Runnable {
    @Override
    public void run() {
        log.info("MyRunnable1  {}",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }
}

Controller接口

/**
 * 动态定时任务Controller测试
 */
@RestController
@RequestMapping("/tbTask/")
public class TbTaskController {

    @Autowired
    private TestScheduler2 testScheduler2;

    @Autowired
    private TbTaskRepository tbTaskRepository;

    /**
     * 启动一个动态定时任务
     * http://localhost:10085/tbTask/start/2
     */
    @RequestMapping("start/{taskId}")
    public String start(@PathVariable("taskId") String taskId){
        testScheduler2.start(taskId);
        return "操作成功";
    }

    /**
     * 停止一个动态定时任务
     * http://localhost:10085/tbTask/stop/2
     */
    @RequestMapping("stop/{taskId}")
    public String stop(@PathVariable("taskId") String taskId){
        testScheduler2.stop(taskId);
        return "操作成功";
    }

    /**
     * 更新一个动态定时任务
     * http://localhost:10085/tbTask/save?taskId=2&taskExp=0/2 * * * * ?&taskClass=cn.huanzi.qch.springboottimer.task.MyRunnable3
     */
    @RequestMapping("save")
    public String save(TbTask task) throws IllegalAccessException {
        //先更新表数据
        TbTask tbTask = tbTaskRepository.getOne(task.getTaskId());

        //null值忽略
        List<String> ignoreProperties = new ArrayList<>(7);

        //反射获取Class的属性(Field表示类中的成员变量)
        for (Field field : task.getClass().getDeclaredFields()) {
            //获取授权
            field.setAccessible(true);
            //属性名称
            String fieldName = field.getName();
            //属性的值
            Object fieldValue = field.get(task);

            //找出值为空的属性,我们复制的时候不进行赋值
            if(null == fieldValue){
                ignoreProperties.add(fieldName);
            }
        }

        //org.springframework.beans BeanUtils.copyProperties(A,B):A中的值付给B
        BeanUtils.copyProperties(task, tbTask,ignoreProperties.toArray(new String[0]));
        tbTaskRepository.save(tbTask);
        TestScheduler2.tasks.clear();

        //停止旧任务
        testScheduler2.stop(tbTask.getTaskId());

        //重新启动
        testScheduler2.start(tbTask.getTaskId());
        return "操作成功";
    }
}

效果演示  

启动

启动一个定时任务,http://localhost:10085/tbTask/start/2

可以看到,id为2的定时任务已经被启动,corn表达式为5秒执行一次,runnable任务为MyRunnable2

修改

修改一个定时任务,http://localhost:10085/tbTask/save?taskId=2&taskExp=0/2 * * * * ?&taskClass=cn.huanzi.qch.springboottimer.task.MyRunnable3

调用修改后,数据库信息被修改,id为2的旧任务被停止重新启用新任务,corn表达式为2秒执行一次,runnable任务类为MyRunnable3

停止

停止一个定时任务,http://localhost:10085/tbTask/stop/2

id为2的定时任务被停止

后记

可以看到,配置动态定时任务后,可以方便、实时的对定时任务进行修改、调整,再也不用重启项目啦

SpringBoot配置动态定时任务暂时先记录到这,后续再进行补充

代码开源

代码已经开源、托管到我的GitHub、码云:

GitHub:https://github.com/huanzi-qch/springBoot

码云:https://gitee.com/huanzi-qch/springBoot

到此这篇关于SpringBoot开发实战系列之动态定时任务的文章就介绍到这了,更多相关SpringBoot动态定时任务内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • springboot动态定时任务的实现方法示例

    1.maven引入quartz包 <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version> </dep

  • Springboot整个Quartz实现动态定时任务的示例代码

    简介 Quartz是一款功能强大的任务调度器,可以实现较为复杂的调度功能,如每月一号执行.每天凌晨执行.每周五执行等等,还支持分布式调度.本文使用Springboot+Mybatis+Quartz实现对定时任务的增.删.改.查.启用.停用等功能.并把定时任务持久化到数据库以及支持集群. Quartz的3个基本要素 Scheduler:调度器.所有的调度都是由它控制. Trigger: 触发器.决定什么时候来执行任务. JobDetail & Job: JobDetail定义的是任务数据,而真正的

  • SpringBoot实现动态控制定时任务支持多参数功能

    由于工作上的原因,需要进行定时任务的动态增删改查,网上大部分资料都是整合quertz框架实现的.本人查阅了一些资料,发现springBoot本身就支持实现定时任务的动态控制.并进行改进,现支持任意多参数定时任务配置 实现结果如下图所示: 后台测试显示如下: github 简单demo地址如下: springboot-dynamic-task 1.定时任务的配置类:SchedulingConfig import org.springframework.context.annotation.Bean

  • springboot整合Quartz实现动态配置定时任务的方法

    前言 在我们日常的开发中,很多时候,定时任务都不是写死的,而是写到数据库中,从而实现定时任务的动态配置,下面就通过一个简单的示例,来实现这个功能. 一.新建一个springboot工程,并添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency

  • 基于Springboot执行多个定时任务并动态获取定时任务信息

    简介 因为一些业务的需要所有需要使用多个不同的定时任务,并且每个定时任务中的定时信息是通过数据库动态获取的.下面是我写的使用了Springboot+Mybatis写的多任务定时器. 主要实现了以下功能: 1.同时使用多个定时任务 2.动态获取定时任务的定时信息 说明 因为我们需要从数据库动态的获取定时任务的信息,所以我们需要集成 SchedulingConfigurer 然后重写 configureTasks 方法即可,调用不同的定时任务只需要通过service方法调用不用的实现返回对应的定时任

  • SpringBoot实现动态多线程并发定时任务

    本文实例为大家分享了SpringBoot实现动态多线程并发定时任务的具体代码,供大家参考,具体内容如下 实现定时任务有多种方式,使用spring自带的,继承SchedulingConfigurer的方式. 一.实现 1.启动类 在启动类添加注解@EnableScheduling开启,不然不起用做. 2.新建任务类 添加注解@Component注册到spring的容器中. package com.example.demo.task; import com.example.demo.entity.M

  • SpringBoot中并发定时任务的实现、动态定时任务的实现(看这一篇就够了)推荐

    一.在JAVA开发领域,目前可以通过以下几种方式进行定时任务 1.单机部署模式 Timer:jdk中自带的一个定时调度类,可以简单的实现按某一频度进行任务执行.提供的功能比较单一,无法实现复杂的调度任务. ScheduledExecutorService:也是jdk自带的一个基于线程池设计的定时任务类.其每个调度任务都会分配到线程池中的一个线程执行,所以其任务是并发执行的,互不影响. Spring Task:Spring提供的一个任务调度工具,支持注解和配置文件形式,支持Cron表达式,使用简单

  • 详解SpringBoot 创建定时任务(配合数据库动态执行)

    序言:创建定时任务非常简单,主要有两种创建方式:一.基于注解(@Scheduled) 二.基于接口(SchedulingConfigurer). 前者相信大家都很熟悉,但是实际使用中我们往往想从数据库中读取指定时间来动态执行定时任务,这时候基于接口的定时任务就大派用场了. 一.静态定时任务(基于注解) 基于注解来创建定时任务非常简单,只需几行代码便可完成. @Scheduled 除了支持灵活的参数表达式cron之外,还支持简单的延时操作,例如 fixedDelay ,fixedRate 填写相应

  • SpringBoot实现动态定时任务

    项目情况: 在当前项目中需要一个定时任务来清除过期的校验码,如果使用数据库存储过程的话不方便维护.因此采用SpringBoot自带的方式来设置定时任务. 技术说明: SpringBoot自带的方式有两种可以实现: 一种是使用@Scheduled注解的方式,只需要在启动类或者它所在的类上添加@EnableScheduling注解允许执行定时任务,并且设置Schecduled注解的参数,诸如: 1.cron是设置定时执行的表达式,如 0 0/5 * * * ?每隔五分钟执行一次 2.zone表示执行

  • 浅谈SpringBoot集成Quartz动态定时任务

    SpringBoot自带schedule 沿用的springboot少xml配置的优良传统,本身支持表达式等多种定时任务 注意在程序启动的时候加上@EnableScheduling @Scheduled(cron="0/5 * * * * ?") public void job(){ System.out.println("每五秒执行一次"); } 为什么要使用Quartz 多任务情况下,quartz更容易管理,可以实现动态配置 执行时间表达式: 表达式示例: 集成

随机推荐