SpringBoot基于Schedule动态定时任务,整合了Mybatis-plus(完整版)

文章展示了如何使用Spring配置定时任务线程池,包括TaskScheduler的设置和ScheduledFuture的封装。通过SchedulingRunnable实现Runnable接口,动态调用bean中的方法。CronTaskRegistrar类用于添加、删除定时任务,基于数据库表schedule_setting进行管理,支持任务的增删改查和状态控制。此外,还提供了Cron表达式的在线生成器链接。

执行定时任务的线程池配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
 
@Configuration
public class SchedulingConfig {  
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        // 定时任务执行线程池核心线程数  
        taskScheduler.setPoolSize(6);  
        taskScheduler.setRemoveOnCancelPolicy(true);  
        taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");  
        return taskScheduler;  
    }  
}  

ScheduledFuture的包装类

ScheduledFuture是ScheduledExecutorService定时任务线程池的执行结果。

import java.util.concurrent.ScheduledFuture;
 
public final class ScheduledTask {
  
    volatile ScheduledFuture<?> future;
    /**  
     * 取消定时任务  
     */  
    public void cancel() {  
        ScheduledFuture<?> future = this.future;  
        if (future != null) {  
            future.cancel(true);  
        }  
    }  
}

Runnable接口实现类

被定时任务线程池调用,用来执行指定bean里面的方法。

import com.ying.demo.utils.springContextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
import java.util.Objects;
 
public class SchedulingRunnable implements Runnable {
 
   private static final Logger logger = LoggerFactory.getLogger(SchedulingRunnable.class);
 
   private String beanName;
 
   private String methodName;
 
   private String params;
 
   public SchedulingRunnable(String beanName, String methodName) {
      this(beanName, methodName, null);
   }
 
   public SchedulingRunnable(String beanName, String methodName, String params) {
      this.beanName = beanName;
      this.methodName = methodName;
      this.params = params;
   }
 
   @Override
   public void run() {
      logger.info("定时任务开始执行 - bean:{},方法:{},参数:{}", beanName, methodName, params);
      long startTime = System.currentTimeMillis();
 
      try {
         Object target = springContextUtils.getBean(beanName);
 
         Method method = null;
         if (!StringUtils.isEmpty(params)) {
            method = target.getClass().getDeclaredMethod(methodName, String.class);
         } else {
            method = target.getClass().getDeclaredMethod(methodName);
         }
 
         ReflectionUtils.makeAccessible(method);
         if (!StringUtils.isEmpty(params)) {
            method.invoke(target, params);
         } else {
            method.invoke(target);
         }
      } catch (Exception ex) {
         logger.error(String.format("定时任务执行异常 - bean:%s,方法:%s,参数:%s ", beanName, methodName, params), ex);
      }
 
      long times = System.currentTimeMillis() - startTime;
      logger.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{} 毫秒", beanName, methodName, params, times);
   }
 
   @Override
   public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      SchedulingRunnable that = (SchedulingRunnable) o;
      if (params == null) {
         return beanName.equals(that.beanName) &&
               methodName.equals(that.methodName) &&
               that.params == null;
      }
 
      return beanName.equals(that.beanName) &&
            methodName.equals(that.methodName) &&
            params.equals(that.params);
   }
 
   @Override
   public int hashCode() {
      if (params == null) {
         return Objects.hash(beanName, methodName);
      }
 
      return Objects.hash(beanName, methodName, params);
   }
}  

定时任务注册类

用来增加、删除定时任务


