前言

年底了,还是想提笔再写篇文章,虽然写的不好,文章也不曾进行深入的讲解,但是客官,懂得都懂啊,您说对吧?(挑眉),来来来,天冷了,我们温壶热茶,让我将本文细细道来,客官你慢品慢听可好?

本文讲述的是定时任务的升级版本——动态定时任务,定时任务的相关可参考这篇文章 java 以及 springboot 分别实现定时器

那么动态定时任务的优点在哪里呢?比如说,我们的项目需要新增一个定时任务或者需要删除一个定时任务,亦或者需要修改某一个定时任务的参数,那我们最常规的做法是什么?只能修改代码,然后替换服务器的代码,对吧?这样做费时费力还容易出错,这时,动态定时任务的优点就体现出来了

  1. 无需修改代码
  2. 无需停服替换代码
  3. 避免意外情况的发生

那么动态定时任务要怎么实现呢?客官且细细看来

创建线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建ThreadPoolTaskScheduler线程池
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.initialize();
//设置线程池容量
executor.setPoolSize(5);
//线程名前缀
executor.setThreadNamePrefix("dynamicTask-");
//当调度器shutdown被调用时等待当前被调度的任务完成
executor.setWaitForTasksToCompleteOnShutdown(true);
//线程关闭的等待时长
executor.setAwaitTerminationSeconds(60);
return executor;
}

ThreadPoolTaskScheduler,可以很方便的对重复执行的任务进行调度管理,相比于通过java自带的周期性任务线程池ScheduleThreadPoolExecutor,此bean对象支持根据cron表达式创建周期性任务

关于线程池的配置,上述代码中已有注释说明,这里就不再赘述

新建 DynamicTask 工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
mport org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import org.wzp.oauth2.util.DateUtil;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;

/**
* @Author: zp.wei
* @DATE: 2020/11/30 11:40
*/
@Component
public class DynamicTask {

@Autowired
private ThreadPoolTaskScheduler threadPoolTaskScheduler;

public Map<String, ScheduledFuture<?>> taskMap = new HashMap<>();


/**
* 添加任务
*
* @param name
* @param cron
* @return
*/
public boolean add(String name, String cron) {
if (null != taskMap.get(name)) {
return false;
}
ScheduledFuture<?> schedule = threadPoolTaskScheduler.schedule(getRunnable(name), new CronTrigger(cron));
taskMap.put(name, schedule);
return true;
}


/**
* 停止任务
*
* @param name
* @return
*/
public boolean stop(String name) {
if (null == taskMap.get(name)) {
return false;
}
ScheduledFuture<?> scheduledFuture = taskMap.get(name);
scheduledFuture.cancel(true);
taskMap.remove(name);
return true;
}


public Runnable getRunnable(String name) {
return new Runnable() {
@Override
public void run() {
System.out.println(name + "---动态定时任务运行---" + DateUtil.formatLocalDateTime());
}
};
}


}

在这里,我只提供了两个方法,一个新增任务的方法,一个删除任务的方法。暂停任务和修改任务的方法也是存在的,当然了,这个可以有,只是我这里没有写出来,你们可以自己写一下

动态定时任务的使用

我们新增一个 taskController,在controller中写下如下代码,当然,我这里仅仅是为了测试哈。所以就把参数写在了代码里,测试结果就不贴了,不出意外的话,不会出问题,如果出了问题,我也绝对不会承认是我的问题(哼哼)

1
2
3
4
5
6
7
@GetMapping("dynamicTask")
public Result dynamicTask() {
String cron = "*/5 * * * * ?";
new DynamicTask().add("A", cron);
new DynamicTask().add("B", cron);
return Result.ok();
}

将参数写入数据库

上面仅仅是解决了动态更改定时任务的问题,可那不是我们每次增加一个定时任务都要去发一次请求,并且将相关参数传递过去吗?一旦某个任务的参数出了问题,就又得调用一次删除的接口,然后再一次新增,可要是记不得是哪一个定时任务出了问题,万一停错了任务,这个锅背在身上那是肯定甩不掉了,不要捉急,对于这个我们也是有解决办法的……那就是将每个任务的参数写进数据库,这样我们每一次新增任务都把相关信息网数据库存一下,一旦某个任务的参数写错了,我们可以通过查看相关参数进行更正,从而达到更正定时任务的目的。

那就肯定有人说,假如我有十几个定时任务,在我项目重启后,我岂不是要请求十几次接口吗?这是不是太麻烦了?有没有更好的处理方式?(嗯……灵魂三连问),对于这三个问题,我只能说,肯定有更好的处理方式呀,客官你不要捉急,热茶要细品,才能存齿留香,回味悠长……

初始化定时任务

上面说到将定时任务的参数写到了数据库,那么我们只需要在项目重启后,查询一遍数据库,然后循环一下启动就好了呀,我们在刚刚的工具类中我们新增以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 初始化任务 在服务器重启后自动启动任务
*/
@PostConstruct
public void initDynamic() {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
String cron = "*/5 * * * * ?";
list.forEach(s -> {
ScheduledFuture<?> schedule = threadPoolTaskScheduler.schedule(getRunnable(s), new CronTrigger(cron));
taskMap.put(s, schedule);
});
}

这里我就不写查询数据库的办法了,直接将数据写在代码里,原理是一样的,然后我们添加一个 @PostConstruct 注解,表示这个东西我只需要在项目启动后执行一次(警告你你别瞎执行哈),这样,我们每次在项目重启以后,这段代码都会自动跑一次,读取数据库,按部就班的执行定时任务

结语

当然了,我这里只是提供一个简单的例子,更多的需要你们自己改一下哦,客官你看,这是不是就是你要的?哦,茶喝完了呀?这么冷的天,来来来,续上续上