在Spring Boot项目中,可以通过@EnableScheduling
注解和@Scheduled
注解实现定时任务,也可以通过SchedulingConfigurer
接口来实现定时任务。但是这两种方式不能动态添加、删除、启动、停止定时任务。要实现动态控制定时任务功能,比较广泛的做法是集成Quartz框架。但是这样就需要依赖框架,在满足项目需求的情况下,尽量少的依赖其它框架,避免项目过于臃肿和复杂。查看spring-context
这个jar包中org.springframework.scheduling.ScheduledTaskRegistrar
这个类的源代码,发现可以通过改造这个类就能实现动态增删启停定时任务功能。
@Configuration
public class SchedulingConfig {
@Bean
public TaskScheduler taskScheduler(){
/*
* ThreadPoolTaskScheduler:线程池任务调度类,能够开启线程池进行任务调度。
* ThreadPoolTaskScheduler.schedule()方法会创建一个定时计划ScheduledFuture,在这个方法需要添加两个参数,Runnable(线程接口类) 和CronTrigger(定时任务触发器)
*/
ThreadPoolTaskScheduler poolTaskScheduler = new ThreadPoolTaskScheduler();
// 定时任务执行线程池核心线程数
poolTaskScheduler.setPoolSize(4);
// 开启 remove-on-cancel
poolTaskScheduler.setRemoveOnCancelPolicy(true);
poolTaskScheduler.setThreadNamePrefix("ThreadPoolTaskScheduler--");
return poolTaskScheduler;
}
}
ScheduledFuture
的包装类。ScheduledFuture
是ScheduledExecutorService
定时任务线程池的执行结果。public final class ScheduledTask {
volatile ScheduledFuture<?> future;
/**
* 取消 定时任务
*/
public void cancel(){
ScheduledFuture<?> future = this.future;
if (future != null) {
future.cancel(true);
}
}
}
public class SchedulingRunnable implements Runnable {
private static final Logger log = LoggerFactory.getLogger(SchedulingRunnable.class);
/**
* Bean 名称
*/
private String beanName;
/**
* 方法 名称
*/
private String methodName;
/**
* 参数
*/
private String params;
public SchedulingRunnable(String beanName, String methodName) {
this.beanName = beanName;
this.methodName = methodName;
}
public SchedulingRunnable(String beanName, String methodName, String params) {
this.beanName = beanName;
this.methodName = methodName;
this.params = params;
}
@Override
public void run() {
log.info("定时任务开始执行:beanName:{},methodName:{},params:{}", beanName, methodName, params);
// 开始执行时间
long startTime = System.currentTimeMillis();
try {
// 获取到bean
Object target = SpringContextUtil.getBean(beanName);
// 获取到bean里面的方法
Method method = null;
if (isBlank(params)) {
method = target.getClass().getDeclaredMethod(methodName);
} else {
method = target.getClass().getDeclaredMethod(methodName, String.class);
}
ReflectionUtils.makeAccessible(method);
if (isBlank(params)) {
method.invoke(target);
} else {
method.invoke(target, params);
}
} catch (Exception e) {
log.error(String.format("定时任务执行异常 - bean:%s,方法:%s,参数:%s ", beanName, methodName, params), e);
}
long times = System.currentTimeMillis() - startTime;
log.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{} 毫秒", beanName, methodName, params, times);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SchedulingRunnable)) return false;
SchedulingRunnable that = (SchedulingRunnable) o;
if (params == null) {
return Objects.equals(beanName, that.beanName) &&
Objects.equals(methodName, that.methodName) &&
Objects.equals(null, that.params);
}
return Objects.equals(beanName, that.beanName) &&
Objects.equals(methodName, that.methodName) &&
Objects.equals(params, that.params);
}
@Override
public int hashCode() {
if (params == null) {
return Objects.hash(beanName, methodName);
}
return Objects.hash(beanName, methodName, params);
}
/**
* 判断字符串是否为空
*
* @param str 参数
* @return true 为空
*/
public static boolean isBlank(String str) {
int strLen;
if (str == null || (strLen = str.length()) == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(str.charAt(i))) {
return false;
}
}
return true;
}
}
@Component
public class CronTaskRegistrar implements DisposableBean {
/**
* 存放定时任务
*/
private final Map<Runnable, ScheduledTask> scheduledTaskMap = new ConcurrentHashMap<>();
@Autowired
private TaskScheduler taskScheduler;
/**
* 添加定时任务类
*
* @param task
* @param cronExpression cron表达式
*/
public void addCronTask(Runnable task, String cronExpression) {
addCronTask(new CronTask(task, cronExpression));
}
/**
* 添加定时任务类
*
* @param cronTask
*/
public void addCronTask(CronTask cronTask) {
if (cronTask != null) {
Runnable task = cronTask.getRunnable();
if (this.scheduledTaskMap.containsKey(task)) {
this.removeCronTask(task);
}
this.scheduledTaskMap.put(task, scheduledCronTask(cronTask));
}
}
/**
* 移除 定时任务,并取消
*
* @param task
*/
public void removeCronTask(Runnable task) {
ScheduledTask scheduledTask = this.scheduledTaskMap.remove(task);
if (scheduledTask != null) {
scheduledTask.cancel();
}
}
/**
* 调用线程池
*
* @param cronTask 定时任务
* @return
*/
public ScheduledTask scheduledCronTask(CronTask cronTask) {
ScheduledTask scheduledTask = new ScheduledTask();
scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
return scheduledTask;
}
/**
* 重写销毁方法
*
* @throws Exception
*/
@Override
public void destroy() throws Exception {
for (ScheduledTask task : this.scheduledTaskMap.values()) {
task.cancel();
}
this.scheduledTaskMap.clear();
}
}
@Component("demoTest")
public class DemoTest {
public void taskWithParams(String params) {
System.out.println(">>>>>执行带参定时任务 params:" + params);
}
public void taskNoParams(){
System.out.println(">>>>>执行无参定时任务");
}
}
public class SysJobVo {
/**
* 定时任务ID
*/
private Integer jobId;
/**
* bean 名称
*/
private String beanName;
/**
* 方法名称
*/
private String methodName;
/**
* 方法参数
*/
private String methodParams;
/**
* cron表达式
*/
private String cronExpression;
/**
* 状态 0=正常;1=暂停
*/
private Integer jobStatus;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 省略 Getter and Setter
*/
}
@Service
public class SysTaskServiceImpl implements ISysTaskService {
@Autowired
private CronTaskRegistrar cronTaskRegistrar;
private static List<SysJobVo> jobList;
static {
jobList = new ArrayList<>();
SysJobVo vo = new SysJobVo();
// 省略数据
jobList.add(vo);
}
/**
* 查询进行中的定时任务类
*
* @param status
*/
@Override
public List<SysJobVo> selectTask(int status) {
List<SysJobVo> list = jobList.stream()
.filter(s -> Objects.equals(s.getJobStatus(), status))
.collect(Collectors.toList());
return list;
}
/**
* 添加定时任务
*
* @param jobVo
*/
@Override
public void addTask(SysJobVo jobVo) {
// 处理数据 插入数据库
// 判断定时任务是否开启
Integer jobStatus = jobVo.getJobStatus();
if (Objects.equals(jobStatus, 0)) {
this.changeTaskStatus(Boolean.TRUE, jobVo);
}
}
/**
* 修改定时任务
*
* @param jobVo
*/
@Override
public void updateTask(SysJobVo jobVo) {
// 获取数据库中已存在的数据
// 判断 原来的定时任务是否开启,如果开启,则先停止
if (Objects.equals(existJob.getJobStatus(), 0)) {
this.changeTaskStatus(Boolean.FALSE, existJob);
}
// 处理数据 插入数据库
// 判断定时任务是否开启
if (Objects.equals(jobVo.getJobStatus(), 0)) {
this.changeTaskStatus(Boolean.TRUE, jobVo);
}
}
/**
* 删除定时任务
*
* @param jobId
*/
@Override
public void deleteTask(Integer jobId) {
// 获取数据库中已存在的数据
// 判断定时任务是否开启
if (Objects.equals(existJob.getJobStatus(), 0)) {
this.changeTaskStatus(Boolean.FALSE, existJob);
}
// 处理数据 插入数据库
}
/**
* 修改定时任务类状态
*
* @param add
* @param jobVo
*/
private void changeTaskStatus(boolean add, SysJobVo jobVo) {
if (add) {
SchedulingRunnable task = new SchedulingRunnable(jobVo.getBeanName(), jobVo.getMethodName(), jobVo.getMethodParams());
cronTaskRegistrar.addCronTask(task, jobVo.getCronExpression());
} else {
SchedulingRunnable task = new SchedulingRunnable(jobVo.getBeanName(), jobVo.getMethodName(), jobVo.getMethodParams());
cronTaskRegistrar.removeCronTask(task);
}
}
}
@Service
public class SysJobRunner implements CommandLineRunner {
private final static Logger log = LoggerFactory.getLogger(SysJobRunner.class);
@Autowired
private ISysTaskService sysTaskService;
@Autowired
private CronTaskRegistrar cronTaskRegistrar;
@Override
public void run(String... args) throws Exception {
List<SysJobVo> list = sysTaskService.selectTask(0);
log.info(">>>>初始化定时任务 list={}",JSONObject.toJSONString(list));
for (SysJobVo jobVo : list) {
SchedulingRunnable task = new SchedulingRunnable(jobVo.getBeanName(), jobVo.getMethodName(), jobVo.getMethodParams());
cronTaskRegistrar.addCronTask(task, jobVo.getCronExpression());
}
log.info(">>>>>定时任务初始化完毕<<<<<");
}
}
GitHub 地址
spring boot实现动态增删启停定时任务
Spring Boot Task定时任务升级(启动、停止、变更执行周期)