在spring boot项目中,可以通过@EnableScheduling注解和@Scheduled注解实现定时任务,也可以通过SchedulingConfigurer接口来实现定时任务。但是这两种方式不能动态添加、删除、启动、停止任务。要实现动态增删启停定时任务功能,比较广泛的做法是集成Quartz框架。但是本人的开发原则是:在满足项目需求的情况下,尽量少的依赖其它框架,避免项目过于臃肿和复杂。查看spring-context这个jar包中org.springframework.scheduling.ScheduledTaskRegistrar这个类的源代码,发现可以通过改造这个类就能实现动态增删启停定时任务功能。
定时任务列表页
定时任务执行日志
添加执行定时任务的线程池配置类
1@Configuration
2public class SchedulingConfig {
3 @Bean
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 @Override
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 @Override
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 @Override
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@Component
2public class CronTaskRegistrar implements DisposableBean {
3
4 private final Map scheduledTasks = new ConcurrentHashMap<>(16); 5 6 @Autowired 7 private TaskScheduler taskScheduler; 8 9 public TaskScheduler getScheduler() {10 return this.taskScheduler;11 }1213 public void addCronTask(Runnable task, String cronExpression) {14 addCronTask(new CronTask(task, cronExpression));15 }1617 public void addCronTask(CronTask cronTask) {18 if (cronTask != null) {19 Runnable task = cronTask.getRunnable();20 if (this.scheduledTasks.containsKey(task)) {21 removeCronTask(task);22 }2324 this.scheduledTasks.put(task, scheduleCronTask(cronTask));25 }26 }2728 public void removeCronTask(Runnable task) {29 ScheduledTask scheduledTask = this.scheduledTasks.remove(task);30 if (scheduledTask != null)31 scheduledTask.cancel();32 }3334 public ScheduledTask scheduleCronTask(CronTask cronTask) {35 ScheduledTask scheduledTask = new ScheduledTask();36 scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());3738 return scheduledTask;39 }404142 @Override43 public void destroy() {44 for (ScheduledTask task : this.scheduledTasks.values()) {45 task.cancel();46 }4748 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@Service
2public class SysJobRunner implements CommandLineRunner {
3
4 private static final Logger logger = LoggerFactory.getLogger(SysJobRunner.class);
5
6 @Autowired
7 private ISysJobRepository sysJobRepository;
8
9 @Autowired
10 private CronTaskRegistrar cronTaskRegistrar;
11
12 @Override
13 public void run(String... args) {
14 // 初始加载数据库里状态为正常的定时任务
15 List 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 }2122 logger.info("定时任务已加载完毕...");23 }24 }25}
工具类SpringContextUtils,用来从spring容器里获取bean
1@Component
2public class SpringContextUtils implements ApplicationContextAware {
3
4 private static ApplicationContext applicationContext;
5
6 @Override
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 getBean(Class requiredType) {17 return applicationContext.getBean(requiredType);18 }1920 public static T getBean(String name, Class requiredType) {21 return applicationContext.getBean(name, requiredType);22 }2324 public static boolean containsBean(String name) {25 return applicationContext.containsBean(name);26 }2728 public static boolean isSingleton(String name) {29 return applicationContext.isSingleton(name);30 }3132 public static Class extends Object> getType(String name) {33 return applicationContext.getType(name);34 }35}