SpringBoot開發(fā)實戰(zhàn)系列之動態(tài)定時任務(wù)
前言
定時器是我們項目中經(jīng)常會用到的,SpringBoot使用@Scheduled注解可以快速啟用一個簡單的定時器(詳情請看我們之前的博客《SpringBoot系列——定時器》),然而這種方式的定時器缺乏靈活性,如果需要對定時器進(jìn)行調(diào)整,需要重啟項目才生效,本文記錄SpringBoot如何靈活配置動態(tài)定時任務(wù)
代碼編寫
首先先建表,重要字段:唯一表id、Runnable任務(wù)類、Cron表達(dá)式,其他的都是一些額外補充字段
DROP TABLE IF EXISTS `tb_task`;
CREATE TABLE `tb_task` (
`task_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '定時任務(wù)id',
`task_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '定時任務(wù)名稱',
`task_desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '定時任務(wù)描述',
`task_exp` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '定時任務(wù)Cron表達(dá)式',
`task_status` int(1) NULL DEFAULT NULL COMMENT '定時任務(wù)狀態(tài),0停用 1啟用',
`task_class` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '定時任務(wù)的Runnable任務(wù)類完整路徑',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新時間',
`create_time` datetime NULL DEFAULT NULL COMMENT '創(chuàng)建時間',
PRIMARY KEY (`task_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '動態(tài)定時任務(wù)表' ROW_FORMAT = Compact;
INSERT INTO `tb_task` VALUES ('1', 'task1', '測試動態(tài)定時任務(wù)1', '0/5 * * * * ?', 0, 'cn.huanzi.qch.springboottimer.task.MyRunnable1', '2021-08-06 17:39:23', '2021-08-06 17:39:25');
INSERT INTO `tb_task` VALUES ('2', 'task2', '測試動態(tài)定時任務(wù)2', '0/5 * * * * ?', 0, 'cn.huanzi.qch.springboottimer.task.MyRunnable2', '2021-08-06 17:39:23', '2021-08-06 17:39:25');
項目引入jpa、數(shù)據(jù)庫驅(qū)動,用于數(shù)據(jù)庫操作
<!--添加springdata-jpa依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--添加MySQL驅(qū)動依賴 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
數(shù)據(jù)庫相關(guān)配置文件
spring:
datasource: #數(shù)據(jù)庫相關(guān)
url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&characterEncoding=utf-8
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
mvc:
date-format: yyyy-MM-dd HH:mm:ss #mvc接收參數(shù)時對日期進(jìn)行格式化
jackson:
date-format: yyyy-MM-dd HH:mm:ss #jackson對響應(yīng)回去的日期參數(shù)進(jìn)行格式化
time-zone: GMT+8
jpa:
show-sql: true
entity實體與數(shù)據(jù)表映射,以及與之對應(yīng)的repository
/**
* 動態(tài)定時任務(wù)表
* 重要屬性:唯一表id、Runnable任務(wù)類、Cron表達(dá)式,
* 其他的都是一些額外補充說明屬性
*/
@Entity
@Table(name = "tb_task")
@Data
public class TbTask {
@Id
private String taskId;//定時任務(wù)id
private String taskName;//定時任務(wù)名稱
private String taskDesc;//定時任務(wù)描述
private String taskExp;//定時任務(wù)Cron表達(dá)式
private Integer taskStatus;//定時任務(wù)狀態(tài),0停用 1啟用
private String taskClass;//定時任務(wù)的Runnable任務(wù)類完整路徑
private Date updateTime;//更新時間
private Date createTime;//創(chuàng)建時間
}
/**
* TbTask動態(tài)定時任務(wù)Repository
*/
@Repository
public interface TbTaskRepository extends JpaRepository<TbTask,String>, JpaSpecificationExecutor<TbTask> {
}
測試動態(tài)定時器的配置類,主要作用:初始化線程池任務(wù)調(diào)度、讀取/更新數(shù)據(jù)庫任務(wù)、啟動/停止定時器等
/**
* 測試定時器2-動態(tài)定時器
*/
@Slf4j
@Component
public class TestScheduler2 {
//數(shù)據(jù)庫的任務(wù)
public static ConcurrentHashMap<String, TbTask> tasks = new ConcurrentHashMap<>(10);
//正在運行的任務(wù)
public static ConcurrentHashMap<String,ScheduledFuture> runTasks = new ConcurrentHashMap<>(10);
//線程池任務(wù)調(diào)度
private ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
@Autowired
private TbTaskRepository tbTaskRepository;
/**
* 初始化線程池任務(wù)調(diào)度
*/
@Autowired
public TestScheduler2(){
this.threadPoolTaskScheduler.setPoolSize(10);
this.threadPoolTaskScheduler.setThreadNamePrefix("task-thread-");
this.threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
this.threadPoolTaskScheduler.initialize();
}
/**
* 獲取所有數(shù)據(jù)庫里的定時任務(wù)
*/
private void getAllTbTask(){
//查詢所有,并put到tasks
TestScheduler2.tasks.clear();
List<TbTask> list = tbTaskRepository.findAll();
list.forEach((task)-> TestScheduler2.tasks.put(task.getTaskId(),task));
}
/**
* 根據(jù)定時任務(wù)id,啟動定時任務(wù)
*/
void start(String taskId){
try {
//如果為空,重新獲取
if(TestScheduler2.tasks.size() <= 0){
this.getAllTbTask();
}
TbTask tbTask = TestScheduler2.tasks.get(taskId);
//獲取并實例化Runnable任務(wù)類
Class<?> clazz = Class.forName(tbTask.getTaskClass());
Runnable runnable = (Runnable)clazz.newInstance();
//Cron表達(dá)式
CronTrigger cron = new CronTrigger(tbTask.getTaskExp());
//執(zhí)行,并put到runTasks
TestScheduler2.runTasks.put(taskId, Objects.requireNonNull(this.threadPoolTaskScheduler.schedule(runnable, cron)));
this.updateTaskStatus(taskId,1);
log.info("{},任務(wù)啟動!",taskId);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
log.error("{},任務(wù)啟動失敗...",taskId);
e.printStackTrace();
}
}
/**
* 根據(jù)定時任務(wù)id,停止定時任務(wù)
*/
void stop(String taskId){
TestScheduler2.runTasks.get(taskId).cancel(true);
TestScheduler2.runTasks.remove(taskId);
this.updateTaskStatus(taskId,0);
log.info("{},任務(wù)停止...",taskId);
}
/**
* 更新數(shù)據(jù)庫動態(tài)定時任務(wù)狀態(tài)
*/
private void updateTaskStatus(String taskId,int status){
TbTask task = tbTaskRepository.getOne(taskId);
task.setTaskStatus(status);
task.setUpdateTime(new Date());
tbTaskRepository.save(task);
}
}
接下來就是編寫測試接口、測試Runnable類(3個Runnable類,這里就不貼那么多了,就貼個MyRunnable1)
/**
* Runnable任務(wù)類1
*/
@Slf4j
public class MyRunnable1 implements Runnable {
@Override
public void run() {
log.info("MyRunnable1 {}",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}
Controller接口
/**
* 動態(tài)定時任務(wù)Controller測試
*/
@RestController
@RequestMapping("/tbTask/")
public class TbTaskController {
@Autowired
private TestScheduler2 testScheduler2;
@Autowired
private TbTaskRepository tbTaskRepository;
/**
* 啟動一個動態(tài)定時任務(wù)
* http://localhost:10085/tbTask/start/2
*/
@RequestMapping("start/{taskId}")
public String start(@PathVariable("taskId") String taskId){
testScheduler2.start(taskId);
return "操作成功";
}
/**
* 停止一個動態(tài)定時任務(wù)
* http://localhost:10085/tbTask/stop/2
*/
@RequestMapping("stop/{taskId}")
public String stop(@PathVariable("taskId") String taskId){
testScheduler2.stop(taskId);
return "操作成功";
}
/**
* 更新一個動態(tài)定時任務(wù)
* http://localhost:10085/tbTask/save?taskId=2&taskExp=0/2 * * * * ?&taskClass=cn.huanzi.qch.springboottimer.task.MyRunnable3
*/
@RequestMapping("save")
public String save(TbTask task) throws IllegalAccessException {
//先更新表數(shù)據(jù)
TbTask tbTask = tbTaskRepository.getOne(task.getTaskId());
//null值忽略
List<String> ignoreProperties = new ArrayList<>(7);
//反射獲取Class的屬性(Field表示類中的成員變量)
for (Field field : task.getClass().getDeclaredFields()) {
//獲取授權(quán)
field.setAccessible(true);
//屬性名稱
String fieldName = field.getName();
//屬性的值
Object fieldValue = field.get(task);
//找出值為空的屬性,我們復(fù)制的時候不進(jìn)行賦值
if(null == fieldValue){
ignoreProperties.add(fieldName);
}
}
//org.springframework.beans BeanUtils.copyProperties(A,B):A中的值付給B
BeanUtils.copyProperties(task, tbTask,ignoreProperties.toArray(new String[0]));
tbTaskRepository.save(tbTask);
TestScheduler2.tasks.clear();
//停止舊任務(wù)
testScheduler2.stop(tbTask.getTaskId());
//重新啟動
testScheduler2.start(tbTask.getTaskId());
return "操作成功";
}
}
效果演示
啟動
啟動一個定時任務(wù),http://localhost:10085/tbTask/start/2

可以看到,id為2的定時任務(wù)已經(jīng)被啟動,corn表達(dá)式為5秒執(zhí)行一次,runnable任務(wù)為MyRunnable2
修改
修改一個定時任務(wù),http://localhost:10085/tbTask/save?taskId=2&taskExp=0/2 * * * * ?&taskClass=cn.huanzi.qch.springboottimer.task.MyRunnable3

調(diào)用修改后,數(shù)據(jù)庫信息被修改,id為2的舊任務(wù)被停止重新啟用新任務(wù),corn表達(dá)式為2秒執(zhí)行一次,runnable任務(wù)類為MyRunnable3
停止
停止一個定時任務(wù),http://localhost:10085/tbTask/stop/2

id為2的定時任務(wù)被停止
后記
可以看到,配置動態(tài)定時任務(wù)后,可以方便、實時的對定時任務(wù)進(jìn)行修改、調(diào)整,再也不用重啟項目啦
SpringBoot配置動態(tài)定時任務(wù)暫時先記錄到這,后續(xù)再進(jìn)行補充
代碼開源
代碼已經(jīng)開源、托管到我的GitHub、碼云:
GitHub:https://github.com/huanzi-qch/springBoot
碼云:https://gitee.com/huanzi-qch/springBoot
到此這篇關(guān)于SpringBoot開發(fā)實戰(zhàn)系列之動態(tài)定時任務(wù)的文章就介紹到這了,更多相關(guān)SpringBoot動態(tài)定時任務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
hadoop client與datanode的通信協(xié)議分析
本文主要分析了hadoop客戶端read和write block的流程. 以及client和datanode通信的協(xié)議, 數(shù)據(jù)流格式等2012-11-11
SpringBoot整合Ip2region獲取IP地址和定位的詳細(xì)過程
ip2region v2.0 - 是一個離線IP地址定位庫和IP定位數(shù)據(jù)管理框架,10微秒級別的查詢效率,提供了眾多主流編程語言的 xdb 數(shù)據(jù)生成和查詢客戶端實現(xiàn) ,這篇文章主要介紹了SpringBoot整合Ip2region獲取IP地址和定位,需要的朋友可以參考下2023-06-06
SpringBoot中集成screw(螺絲釘)實現(xiàn)數(shù)據(jù)庫表結(jié)構(gòu)文檔生成方法
這篇文章主要介紹了SpringBoot中集成screw(螺絲釘)實現(xiàn)數(shù)據(jù)庫表結(jié)構(gòu)文檔生成,下面以連接mysql數(shù)據(jù)庫并生成html格式的數(shù)據(jù)庫結(jié)構(gòu)文檔為例,插件的使用方式除可以使用代碼外,還可以使用Maven插件的方式,需要的朋友可以參考下2024-07-07
IntelliJ?IDEA設(shè)置JVM運行參數(shù)的圖文介紹
這篇文章主要介紹了IntelliJ?IDEA設(shè)置JVM運行參數(shù)的方法,包括配置方式及優(yōu)先級,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-04-04
Springboot實現(xiàn)通用Auth認(rèn)證的幾種方式
本文主要介紹了Springboot實現(xiàn)通用Auth認(rèn)證的幾種方式,主要介紹了4種方式,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-07-07
SpringBoot實現(xiàn)短信驗證碼校驗方法思路詳解
最近做項目遇到這樣的需求,前端是基于BootStrap,html代碼中有BootStrap樣式實現(xiàn)的,具體后臺實現(xiàn)代碼大家通過本文一起學(xué)習(xí)吧2017-08-08

