在spring boot项目中定时任务的开发方式:
一、可通过@EnableScheduling注解和@Scheduled注解实现
二、可通过SchedulingConfigurer接口来实现
三、集成Quartz框架实现
注意:第一和第二方式不能动态添加、删除、启动、停止任务。在满足项目需求的情况下,尽量少的依赖其它框架,避免项目过于臃肿和复杂是最基本的开发原则。
查看 spring-context 这个 jar 包中 org.springframework.scheduling.ScheduledTaskRegistrar 这个类的源代码,发现可以通过改造这个类(主要是基于TaskScheduler和CronTask两个类来实现)就能实现动态增删启停定时任务功能。

项目依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-boot-project</artifactId>
<groupId>com.example</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>scheduledTaskRegistrar-demo</artifactId>

<dependencies>
<!--Web 项目开发starter-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>

<!--校验-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<!--lombok 简化插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>

<!--通用mapper-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>RELEASE</version>
</dependency>

<!--mysql数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
</project>

配置文件

server:
port: 8080
spring:
datasource:
url: 数据库连接地址
username: 数据库名
password: 数据库密码
driver-class-name: com.mysql.jdbc.Driver

mybatis:
# xml文件地址
mapper-locations: classpath:mapper/*Mapper.xml
# 打印mybatis的执行sql语句
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 开启驼峰自动转换
map-underscore-to-camel-case: true

启动引导类

package com.scheduledtask;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;

/**
* @author Nicky
* @version 1.0
* @className scheduledTaskApplication
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 启动引导类
* @date 2020/10/26 17:24
*/
@SpringBootApplication
@MapperScan("com.scheduledTask.mapper")
public class ScheduledTaskApplication {
public static void main(String[] args) {
SpringApplication.run(ScheduledTaskApplication.class, args);
}
}

@MapperScan:指定扫描的Mapper类的包的路径,简化直接在每个Mapper类上添加注解@Mapper

配置类

配置线程池

package com.scheduledtask.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

/**
* @author Nicky
* @version 1.0
* @className ScheduledConfig
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 配置线程池
* @date 2020/10/27 14:48
*/
@Configuration
public class SchedulingConfig {

@Bean
public TaskScheduler taskScheduler() {
// 创建任务调度线程池
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
// 初始化线程池数量
taskScheduler.setPoolSize(4);
// 是否将取消后的任务,从队列中删除
taskScheduler.setRemoveOnCancelPolicy(true);
// 设置线程名前缀
taskScheduler.setThreadNamePrefix("ThreadPool-");
return taskScheduler;
}
}

执行类

同步处理定时任务类

package com.scheduledtask.task;

import java.util.concurrent.ScheduledFuture;

/**
* @author Nicky
* @version 1.0
* @className ScheduledTask
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 同步处理任务类
* @date 2020/10/27 15:52
*/
public class ScheduledTask {
// 使用volatile同步机制,处理定时任务
public volatile ScheduledFuture future;

/**
* @return void
* @method cancel
* @description 取消定时任务
*/
public void cancel() {
ScheduledFuture future = this.future;
if (future != null) {
future.cancel(true);
}
}
}

定时任务注册类

package com.scheduledtask.task;

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;