import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.config.CronTask;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class CronTaskRegistrar implements DisposableBean {

    private final Map<Runnable, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(16);

    @Autowired
    private TaskScheduler taskScheduler;

    public TaskScheduler getScheduler() {
        return this.taskScheduler;
    }

    public void addCronTask(Runnable task, String cronExpression) {
        addCronTask(new CronTask(task, cronExpression));
    }

    public void addCronTask(CronTask cronTask) {
        if (cronTask != null) {
            Runnable task = cronTask.getRunnable();
            if (this.scheduledTasks.containsKey(task)) {
                removeCronTask(task);
            }

            this.scheduledTasks.put(task, scheduleCronTask(cronTask));
        }
    }

    public void removeCronTask(Runnable task) {
        ScheduledTask scheduledTask = this.scheduledTasks.remove(task);
        if (scheduledTask != null)
            scheduledTask.cancel();
    }

    public ScheduledTask scheduleCronTask(CronTask cronTask) {
        ScheduledTask scheduledTask = new ScheduledTask();
        scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());

        return scheduledTask;
    }


    @Override
    public void destroy() {
        for (ScheduledTask task : this.scheduledTasks.values()) {
            task.cancel();
        }

        this.scheduledTasks.clear();
    }
}

定时任务示例类

@Slf4j
@Component("taskDemo")
public class Task1 {  
    public void taskByParams(String params) {
        log.info("taskByParams执行时间:{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        log.info("taskByParams执行有参示例任务:{}",params);
    }  
  
    public void taskNoParams() {
        log.info("taskByParams执行时间:{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        log.info("taskNoParams执行无参示例任务");
    }
 
    public void test(String params) {
        log.info("test执行时间:{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        log.info("test执行有参示例任务:{}",params);
    }
} 

数据库表设计

CREATE TABLE `schedule_setting` (
  `job_id` int NOT NULL AUTO_INCREMENT COMMENT '任务ID',
  `bean_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'bean名称',
  `method_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '方法名称',
  `method_params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '方法参数',
  `cron_expression` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'cron表达式',
  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '备注',
  `job_status` int DEFAULT NULL COMMENT '状态(1正常 0暂停)',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  PRIMARY KEY (`job_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

实体类

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.util.Date;

@Data
@TableName("sys_schedule_setting")
public class ScheduleSetting {
    /**
     * 任务ID
     */
    @TableId
    private Integer jobId;
    /**
     * bean名称
     */
    private String beanName;
    /**
     * 方法名称
     */
    private String methodName;
    /**
     * 方法参数
     */
    private String methodParams;
    /**
     * cron表达式
     */
    private String cronExpression;
    /**
     * 状态(1正常 0暂停)
     */
    private Integer jobStatus;
    /**
     * 备注
     */
    private String remark;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 更新时间
     */
    private Date updateTime;
}

定时任务预热

spring boot项目启动完成后,加载数据库里状态为正常的定时任务

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.scheduling.config.domain.ScheduleSetting;
import com.ruoyi.scheduling.config.mapper.ScheduleSettingMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

@Service
public class SysJobRunner implements CommandLineRunner {

    private static final Logger logger = LoggerFactory.getLogger(SysJobRunner.class);

    @Autowired
    private CronTaskRegistrar cronTaskRegistrar;

    @Resource
    private ScheduleSettingMapper scheduleSettingMapper;

    @Override
    public void run(String... args) {
        // 初始加载数据库里状态为正常的定时任务
        List<ScheduleSetting> jobList = scheduleSettingMapper.selectList(
            Wrappers.lambdaQuery(ScheduleSetting.class).eq(ScheduleSetting::getJobStatus,1));
        if (CollUtil.isNotEmpty(jobList)) {
            for (ScheduleSetting job : jobList) {
                SchedulingRunnable task = new SchedulingRunnable(job.getBeanName(), job.getMethodName(), job.getMethodParams());
                cronTaskRegistrar.addCronTask(task, job.getCronExpression());
            }
            logger.info("定时任务已加载完毕...");
        }
    }
}

工具类

用来从spring容器里获取bean

@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> T getBean(Class<T> requiredType) {  
        return applicationContext.getBean(requiredType);  
    }  
  
    public static <T> T getBean(String name, Class<T> 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<? extends Object> getType(String name) {  
        return applicationContext.getType(name);  
    }  
}

Mapper类:查询数据库

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.scheduling.config.domain.ScheduleSetting;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface ScheduleSettingMapper extends BaseMapper<ScheduleSetting> {
}

定时任务的:增/删/改/启动/暂停

import com.ruoyi.scheduling.config.domain.ScheduleSetting;
import com.ruoyi.scheduling.config.mapper.ScheduleSettingMapper;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.Date;

@RestController
@Api(tags = {"定时任务"})
@RequestMapping("/task")
public class TestController {

    @Autowired
    private CronTaskRegistrar cronTaskRegistrar;

    @Resource
    private ScheduleSettingMapper scheduleSettingMapper;

    /**
     * 添加定时任务
     *
     * @param sysJob
     * @return
     */
    @PostMapping("add")
    public boolean add(@RequestBody ScheduleSetting sysJob) {
        sysJob.setCreateTime(new Date());
        sysJob.setUpdateTime(new Date());

        int count = scheduleSettingMapper.insert(sysJob);
        if (count > 0) {
            if (sysJob.getJobStatus().equals(1)) {// 添加成功,并且状态是1,直接放入任务器
                SchedulingRunnable task = new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams());
                cronTaskRegistrar.addCronTask(task, sysJob.getCronExpression());
                return true;
            }
        }
        return false;
    }

    /**
     * 修改定时任务
     *
     * @param sysJob
     * @return
     */
    @PostMapping("update")
    public boolean update(@RequestBody ScheduleSetting sysJob) {
        sysJob.setCreateTime(new Date());
        sysJob.setUpdateTime(new Date());

        // 查询修改前任务
        ScheduleSetting existedSysJob = scheduleSettingMapper.selectById(sysJob.getJobId());
        // 修改任务
        int update = scheduleSettingMapper.updateById(sysJob);
        if (update > 0) {
            // 修改成功,则先删除任务器中的任务,并重新添加
            SchedulingRunnable task1 = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());
            cronTaskRegistrar.removeCronTask(task1);
            if (sysJob.getJobStatus().equals(1)) {// 如果修改后的任务状态是1就加入任务器
                SchedulingRunnable task = new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams());
                cronTaskRegistrar.addCronTask(task, sysJob.getCronExpression());
            }
            return true;
        }
        return false;
    }

    /**
     * 删除任务
     *
     * @param jobId
     * @return
     */
    @PostMapping("del/{jobId}")
    public boolean del(@PathVariable("jobId") Integer jobId) {
        // 先查询要删除的任务信息
        ScheduleSetting existedSysJob = scheduleSettingMapper.selectById(jobId);

        // 删除
        int del = scheduleSettingMapper.deleteById(jobId);
        if (del > 0) {
            // 删除成功时要清除定时任务器中的对应任务
            SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());
            cronTaskRegistrar.removeCronTask(task);
            return true;
        }
        return false;
    }

