springboot内置TaskScheduler实现动态定时任务

我之前用过 quartz实现动态定时任务,但是这次使用 springboot 内置的定时任务来实现动态定时任务,如果想要用 quartz 实现动态定时任务,请阅读

我在项目里是做成了一个自定义的 task-spring-boot-starter,方便其他地方使用,引入依赖


    io.github.chichengyu
    task-spring-boot-starter
    1.2.7.RELEASE

即可使用。下面进入正题

正题

首先创建新的springboot工作,导包


   
      tk.mybatis
      mapper-spring-boot-starter
      2.1.5
   
   
      mysql
      mysql-connector-java
   
   
      org.springframework.boot
      spring-boot-starter-web
   
   
      com.alibaba
      fastjson
      1.2.71
   
   
      org.projectlombok
      lombok
      true
   

application.yml配置

server:
  port: 8888
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/task?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT
    username: root
    password: root

mybatis:
  type-aliases-package: com.example.demo.pojo
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: true #使全局的映射器启用或禁用缓存。
    lazy-loading-enabled: true #全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。
    aggressive-lazy-loading: true #当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。
    jdbc-type-for-null: null #设置但JDBC类型为空时,某些驱动程序 要指定值,default:OTHER,插入空值时不需要指定类型

第一步先创建一个工具类 SpringContextUtils

package com.example.demo;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @author xiaochi
 * @date 2022/6/11 11:02
 * @desc SpringContextUtils
 */
@Component
public class SpringContextUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtils.applicationContext = applicationContext;
    }

    public static Object getBean(String name){
        return applicationContext.getBean(name);
    }

    public static T getBean(Class clazz){
        return applicationContext.getBean(clazz);
    }

    public static T getBean(String name,Class requiredType){
        return applicationContext.getBean(name,requiredType);
    }

    public static boolean containsBean(String name){
        return applicationContext.containsBean(name);
    }

    public static boolean isSingleton(String name) {
        return applicationContext.isSingleton(name);
    }

    public static Class getType(String name){
        return applicationContext.getType(name);
    }

    public static  Map getBeansOfType(Class clazz){
        return applicationContext.getBeansOfType(clazz);
    }
}

创建定时任务管理工具类 SchudleManager

package com.example.demo.task;

import com.alibaba.fastjson.JSON;
import com.example.demo.dao.ScheduleJobLogDao;
import com.example.demo.pojo.ScheduleJob;
import com.example.demo.pojo.ScheduleJobLog;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.task.TaskSchedulerBuilder;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.CronTask;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

/**
 * @author xiaochi
 * @date 2022/6/11 16:52
 * @desc SchudleManager
 */
@Slf4j
@Component
public class SchudleManager implements DisposableBean {
    // 正在运行的定时任务
    private ConcurrentHashMap taskContainer = new ConcurrentHashMap<>();

    private TaskScheduler taskScheduler;

    @Autowired
    private ScheduleJobLogDao scheduleJobLogDao;

    @PostConstruct
    public void init(){
        ThreadPoolTaskScheduler taskScheduler = new TaskSchedulerBuilder()
                .poolSize(10)
                .threadNamePrefix("job_")
                .build();
        taskScheduler.setRemoveOnCancelPolicy(true);//是否将取消后的任务从队列删除
        taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        taskScheduler.initialize();
        this.taskScheduler = taskScheduler;
    }

    /**
     * 刷新所有定时任务
     */
    public void refresh(List taskPojoList) throws Exception {
        if (!taskPojoList.isEmpty()){
            destroy();
            for (ScheduleJob pojo : taskPojoList){
                addCronTask(pojo);
            }
        }
    }

    /**
     * 创建并启动定时任务
     * @param pojo
     */
    public void addCronTask(ScheduleJob pojo){
        cancel(pojo.getJobId());
        /*CronTask cronTask = new CronTask(() -> {
            System.out.println("执行定时器【"+ pojo.getName() +"】任务:" + LocalDateTime.now().toLocalTime());
        },pojo.getCron());*/
        CronTask cronTask = new CronTask(() -> execute(pojo),pojo.getCronExpression());
        ScheduledRealTaskFuture realTaskFuture = new ScheduledRealTaskFuture();
        realTaskFuture.future = taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
        taskContainer.put(pojo.getJobId(),realTaskFuture);
    }