/**
* @author Nicky
* @version 1.0
* @className CronTaskRegistrar
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 定时任务注册类
* @date 2020/10/27 17:23
*/
@Component
public class CronTaskRegistrar implements DisposableBean {

/**
* 在多线程下,使用并发集合做为缓存,初始化容量16
*/
private final Map<Runnable, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(16);

// 注入定时任务接口
@Autowired
private TaskScheduler taskScheduler;

/**
* @param [task 定时任务, cronExpression cron表达式]
* @return void
* @method addCronTask
* @description 添加定时任务
*/
public void addCronTask(Runnable task, String cronExpression) {
addCronTask(new CronTask(task, cronExpression));
}

/**
* @method addCronTask
* @description 注册定时任务,并将任务加入到缓存中
* @param [cronTask] 定时任务对象
* @return void
*/
public void addCronTask(CronTask cronTask) {
if (cronTask != null) {
Runnable task = cronTask.getRunnable();
if (scheduledTasks.containsKey(task)) {
removeCronTask(task);
}
scheduledTasks.put(task, scheduleCronTask(cronTask));
}
}

/**
* @method removeCronTask
* @description 取消定时任务,并将缓存中的任务记录删除
* @param [task] 线程对象
* @return void
*/
public void removeCronTask(Runnable task) {
ScheduledTask scheduledTask = scheduledTasks.remove(task);
if (scheduledTask != null) {
scheduledTask.cancel();
}
}

/**
* @method scheduleCronTask
* @description 调用线程池
* @param [cronTask] 定时任务对象
* @return com.scheduledtask.task.ScheduledTask
*/
public ScheduledTask scheduleCronTask(CronTask cronTask) {
ScheduledTask scheduledTask = new ScheduledTask();
// 指定一个触发器执行定时任务,并返回执行结果
scheduledTask.future = taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
return scheduledTask;
}

/**
* @method destroy
* @description 销毁所有定时任务,并将缓存清除
* @return void
*/
@Override
public void destroy() throws Exception {
for (ScheduledTask task : scheduledTasks.values()) {
task.cancel();
}
scheduledTasks.clear();
}
}

此处用Map来模拟缓存,当然可以换教专业的缓存组件,如redis等等

初始化定时任务类

package com.scheduledtask.task;

import com.scheduledtask.pojo.Task;
import com.scheduledtask.service.TaskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;

/**
* @author Nicky
* @version 1.0
* @className TaskRunner
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 初始化定时任务类
* 如有多个组件实现了CommandLineRunner接口来实现启动加载功能,Order注解可实现先后加载顺序
* @date 2020/10/28 14:33
*/
@Slf4j
@Component
// @Order(value = 1)
public class TaskRunner implements CommandLineRunner {

@Autowired
private TaskService taskService;

@Autowired
private CronTaskRegistrar cronTaskRegistrar;

@Override
public void run(String... args) {
// 重新开启线程,避免影响主程序的启动
new Thread() {
public void run() {
// 查找任务状态为正常的任务数据
List<Task> taskList = taskService.getTaskListByStatus(1);

if (!CollectionUtils.isEmpty(taskList)) {
for (Task task : taskList) {
// 执行定时任务
SchedulingRunnable runnable = new SchedulingRunnable(task.getBeanName(), task.getMethodName(), task.getMethodParams());
// 注册任务数据
cronTaskRegistrar.addCronTask(runnable, task.getCronExpression());
}
log.info("定时任务加载完毕......");
}
}
}.start();
}
}

注意:Spring Boot中提供了CommandLineRunner(实现启动初始化功能)和ApplicationRunner(引导类)两个接口来实现容器启动
CommandLineRunner的执行是整个应用启动的一部分,避免CommandLineRunner启动中抛出异常(java.lang.IllegalStateException: Failed to execute CommandLineRunner),直接影响主程序的启动,从而此处重新开启一个线程,让CommandLineRunner和主线程相互独立,此时抛出异常并不会影响到主线程,防止踩坑

定时任务执行类

package com.scheduledtask.task;

import com.scheduledtask.util.SpringContextUtils;
import lombok.*;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.Objects;

/**
* @author Nicky
* @version 1.0
* @className SchedulingRunnable
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 反射机制执行定时任务
* @date 2020/10/27 16:05
*/
@Slf4j
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@Data
public class SchedulingRunnable implements Runnable {
private String beanName;
private String methodName;
private String params;

@Override
public void run() {
log.info("定时任务开始执行 - bean:{},方法:{},参数:{}", beanName, methodName, params);
long startTime = System.currentTimeMillis();

try {
Object target = SpringContextUtils.getBean(beanName);

Method method = StringUtils.isEmpty(params) ?
target.getClass().getDeclaredMethod(methodName) :
target.getClass().getDeclaredMethod(methodName, String.class);

ReflectionUtils.makeAccessible(method);

if (StringUtils.isEmpty(params)) {
method.invoke(target);
} else {
method.invoke(target, params);
}
} catch (Exception e) {
e.printStackTrace();
log.error("定时任务异常 - bean:{},方法:{},参数:{}", beanName, methodName, params);
}
long time = System.currentTimeMillis() - startTime;
log.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{}", beanName, methodName, params, time);
}

@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);
}
}

