相关文章推荐
傻傻的荒野  ·  SpringBoot整合c3p0、Druid ...·  1 年前    · 
稳重的自行车  ·  Oracle Netsuite - Tableau·  1 年前    · 
知识渊博的草稿本  ·  Linux ...·  1 年前    · 
本文主要介绍了SpringBoot架构下动态定时任务的使用,定时任务表达式配置在数据库中,其它项目架构也可以借鉴,实现思路是一样的。
支持查看任务状态,动态修改任务时间,停止任务等;

1.前置准备

1.1 创建任务表

CREATE TABLE `scheduled_task` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `task_key` varchar(128) NOT NULL COMMENT '任务key值(使用bean名称)',
  `task_desc` varchar(128) DEFAULT NULL COMMENT '任务描述',
  `task_cron` varchar(128) NOT NULL COMMENT '任务表达式',
  `init_start_flag` int(2) NOT NULL DEFAULT '1' COMMENT '程序初始化是否启动 1 是 0 否',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniqu_task_key` (`task_key`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

1.2添加初始化测试数据

INSERT INTO `cron`.`scheduled_task`(`id`, `task_key`, `task_desc`, `task_cron`, `init_start_flag`, `create_time`, `update_time`) VALUES (1, 'scheduledTask01', '定时任务01', '0/5 * * * * ?', 1, NOW(), NOW());
INSERT INTO `cron`.`scheduled_task`(`id`, `task_key`, `task_desc`, `task_cron`, `init_start_flag`, `create_time`, `update_time`) VALUES (2, 'scheduledTask02', '定时任务02', '0/2 * * * * ?', 0, NOW(), NOW());
INSERT INTO `cron`.`scheduled_task`(`id`, `task_key`, `task_desc`, `task_cron`, `init_start_flag`, `create_time`, `update_time`) VALUES (3, 'scheduledTask03', '定时任务03', '0/2 * * * * ?', 1, NOW(), NOW());

1.3创建Maven项目并添加Maven依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.12.RELEASE</version>
    </parent>
    <!--版本号 -->
    <properties>
        <fastjson.version>1.2.47</fastjson.version>
        <mybatis-spring-boot.version>1.3.1</mybatis-spring-boot.version>
        <mybatis-spring-boot-starter.version>1.3.0</mybatis-spring-boot-starter.version>
        <mybatis-plus.version>2.1-gamma</mybatis-plus.version>
        <druid.version>1.1.2</druid.version>
    </properties>
    <dependencies>
        <!--依赖管理 -->
        <dependency><!--添加Web依赖 -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency><!--添加Mybatis依赖 -->
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis-spring-boot.version}</version>
        </dependency>
        <dependency><!--添加MySql依赖 -->
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency><!--添加Test依赖 -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- json 工具 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
    </dependencies>

1.4添加配置文件application.properties

server.port=8081
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC
spring.datasource.password=root
spring.datasource.username=root

1.5添加SpringBoot启动类

@SpringBootApplication
//开启定时任务 使用ThreadPoolTaskScheduler不用开启, 使用@Scheduled时需开启
@EnableScheduling
public class NexusDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(NexusDemoApplication.class, args);

2.任务调度

2.1创建定时任务线程池,初始化任务Map

package com.hy.nexus.config;
import com.hy.nexus.enums.ScheduledTaskEnum;
import com.hy.nexus.service.ScheduledTaskJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import java.util.Map;
 * @author hy
 * @description:
 * @date 2020/09/28
@Configuration
public class ScheduledTaskConfig {
    private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTaskConfig.class);
    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        LOGGER.info("创建定时任务调度线程池 start");
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(20);
        threadPoolTaskScheduler.setThreadNamePrefix("taskExecutor-");
        threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
        LOGGER.info("创建定时任务调度线程池 end");
        return threadPoolTaskScheduler;
     * 通过枚举初始化定时任务Map
     * key :任务key
     * value : 执行接口实现
    @Bean(name = "scheduledTaskJobMap")
    public Map<String, ScheduledTaskJob> scheduledTaskJobMap() {
        return ScheduledTaskEnum.initScheduledTask();

2.2创建任务实体Bean

public class ScheduledTaskBean {
     * 任务key值 唯一
    private String taskKey;
     * 任务描述
    private String taskDesc;
     * 任务表达式
    private String taskCron;
     * 程序初始化是否启动 1 是 0 否
    private Integer initStartFlag;
     * 当前是否已启动
    private boolean startFlag;
    //省略 get,set

2.3Mapper接口

* 定时任务表 mapper * demo 项目未使用 xml方式,使用注解方式查询数据以便演示 @Mapper public interface ScheduledTaskMapper { * 根据key 获取 任务信息 @Select("select task_key as taskKey,task_desc as taskDesc,task_cron as taskCron,init_start_flag as initStartFlag from scheduled_task where task_key = '${taskKey}' ") ScheduledTaskBean getByKey(@Param("taskKey") String taskKey); * 获取程序初始化需要自启的任务信息 @Select("select task_key as taskKey,task_desc as taskDesc,task_cron as taskCron,init_start_flag as initStartFlag from scheduled_task where init_start_flag=1 ") List<ScheduledTaskBean> getAllNeedStartTask(); * 获取所有任务 @Select("select task_key as taskKey,task_desc as taskDesc,task_cron as taskCron,init_start_flag as initStartFlag from scheduled_task ") List<ScheduledTaskBean> getAllTask();

2.4创建调度任务公共父接口

* 调度任务公共父接口 public interface ScheduledTaskJob extends Runnable {

2.5实现父接口,重写run方法

import com.scheduled.dynamic.service.ScheduledTaskJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 * 任务 01
public class ScheduledTask01 implements ScheduledTaskJob {
    private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTask01.class);
    @Override
    public void run() {
        LOGGER.info("ScheduledTask => 01  run  当前线程名称 {} ", Thread.currentThread().getName());

2.6创建定时任务枚举类

import com.scheduled.dynamic.service.ScheduledTaskJob;
import com.scheduled.dynamic.task.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 * 定时任务枚举值
 * 注:key 需要与数据库保持一致
public enum ScheduledTaskEnum {
     * 任务1
    TASK_01("scheduledTask01", new ScheduledTask01()),
     * 任务2
    TASK_02("scheduledTask02", new ScheduledTask02()),
     * 任务3
    TASK_03("scheduledTask03", new ScheduledTask03()),
     * 定时任务key
    private String taskKey;
     * 定时任务 执行实现类
    private ScheduledTaskJob scheduledTaskJob;
    ScheduledTaskEnum(String taskKey, ScheduledTaskJob scheduledTaskJob) {
        this.taskKey = taskKey;
        this.scheduledTaskJob = scheduledTaskJob;
     * 初始化 所有任务
    public static Map<String, ScheduledTaskJob> initScheduledTask() {
        if (ScheduledTaskEnum.values().length < 0) {
            return new ConcurrentHashMap<>();
        Map<String, ScheduledTaskJob> scheduledTaskJobMap = new ConcurrentHashMap<>();
        for (ScheduledTaskEnum taskEnum : ScheduledTaskEnum.values()) {
            scheduledTaskJobMap.put(taskEnum.taskKey, taskEnum.scheduledTaskJob);
        return scheduledTaskJobMap;

2.7功能核心接口

* 定时任务接口 public interface ScheduledTaskService { * 所有任务列表 List<ScheduledTaskBean> taskList(); * 根据任务key 启动任务 Boolean start(String taskKey); * 根据任务key 停止任务 Boolean stop(String taskKey); * 根据任务key 重启任务 Boolean restart(String taskKey); * 程序启动时初始化 ==> 启动所有正常状态的任务 void initAllTask(List<ScheduledTaskBean> scheduledTaskBeanList); * 定时任务实现 @Service public class ScheduledTaskServiceImpl implements ScheduledTaskService { private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTaskServiceImpl.class); @Autowired private ScheduledTaskMapper taskMapper; * 可重入锁 private ReentrantLock lock = new ReentrantLock(); * 定时任务线程池 @Autowired private ThreadPoolTaskScheduler threadPoolTaskScheduler; * 所有定时任务存放Map * key :任务 key * value:任务实现 @Autowired @Qualifier(value = "scheduledTaskJobMap") private Map<String, ScheduledTaskJob> scheduledTaskJobMap; * 存放已经启动的任务map private Map<String, ScheduledFuture> scheduledFutureMap = new ConcurrentHashMap<>(); * 所有任务列表 @Override public List<ScheduledTaskBean> taskList() { LOGGER.info(">>>>>> 获取任务列表开始 >>>>>> "); //数据库查询所有任务 => 未做分页 List<ScheduledTaskBean> taskBeanList = taskMapper.getAllTask(); if (CollectionUtils.isEmpty(taskBeanList)) { return new ArrayList<>(); for (ScheduledTaskBean taskBean : taskBeanList) { String taskKey = taskBean.getTaskKey(); //是否启动标记处理 taskBean.setStartFlag(this.isStart(taskKey)); LOGGER.info(">>>>>> 获取任务列表结束 >>>>>> "); return taskBeanList; * 根据任务key 启动任务 @Override public Boolean start(String taskKey) { LOGGER.info(">>>>>> 启动任务 {} 开始 >>>>>>", taskKey); //添加锁放一个线程启动,防止多人启动多次 lock.lock(); LOGGER.info(">>>>>> 添加任务启动锁完毕"); try { //校验是否已经启动 if (this.isStart(taskKey)) { LOGGER.info(">>>>>> 当前任务已经启动,无需重复启动!"); return false; //校验任务是否存在 if (!scheduledTaskJobMap.containsKey(taskKey)) { return false; //根据key数据库获取任务配置信息 ScheduledTaskBean scheduledTask = taskMapper.getByKey(taskKey); //启动任务 this.doStartTask(scheduledTask); } finally { // 释放锁 lock.unlock(); LOGGER.info(">>>>>> 释放任务启动锁完毕"); LOGGER.info(">>>>>> 启动任务 {} 结束 >>>>>>", taskKey); return true; * 根据 key 停止任务 @Override public Boolean stop(String taskKey) { LOGGER.info(">>>>>> 进入停止任务 {} >>>>>>", taskKey); //当前任务实例是否存在 boolean taskStartFlag = scheduledFutureMap.containsKey(taskKey); LOGGER.info(">>>>>> 当前任务实例是否存在 {}", taskStartFlag); if (taskStartFlag) { //获取任务实例 ScheduledFuture scheduledFuture = scheduledFutureMap.get(taskKey); //关闭实例 scheduledFuture.cancel(true); LOGGER.info(">>>>>> 结束停止任务 {} >>>>>>", taskKey); return taskStartFlag; * 根据任务key 重启任务 @Override public Boolean restart(String taskKey) { LOGGER.info(">>>>>> 进入重启任务 {} >>>>>>", taskKey); //先停止 this.stop(taskKey); //再启动 return this.start(taskKey); * 程序启动时初始化 ==> 启动所有正常状态的任务 @Override public void initAllTask(List<ScheduledTaskBean> scheduledTaskBeanList) { LOGGER.info("程序启动 ==> 初始化所有任务开始 !size={}", scheduledTaskBeanList.size()); if (CollectionUtils.isEmpty(scheduledTaskBeanList)) { return; for (ScheduledTaskBean scheduledTask : scheduledTaskBeanList) { //任务 key String taskKey = scheduledTask.getTaskKey(); //校验是否已经启动 if (this.isStart(taskKey)) { continue; //启动任务 this.doStartTask(scheduledTask); LOGGER.info("程序启动 ==> 初始化所有任务结束 !size={}", scheduledTaskBeanList.size()); * 执行启动任务 private void doStartTask(ScheduledTaskBean scheduledTask) { //任务key String taskKey = scheduledTask.getTaskKey(); //定时表达式 String taskCron = scheduledTask.getTaskCron(); //获取需要定时调度的接口 ScheduledTaskJob scheduledTaskJob = scheduledTaskJobMap.get(taskKey); LOGGER.info(">>>>>> 任务 [ {} ] ,cron={}", scheduledTask.getTaskDesc(), taskCron); ScheduledFuture scheduledFuture = threadPoolTaskScheduler.schedule(scheduledTaskJob, new Trigger() { @Override public Date nextExecutionTime(TriggerContext triggerContext) { CronTrigger cronTrigger = new CronTrigger(taskCron); return cronTrigger.nextExecutionTime(triggerContext); }); //将启动的任务放入 map scheduledFutureMap.put(taskKey, scheduledFuture); * 任务是否已经启动 private Boolean isStart(String taskKey) { //校验是否已经启动 if (scheduledFutureMap.containsKey(taskKey)) { if (!scheduledFutureMap.get(taskKey).isCancelled()) { return true; return false;

2.8 定时任务Controller

* 定时任务 controller @RestController @RequestMapping("/scheduled") public class ScheduledController { @Autowired private ScheduledTaskService scheduledTaskService; * 所有任务列表 @RequestMapping("/taskList") public List<ScheduledTaskBean> taskList() { return scheduledTaskService.taskList(); * 根据任务key => 启动任务 @RequestMapping("/start") public String start(@RequestParam("taskKey") String taskKey) { scheduledTaskService.start(taskKey); return "start success"; * 根据任务key => 停止任务 @RequestMapping("/stop") public String stop(@RequestParam("taskKey") String taskKey) { scheduledTaskService.stop(taskKey); return "stop success"; * 根据任务key => 重启任务 @RequestMapping("/restart") public String restart(@RequestParam("taskKey") String taskKey) { scheduledTaskService.restart(taskKey); return "restart success";

3.效果展示

截止到这来,已经可以实现,动态管理任务了,包括动态修改启动表达式,停止任务,启动任务,重启任务等。
但是,还有一个问题,项目重启后,需要人工来启动需要启动的任务。

2.9实现项目启动时自启动

注:SpringBoot给我们提供了两个接口来帮助我们实现这种需求。这两个接口分别为CommandLineRunner和ApplicationRunner。他们的执行时机为容器启动完成的时候

* @see @Order注解的执行优先级是按value值从小到大顺序。 * 项目启动完毕后开启需要自启的任务 @Component @Order(value = 1) public class ScheduledTaskRunner implements ApplicationRunner { private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTaskRunner.class); @Autowired private ScheduledTaskMapper taskMapper; @Autowired private ScheduledTaskService scheduledTaskService; * 程序启动完毕后,需要自启的任务 @Override public void run(ApplicationArguments applicationArguments) throws Exception { LOGGER.info(" >>>>>> 项目启动完毕, 开启 => 需要自启的任务 开始!"); List<ScheduledTaskBean> scheduledTaskBeanList = taskMapper.getAllNeedStartTask(); scheduledTaskService.initAllTask(scheduledTaskBeanList); LOGGER.info(" >>>>>> 项目启动完毕, 开启 => 需要自启的任务 结束!");

优点:动态启动任务,修改任务间隔时间
缺点:分布式多机部署,也只能实现对某一台机器的任务做动态处理,因为启动的定时任务,放在当前机器内存中了,可以采用redis存储定时任务组Map

在这里插入图片描述

void execute(JobExecutionContext context) JobDetail表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,... 实际开发中,经常会碰到“定期定时去做一些重复操作”的需求,这个时候,定时任务显得是那么的方便.本章,我们来讲讲SpringBoot定时任务如何使用. 使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式 基于注解(@Scheduled) 基于接口(SchedulingConfigurer) 基于注解多线程定时任务 基于注解(单线程) 基于注解@Schedule... 查看是否开启spring延迟类加载,开启后只有用到的类才会被加载进去(可以加快项目启动速度) 如果在application.properties写了以下配置 #################是否启用bean延迟加载######################### spring.main.lazy-initialization=tr CREATE TABLE `scheduled_task` ( `id` int(11) NOT NULL AUTO_INCREMENT, `task_key` varchar(128) NOT NULL COMMENT '任务key值(使用bean名称)', `task_desc` varchar(128) DEFAULT NULL COMMENT '... 前端时间开发接触了一个开源框架jeecg,里面封装了spring与quartz整合的定时任务实现方式。因为之前尝试过单纯使用quartz来实现定时任务,遇到一些问题,比如,无法通过spring注入的方式添加自己的注入类。         首先了解一下,定时任务有三种技术实现方式:java自带的Timer类,可以让程序保持一定频度执行,但是无法按照某个时间执行;quartz,一个功能强大的 由于业务需求,需要提供一个能够让用户动态配置定时任务的入口,定时去同步数据 1、简单的业务处理,直接使用@Scheduled注解就能开启定时任务,例如在方法上@Scheduled(cron = “0 0/1 * * * ?”),书写cron表达式就能配置定时任务。 * 每分钟触发一次 @Scheduled(cron = "0 0/1 * * * ?") public void execEveryDay() { checkPlan(. 实现SchedulingConfigurer接口,并且重写configureTasks方法。 package cn.tongdun.spartan.biz.support.impl; import cn.tongdun.spartan.biz.support.service.DockCustomerService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factor 业务场景描述: 定时任务任务名称,cron(定时任务表达式),定时任务开关,存储在数据库表中。在不重启项目的情况下,修改定时任务表达式,可以实时获取新的定时任务执行时间规则;修改定时任务执行状态,可以随时开关定时任务。 使用技术:基于接口 SchedulingConfigurer 1、建表 管理定时任务 DROP TABLE IF EXISTS `scheduled`; CREATE TABLE `scheduled` ( `name` varchar(20) DEFAULT NULL, @schedule 注解是springboot 常用的定时任务注解,使用起来简单方便,但是如果定时任务非常多,或者有的任务很耗时,会影响到其他定时任务的执行,因为schedule 默认是单线程的,一个任务在执行时,其他任务是不能执行的.解决办法是重新配置schedule,改为多线程执行.只需要增加下面的配置类就可以了. import org.springframework.boot.autoconfigure.batch.BatchProperties; import org.springframew. package com.wkx.test.springStart; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import ... 先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。之前写过文章记录怎么在SpringBoot项目中简单使用定时任务,不过由于要借助cron表达式且都提前定义好放在配置文件里,不能在项目运行中动态修改任务执行时间,实在不太灵活。除了上面的借助cron表达式的方法,还有另一种触发器,区别于CronTrigger触发器,该触发器可随意设置循环间隔时间,不像cron表达式只能定义小于等于间隔59秒。可以看到任务变成了15秒执行一次。...