有时系统中需要定时任务做别的事情,但是简单的定时任务是无法人为去控制的。
在SpringBoot中可以通过@EnableScheduling注解和@Scheduled注解实现定时任务,也可以通过SchedulingConfigurer接口来实现定时任务。但是这两种方式不能动态添加、删除、启动、停止任务。
要实现上面的需求,一般来说可以使用框架——Quartz框架。
下面要说的就是不去依赖别的定时任务框架实现需求。
本篇博客所分享知识非本人原创,参考某一日在微信看到的一篇公众号发的文章,目前找不到了。
添加执行定时任务的线程池配置类
package com.likegakki.springbootschedul.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
* date: 2021/7/24 16:14
*
* @author LOVEGAKKI
* Description: 添加执行定时任务的线程池配置类
*/
@Configuration
public class SchedulingConfig {
@Bean
public TaskScheduler taskScheduler(){
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
//定时任务执行线程池核心数
taskScheduler.setPoolSize(4);
taskScheduler.setRemoveOnCancelPolicy(true);
taskScheduler.setThreadNamePrefix("ThreadPoolTaskScheduler-");
return taskScheduler;
}
}
添加ScheduledFuture的包装类。ScheduledFuture是ScheduledExecutorService定时任务线程池的执行结果。
package com.likegakki.springbootschedul.scheduler;
import java.util.concurrent.ScheduledFuture;
/**
* date: 2021/7/24 16:17
*
* @author LOVEGAKKI
* Description:
*/
public final class ScheduledTask {
volatile ScheduledFuture<?> future;
/**
* 取消定时任务
*/
public void cancel(){
ScheduledFuture<?> future = this.future;
if (future != null){
future.cancel(true);
}
}
}
添加Runnable接口实现类,被定时任务线程池调用,用来执行指定bean里面的方法。
package com.likegakki.springbootschedul.scheduler;
import com.likegakki.springbootschedul.util.SpringContextUtils;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
/**
* date: 2021/7/24 16:23
*
* @author LOVEGAKKI
* Description: 添加Runnable接口实现类,被定时任务线程池调用,用来执行指定bean里面的方法。
*/
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
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);
}
@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 e){
logger.error(String.format("定时任务执行异常 - bean:%s,方法:%s,参数:%s ", beanName, methodName, params), e);
}
long times = System.currentTimeMillis() - startTime;
logger.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{} 毫秒", beanName, methodName, params, times);
}
}
@NoArgsConstructor,@AllArgsConstructor,@EqualsAndHashCode
这三个注解是来自Lombok,分别是:无参构造,全参构造,重写Equals和HashCode方法。
这里使用的SpringContextUtils代码如下:
package com.likegakki.springbootschedul.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@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);
}
}
添加定时任务注册类,用来增加、删除定时任务。
package com.likegakki.springbootschedul.scheduler;
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;
/**
* date: 2021/7/24 18:02
*
* @author LOVEGAKKI
* Description: 添加定时任务注册类,用来增加、删除定时任务。
*/
@Component
public class CronTaskRegistrar implements DisposableBean {
private final Map<Runnable,ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(16);
@Autowired
private TaskScheduler taskScheduler;
public TaskScheduler getTaskScheduler(){
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,scheduledCronTask(cronTask));
}
}
public void removeCronTask(Runnable task){
ScheduledTask scheduledTask = this.scheduledTasks.remove(task);
if (scheduledTask != null){
scheduledTask.cancel();
}
}
public ScheduledTask scheduledCronTask(CronTask cronTask){
ScheduledTask scheduledTask = new ScheduledTask();
scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(),cronTask.getTrigger());
return scheduledTask;
}
@Override
public void destroy() throws Exception {
for (ScheduledTask task : this.scheduledTasks.values()){
task.cancel();
}
this.scheduledTasks.clear();
}
}
编写定时任务代码
package com.likegakki.springbootschedul.scheduler;
import org.springframework.stereotype.Component;
/**
* date: 2021/7/24 20:11
*
* @author LOVEGAKKI
* Description:
*/
@Component("demoTask")
public class DemoTask {
public void taskWithParams(String param){
System.out.println("执行有参示例任务:" + param);
}
public void taskNoParams(){
System.out.println("执行无参示例任务");
}
}
创建定时任务实体类对象
package com.likegakki.springbootschedul.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* date: 2021/7/24 20:17
*
* @author LOVEGAKKI
* Description:
*/
@ApiModel("定时任务实体类")
@Data
@TableName("system_job")
public class SystemJobEntity implements Serializable {
/**
* 任务ID
*/
@TableId(type = IdType.AUTO)
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;
}
定时任务状态枚举类
package com.likegakki.springbootschedul.enums;
public enum SystemJobStatus {
/**
* 暂停
*/
PAUSE,
/**
* 正常
*/
NORMAL
}
对定时任务的增删改查(包含增改启停)
package com.likegakki.springbootschedul.controller;
import com.likegakki.springbootschedul.entity.SystemJobEntity;
import com.likegakki.springbootschedul.enums.SystemJobStatus;
import com.likegakki.springbootschedul.scheduler.CronTaskRegistrar;
import com.likegakki.springbootschedul.scheduler.SchedulingRunnable;
import com.likegakki.springbootschedul.service.SystemJobService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* date: 2021/7/24 20:23
*
* @author LOVEGAKKI
* Description:
*/
@Api(value = "SystemJobController")
@RestController
@RequestMapping("/system/job")
public class SystemJobController {
@Autowired
private SystemJobService systemJobService;
@Autowired
private CronTaskRegistrar cronTaskRegistrar;
@ApiOperation("新增定时任务")
@PostMapping
public String add(@RequestBody SystemJobEntity systemJobEntity){
boolean success = systemJobService.save(systemJobEntity);
if (success){
//判断当前创建的任务状态,如果是启动,添加
if (systemJobEntity.getJobStatus().equals(SystemJobStatus.NORMAL.ordinal())){
SchedulingRunnable task = new SchedulingRunnable(systemJobEntity.getBeanName(), systemJobEntity.getMethodName(), systemJobEntity.getMethodParams());
cronTaskRegistrar.addCronTask(task,systemJobEntity.getCronExpression());
}
return "ok";
}
return "error";
}
@ApiOperation("删除定时任务")
@DeleteMapping("/{jobId}")
public String delete(@PathVariable String jobId){
SystemJobEntity systemJobEntity = systemJobService.getById(jobId);
boolean success = systemJobService.removeById(jobId);
if (success){
if (systemJobEntity.getJobStatus().equals(SystemJobStatus.NORMAL.ordinal())){
SchedulingRunnable task = new SchedulingRunnable(systemJobEntity.getBeanName(), systemJobEntity.getMethodName(), systemJobEntity.getMethodParams());
cronTaskRegistrar.removeCronTask(task);
}
return "ok";
}
return "error";
}
@ApiOperation("修改定时任务")
@PutMapping
public String edit(@RequestBody SystemJobEntity systemJobEntity){
SystemJobEntity oldSystemJobEntity = systemJobService.getById(systemJobEntity.getJobId());
boolean success = systemJobService.updateById(systemJobEntity);
if (success){
//先移除再添加
if (oldSystemJobEntity.getJobStatus().equals(SystemJobStatus.NORMAL.ordinal())){
SchedulingRunnable task = new SchedulingRunnable(oldSystemJobEntity.getBeanName(), oldSystemJobEntity.getMethodName(), oldSystemJobEntity.getMethodParams());
cronTaskRegistrar.removeCronTask(task);
}
if (systemJobEntity.getJobStatus().equals(SystemJobStatus.NORMAL.ordinal())){
SchedulingRunnable task = new SchedulingRunnable(systemJobEntity.getBeanName(), systemJobEntity.getMethodName(), systemJobEntity.getMethodParams());
cronTaskRegistrar.addCronTask(task,systemJobEntity.getCronExpression());
}
return "ok";
}
return "error";
}
@ApiOperation("获取所有定时任务")
@GetMapping
public List<SystemJobEntity> list(){
return systemJobService.list();
}
}
定时任务的代码肯定是提前写好的,做不到动态增加定时任务业务代码。