    /**
     * 更新定时任务
     */
    public void updateCronTask(ScheduleJob pojo) throws Exception {
        cancel(pojo.getJobId());
        addCronTask(pojo);
    }

    /**
     * 立即执行定时任务
     * @param scheduleJob
     */
    public void runNow(ScheduleJob scheduleJob){
        if (scheduleJob != null){
            execute(scheduleJob);
        }
    }

    /**
     * 取消定时任务
     * @param jobId
     */
    public void cancel(Long jobId){
        if (taskContainer.containsKey(jobId)){
            ScheduledRealTaskFuture taskFuture = taskContainer.get(jobId);
            if (taskFuture != null){
                taskFuture.cancel();
            }
            taskContainer.remove(jobId);
        }
    }

    /**
     * 销毁的时候停止定时任务
     */
    @Override
    public void destroy(){
        for (ScheduledRealTaskFuture future : taskContainer.values()){
            future.cancel();
        }
        taskContainer.clear();// 清空容器
    }

    /**
     * 取消任务的 ScheduledFuture
     */
    private static class ScheduledRealTaskFuture{
        public volatile ScheduledFuture future;
        /**
         * 取消定时任务
         */
        public void cancel() {
            ScheduledFuture future = this.future;
            if (future != null) {
                future.cancel(true);
            }
        }
    }

    /**
     * 定时任务执行且日志入库
     * @param scheduleJob
     */
    private void execute(ScheduleJob scheduleJob){
        //数据库保存执行记录
        ScheduleJobLog jobLog = ScheduleJobLog.builder().jobId(scheduleJob.getJobId()).beanName(scheduleJob.getBeanName()).params(scheduleJob.getParams()).createTime(new Date()).build();
        // 任务开始执行时间
        long startTime = System.currentTimeMillis();
        try {
            log.info("定时任务[{}]准备执行", scheduleJob.getJobId());
            Object target = SpringContextUtils.getBean(scheduleJob.getBeanName());
            Method method = target.getClass().getDeclaredMethod("run", String.class);
            R result = (R)method.invoke(target, JSON.toJSONString(scheduleJob));
            // 任务执行时长
            long times = System.currentTimeMillis() - startTime;
            jobLog.setTimes((int) times);
            jobLog.setStatus(0);
            if (null != result) {
                jobLog.setStatus(result.getCode());
                jobLog.setMessage(result.getMsg());
            }
            log.info("定时任务[{}]执行完毕,总共耗时:{}毫秒",scheduleJob.getJobId(),times);
        }catch (Exception e){
            log.error("定时任务[{}]执行失败,异常信息:{}",scheduleJob.getJobId(),e);
            // 任务执行时长
            long times = System.currentTimeMillis() - startTime;
            // 任务状态    0:成功  1:失败  记录数据库
            jobLog.setTimes((int)times);
            jobLog.setStatus(1);
            jobLog.setError(e.toString());
        }finally {
            // 最终记录到数据库
            scheduleJobLogDao.insertSelective(jobLog);
        }
    }
}

这时候再继续创建定时任务接口,所有定时任务都要实现该接口 ITask

package com.example.demo.task;

/** 定时任务接口,所有定时任务都要实现该接口
 * @author xiaochi
 * @date 2022/6/11 16:33
 * @desc ITask
 */
public interface ITask {

    /**
     * 执行定时任务接口
     * @param params   参数,多参数使用JSON数据
     * @return R
     */
    R run(String params);
}

接着创建定时任务配置类,主要就是项目启动的时候去加载数据库的定时任务,配置类 SchedulingConfig

package com.example.demo.task.config;

import com.example.demo.job.dao.ScheduleJobDao;
import com.example.demo.job.pojo.ScheduleJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

import java.util.List;

/** 定时任务配置(启动时执行,跟 CommandLineRunner 效果一样)
 * @author xiaochi
 * @date 2022/6/11 9:19
 * @desc SchedulingConfig
 */
@Slf4j
@Configuration
@EnableScheduling // 必须开启定时任务,才能执行
public class SchedulingConfig implements SchedulingConfigurer {

