2025年3月13日 星期四 甲辰(龙)年 月十二 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > Spring Boot

SpringBoot 实现固定、动态定时任务 | 三种实现方式 | 附源代码

时间:06-15来源:作者:点击数:52

背景:📅

最近要用到这个定时任务,之前就简单使用注解的那种方式,需求一变化,就得重新修改。

就想到了动态定时任务,连接数据库来动态选择,这样确实解决了问题。

但是仍然有一个缺陷,就是没法设置任务的执行时间,无法做到像 QQ 发说说那样,给 xdm 祝福生日时,设定说说为晚上00:00发布。

本文就以上三点用自己的思路写了一个小Demo,希望对大家有所帮助。

👩‍💻

前言:

阅读完本文:🐱‍👓

  1. 知晓SpringBoot用注解如何实现定时任务
  2. 明白SpringBoot如何实现一个动态定时任务 (与数据库相关联实现)
  3. 理解SpringBoot实现设置时间执行定时任务 (使用ThreadPoolTaskScheduler实现)

一、注解实现定时任务

用注解实现是真的简单,只要会 cron 表达式就行。🧙‍♂️

第一步: 主启动类上加上@EnableScheduling注解

  • @EnableScheduling
  • @SpringBootApplication
  • public class SpringBootScheduled {
  • public static void main(String[] args) {
  • SpringApplication.run(SpringBootScheduled.class);
  • }
  • }

第二步:写一个类,注入到Spring,关键就是@Scheduled注解。 () 里就是 cron 表达式,用来说明这个方法的执行周期的。 🛌

  • /**
  • * 定时任务 静态定时任务
  • *
  • * 第一位,表示秒,取值0-59
  • * 第二位,表示分,取值0-59
  • * 第三位,表示小时,取值0-23
  • * 第四位,日期天/日,取值1-31
  • * 第五位,日期月份,取值1-12
  • * 第六位,星期,取值1-7,1表示星期天,2表示星期一
  • * 第七位,年份,可以留空,取值1970-2099
  • * @author crush
  • * @since 1.0.0
  • * @Date: 2021-07-27 21:13
  • */
  • @Component
  • public class SchedulingTaskBasic {
  • /**
  • * 每五秒执行一次
  • */
  • @Scheduled(cron = "*/5 * * * * ?")
  • private void printNowDate() {
  • long nowDateTime = System.currentTimeMillis();
  • System.out.println("固定定时任务执行:--->"+nowDateTime+",此任务为每五秒执行一次");
  • }
  • }

执行效果:

image-20210929115011311

源码在文末。🏍

二、动态定时任务

其实也非常的简单。

2.1、建数据表

