SpringBoot開(kāi)發(fā)實(shí)戰(zhàn)系列之動(dòng)態(tài)定時(shí)任務(wù)
前言
定時(shí)器是我們項(xiàng)目中經(jīng)常會(huì)用到的,SpringBoot使用@Scheduled注解可以快速啟用一個(gè)簡(jiǎn)單的定時(shí)器(詳情請(qǐng)看我們之前的博客《SpringBoot系列——定時(shí)器》),然而這種方式的定時(shí)器缺乏靈活性,如果需要對(duì)定時(shí)器進(jìn)行調(diào)整,需要重啟項(xiàng)目才生效,本文記錄SpringBoot如何靈活配置動(dòng)態(tài)定時(shí)任務(wù)
代碼編寫(xiě)
首先先建表,重要字段:唯一表id、Runnable任務(wù)類、Cron表達(dá)式,其他的都是一些額外補(bǔ)充字段
DROP TABLE IF EXISTS `tb_task`; CREATE TABLE `tb_task` ( `task_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '定時(shí)任務(wù)id', `task_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '定時(shí)任務(wù)名稱', `task_desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '定時(shí)任務(wù)描述', `task_exp` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '定時(shí)任務(wù)Cron表達(dá)式', `task_status` int(1) NULL DEFAULT NULL COMMENT '定時(shí)任務(wù)狀態(tài),0停用 1啟用', `task_class` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '定時(shí)任務(wù)的Runnable任務(wù)類完整路徑', `update_time` datetime NULL DEFAULT NULL COMMENT '更新時(shí)間', `create_time` datetime NULL DEFAULT NULL COMMENT '創(chuàng)建時(shí)間', PRIMARY KEY (`task_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '動(dòng)態(tài)定時(shí)任務(wù)表' ROW_FORMAT = Compact; INSERT INTO `tb_task` VALUES ('1', 'task1', '測(cè)試動(dòng)態(tài)定時(shí)任務(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', '測(cè)試動(dòng)態(tài)定時(shí)任務(wù)2', '0/5 * * * * ?', 0, 'cn.huanzi.qch.springboottimer.task.MyRunnable2', '2021-08-06 17:39:23', '2021-08-06 17:39:25');
項(xiàng)目引入jpa、數(shù)據(jù)庫(kù)驅(qū)動(dòng),用于數(shù)據(jù)庫(kù)操作
<!--添加springdata-jpa依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!--添加MySQL驅(qū)動(dòng)依賴 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
數(shù)據(jù)庫(kù)相關(guān)配置文件
spring: datasource: #數(shù)據(jù)庫(kù)相關(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ù)時(shí)對(duì)日期進(jìn)行格式化 jackson: date-format: yyyy-MM-dd HH:mm:ss #jackson對(duì)響應(yīng)回去的日期參數(shù)進(jìn)行格式化 time-zone: GMT+8 jpa: show-sql: true
entity實(shí)體與數(shù)據(jù)表映射,以及與之對(duì)應(yīng)的repository
/** * 動(dòng)態(tài)定時(shí)任務(wù)表 * 重要屬性:唯一表id、Runnable任務(wù)類、Cron表達(dá)式, * 其他的都是一些額外補(bǔ)充說(shuō)明屬性 */ @Entity @Table(name = "tb_task") @Data public class TbTask { @Id private String taskId;//定時(shí)任務(wù)id private String taskName;//定時(shí)任務(wù)名稱 private String taskDesc;//定時(shí)任務(wù)描述 private String taskExp;//定時(shí)任務(wù)Cron表達(dá)式 private Integer taskStatus;//定時(shí)任務(wù)狀態(tài),0停用 1啟用 private String taskClass;//定時(shí)任務(wù)的Runnable任務(wù)類完整路徑 private Date updateTime;//更新時(shí)間 private Date createTime;//創(chuàng)建時(shí)間 }
/** * TbTask動(dòng)態(tài)定時(shí)任務(wù)Repository */ @Repository public interface TbTaskRepository extends JpaRepository<TbTask,String>, JpaSpecificationExecutor<TbTask> { }
測(cè)試動(dòng)態(tài)定時(shí)器的配置類,主要作用:初始化線程池任務(wù)調(diào)度、讀取/更新數(shù)據(jù)庫(kù)任務(wù)、啟動(dòng)/停止定時(shí)器等
/** * 測(cè)試定時(shí)器2-動(dòng)態(tài)定時(shí)器 */ @Slf4j @Component public class TestScheduler2 { //數(shù)據(jù)庫(kù)的任務(wù) public static ConcurrentHashMap<String, TbTask> tasks = new ConcurrentHashMap<>(10); //正在運(yùn)行的任務(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ù)庫(kù)里的定時(shí)任務(wù) */ private void getAllTbTask(){ //查詢所有,并put到tasks TestScheduler2.tasks.clear(); List<TbTask> list = tbTaskRepository.findAll(); list.forEach((task)-> TestScheduler2.tasks.put(task.getTaskId(),task)); } /** * 根據(jù)定時(shí)任務(wù)id,啟動(dòng)定時(shí)任務(wù) */ void start(String taskId){ try { //如果為空,重新獲取 if(TestScheduler2.tasks.size() <= 0){ this.getAllTbTask(); } TbTask tbTask = TestScheduler2.tasks.get(taskId); //獲取并實(shí)例化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ù)啟動(dòng)!",taskId); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { log.error("{},任務(wù)啟動(dòng)失敗...",taskId); e.printStackTrace(); } } /** * 根據(jù)定時(shí)任務(wù)id,停止定時(shí)任務(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ù)庫(kù)動(dòng)態(tài)定時(shí)任務(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); } }
接下來(lái)就是編寫(xiě)測(cè)試接口、測(cè)試Runnable類(3個(gè)Runnable類,這里就不貼那么多了,就貼個(gè)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接口
/** * 動(dòng)態(tài)定時(shí)任務(wù)Controller測(cè)試 */ @RestController @RequestMapping("/tbTask/") public class TbTaskController { @Autowired private TestScheduler2 testScheduler2; @Autowired private TbTaskRepository tbTaskRepository; /** * 啟動(dòng)一個(gè)動(dòng)態(tài)定時(shí)任務(wù) * http://localhost:10085/tbTask/start/2 */ @RequestMapping("start/{taskId}") public String start(@PathVariable("taskId") String taskId){ testScheduler2.start(taskId); return "操作成功"; } /** * 停止一個(gè)動(dòng)態(tài)定時(shí)任務(wù) * http://localhost:10085/tbTask/stop/2 */ @RequestMapping("stop/{taskId}") public String stop(@PathVariable("taskId") String taskId){ testScheduler2.stop(taskId); return "操作成功"; } /** * 更新一個(gè)動(dòng)態(tài)定時(shí)任務(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ù)制的時(shí)候不進(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()); //重新啟動(dòng) testScheduler2.start(tbTask.getTaskId()); return "操作成功"; } }
效果演示
啟動(dòng)
啟動(dòng)一個(gè)定時(shí)任務(wù),http://localhost:10085/tbTask/start/2
可以看到,id為2的定時(shí)任務(wù)已經(jīng)被啟動(dòng),corn表達(dá)式為5秒執(zhí)行一次,runnable任務(wù)為MyRunnable2
修改
修改一個(gè)定時(shí)任務(wù),http://localhost:10085/tbTask/save?taskId=2&taskExp=0/2 * * * * ?&taskClass=cn.huanzi.qch.springboottimer.task.MyRunnable3
調(diào)用修改后,數(shù)據(jù)庫(kù)信息被修改,id為2的舊任務(wù)被停止重新啟用新任務(wù),corn表達(dá)式為2秒執(zhí)行一次,runnable任務(wù)類為MyRunnable3
停止
停止一個(gè)定時(shí)任務(wù),http://localhost:10085/tbTask/stop/2
id為2的定時(shí)任務(wù)被停止
后記
可以看到,配置動(dòng)態(tài)定時(shí)任務(wù)后,可以方便、實(shí)時(shí)的對(duì)定時(shí)任務(wù)進(jìn)行修改、調(diào)整,再也不用重啟項(xiàng)目啦
SpringBoot配置動(dòng)態(tài)定時(shí)任務(wù)暫時(shí)先記錄到這,后續(xù)再進(jìn)行補(bǔ)充
代碼開(kāi)源
代碼已經(jīng)開(kāi)源、托管到我的GitHub、碼云:
GitHub:https://github.com/huanzi-qch/springBoot
碼云:https://gitee.com/huanzi-qch/springBoot
到此這篇關(guān)于SpringBoot開(kāi)發(fā)實(shí)戰(zhàn)系列之動(dòng)態(tài)定時(shí)任務(wù)的文章就介紹到這了,更多相關(guān)SpringBoot動(dòng)態(tài)定時(shí)任務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot設(shè)置動(dòng)態(tài)定時(shí)任務(wù)的方法詳解
- SpringBoot?實(shí)現(xiàn)動(dòng)態(tài)添加定時(shí)任務(wù)功能
- Springboot自帶定時(shí)任務(wù)實(shí)現(xiàn)動(dòng)態(tài)配置Cron參數(shù)方式
- SpringBoot實(shí)現(xiàn)動(dòng)態(tài)多線程并發(fā)定時(shí)任務(wù)
- springboot動(dòng)態(tài)定時(shí)任務(wù)的實(shí)現(xiàn)方法示例
- SpringBoot實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)
- Springboot實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)流程詳解
相關(guān)文章
hadoop client與datanode的通信協(xié)議分析
本文主要分析了hadoop客戶端read和write block的流程. 以及client和datanode通信的協(xié)議, 數(shù)據(jù)流格式等2012-11-11SpringBoot整合Ip2region獲取IP地址和定位的詳細(xì)過(guò)程
ip2region v2.0 - 是一個(gè)離線IP地址定位庫(kù)和IP定位數(shù)據(jù)管理框架,10微秒級(jí)別的查詢效率,提供了眾多主流編程語(yǔ)言的 xdb 數(shù)據(jù)生成和查詢客戶端實(shí)現(xiàn) ,這篇文章主要介紹了SpringBoot整合Ip2region獲取IP地址和定位,需要的朋友可以參考下2023-06-06SpringBoot中集成screw(螺絲釘)實(shí)現(xiàn)數(shù)據(jù)庫(kù)表結(jié)構(gòu)文檔生成方法
這篇文章主要介紹了SpringBoot中集成screw(螺絲釘)實(shí)現(xiàn)數(shù)據(jù)庫(kù)表結(jié)構(gòu)文檔生成,下面以連接mysql數(shù)據(jù)庫(kù)并生成html格式的數(shù)據(jù)庫(kù)結(jié)構(gòu)文檔為例,插件的使用方式除可以使用代碼外,還可以使用Maven插件的方式,需要的朋友可以參考下2024-07-07IntelliJ?IDEA設(shè)置JVM運(yùn)行參數(shù)的圖文介紹
這篇文章主要介紹了IntelliJ?IDEA設(shè)置JVM運(yùn)行參數(shù)的方法,包括配置方式及優(yōu)先級(jí),本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04Springboot實(shí)現(xiàn)通用Auth認(rèn)證的幾種方式
本文主要介紹了Springboot實(shí)現(xiàn)通用Auth認(rèn)證的幾種方式,主要介紹了4種方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-07-07SpringBoot實(shí)現(xiàn)短信驗(yàn)證碼校驗(yàn)方法思路詳解
最近做項(xiàng)目遇到這樣的需求,前端是基于BootStrap,html代碼中有BootStrap樣式實(shí)現(xiàn)的,具體后臺(tái)實(shí)現(xiàn)代碼大家通過(guò)本文一起學(xué)習(xí)吧2017-08-08