    @Autowired
    private ScheduleJobDao scheduleJobDao;
    @Autowired
    private SchudleManager schudleManager;

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        log.info("[定时任务将在30s后执行] after 30s .");
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
        executorService.schedule(() -> {
            log.info("[定时任务开始执行] start running");
            try {
                ScheduleJob job = ScheduleJob.builder().status(0).build();
                schudleManager.refresh(scheduleJobDao.select(job));
            } catch (Exception e) {
                log.error("[加载定时任务失败],异常信息:{}",e);
            }
        },30, TimeUnit.SECONDS);
        executorService.shutdown();
        /*List taskPojoList = schudleTaskMapper.selectAll();
        //taskRegistrar.addFixedDelayTask(() -> System.out.println(1),0);
        for (SchudleTaskPojo pojo : taskPojoList){
            taskRegistrar.addTriggerTask(() -> {
                System.out.println("执行定时器【"+ pojo.getName() +"】任务:" + LocalDateTime.now().toLocalTime());
            },triggerContext -> {
                System.out.println(1);
                return new CronTrigger(pojo.getCron()).nextExecutionTime(triggerContext);
            });
        }*/
    }
}

接下来就是创建测试类进行测试了 TaskControlelr,还有测试的定时任务bean(需要实现 ITask)
测试的定时任务bean(需要实现 ITask) TestTask

package com.example.demo.task;

import org.springframework.stereotype.Component;

/**
 * @author xiaochi
 * @date 2022/6/11 16:37
 * @desc TestTask
 */
@Component("testTask")// 该名称必须与我们自定义的定时器表中的 Bean 名称一致
public class TestTask implements ITask {

    /**
     * 执行定时任务接口
     * @param params 参数,多参数使用JSON数据
     * @return R
     */
    @Override
    public R run(String params) {
        System.out.println("测试定时任务执行了,参数:"+params);
        return R.ok();
    }
}

测试 TaskControlelr

package com.example.demo.task.controller;

import com.example.demo.dao.ScheduleJobDao;
import com.example.demo.pojo.ScheduleJob;
import com.example.demo.task.SchudleManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

/**
 * @author xiaochi
 * @date 2022/6/11 9:34
 * @desc TaskControlelr
 */
@RestController
@RequestMapping("/task")
public class TaskControlelr {

    @Autowired
    private ScheduleJobDao scheduleJobDao;
    @Autowired
    private SchudleManager schudleManager;

    /**
     * 刷新定时任务
     * @return
     */
    @GetMapping("/refresh")
    public String refresh() throws Exception {
        schudleManager.refresh(scheduleJobDao.selectAll());
        return "ok";
    }

    /**
     * 立即执行定时任务
     * @return
     */
    @GetMapping("/now/{id}")
    public String now(@PathVariable("id") Long id) throws Exception {
        ScheduleJob scheduleJob = scheduleJobDao.selectOne(ScheduleJob.builder().jobId(id).build());
        schudleManager.runNow(scheduleJob);
        return "ok";
    }

    /**
     * 添加定时任务
     * @return
     */
    @GetMapping("/add")
    public String add(){
        ScheduleJob scheduleJob = new ScheduleJob();
        scheduleJob.setBeanName("testTask");// TestTask 类的 BeanName名称
        scheduleJob.setJobId(100L);
        scheduleJob.setStatus(0);
        scheduleJob.setParams("我是测试定时器");
        scheduleJob.setCronExpression("*/2 * * * * ?");
        scheduleJob.setRemark("测试");
        scheduleJob.setCreateTime(new Date());
        scheduleJobDao.insertSelective(scheduleJob);// 插入数据库
        // 同时添加定时任务
        schudleManager.addCronTask(scheduleJob);
        return "ok";
    }

    /**
     * 暂停定时任务
     * @return
     */
    @GetMapping("/suspend/{id}")
    public String suspend(@PathVariable("id") Long id) throws Exception {
        scheduleJobDao.updateByPrimaryKeySelective(ScheduleJob.builder().jobId(id).status(1).build());
        schudleManager.cancel(id);
        return "ok";
    }

    /**
     * 更新定时任务
     * @return
     */
    @GetMapping("/update/{id}")
    public String update(@PathVariable("id") Long id) throws Exception {
        ScheduleJob scheduleJob = scheduleJobDao.selectOne(ScheduleJob.builder().jobId(id).build());
        scheduleJob.setStatus(0);
        scheduleJobDao.updateByPrimaryKeySelective(scheduleJob);
        schudleManager.updateCronTask(scheduleJob);
        return "ok";
    }