    // 停止/启动任务
    @PostMapping("changesStatus/{jobId}/{stop}")
    public boolean changesStatus(@PathVariable("jobId") Integer jobId, @PathVariable("stop") Integer stop) {
        // 修改任务状态
        ScheduleSetting scheduleSetting = new ScheduleSetting();
        scheduleSetting.setJobId(jobId);
        scheduleSetting.setJobStatus(stop);
        int job_id = scheduleSettingMapper.updateById(scheduleSetting);
        if (job_id > 0) {
            // 查询修改后的任务信息
            ScheduleSetting existedSysJob = scheduleSettingMapper.selectById(jobId);

            // 如果状态是1则添加任务
            if (existedSysJob.getJobStatus().equals(1)) {
                SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());
                cronTaskRegistrar.addCronTask(task, existedSysJob.getCronExpression());
            } else {
                // 否则清除任务
                SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());
                cronTaskRegistrar.removeCronTask(task);
            }

            return true;
        }
        return false;
    }
}

cron在线表达式生成器:https://cron.12qqe2.com/

每隔5秒执行一次:*/5 * * * * ?

每隔1分钟执行一次:0 */1 * * * ?

每天23点执行一次:0 0 23 * * ?

每天凌晨1点执行一次:0 0 1 * * ?

每月1号凌晨1点执行一次:0 0 1 1 * ?

每月最后一天23点执行一次:0 0 23 L * ?

每周星期六凌晨1点实行一次:0 0 1 ? * L

在26分、29分、33分执行一次:0 26,29,33 * * * ?

每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?

 

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值