Spring Boot实现动态增删启停定时任务
本文来源:
https://www.jianshu.com/p/0f68936393fd
在spring boot项目中,可以通过@EnableScheduling注解和@Scheduled注解实现定时任务,也可以通过SchedulingConfigurer接口来实现定时任务。但是这两种方式不能动态添加、删除、启动、停止任务。要实现动态增删启停定时任务功能,比较广泛的做法是集成Quartz框架。但是本人的开发原则是:在满足项目需求的情况下,尽量少的依赖其它框架,避免项目过于臃肿和复杂。查看spring-context这个jar包中org.springframework.scheduling.ScheduledTaskRegistrar这个类的源代码,发现可以通过改造这个类就能实现动态增删启停定时任务功能。
定时任务列表页
定时任务执行日志
添加执行定时任务的线程池配置类
1
2public class SchedulingConfig {
3
4 public TaskScheduler taskScheduler() {
5 ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
6 // 定时任务执行线程池核心线程数
7 taskScheduler.setPoolSize(4);
8 taskScheduler.setRemoveOnCancelPolicy(true);
9 taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");
10 return taskScheduler;
11 }
12}
添加ScheduledFuture的包装类。ScheduledFuture是ScheduledExecutorService定时任务线程池的执行结果。
1public final class ScheduledTask {
2
3 volatile ScheduledFuture<?> future;
4
5 /**
6 * 取消定时任务
7 */
8 public void cancel() {
9 ScheduledFuture<?> future = this.future;
10 if (future != null) {
11 future.cancel(true);
12 }
13 }
14}
添加Runnable接口实现类,被定时任务线程池调用,用来执行指定bean里面的方法。
1public class SchedulingRunnable implements Runnable {
2
3 private static final Logger logger = LoggerFactory.getLogger(SchedulingRunnable.class);
4
5 private String beanName;
6
7 private String methodName;
8
9 private String params;
10
11 public SchedulingRunnable(String beanName, String methodName) {
12 this(beanName, methodName, null);
13 }
14
15 public SchedulingRunnable(String beanName, String methodName, String params) {
16 this.beanName = beanName;
17 this.methodName = methodName;
18 this.params = params;
19 }
20
21
22 public void run() {
23 logger.info("定时任务开始执行 - bean:{},方法:{},参数:{}", beanName, methodName, params);
24 long startTime = System.currentTimeMillis();
25
26 try {
27 Object target = SpringContextUtils.getBean(beanName);
28
29 Method method = null;
30 if (StringUtils.isNotEmpty(params)) {
31 method = target.getClass().getDeclaredMethod(methodName, String.class);
32 } else {
33 method = target.getClass().getDeclaredMethod(methodName);
34 }
35
36 ReflectionUtils.makeAccessible(method);
37 if (StringUtils.isNotEmpty(params)) {
38 method.invoke(target, params);
39 } else {
40 method.invoke(target);
41 }
42 } catch (Exception ex) {
43 logger.error(String.format("定时任务执行异常 - bean:%s,方法:%s,参数:%s ", beanName, methodName, params), ex);
44 }
45
46 long times = System.currentTimeMillis() - startTime;
47 logger.info("定时任务执行结束 - bean:{},方法:{},参数:{},耗时:{} 毫秒", beanName, methodName, params, times);
48 }
49
50
51 public boolean equals(Object o) {
52 if (this == o) return true;
53 if (o == null || getClass() != o.getClass()) return false;
54 SchedulingRunnable that = (SchedulingRunnable) o;
55 if (params == null) {
56 return beanName.equals(that.beanName) &&
57 methodName.equals(that.methodName) &&
58 that.params == null;
59 }
60
61 return beanName.equals(that.beanName) &&
62 methodName.equals(that.methodName) &&
63 params.equals(that.params);
64 }
65
66
67 public int hashCode() {
68 if (params == null) {
69 return Objects.hash(beanName, methodName);
70 }
71
72 return Objects.hash(beanName, methodName, params);
73 }
74}
添加定时任务注册类,用来增加、删除定时任务。
1
2public class CronTaskRegistrar implements DisposableBean {
3
4 private final Map<Runnable, ScheduledTask> scheduledTasks = new ConcurrentHashMap<>(16);
5
6
7 private TaskScheduler taskScheduler;
8
9 public TaskScheduler getScheduler() {
10 return this.taskScheduler;
11 }
12
13 public void addCronTask(Runnable task, String cronExpression) {
14 addCronTask(new CronTask(task, cronExpression));
15 }
16
17 public void addCronTask(CronTask cronTask) {
18 if (cronTask != null) {
19 Runnable task = cronTask.getRunnable();
20 if (this.scheduledTasks.containsKey(task)) {
21 removeCronTask(task);
22 }
23
24 this.scheduledTasks.put(task, scheduleCronTask(cronTask));
25 }
26 }
27
28 public void removeCronTask(Runnable task) {
29 ScheduledTask scheduledTask = this.scheduledTasks.remove(task);
30 if (scheduledTask != null)
31 scheduledTask.cancel();
32 }
33
34 public ScheduledTask scheduleCronTask(CronTask cronTask) {
35 ScheduledTask scheduledTask = new ScheduledTask();
36 scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
37
38 return scheduledTask;
39 }
40
41
42
43 public void destroy() {
44 for (ScheduledTask task : this.scheduledTasks.values()) {
45 task.cancel();
46 }
47
48 this.scheduledTasks.clear();
49 }
50}
添加定时任务示例类
1@Component("demoTask")
2public class DemoTask {
3 public void taskWithParams(String params) {
4 System.out.println("执行有参示例任务:" + params);
5 }
6
7 public void taskNoParams() {
8 System.out.println("执行无参示例任务");
9 }
10}
定时任务数据库表设计
定时任务数据库表设计
添加定时任务实体类
1public class SysJobPO {
2 /**
3 * 任务ID
4 */
5 private Integer jobId;
6 /**
7 * bean名称
8 */
9 private String beanName;
10 /**
11 * 方法名称
12 */
13 private String methodName;
14 /**
15 * 方法参数
16 */
17 private String methodParams;
18 /**
19 * cron表达式
20 */
21 private String cronExpression;
22 /**
23 * 状态(1正常 0暂停)
24 */
25 private Integer jobStatus;
26 /**
27 * 备注
28 */
29 private String remark;
30 /**
31 * 创建时间
32 */
33 private Date createTime;
34 /**
35 * 更新时间
36 */
37 private Date updateTime;
38
39 public Integer getJobId() {
40 return jobId;
41 }
42
43 public void setJobId(Integer jobId) {
44 this.jobId = jobId;
45 }
46
47 public String getBeanName() {
48 return beanName;
49 }
50
51 public void setBeanName(String beanName) {
52 this.beanName = beanName;
53 }
54
55 public String getMethodName() {
56 return methodName;
57 }
58
59 public void setMethodName(String methodName) {
60 this.methodName = methodName;
61 }
62
63 public String getMethodParams() {
64 return methodParams;
65 }
66
67 public void setMethodParams(String methodParams) {
68 this.methodParams = methodParams;
69 }
70
71 public String getCronExpression() {
72 return cronExpression;
73 }
74
75 public void setCronExpression(String cronExpression) {
76 this.cronExpression = cronExpression;
77 }
78
79 public Integer getJobStatus() {
80 return jobStatus;
81 }
82
83 public void setJobStatus(Integer jobStatus) {
84 this.jobStatus = jobStatus;
85 }
86
87 public String getRemark() {
88 return remark;
89 }
90
91 public void setRemark(String remark) {
92 this.remark = remark;
93 }
94
95 public Date getCreateTime() {
96 return createTime;
97 }
98
99 public void setCreateTime(Date createTime) {
100 this.createTime = createTime;
101 }
102
103 public Date getUpdateTime() {
104 return updateTime;
105 }
106
107 public void setUpdateTime(Date updateTime) {
108 this.updateTime = updateTime;
109 }
110
111}
新增定时任务
新增定时任务
1boolean success = sysJobRepository.addSysJob(sysJob);
2if (!success)
3 return OperationResUtils.fail("新增失败");
4else {
5 if (sysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())) {
6 SchedulingRunnable task = new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams());
7 cronTaskRegistrar.addCronTask(task, sysJob.getCronExpression());
8 }
9}
10
11return OperationResUtils.success();
修改定时任务,先移除原来的任务,再启动新任务
1boolean success = sysJobRepository.editSysJob(sysJob);
2if (!success)
3 return OperationResUtils.fail("编辑失败");
4else {
5 //先移除再添加
6 if (existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())) {
7 SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());
8 cronTaskRegistrar.removeCronTask(task);
9 }
10
11 if (sysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())) {
12 SchedulingRunnable task = new SchedulingRunnable(sysJob.getBeanName(), sysJob.getMethodName(), sysJob.getMethodParams());
13 cronTaskRegistrar.addCronTask(task, sysJob.getCronExpression());
14 }
15}
16
17return OperationResUtils.success();
删除定时任务
1boolean success = sysJobRepository.deleteSysJobById(req.getJobId());
2if (!success)
3 return OperationResUtils.fail("删除失败");
4else{
5 if (existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())) {
6 SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());
7 cronTaskRegistrar.removeCronTask(task);
8 }
9}
10
11return OperationResUtils.success();
定时任务启动/停止状态切换
1if (existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())) {
2 SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());
3 cronTaskRegistrar.addCronTask(task, existedSysJob.getCronExpression());
4} else {
5 SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());
6 cronTaskRegistrar.removeCronTask(task);
7}
添加实现了CommandLineRunner接口的SysJobRunner类,当spring boot项目启动完成后,加载数据库里状态为正常的定时任务。
1
2public class SysJobRunner implements CommandLineRunner {
3
4 private static final Logger logger = LoggerFactory.getLogger(SysJobRunner.class);
5
6
7 private ISysJobRepository sysJobRepository;
8
9
10 private CronTaskRegistrar cronTaskRegistrar;
11
12
13 public void run(String... args) {
14 // 初始加载数据库里状态为正常的定时任务
15 List<SysJobPO> jobList = sysJobRepository.getSysJobListByStatus(SysJobStatus.NORMAL.ordinal());
16 if (CollectionUtils.isNotEmpty(jobList)) {
17 for (SysJobPO job : jobList) {
18 SchedulingRunnable task = new SchedulingRunnable(job.getBeanName(), job.getMethodName(), job.getMethodParams());
19 cronTaskRegistrar.addCronTask(task, job.getCronExpression());
20 }
21
22 logger.info("定时任务已加载完毕...");
23 }
24 }
25}
工具类SpringContextUtils,用来从spring容器里获取bean
1
2public class SpringContextUtils implements ApplicationContextAware {
3
4 private static ApplicationContext applicationContext;
5
6
7 public void setApplicationContext(ApplicationContext applicationContext)
8 throws BeansException {
9 SpringContextUtils.applicationContext = applicationContext;
10 }
11
12 public static Object getBean(String name) {
13 return applicationContext.getBean(name);
14 }
15
16 public static <T> T getBean(Class<T> requiredType) {
17 return applicationContext.getBean(requiredType);
18 }
19
20 public static <T> T getBean(String name, Class<T> requiredType) {
21 return applicationContext.getBean(name, requiredType);
22 }
23
24 public static boolean containsBean(String name) {
25 return applicationContext.containsBean(name);
26 }
27
28 public static boolean isSingleton(String name) {
29 return applicationContext.isSingleton(name);
30 }
31
32 public static Class<? extends Object> getType(String name) {
33 return applicationContext.getType(name);
34 }
35}
● 如何设计缓存系统:缓存穿透,缓存击穿,缓存雪崩解决方案分析
● Spring Boot实战:逐行释义HelloWorld
● Spring Boot的自动配置、Command-line Runner
● Spring Boot 应用 - 静态视频资源实时播放新姿势