Springboot实现动态定时任务流程详解

目录
  • 一、静态
  • 二、动态
    • 1、基本代码
    • 2、方案详解
      • 2.1 初始化
      • 2.2 单次执行
      • 2.3 停止任务
      • 2.4 启用任务
  • 三、小结

一、静态

静态的定时任务可以直接使用注解@Scheduled,并在启动类上配置@EnableScheduling即可

  @PostMapping("/list/test1")
  @Async
  @Scheduled(cron = "0 * * * * ?")
  public void test1() throws Exception {
    Object obj = this.getClass().newInstance();
    log.info("执行静态定时任务时间/test1:param:{}", new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
  }

二、动态

动态的定时任务需要根据ScheduledTaskRegistrar的addTriggerTask方法进行实现,网上大多数资料是在Trigger中动态查询cron表达式来实现,但存在一个问题就是只有在下一次执行的时候才会刷新,比如一开始设置的每天12点执行一次,如果项目启动后,修改为每小时执行一次的话,需要等到下一次12点执行之后,才会刷新cron表达式,所以通过对ScheduledTaskRegistrar的源码分析,构建了以下解决方案,可以实现对定时任务的额单次额外执行、停止、启动三个基本功能。在此只列出关键代码,关于项目启动、数据库连接的代码等就不过多说明了。

1、基本代码

首先新建定时任务信息的库表及实体类

@Data
@EqualsAndHashCode(callSuper = false)
@TableName("cron")
public class Cron implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.ASSIGN_UUID)
    private String id;
    private String cronFlag;
    private String cronExpression;
    private String className;
    private String methodName;
}

用于获取bean实例的工具类

@Component
public class BeansUtils implements ApplicationContextAware {
  private static ApplicationContext context;
  public static <T> T getBean(Class<T> bean) {
    return context.getBean(bean);
  }
  public static <T> T getBean(String var1, @Nullable Class<T> var2){
    return context.getBean(var1, var2);
  }
  public static ApplicationContext getContext() {
    return context;
  }
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    context = applicationContext;
  }
}

实现定时任务类