第一步:建个数据库表。

  • CREATE TABLE `tb_cron` (
  • `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '动态定时任务时间表',
  • `cron_expression` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '定时任务表达式',
  • `cron_describe` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
  • PRIMARY KEY (`id`) USING BTREE
  • ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
  • INSERT INTO `tb_cron` VALUES (1, '0 0/1 * * * ?', '每分钟执行一次');

2.2、导入依赖,基础编码

第二步:导入数据库相关依赖,做到能从数据库查询数据。大家都会。🤸‍♂️

第三步: 编码

实体类:

  • @Data
  • @TableName("tb_cron")
  • public class Cron {
  • private Long id;
  • private String cronExpression;
  • private String cronDescribe;
  • }

mapper层:

  • @Repository
  • public interface CronMapper extends BaseMapper<Cron> {
  • @Select("select cron_expression from tb_cron where id=1")
  • String getCron1();
  • }

2.3、主要实现代码

第四步:写一个类 实现SchedulingConfigurer🍻

实现void configureTasks(ScheduledTaskRegistrar taskRegistrar);方法,此方法的作用就是根据给定的 ScheduledTaskRegistrar 注册 TaskScheduler 和特定的Task实例

  • @Component
  • public class CompleteScheduleConfig implements SchedulingConfigurer {
  • @Autowired
  • @SuppressWarnings("all")
  • CronMapper cronMapper;
  • /**
  • * 执行定时任务.
  • */
  • @Override
  • public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
  • taskRegistrar.addTriggerTask(
  • //1.添加任务内容(Runnable)
  • () -> System.out.println("执行动态定时任务1: " + LocalDateTime.now().toLocalTime()+",此任务执行周期由数据库中的cron表达式决定"),
  • //2.设置执行周期(Trigger)
  • triggerContext -> {
  • //2.1 从数据库获取执行周期
  • String cron = cronMapper.getCron1();
  • //2.2 合法性校验.
  • if (cron!=null) {
  • // Omitted Code ..
  • }
  • //2.3 返回执行周期(Date)
  • return new CronTrigger(cron).nextExecutionTime(triggerContext);
  • }
  • );
  • }
  • }

2.4、效果

image-20210929123613106

注意:当你修改了任务执行周期后,生效时间为执行完最近一次任务后。这一点是需要注意的,用生活中的例子理解就是我们取消电话卡的套餐也要下个月生效,含义是一样的。

源码同样在文末。

三、实现设置时间定时任务

通常业务场景是我前言中说的那样,是一次性的定时任务。如:我设置了我写的这篇文章的发布时间为今天下午的两点,执行完就删除没有了。一次性的。

实现主要依靠于TaskSchedulerScheduledFuture<?> schedule(Runnable task, Trigger trigger);方法来实现。其本质和动态定时任务的实现是一样的。

3.1、实现重点

代码中都含有注解,不多做阐述。

  • import cn.hutool.core.convert.ConverterRegistry;
  • import com.crush.scheduled.entity.Task;
  • import lombok.extern.slf4j.Slf4j;
  • import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
  • import org.springframework.stereotype.Component;
  • import java.time.LocalDateTime;
  • import java.util.*;
  • import java.util.concurrent.ConcurrentHashMap;
  • import java.util.concurrent.CopyOnWriteArrayList;
  • import java.util.concurrent.ScheduledFuture;
  • /**
  • * @author crush
  • */
  • @Component
  • @Slf4j
  • public class DynamicTaskService {
  • /**
  • * 以下两个都是线程安全的集合类。
  • */
  • public Map<String, ScheduledFuture<?>> taskMap = new ConcurrentHashMap<>();
  • public List<String> taskList = new CopyOnWriteArrayList<String>();
  • private final ThreadPoolTaskScheduler syncScheduler;
  • public DynamicTaskService(ThreadPoolTaskScheduler syncScheduler) {
  • this.syncScheduler = syncScheduler;
  • }
  • /**
  • * 查看已开启但还未执行的动态任务
  • * @return
  • */
  • public List<String> getTaskList() {
  • return taskList;
  • }
  • /**
  • * 添加一个动态任务
  • *
  • * @param task
  • * @return
  • */
  • public boolean add(Task task) {
  • // 此处的逻辑是 ,如果当前已经有这个名字的任务存在,先删除之前的,再添加现在的。(即重复就覆盖)
  • if (null != taskMap.get(task.getName())) {
  • stop(task.getName());
  • }
  • // hutool 工具包下的一个转换类型工具类 好用的很
  • ConverterRegistry converterRegistry = ConverterRegistry.getInstance();
  • Date startTime = converterRegistry.convert(Date.class, task.getStart());
  • // schedule :调度给定的Runnable ,在指定的执行时间调用它。
  • //一旦调度程序关闭或返回的ScheduledFuture被取消,执行将结束。
  • //参数:
  • //任务 – 触发器触发时执行的 Runnable
  • //startTime – 任务所需的执行时间(如果这是过去,则任务将立即执行,即尽快执行)
  • ScheduledFuture<?> schedule = syncScheduler.schedule(getRunnable(task), startTime);
  • taskMap.put(task.getName(), schedule);
  • taskList.add(task.getName());
  • return true;
  • }
  • /**
  • * 运行任务
  • *
  • * @param task
  • * @return
  • */
  • public Runnable getRunnable(Task task) {
  • return () -> {
  • log.info("---动态定时任务运行---");
  • try {
  • System.out.println("此时时间==>" + LocalDateTime.now());
  • System.out.println("task中设定的时间==>" + task);
  • Thread.sleep(10);
  • } catch (InterruptedException e) {
  • e.printStackTrace();
  • }
  • log.info("---end--------");
  • };
  • }
  • /**
  • * 停止任务
  • *
  • * @param name
  • * @return
  • */
  • public boolean stop(String name) {
  • if (null == taskMap.get(name)) {
  • return false;
  • }
  • ScheduledFuture<?> scheduledFuture = taskMap.get(name);
  • scheduledFuture.cancel(true);
  • taskMap.remove(name);
  • taskList.remove(name);
  • return true;
  • }
  • }

3.2、异步线程池的配置

  • /**
  • * 异步线程池ThreadPoolExecutor 配置类
  • *
  • * @Author: crush
  • * @Date: 2021-07-23 14:14
  • */
  • @Configuration
  • public class ThreadPoolTaskExecutorConfig {
  • @Bean
  • public ThreadPoolTaskScheduler syncScheduler() {
  • ThreadPoolTaskScheduler syncScheduler = new ThreadPoolTaskScheduler();
  • syncScheduler.setPoolSize(5);
  • // 这里给线程设置名字,主要是为了在项目能够更快速的定位错误。
  • syncScheduler.setThreadGroupName("syncTg");
  • syncScheduler.setThreadNamePrefix("syncThread-");
  • syncScheduler.initialize();
  • return syncScheduler;
  • }
  • }

3.3、业务代码

这里需要注意一个点,我给项目中的LocalDateTime做了类型转换。这里没贴出来(主要是复制以前的代码遗留下来的,源码中都有)

大家简单使用,可以直接用注解 标注在LocalDateTime属性上即可。

  • package com.crush.scheduled.controller;
  • import com.crush.scheduled.entity.Task;
  • import com.crush.scheduled.service.DynamicTaskService;
  • import org.springframework.web.bind.annotation.*;
  • import java.util.List;
  • /**
  • * @Author: crush
  • * @Date: 2021-07-29 15:26
  • * version 1.0
  • */
  • @RestController
  • @RequestMapping("/dynamicTask")
  • public class DynamicTaskController {
  • private final DynamicTaskService dynamicTask;
  • public DynamicTaskController(DynamicTaskService dynamicTask) {
  • this.dynamicTask = dynamicTask;
  • }
  • /**
  • * 查看已开启但还未执行的动态任务
  • * @return
  • */
  • @GetMapping
  • public List<String> getStartingDynamicTask(){
  • return dynamicTask.getTaskList();
  • }
  • /**
  • * 开启一个动态任务
  • * @param task
  • * @return
  • */
  • @PostMapping("/dynamic")
  • public String startDynamicTask(@RequestBody Task task){
  • // 将这个添加到动态定时任务中去
  • dynamicTask.add(task);
  • return "动态任务:"+task.getName()+" 已开启";
  • }
  • /**
  • * 根据名称 停止一个动态任务
  • * @param name
  • * @return
  • */
  • @DeleteMapping("/{name}")
  • public String stopDynamicTask(@PathVariable("name") String name){
  • // 将这个添加到动态定时任务中去
  • if(!dynamicTask.stop(name)){
  • return "停止失败,任务已在进行中.";
  • }
  • return "任务已停止";
  • }
  • }

简单封装的一个实体类:

  • /**
  • * @Author: crush
  • * @Date: 2021-07-29 15:35
  • * version 1.0
  • */
  • @Data
  • @Accessors(chain = true) // 方便链式编写 习惯所然
  • public class Task {
  • /**
  • * 动态任务名曾
  • */
  • private String name;
  • /**
  • * 设定动态任务开始时间
  • */
  • private LocalDateTime start;
  • }

3.4、效果

💫💨

开启一个动态任务:

image-20210929125405707

查看开启还未执行的动态任务:

image-20210929125423119

执行结果:

image-20210929125549282

和我们代码中是一模一样的。

image-20210929125638509

停止任务:

image-20210929125706997
image-20210929125737363

再去查看就是已经停止的拉

源码springboot-scheduled

本文就是简单介绍了,具体使用时还需要根据具体情况具体分析啦。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门