SpringBoot实现固定和动态定时任务的三种方法

 更新时间:2023年09月14日 15:43:38   作者:搬山道猿  
定时器是我们项目中经常会用到的,本文主要介绍了SpringBoot实现固定和动态定时任务的三种方法,具有一定的参考价值,感兴趣的可以了解一下

前言:

阅读完本文:

  • 知晓 SpringBoot 用注解如何实现定时任务
  • 明白 SpringBoot 如何实现一个动态定时任务 (与数据库相关联实现)
  • 理解 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+",此任务为每五秒执行一次");
    }
}

执行效果:

二、动态定时任务

其实也非常的简单。

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、效果

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

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

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

实现主要依靠于 TaskScheduler ScheduledFuture<?> 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、效果

开启一个动态任务:

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

执行结果:

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

停止任务:

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

到此这篇关于SpringBoot实现固定和动态定时任务的三种方法的文章就介绍到这了,更多相关SpringBoot 固定和动态定时任务内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot bean查询加载顺序流程详解

    SpringBoot bean查询加载顺序流程详解

    当你在项目启动时需要提前做一个业务的初始化工作时,或者你正在开发某个中间件需要完成自动装配时。你会声明自己的Configuration类,但是可能你面对的是好几个有互相依赖的Bean
    2023-03-03
  • Java if-else 多重嵌套的优化方式

    Java if-else 多重嵌套的优化方式

    这篇文章主要介绍了Java if-else 多重嵌套的优化方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Java 并发编程之ForkJoin框架

    Java 并发编程之ForkJoin框架

    这篇文章主要为大家介绍了Java ForkJoin框架,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助,希望能够给你带来帮助
    2021-11-11
  • spring-boot 如何实现单次执行程序

    spring-boot 如何实现单次执行程序

    这篇文章主要介绍了spring-boot 实现单次执行程序方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • Java 多线程并发AbstractQueuedSynchronizer详情

    Java 多线程并发AbstractQueuedSynchronizer详情

    这篇文章主要介绍了Java 多线程并发AbstractQueuedSynchronizer详情,文章围绕主题展开想象的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下
    2022-06-06
  • java实现注册登录系统

    java实现注册登录系统

    这篇文章主要为大家详细介绍了java实现注册登录系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • Mybatis批量插入返回插入成功后的主键id操作

    Mybatis批量插入返回插入成功后的主键id操作

    这篇文章主要介绍了Mybatis批量插入返回插入成功后的主键id操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-03-03
  • Fluent Mybatis快速入门详细教程

    Fluent Mybatis快速入门详细教程

    由于FluentMybatis是基于mybatis上做封装和扩展的,所以这里主要聊聊mybatis处理的方式,以及给出FluentMybatis的解放方案。对Fluent Mybatis入门相关知识感兴趣的朋友一起看看吧
    2021-08-08
  • Java concurrency之锁_动力节点Java学院整理

    Java concurrency之锁_动力节点Java学院整理

    这篇文章主要为大家详细介绍了Java concurrency之锁的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06
  • Java中double数值保留两位小数的4种实现方式举例

    Java中double数值保留两位小数的4种实现方式举例

    在Java编程中,我们经常遇到需要对double类型的浮点数进行精确截断或四舍五入保留两位小数的需求,这篇文章主要给大家介绍了关于Java中double数值保留两位小数的4种实现方式,需要的朋友可以参考下
    2024-07-07

最新评论