@Configuration
@EnableScheduling
@EnableAsync
@Slf4j
public class RCScheduleTask implements SchedulingConfigurer {
  @Autowired
  private ICronService iCronService;
  private static ScheduledTaskRegistrar scheduledTaskRegistrar;
  public static Map<String, TriggerTask> triggerTaskMap;
  @Override
  public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
    scheduledTaskRegistrar = taskRegistrar;
    Cron cron = new Cron();
    cron.setCronFlag("1");
    List<Cron> list = iCronService.getList(cron);
    if (list != null) {
      initTriggerTask(list);
    }
  }
  public void initTriggerTask(List<Cron> list) {
    triggerTaskMap = new HashMap<>();
    for (Cron cron : list) {
      TriggerTask triggerTask = new TriggerTask(getRunnable(cron), getTrigger(cron));
      scheduledTaskRegistrar.addTriggerTask(triggerTask);
      triggerTaskMap.put(cron.getId(), triggerTask);
    }
  }
  private static Runnable getRunnable(Cron cron) {
    return new Runnable() {
      @Override
      public void run() {
        Class<?> clazz;
        try {
          clazz = Class.forName(cron.getClassName());
          Object bean = BeansUtils.getBean(clazz);
          Method method = ReflectionUtils.findMethod(bean.getClass(), cron.getMethodName());
          ReflectionUtils.invokeMethod(method, bean);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    };
  }
  private static Trigger getTrigger(Cron cron) {
    return new Trigger() {
      @Override
      public Date nextExecutionTime(TriggerContext triggerContext) {
        CronTrigger trigger = new CronTrigger(cron.getCronExpression());
        Date nextExec = trigger.nextExecutionTime(triggerContext);
        return nextExec;
      }
    };
  }
  public static boolean run(String id) {
    TriggerTask tt = triggerTaskMap.get(id);
    if (tt != null) {
      tt.getRunnable().run();
      return true;
    }
    return false;
  }
  public static boolean stop(String id) {
    TriggerTask tt = triggerTaskMap.get(id);
    if (tt != null) {
      Set<ScheduledTask> scheduledTasks = scheduledTaskRegistrar.getScheduledTasks();
      for (ScheduledTask st:scheduledTasks) {
        boolean b = st.getTask().getRunnable() == tt.getRunnable();
        if (b) {
          st.cancel();
          return true;
        }
      }
    }
    return false;
  }
  public static boolean start(Cron cron) throws Exception {
    try {
      triggerTaskMap.remove(cron.getId());
      TriggerTask tt = new TriggerTask(getRunnable(cron), getTrigger(cron));
      triggerTaskMap.put(cron.getId(), tt);
      scheduledTaskRegistrar.scheduleTriggerTask(tt);
      return true;
    } catch (Exception e) {
      e.printStackTrace();
    }
    return false;
  }
}

控制调用类,RCResult为自定义封装的返回结果类,可自行定义

@RestController
@RequestMapping("/cron")
@Slf4j
public class CronController {
  @Autowired
  private ICronService iCronService;
  @Autowired
  private RCScheduleTask rcScheduleTask;
  @GetMapping("/task/1")
  @Async
  public void task1() {
    log.info("模拟任务1-执行时间:{}", new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
  }
  @GetMapping("/task/2")
  @Async
  public void task2() {
    log.info("模拟任务2-执行时间:{}", new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
  }
  @GetMapping("/task/3")
  @Async
  public void task3() {
    log.info("模拟任务3-执行时间:{}", new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
  }
  @GetMapping("/run/{id}")
  public RCResult run(@PathVariable("id") String id) {
    Boolean b = RCScheduleTask.run(id);
    if (b) {
      return RCResult.ok("执行任务" + id + "成功");
    }
    return RCResult.error("执行任务" + id + "失败");
  }
  @GetMapping("/stop/{id}")
  public RCResult stop(@PathVariable("id") String id) {
    Boolean b = RCScheduleTask.stop(id);
    if (b) {
      return RCResult.ok("停止任务" + id + "成功");
    }
    return RCResult.error("停止任务" + id + "失败");
  }
  @GetMapping("/start/{id}")
  public RCResult start(@PathVariable("id") String id) throws Exception {
    Cron cron = iCronService.getById(id);
    if (cron != null) {
      Boolean b = RCScheduleTask.start(cron);
      if (b) {
        return RCResult.ok("开始任务" + id + "成功");
      }
    }
    return RCResult.error("开始任务" + id + "失败");
  }
}

2、方案详解

2.1 初始化

通过@EnableScheduling和@EnableAsync两个标签,开启定时任务和多线程

@Configuration
@EnableScheduling
@EnableAsync
@Slf4j
public class RCScheduleTask implements SchedulingConfigurer {

重写SchedulingConfigurer的configureTasks方法,可以在项目启动时自动加载状态为启用(cron_flag为1)定时任务列表

@Override
  public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
    scheduledTaskRegistrar = taskRegistrar;
    Cron cron = new Cron();
    cron.setCronFlag("1");
    List<Cron> list = iCronService.getList(cron);
    if (list != null) {
      initTriggerTask(list);
    }
  }

TriggerTask创建的两个参数Runnable和Trigger可以理解为实现的操作和执行的周期,在Runnable中我们通过反射的方式,从库中取到调用方法的类名和方法名,来执行接口操作,在Trigger中我们根据库中的cron表达式来设置执行周期

public void initTriggerTask(List<Cron> list) {
    triggerTaskMap = new HashMap<>();
    for (Cron cron : list) {
      TriggerTask triggerTask = new TriggerTask(getRunnable(cron), getTrigger(cron));
      scheduledTaskRegistrar.addTriggerTask(triggerTask);
      triggerTaskMap.put(cron.getId(), triggerTask);
    }
  }
  private static Runnable getRunnable(Cron cron) {
    return new Runnable() {
      @Override
      public void run() {
        Class<?> clazz;
        try {
          clazz = Class.forName(cron.getClassName());
          Object bean = BeansUtils.getBean(clazz);
          Method method = ReflectionUtils.findMethod(bean.getClass(), cron.getMethodName());
          ReflectionUtils.invokeMethod(method, bean);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    };
  }
  private static Trigger getTrigger(Cron cron) {
    return new Trigger() {
      @Override
      public Date nextExecutionTime(TriggerContext triggerContext) {
        CronTrigger trigger = new CronTrigger(cron.getCronExpression());
        Date nextExec = trigger.nextExecutionTime(triggerContext);
        return nextExec;
      }
    };
  }

库表数据

从执行结果中可以看到状态为已启用的task1和task2执行了,而task3并没有执行

2.2 单次执行

我们可以通过获取TriggerTask的Runnable来执行run()方法,来单次执行定时任务

任务类中定义run方法

public static boolean run(String id) {
    TriggerTask tt = triggerTaskMap.get(id);
    if (tt != null) {
      tt.getRunnable().run();
      return true;
    }
    return false;
  }

接口调用

@GetMapping("/run/{id}")
  public RCResult run(@PathVariable("id") String id) {
    Boolean b = RCScheduleTask.run(id);
    if (b) {
      return RCResult.ok("执行任务" + id + "成功");
    }
    return RCResult.error("执行任务" + id + "失败");
  }

模拟接口调用

从调用结果中可以看到,task1在06秒的时候单独执行了一次,并且没有影响后续执行

2.3 停止任务

停止任务需要执行ScheduledTask类的cancel()方法,由于在初始化时通过addTriggerTask方法并不会立刻加入到ScheduledTask列表中,所以需要在调用时通过ScheduledTaskRegistrar获取ScheduledTask列表,然后与TriggerTask的Runnable进行比较判断是否一致

任务类中定义stop

public static boolean stop(String id) {
    TriggerTask tt = triggerTaskMap.get(id);
    if (tt != null) {
      Set<ScheduledTask> scheduledTasks = scheduledTaskRegistrar.getScheduledTasks();
      for (ScheduledTask st:scheduledTasks) {
        boolean b = st.getTask().getRunnable() == tt.getRunnable();
        if (b) {
          st.cancel();
          return true;
        }
      }
    }
    return false;
  }

调用

@GetMapping("/stop/{id}")
  public RCResult stop(@PathVariable("id") String id) {
    Boolean b = RCScheduleTask.stop(id);
    if (b) {
      return RCResult.ok("停止任务" + id + "成功");
    }
    return RCResult.error("停止任务" + id + "失败");
  }

在调用结果中可以看到,task1不再执行了

2.4 启用任务

启用任务需要通过ScheduledTaskRegistrar的scheduleTriggerTask()方法进行调用,由于源码中并未提供针对Runnable或Trigger的单独修改方法,所以在这里我们通过新建实例进行替换,在执行前先停止任务

public static boolean start(Cron cron) throws Exception {
    try {
      stop(cron.getId());
      triggerTaskMap.remove(cron.getId());
      TriggerTask tt = new TriggerTask(getRunnable(cron), getTrigger(cron));
      triggerTaskMap.put(cron.getId(), tt);
      scheduledTaskRegistrar.scheduleTriggerTask(tt);
      return true;
    } catch (Exception e) {
      e.printStackTrace();
    }
    return false;
  }

调用

@GetMapping("/start/{id}")
  public RCResult start(@PathVariable("id") String id) throws Exception {
    Cron cron = iCronService.getById(id);
    if (cron != null) {
      Boolean b = RCScheduleTask.start(cron);
      if (b) {
        return RCResult.ok("开始任务" + id + "成功");
      }
    }
    return RCResult.error("开始任务" + id + "失败");
  }

通过调用结果,可以看到在启用后,task1重新加入到定时任务队列中了

在修改task1的执行周期后再次调用start方法

从调用结果中可以看到,task1的周期已被更改

三、小结

昨天刚接触定时任务,一开始也是在网上搜索资料,后来发现案例都无法满足自身需求,或者只是讲解怎么使用,根本无法映射到实际业务上,所以通过对源码的分析,一层层的梳理,模拟了此篇关于定时任务的解决方案,由于时间原因,在此省略了前端界面的编写,但大多数实际需求无非也就是通过一个开关或按钮来对任务进行启用、停止操作,或者额外需要单次执行任务,在此对动态定时任务的方案进行记录,同时也希望能帮助到遇到同样问题的同学们

到此这篇关于Springboot实现动态定时任务流程详解的文章就介绍到这了,更多相关Springboot动态定时任务内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot整合Quartz实现定时任务详解

    目录 Quartz简介 核心概念 Scheduler JobDetail Job Trigger SpringBoot整合Quartz 准备数据库表 Maven相关依赖 配置文件 quartz配置类 创建任务类 创建监听类 运行结果 Quartz简介 Quartz 是一个开源的作业调度框架,它完全由 Java 写成,并设计用于 J2SE 和 J2EE 应用中.它提供了巨大的灵活性而不牺牲简单性.你能够用它来为执行一个作业而创建简单的或复杂的调度.它有很多特征,如:数据库支持,集群,插件,EJB

  • SpringBoot定时任务实现数据同步的方法

    本文实例为大家分享了SpringBoot定时任务实现数据同步的具体代码,供大家参考,具体内容如下 前言 业务的需求是,通过中台调用api接口获得,设备数据,要求现实设备数据的同步. 方案一:通过轮询接口的方式执行 pullData() 方法实现数据同步 该方式的原理是先清空之前的所有数据,然后重新插入通过api调用获取的最新数据.该方法的优点,逻辑简单.缺点是,频繁删除.插入数据.再调用查询数据时候,某一时刻,数据全部删除,还没及时插入的时候.数据可能有异常. 方案二:通过轮询接口的方式执行 p

  • SpringBoot 整合 Quartz 定时任务框架详解

    目录 前言 一.简单聊一聊 Quartz 1.1.Quartz 概念 二.SpringBoot 使用 Quartz 2.1.基本步骤 2.2.执行 Quartz 需要的SQL文件 2.3.Controller 2.4.Service 划重点 2.5.实体类 2.6.简单的 Job 案例 2.7.那么该如何使用呢? 前言 在选择技术栈之前,一定要先明确一件事情,你真的需要用它吗?还有其他方式可以使用吗? 相比其他技术技术,优点在哪里呢?使用了之后的利与弊等等. 写这个主要是因为一直想写一下定时任务

  • SpringBoot日程管理Quartz与定时任务Task实现详解

    目录 1 Quartz 核⼼概念 Springboot整合Quartz 1.导⼊springboot整合Quartz的starter 2.定义任务Bean 3.创建Quartz配置类 2. Task 1.开启定时任务功能 2.定义Bean 3.配置文件 任务系统指的是定时任务.定时任务是企业级开发中必不可少的组成部分,诸如⻓周期业务数据的计算,都会⽤到定时任务 1 Quartz Quartz是OpenSymphony开源组织在Job scheduling领域的⼀个开源项⽬,完全由Java开发的⼀

  • 详解SpringBoot定时任务功能

    目录 一 背景 二 动态定时任务调度 三 多节点任务执行问题 四 后记 一 背景 项目中需要一个可以动态新增定时定时任务的功能,现在项目中使用的是xxl-job定时任务调度系统,但是经过一番对xxl-job功能的了解,发现xxl-job对项目动态新增定时任务,动态删除定时任务的支持并不是那么好,所以需要自己手动实现一个定时任务的功能 二 动态定时任务调度 1 技术选择 Timer or ScheduledExecutorService 这两个都能实现定时任务调度,先看下Timer的定时任务调度

  • SpringBoot集成quartz实现定时任务详解

    目录 准备知识点 什么是Quartz Quartz的体系结构 什么是Quartz持久化 实现案例 - 单实例方式 实现案例 - 分布式方式 后端实现 前端实现 测试效果 准备知识点 需要了解常用的Quartz框架. 什么是Quartz 来源百度百科, 官网地址:http://www.quartz-scheduler.org/ Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用.Quartz可以用来

  • SpringBoot+Quartz实现动态定时任务

    本文实例为大家分享了springBoot+Quartz实现动态定时任务的具体代码,供大家参考,具体内容如下 目前常用的几种任务调度 Timer,简单无门槛,一般也没人用. spring @Scheduled注解,一般集成于项目中,小任务很方便. 开源工具 Quartz,分布式集群开源工具,以下两个分布式任务应该都是基于Quartz实现的,可以说是中小型公司必选,当然也视自身需求而定. 分布式任务 XXL-JOB,是一个轻量级分布式任务调度框架,支持通过 Web 页面对任务进行 CRUD 操作,支

  • Springboot实现动态定时任务流程详解

    目录 一.静态 二.动态 1.基本代码 2.方案详解 2.1 初始化 2.2 单次执行 2.3 停止任务 2.4 启用任务 三.小结 一.静态 静态的定时任务可以直接使用注解@Scheduled,并在启动类上配置@EnableScheduling即可 @PostMapping("/list/test1") @Async @Scheduled(cron = "0 * * * * ?") public void test1() throws Exception { Ob

  • SpringBoot解析yml全流程详解

    目录 背景 加载监听器 执行run方法 加载配置文件 封装Node 调用构造器 思考 背景 前几天的时候,项目里有一个需求,需要一个开关控制代码中是否执行一段逻辑,于是理所当然的在yml文件中配置了一个属性作为开关,再配合nacos就可以随时改变这个值达到我们的目的,yml文件中是这样写的: switch: turnOn: on 程序中的代码也很简单,大致的逻辑就是下面这样,如果取到的开关字段是on的话,那么就执行if判断中的代码,否则就不执行: @Value("${switch.turnOn}

  • SpringBoot响应处理实现流程详解

    目录 1.相关依赖 2.ReturnValueHandlers—返回值处理器 3.HttpMessageConvert—消息转换器 4.开启浏览器参数方式内容协商功能 1.相关依赖 web项目引入的启动器spring-boot-starter-web中含有 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</arti

  • SpringBoot自定义Starter实现流程详解

    目录 starter起步依赖 starter命名规则 自定义starter new module 添加依赖 simplebean 自动配置类 META-INF\spring.factories 在spring-boot-mytest中引入mystarter-spring-boot-starter 添加配置 通过@Autowired引用 启动访问 starter起步依赖 starter起步依赖是springboot一种非常重要的机制, 它打包了某些场景下需要用到依赖,将其统一集成到starter,

  • SpringBoot自定义启动器Starter流程详解

    目录 一.背景 二.自定义启动器 1.创建一个启动器的自动配置模块 2.创建一个启动器模块 3.在业务模块中引入启动器 一.背景 虽然Spring官方给我们提供了很多的启动器供我们使用 但有时候我们也会遇到某些特殊场景,这些启动器满足不了 这个时候就需要自定义一个启动器供我们使用 二.自定义启动器 在之前学习Spring Boot的过程中,我们已经对启动器有了一个大致的了解 Spring Boot实现某个功能,一般是引入对应场景的启动器(一般不写代码,只是声明这个启动器需要引用哪些依赖),然后这

  • SpringBoot如何实现定时任务示例详解

    目录 写在前面 一.基于注解(@Scheduled) 二.数据库动态配置 1.表数据添加,资源配置 1.1 添加表 1.2 插入两条数据,job_key根据是完整的类名 1.3 引入依赖 1.4 配置application.yml 2.疯狂贴代码 2.1 创建定时任务线程池 2.2 项目启动时初始化定时任务 2.3 定时任务公共接口 2.4 创建两个定时任务实现类 2.5 定时任务管理接口 2.6 定时任务管理实现类 2.8 上面用到的获取Bean的工具类SpringContextUtil 2.

  • SpringBoot设置动态定时任务的方法详解

    之前写过文章记录怎么在SpringBoot项目中简单使用定时任务,不过由于要借助cron表达式且都提前定义好放在配置文件里,不能在项目运行中动态修改任务执行时间,实在不太灵活. 经过网上搜索学习后,特此记录如何在SpringBoot项目中实现动态定时任务. 因为只是一个demo,所以只引入了需要的依赖: <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <ar

  • SpringBoot定时任务动态扩展ScheduledTaskRegistrar详解

    目录 摘要 ScheduledTaskRegistrar类简要描述 平常使用方式配置 原理分析 DynamicScheduledTaskRegistrar 动态任务注册类 线程池数量问题 新增调度任务 删除调度任务 摘要 本文主要介绍基于SpringBoot定时任务ScheduledTaskRegistrar的动态扩展,实现定时任务的动态新增和删除. ScheduledTaskRegistrar类简要描述 平常使用方式配置 Application启动类上添加注解@EnableScheduling

  • Java nacos动态配置实现流程详解

    目录 一.前言 二.在nacos上创建配置文件 创建配置文件 配置说明 发布并检查配置文件 三. 修改项目配置与动态读取配置文件 添加 nacos 动态配置依赖 在controller与service中使用动态配置 四. 动态配置网关的使用 一.前言 使用动态配置的原因: properties 和 yaml 是写到项目中的,好多时候有些配置需要修改,每次修改就要重新启动项目,不仅增加了系统的不稳定性,也大大提高了维护成本,非常麻烦,且耗费时间. 使用动态配置,则可以避免这些麻烦,可以动态的修改配

随机推荐