SpringBoot 实现动态添加定时任务功能
作者:lijiahangmax 发布时间:2022-07-12 05:07:40
标签:SpringBoot,定时任务
最近的需求有一个自动发布的功能, 需要做到每次提交都要动态的添加一个定时任务
代码结构
1. 配置类
package com.orion.ops.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
* 调度器配置
*
* @author Jiahang Li
* @version 1.0.0
* @since 2022/2/14 9:51
*/
@EnableScheduling
@Configuration
public class SchedulerConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(4);
scheduler.setRemoveOnCancelPolicy(true);
scheduler.setThreadNamePrefix("scheduling-task-");
return scheduler;
}
}
2. 定时任务类型枚举
package com.orion.ops.handler.scheduler;
import com.orion.ops.consts.Const;
import com.orion.ops.handler.scheduler.impl.ReleaseTaskImpl;
import com.orion.ops.handler.scheduler.impl.SchedulerTaskImpl;
import lombok.AllArgsConstructor;
import java.util.function.Function;
/**
* 任务类型
*
* @author Jiahang Li
* @version 1.0.0
* @since 2022/2/14 10:16
*/
@AllArgsConstructor
public enum TaskType {
/**
* 发布任务
*/
RELEASE(id -> new ReleaseTaskImpl((Long) id)) {
@Override
public String getKey(Object params) {
return Const.RELEASE + "-" + params;
}
},
* 调度任务
SCHEDULER_TASK(id -> new SchedulerTaskImpl((Long) id)) {
return Const.TASK + "-" + params;
;
private final Function<Object, Runnable> factory;
* 创建任务
*
* @param params params
* @return task
public Runnable create(Object params) {
return factory.apply(params);
}
* 获取 key
* @return key
public abstract String getKey(Object params);
}
这个枚举的作用是生成定时任务的 runnable 和 定时任务的唯一值, 方便后续维护
3. 实际执行任务实现类
package com.orion.ops.handler.scheduler.impl;
import com.orion.ops.service.api.ApplicationReleaseService;
import com.orion.spring.SpringHolder;
import lombok.extern.slf4j.Slf4j;
/**
* 发布任务实现
*
* @author Jiahang Li
* @version 1.0.0
* @since 2022/2/14 10:25
*/
@Slf4j
public class ReleaseTaskImpl implements Runnable {
protected static ApplicationReleaseService applicationReleaseService = SpringHolder.getBean(ApplicationReleaseService.class);
private Long releaseId;
public ReleaseTaskImpl(Long releaseId) {
this.releaseId = releaseId;
}
@Override
public void run() {
log.info("定时执行发布任务-触发 releaseId: {}", releaseId);
applicationReleaseService.runnableAppRelease(releaseId, true);
}
4. 定时任务包装器
package com.orion.ops.handler.scheduler;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import java.util.Date;
import java.util.concurrent.ScheduledFuture;
/**
* 定时 任务包装器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2022/2/14 10:34
*/
public class TimedTask {
/**
* 任务
*/
private Runnable runnable;
* 异步执行
private volatile ScheduledFuture<?> future;
public TimedTask(Runnable runnable) {
this.runnable = runnable;
}
* 提交任务 一次性
*
* @param scheduler scheduler
* @param time time
public void submit(TaskScheduler scheduler, Date time) {
this.future = scheduler.schedule(runnable, time);
* 提交任务 cron表达式
* @param trigger trigger
public void submit(TaskScheduler scheduler, Trigger trigger) {
this.future = scheduler.schedule(runnable, trigger);
* 取消定时任务
public void cancel() {
if (future != null) {
future.cancel(true);
}
}
这个类的作用是包装实际执行任务, 以及提供调度器的执行方法
5. 任务注册器 (核心)
package com.orion.ops.handler.scheduler;
import com.orion.ops.consts.MessageConst;
import com.orion.utils.Exceptions;
import com.orion.utils.collect.Maps;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
import java.util.Map;
/**
* 任务注册器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2022/2/14 10:46
*/
@Component
public class TaskRegister implements DisposableBean {
private final Map<String, TimedTask> taskMap = Maps.newCurrentHashMap();
@Resource
@Qualifier("taskScheduler")
private TaskScheduler scheduler;
/**
* 提交任务
*
* @param type type
* @param time time
* @param params params
*/
public void submit(TaskType type, Date time, Object params) {
// 获取任务
TimedTask timedTask = this.getTask(type, params);
// 执行任务
timedTask.submit(scheduler, time);
}
* @param cron cron
public void submit(TaskType type, String cron, Object params) {
timedTask.submit(scheduler, new CronTrigger(cron));
* 获取任务
private TimedTask getTask(TaskType type, Object params) {
// 生成任务
Runnable runnable = type.create(params);
String key = type.getKey(params);
// 判断是否存在任务
if (taskMap.containsKey(key)) {
throw Exceptions.init(MessageConst.TASK_PRESENT);
}
TimedTask timedTask = new TimedTask(runnable);
taskMap.put(key, timedTask);
return timedTask;
* 取消任务
public void cancel(TaskType type, Object params) {
TimedTask task = taskMap.get(key);
if (task != null) {
taskMap.remove(key);
task.cancel();
* 是否存在
public boolean has(TaskType type, Object params) {
return taskMap.containsKey(type.getKey(params));
@Override
public void destroy() {
taskMap.values().forEach(TimedTask::cancel);
taskMap.clear();
}
这个类提供了执行, 提交任务的api, 实现 DisposableBean 接口, 便于在bean销毁时将任务一起销毁
6. 使用
@Resource
private TaskRegister taskRegister;
/**
* 提交发布
*/
@RequestMapping("/submit")
@EventLog(EventType.SUBMIT_RELEASE)
public Long submitAppRelease(@RequestBody ApplicationReleaseRequest request) {
Valid.notBlank(request.getTitle());
Valid.notNull(request.getAppId());
Valid.notNull(request.getProfileId());
Valid.notNull(request.getBuildId());
Valid.notEmpty(request.getMachineIdList());
TimedReleaseType timedReleaseType = Valid.notNull(TimedReleaseType.of(request.getTimedRelease()));
if (TimedReleaseType.TIMED.equals(timedReleaseType)) {
Date timedReleaseTime = Valid.notNull(request.getTimedReleaseTime());
Valid.isTrue(timedReleaseTime.compareTo(new Date()) > 0, MessageConst.TIMED_GREATER_THAN_NOW);
}
// 提交
Long id = applicationReleaseService.submitAppRelease(request);
// 提交任务
taskRegister.submit(TaskType.RELEASE, request.getTimedReleaseTime(), id);
return id;
}
最后
这是一个简单的动态添加定时任务的工具, 有很多的改造空间, 比如想持久化可以插入到库中, 定义一个 CommandLineRunner 在启动时将定时任务全部加载, 还可以给任务加钩子自动提交,自动删除等, 代码直接cv一定会报错, 就是一些工具, 常量类会报错, 改改就好了, 本人已亲测可用, 有什么问题可以在评论区沟通
来源:https://blog.csdn.net/qq_41011894/article/details/123116277


猜你喜欢
- 今天实现一个很多app中使用到的加载进度条的效果,可能我们平时数据加载都使用到的是系统自带的,但是也有很多app加载进度条的效果实现挺好看,
- 本文介绍SpringBoot如何使用Prometheus配合Grafana监控。1.关于PrometheusPrometheus是一个根据应
- java中有关单链表反转的方法有很多种,这里记录一种并附上详细步骤:代码如下/** * Definition for singly
- 刚刚看MSDN的一个例子无意发现的小技巧,大家一看就明白了,不过好像蛮有用的,先记下咯,以后慢慢研究。using System;namesp
- 说明:1、集合类型参数化;2、可根据集合中的对象的各个属性进行排序,传入属性名称即可;注:属性必须实现了IComparable接口,C#中i
- 1.下载jenkins 下载地址:https://jenkins.io/  
- 本文以一个C#的SQL数据库字串操作函数为例,说明如何实现对SQL字符串过滤、检测SQL是否有危险字符、修正sql语句中的转义字符,确保SQ
- 本文实例为大家分享了Unity Shader实现描边OutLine效果的具体代码,供大家参考,具体内容如下Shader实现描边流程大致为:对
- 什么是递归?用Java写一个简单的递归程序递归的定义递归(recursion):以此类推是递归的基本思想,将规模大的问题转化为规模小的问题来
- 目录前言整合swagger api自定义配置信息简单使用Swagger常用注解Api标记ApiOperation标记ApiParam标记Ap
- 一、 HttpURLConnection以GET方式访问网络:HttpURLConnection connection = null;try
- java向文件中追加内容与读写文件内容源码实例代码向文件尾加入内容有多种方法,常见的方法有两种:RandomAccessFile类可以实现随
- 在早期的Java版本中,如果需要对指定目录下的文件进行遍历,则需要用递归的方法来实现,这种方法有点复杂,而且灵活性也不高。而使用Java7中
- 今天从数据库生成了一份数据字典,但是没有备注,所以需要程序把表格都读出来。用到了下面的代码,亲测可用~~object oFileName =
- 目录场景介绍自动填充处理器Mybatis-Plus配置类配置实体类中相关字段的自动填充策略在阿里开发手册的建表规约中有说明,数据库表中应该都
- Java main 方法面试题的详细整理1.不用main方法如何定义一个类?不行,没有main方法我们不能运行Java类。在java 7之前
- 一,使用注解: 在spring的配置文件applicationContext.xml中,加入注解扫描。配
- 笔者在使用Entity Framework中的Scaffolding机制自动创建拓展名为mdf的数据库及表单时,遇到如下的错误:A file
- aes 对称加密密钥必须是32字节using System;using System.Security.Cryptography;using
- 全局变量顾名思义就是在整个的类中或者可在多个函数中调用的变量。也称为外部变量。局部变量则是特定过程或函数中可以访问的变量。声明一个变量是很