    /**
     * 取消定时任务
     * @return
     */
    @GetMapping("/cancel/{id}")
    public String cancel(@PathVariable("id") Long id){
        schudleManager.cancel(id);
        return "ok";
    }
}
提供一下用到的两张表与实体类、R类,dao自己需要创建 ScheduleJobDaoScheduleJobLogDao,这里我就不提供了

数据库定时任务的表 schedule_jobschedule_job_log

DROP TABLE IF EXISTS `schedule_job`;
CREATE TABLE `schedule_job` (
  `job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务id',
  `bean_name` varchar(200) DEFAULT NULL COMMENT 'spring bean名称',
  `params` text COMMENT '参数',
  `cron_expression` varchar(100) DEFAULT NULL COMMENT 'cron表达式',
  `status` tinyint(4) DEFAULT NULL COMMENT '任务状态  0:正常  1:暂停',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`job_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='定时任务';

DROP TABLE IF EXISTS `schedule_job_log`;
CREATE TABLE `schedule_job_log` (
  `log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务日志id',
  `job_id` bigint(20) NOT NULL COMMENT '任务id',
  `bean_name` varchar(200) DEFAULT NULL COMMENT 'spring bean名称',
  `params` text COMMENT '参数',
  `status` tinyint(4) NOT NULL COMMENT '任务状态    0:成功    1:失败',
  `message` text COMMENT '正常信息',
  `error` text COMMENT '失败信息',
  `times` int(11) NOT NULL COMMENT '耗时(单位:毫秒)',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`log_id`),
  KEY `job_id` (`job_id`),
  KEY `create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='定时任务日志';

实体 ScheduleJob

package com.example.demo.pojo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;

/** 定时任务
 * @author xiaochi
 * @date 2022/6/11 9:37
 * @desc SchudleTaskPojo
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "schedule_job")
public class ScheduleJob implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    private Long jobId;// 任务id
    private String beanName;//spring bean名称
    private String params;//参数
    private String cronExpression;// cron表达式
    private Integer status;// 任务状态
    private String remark;// 备注
    private Date createTime;// 创建时间

}

定时任务日志实体类 ScheduleJobLog

package com.example.demo.pojo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;

/** 定时任务日志
 * @author xiaochi
 * @date 2022/6/11 11:37
 * @desc ScheduleJobLog
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "schedule_job_log")
public class ScheduleJobLog implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    private Long logId;// 日志主建id
    private Long jobId;// 任务id
    private String beanName;// spring bean名称
    private String params;// 参数
    private Integer status;// 任务状态0:成功1:失败
    private String message;// 正常信息
    private String error;// 失败信息
    private Integer times;// 耗时(单位:毫秒)
    private Date createTime;// 创建时间
}

R类

package com.example.demo.task;

import com.fasterxml.jackson.annotation.JsonInclude;

import java.io.Serializable;

/**
 * @author xiaochi
 * @date 2022/5/13 14:13
 * @desc R
 */
@JsonInclude(JsonInclude.Include.NON_NULL)
public class R implements Serializable {
    private static final long serialVersionUID = 7735505903525411467L;
    private static final int SUCCESS_CODE = 0;// 成功
    private static final int ERROR_CODE = 1;// 失败

    private int code;//状态码
    private String msg;//消息
    private T data;//返回数据

    private R(int code){
        this.code = code;
    }
    private R(int code, T data){
        this.code = code;
        this.data = data;
    }
    private R(int code, String msg){
        this.code = code;
        this.msg = msg;
    }
    private R(int code, String msg, T data){
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static  R ok(){
        return new R(SUCCESS_CODE,"success");
    }
    public static  R ok(String msg){
        return new R(SUCCESS_CODE,msg);
    }
    public static  R ok(T data){
        return new R(SUCCESS_CODE,data);
    }
    public static  R ok(String msg, T data){
        return new R(SUCCESS_CODE,msg,data);
    }

    public static  R error(){
        return new R(ERROR_CODE,"error");
    }
    public static  R error(String msg){
        return new R(ERROR_CODE,msg);
    }
    public static  R error(int code, String msg){
        return new R(code,msg);
    }

    public int getCode(){
        return code;
    }
    public String getMsg(){
        return msg;
    }
    public T getData(){
        return data;
    }
}

你可能感兴趣的:(springboot内置TaskScheduler实现动态定时任务)