工具类

获取实例对象

package com.scheduledtask.util;

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

/**
* @author Nicky
* @version 1.0
* @className SpringContextUtils
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 获取当前上下文对象工具类
* @date 2020/10/27 16:07
*/
@Component
public class SpringContextUtils implements ApplicationContextAware {

// 应用上下文对象
private static ApplicationContext applicationContext = null;

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

/**
* @method getBean
* @description 获取当前上下文对象
* @param [name]
* @return java.lang.Object
*/
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
}

就是获取定时任务业务逻辑类注解@Component上配置的实例名(对应数据库中的beanName)

实体对象与sql脚本

任务实体类

package com.scheduledtask.pojo;


import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import java.util.Date;

/**
* @author Nicky
* @version 1.0
* @className Task
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 定时任务实体类
* @date 2020/10/28 10:53
*/
@Data
@AllArgsConstructor //全参构造
@NoArgsConstructor // 无参构造
@Table(name = "task")
public class Task {
@Id
private Integer id;

@Column(name = "beanName")
@NotBlank(message = "对象名不能为空!")
private String beanName;

@Column(name = "methodName")
@NotBlank(message = "方法名不能为空!")
private String methodName;

@Column(name = "methodParams")
private String methodParams;

@Column(name = "cronExpression")
@NotBlank(message = "cron表达式不能为空!")
@Pattern(message = "cron表达式错误!", regexp = "^\\s*($|#|\\w+\\s*=|(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[01]?\\d|2[0-3])(?:(?:-|\\/|\\,)(?:[01]?\\d|2[0-3]))?(?:,(?:[01]?\\d|2[0-3])(?:(?:-|\\/|\\,)(?:[01]?\\d|2[0-3]))?)*)\\s+(\\?|\\*|(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?(?:,(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?)*)\\s+(\\?|\\*|(?:[1-9]|1[012])(?:(?:-|\\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?(?:,(?:[1-9]|1[012])(?:(?:-|\\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?)*|\\?|\\*|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?(?:,(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)*)\\s+(\\?|\\*|(?:[0-6])(?:(?:-|\\/|\\,|#)(?:[0-6]))?(?:L)?(?:,(?:[0-6])(?:(?:-|\\/|\\,|#)(?:[0-6]))?(?:L)?)*|\\?|\\*|(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?(?:,(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?)*)(|\\s)+(\\?|\\*|(?:|\\d{4})(?:(?:-|\\/|\\,)(?:|\\d{4}))?(?:,(?:|\\d{4})(?:(?:-|\\/|\\,)(?:|\\d{4}))?)*))$")
private String cronExpression;

@Column(name = "jobStatus")
private Integer jobStatus;

private String remark;

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Column(name = "createTime")
private Date createTime;

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Column(name = "updateTime")
private Date updateTime;

}

创表语句

CREATE TABLE `task` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`beanName` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '任务名称',
`methodName` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '方法名称',
`methodParams` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '方法参数',
`cronExpression` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'cron表达式',
`jobStatus` char(1) COLLATE utf8_unicode_ci DEFAULT '0' COMMENT '任务状态 0暂停 1正常',
`remark` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '备注',
`createTime` date DEFAULT NULL COMMENT '创建时间',
`updateTime` date DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `beanName` (`beanName`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='定时任务表';

数据处理

package com.scheduledtask.service;

import com.scheduledtask.mapper.TaskMapper;
import com.scheduledtask.pojo.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import java.util.List;

/**
* @author Nicky
* @version 1.0
* @className TaskService
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 业务逻辑层
* @date 2020/10/28 15:00
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class TaskService {

@Autowired
private TaskMapper taskMapper;

public int insertTask(Task task) {
return taskMapper.insertSelective(task);
}

public List<Task> getTaskListByStatus(Integer jobStatus) {
Example example = new Example(Task.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("jobStatus", jobStatus);
return taskMapper.selectByExample(example);
}

public Task findTaskByJobId(Integer id) {
return taskMapper.selectByPrimaryKey(id);
}

public boolean deleteTaskByJobId(Integer id) {
return taskMapper.deleteByPrimaryKey(id) > 0 ? true : false;
}

public boolean updateTask(Task task) {
return taskMapper.updateByPrimaryKeySelective(task) > 0 ? true : false;
}

}

数据连接

数据层类

package com.scheduledtask.mapper;

import com.scheduledtask.pojo.Task;
import org.springframework.stereotype.Repository;
import tk.mybatis.mapper.common.Mapper;

/**
* @author Nicky
* @version 1.0
* @className TaskMapper
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 数据连接层
* @date 2020/10/28 11:10
*/
@Repository
public interface TaskMapper extends Mapper<Task> {


}

注意:因为继承Mapper类,使用通用mapper插件做数据层处理,基本的CRUD单表操作方法都已有

映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.scheduledtask.mapper.TaskMapper">

</mapper>

枚举

package com.scheduledtask.enums;

import lombok.AllArgsConstructor;

/**
* @author Nicky
* @version 1.0
* @className TaskStatus
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 任务状态枚举
* @date 2020/10/28 11:15
*/
@AllArgsConstructor // 全参构造
public enum TaskStatus {
SUSPEND("暂停", 0),
NORMAL("正常", 1);

private String desc;
private int value;

public String desc() {
return this.desc;
}

public int value() {
return this.value;
}
}

定时任务业务逻辑类

package com.scheduledtask.component;

import org.springframework.stereotype.Component;

/**
* @author Nicky
* @version 1.0
* @className TaskOne
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 定时任务业务逻辑类
* Component中的value对应数据库的beanName字段
* 方法名对应数据库的methodName字段
* 参数对应数据库的methodParams字段
* @date 2020/10/30 16:16
*/
@Component("TaskOne")
public class TaskOne {
public void taskWithParams(String params) {
/*
* 此处写有参定时任务的业务逻辑
*/
}

public void taskNoParams() {
/*
* 此处写无参定时任务的业务逻辑
*/
}
}

控制层类

package com.scheduledtask.controller;

import com.scheduledtask.enums.TaskStatus;
import com.scheduledtask.pojo.Task;
import com.scheduledtask.service.TaskService;
import com.scheduledtask.task.CronTaskRegistrar;
import com.scheduledtask.task.SchedulingRunnable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

/**
* @author Nicky
* @version 1.0
* @className TaskController
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 控制层类
* @date 2020/10/28 16:41
*/
@RestController
@Validated
public class TaskController {

@Autowired
private TaskService taskService;

@Autowired
private CronTaskRegistrar cronTaskRegistrar;

/**
* 添加任务
*/
@PostMapping("/addTask")
public String addTask(@Valid Task task) {
if (taskService.insertTask(task) <= 0) {
return "新增失败!";
} else {
if (task.getJobStatus().equals(TaskStatus.NORMAL.value())) {
SchedulingRunnable runnable = new SchedulingRunnable(task.getBeanName(), task.getMethodName(), task.getMethodParams());
cronTaskRegistrar.addCronTask(runnable, task.getCronExpression());
}
}
return "新增成功!";
}

/**
* 删除任务
*/
@DeleteMapping("/deleteTask/{id}")
public String deleteTask(@PathVariable Integer id) {
Task task = taskService.findTaskByJobId(id);
if (!taskService.deleteTaskByJobId(id)) {
return "删除失败!";
} else {
if (task.getJobStatus().equals(TaskStatus.NORMAL.value())) {
SchedulingRunnable runnable = new SchedulingRunnable(task.getBeanName(), task.getMethodName(), task.getMethodParams());
cronTaskRegistrar.removeCronTask(runnable);
}
}
return "删除成功!";
}

/**
* 修改任务
*/
@PostMapping("/updateTask")
public String updateTask(Task taskNew) {
Task taskOld = taskService.findTaskByJobId(taskNew.getId());
if (taskService.updateTask(taskNew)) {
// 先取消原有的定时任务,并删除缓存
if (taskOld.getJobStatus().equals(TaskStatus.NORMAL.value())) {
// 链式编程,使用了lombok的注解@Accessors
SchedulingRunnable runnable = new SchedulingRunnable()
.setBeanName(taskOld.getBeanName())
.setMethodName(taskOld.getMethodName())
.setParams(taskOld.getMethodParams());
cronTaskRegistrar.removeCronTask(runnable);
}
// 新增定时任务,并添加到缓存
if (taskNew.getJobStatus().equals(TaskStatus.NORMAL.value())) {
SchedulingRunnable runnable = new SchedulingRunnable(taskNew.getBeanName(), taskNew.getMethodName(), taskNew.getMethodParams());
cronTaskRegistrar.addCronTask(runnable, taskNew.getCronExpression());
}
} else {
return "更新失败!";
}
return "更新成功!";
}

/**
* 修改任务状态
*/
@GetMapping("/updateTaskStatus/{id}")
public String updateTaskStatus(@PathVariable Integer id) {
Task task = taskService.findTaskByJobId(id);
// 如原先是启动状态,便设置为停止,并从缓存中删除,反之亦然
if (task.getJobStatus().equals(TaskStatus.NORMAL.value())) {
SchedulingRunnable runnable = new SchedulingRunnable(task.getBeanName(), task.getMethodName(), task.getMethodParams());
cronTaskRegistrar.removeCronTask(runnable);
task.setJobStatus(0);
return taskService.updateTask(task) ? "修改成功!" : "修改失败!";
} else {
SchedulingRunnable runnable = new SchedulingRunnable(task.getBeanName(), task.getMethodName(), task.getMethodParams());
cronTaskRegistrar.addCronTask(runnable, task.getCronExpression());
task.setJobStatus(1);
return taskService.updateTask(task) ? "修改成功!" : "修改失败!";
}
}
}

注意:此处用到的校验注解是在org.springframework.validation包下的,如只在方法参数上加@Validated校验注解无效的话,则在类上加@Validated注解,并在方法参数上加@Valid注解

测试

启动加载

项目启动

测试类

package com.scheduledtask;

import com.scheduledtask.controller.TaskController;
import com.scheduledtask.service.TaskService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
* @author Nicky
* @version 1.0
* @className ScheduledTaskTest
* @blog goitman.cn | blog.csdn.net/minkeyto
* @description 测试类
* @date 2020/10/29 15:31
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ScheduledTaskApplication.class)
public class ScheduledTaskTest {

@Autowired
private TaskController taskController;

@Autowired
private TaskService taskService;

@Test
public void testTaskController(){
/* Task task = new Task();
task.setBeanName("TaskOne");
task.setMethodName("taskWithParams");
task.setMethodParams("111");
task.setCronExpression("0/5 * * * * ?");
task.setJobStatus(1);
task.setRemark("111");
task.setCreateTime(new Date());
task.setUpdateTime(new Date());
String msg = taskController.addTask(task);*/


// String msg = taskController.deleteTask(2);


/* Task task = new Task();
task.setId(50);
task.setBeanName("TaskOne");
task.setMethodName("taskNoParams");
task.setMethodParams("");
task.setCronExpression("0/5 * * * * ?");
task.setJobStatus(1);
task.setRemark("111");
task.setCreateTime(new Date());
task.setUpdateTime(new Date());
String msg = taskController.updateTask(task);*/

String msg = taskController.updateTaskStatus(50);
System.out.println(msg);
}
}

源码地址:https://github.com/wangdaicong/spring-boot-project/tree/master/scheduledTaskRegistrar-demo