Spring Boot實現(xiàn)定時任務的動態(tài)增刪啟停

作者 |?jessehua
在spring boot項目中,可以通過@EnableScheduling注解和@Scheduled注解實現(xiàn)定時任務,也可以通過SchedulingConfigurer接口來實現(xiàn)定時任務。但是這兩種方式不能動態(tài)添加、刪除、啟動、停止任務。
要實現(xiàn)動態(tài)增刪啟停定時任務功能,比較廣泛的做法是集成Quartz框架。但是本人的開發(fā)原則是:在滿足項目需求的情況下,盡量少的依賴其它框架,避免項目過于臃腫和復雜。
查看spring-context這個jar包中org.springframework.scheduling.ScheduledTaskRegistrar這個類的源代碼,發(fā)現(xiàn)可以通過改造這個類就能實現(xiàn)動態(tài)增刪啟停定時任務功能。
如果您正在學習Spring Boot,推薦一個連載多年還在繼續(xù)更新的免費教程:http://blog.didispace.com/spring-boot-learning-2x/
定時任務列表頁
?定時任務執(zhí)行日志
添加執(zhí)行定時任務的線程池配置類
@Configuration??
public?class?SchedulingConfig?{??
????@Bean??
????public?TaskScheduler?taskScheduler()?{??
????????ThreadPoolTaskScheduler?taskScheduler?=?new?ThreadPoolTaskScheduler();??
????????//?定時任務執(zhí)行線程池核心線程數(shù)??
????????taskScheduler.setPoolSize(4);??
????????taskScheduler.setRemoveOnCancelPolicy(true);??
????????taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");??
????????return?taskScheduler;??
????}??
}??
添加ScheduledFuture的包裝類。ScheduledFuture是ScheduledExecutorService定時任務線程池的執(zhí)行結果。
public?final?class?ScheduledTask?{??
??
????volatile?ScheduledFuture>?future;??
??
????/**??
?????*?取消定時任務??
?????*/??
????public?void?cancel()?{??
????????ScheduledFuture>?future?=?this.future;??
????????if?(future?!=?null)?{??
????????????future.cancel(true);??
????????}??
????}??
}??
添加Runnable接口實現(xiàn)類,被定時任務線程池調用,用來執(zhí)行指定bean里面的方法。
public?class?SchedulingRunnable?implements?Runnable?{??
??
????private?static?final?Logger?logger?=?LoggerFactory.getLogger(SchedulingRunnable.class);??
??
????private?String?beanName;??
??
????private?String?methodName;??
??
????private?String?params;??
??
????public?SchedulingRunnable(String?beanName,?String?methodName)?{??
????????this(beanName,?methodName,?null);??
????}??
??
????public?SchedulingRunnable(String?beanName,?String?methodName,?String?params)?{??
????????this.beanName?=?beanName;??
????????this.methodName?=?methodName;??
????????this.params?=?params;??
????}??
??
????@Override??
????public?void?run()?{??
????????logger.info("定時任務開始執(zhí)行?- bean:{},方法:{},參數(shù):{}",?beanName,?methodName,?params);??
????????long?startTime?=?System.currentTimeMillis();??
??
????????try?{??
????????????Object?target?=?SpringContextUtils.getBean(beanName);??
??
????????????Method?method?=?null;??
????????????if?(StringUtils.isNotEmpty(params))?{??
????????????????method?=?target.getClass().getDeclaredMethod(methodName,?String.class);??
????????????}?else?{??
????????????????method?=?target.getClass().getDeclaredMethod(methodName);??
????????????}??
??
????????????ReflectionUtils.makeAccessible(method);??
????????????if?(StringUtils.isNotEmpty(params))?{??
????????????????method.invoke(target,?params);??
????????????}?else?{??
????????????????method.invoke(target);??
????????????}??
????????}?catch?(Exception?ex)?{??
????????????logger.error(String.format("定時任務執(zhí)行異常?- bean:%s,方法:%s,參數(shù):%s ",?beanName,?methodName,?params),?ex);??
????????}??
??
????????long?times?=?System.currentTimeMillis()?-?startTime;??
????????logger.info("定時任務執(zhí)行結束?- bean:{},方法:{},參數(shù):{},耗時:{}?毫秒",?beanName,?methodName,?params,?times);??
????}??
??
????@Override??
????public?boolean?equals(Object?o)?{??
????????if?(this?==?o)?return?true;??
????????if?(o?==?null?||?getClass()?!=?o.getClass())?return?false;??
????????SchedulingRunnable?that?=?(SchedulingRunnable)?o;??
????????if?(params?==?null)?{??
????????????return?beanName.equals(that.beanName)?&&??
????????????????????methodName.equals(that.methodName)?&&??
????????????????????that.params?==?null;??
????????}??
??
????????return?beanName.equals(that.beanName)?&&??
????????????????methodName.equals(that.methodName)?&&??
????????????????params.equals(that.params);??
????}??
??
????@Override??
????public?int?hashCode()?{??
????????if?(params?==?null)?{??
????????????return?Objects.hash(beanName,?methodName);??
????????}??
??
????????return?Objects.hash(beanName,?methodName,?params);??
????}??
}??
添加定時任務注冊類,用來增加、刪除定時任務。
@Component??
public?class?CronTaskRegistrar?implements?DisposableBean?{??
??
????private?final?Map?scheduledTasks?=?new?ConcurrentHashMap<>(16);??
??
????@Autowired??
????private?TaskScheduler?taskScheduler;??
??
????public?TaskScheduler?getScheduler()?{??
????????return?this.taskScheduler;??
????}??
??
????public?void?addCronTask(Runnable?task,?String?cronExpression)?{??
????????addCronTask(new?CronTask(task,?cronExpression));??
????}??
??
????public?void?addCronTask(CronTask?cronTask)?{??
????????if?(cronTask?!=?null)?{??
????????????Runnable?task?=?cronTask.getRunnable();??
????????????if?(this.scheduledTasks.containsKey(task))?{??
????????????????removeCronTask(task);??
????????????}??
??
????????????this.scheduledTasks.put(task,?scheduleCronTask(cronTask));??
????????}??
????}??
??
????public?void?removeCronTask(Runnable?task)?{??
????????ScheduledTask?scheduledTask?=?this.scheduledTasks.remove(task);??
????????if?(scheduledTask?!=?null)??
????????????scheduledTask.cancel();??
????}??
??
????public?ScheduledTask?scheduleCronTask(CronTask?cronTask)?{??
????????ScheduledTask?scheduledTask?=?new?ScheduledTask();??
????????scheduledTask.future?=?this.taskScheduler.schedule(cronTask.getRunnable(),?cronTask.getTrigger());??
??
????????return?scheduledTask;??
????}??
??
??
????@Override??
????public?void?destroy()?{??
????????for?(ScheduledTask?task?:?this.scheduledTasks.values())?{??
????????????task.cancel();??
????????}??
??
????????this.scheduledTasks.clear();??
????}??
}??
添加定時任務示例類
@Component("demoTask")??
public?class?DemoTask?{??
????public?void?taskWithParams(String?params)?{??
????????System.out.println("執(zhí)行有參示例任務:"?+?params);??
????}??
??
????public?void?taskNoParams()?{??
????????System.out.println("執(zhí)行無參示例任務");??
????}??
}??
定時任務數(shù)據(jù)庫表設計
?定時任務數(shù)據(jù)庫表設計
添加定時任務實體類
public?class?SysJobPO?{??
????/**??
?????*?任務ID??
?????*/??
????private?Integer?jobId;??
????/**??
?????*?bean名稱??
?????*/??
????private?String?beanName;??
????/**??
?????*?方法名稱??
?????*/??
????private?String?methodName;??
????/**??
?????*?方法參數(shù)??
?????*/??
????private?String?methodParams;??
????/**??
?????*?cron表達式??
?????*/??
????private?String?cronExpression;??
????/**??
?????*?狀態(tài)(1正常?0暫停)??
?????*/??
????private?Integer?jobStatus;??
????/**??
?????*?備注??
?????*/??
????private?String?remark;??
????/**??
?????*?創(chuàng)建時間??
?????*/??
????private?Date?createTime;??
????/**??
?????*?更新時間??
?????*/??
????private?Date?updateTime;??
??
????public?Integer?getJobId()?{??
????????return?jobId;??
????}??
??
????public?void?setJobId(Integer?jobId)?{??
????????this.jobId?=?jobId;??
????}??
??
????public?String?getBeanName()?{??
????????return?beanName;??
????}??
??
????public?void?setBeanName(String?beanName)?{??
????????this.beanName?=?beanName;??
????}??
??
????public?String?getMethodName()?{??
????????return?methodName;??
????}??
??
????public?void?setMethodName(String?methodName)?{??
????????this.methodName?=?methodName;??
????}??
??
????public?String?getMethodParams()?{??
????????return?methodParams;??
????}??
??
????public?void?setMethodParams(String?methodParams)?{??
????????this.methodParams?=?methodParams;??
????}??
??
????public?String?getCronExpression()?{??
????????return?cronExpression;??
????}??
??
????public?void?setCronExpression(String?cronExpression)?{??
????????this.cronExpression?=?cronExpression;??
????}??
??
????public?Integer?getJobStatus()?{??
????????return?jobStatus;??
????}??
??
????public?void?setJobStatus(Integer?jobStatus)?{??
????????this.jobStatus?=?jobStatus;??
????}??
??
????public?String?getRemark()?{??
????????return?remark;??
????}??
??
????public?void?setRemark(String?remark)?{??
????????this.remark?=?remark;??
????}??
??
????public?Date?getCreateTime()?{??
????????return?createTime;??
????}??
??
????public?void?setCreateTime(Date?createTime)?{??
????????this.createTime?=?createTime;??
????}??
??
????public?Date?getUpdateTime()?{??
????????return?updateTime;??
????}??
??
????public?void?setUpdateTime(Date?updateTime)?{??
????????this.updateTime?=?updateTime;??
????}??
??
}??
新增定時任務
新增定時任務
boolean?success?=?sysJobRepository.addSysJob(sysJob);??
if?(!success)??
????return?OperationResUtils.fail("新增失敗");??
else?{??
????if?(sysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal()))?{??
????????SchedulingRunnable?task?=?new?SchedulingRunnable(sysJob.getBeanName(),?sysJob.getMethodName(),?sysJob.getMethodParams());??
????????cronTaskRegistrar.addCronTask(task,?sysJob.getCronExpression());??
????}??
}??
??
return?OperationResUtils.success();??
修改定時任務,先移除原來的任務,再啟動新任務
boolean?success?=?sysJobRepository.editSysJob(sysJob);??
if?(!success)??
????return?OperationResUtils.fail("編輯失敗");??
else?{??
????//先移除再添加??
????if?(existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal()))?{??
????????SchedulingRunnable?task?=?new?SchedulingRunnable(existedSysJob.getBeanName(),?existedSysJob.getMethodName(),?existedSysJob.getMethodParams());??
????????cronTaskRegistrar.removeCronTask(task);??
????}??
??
????if?(sysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal()))?{??
????????SchedulingRunnable?task?=?new?SchedulingRunnable(sysJob.getBeanName(),?sysJob.getMethodName(),?sysJob.getMethodParams());??
????????cronTaskRegistrar.addCronTask(task,?sysJob.getCronExpression());??
????}??
}??
??
return?OperationResUtils.success();??
刪除定時任務
boolean?success?=?sysJobRepository.deleteSysJobById(req.getJobId());??
if?(!success)??
????return?OperationResUtils.fail("刪除失敗");??
else{??
????if?(existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal()))?{??
????????SchedulingRunnable?task?=?new?SchedulingRunnable(existedSysJob.getBeanName(),?existedSysJob.getMethodName(),?existedSysJob.getMethodParams());??
????????cronTaskRegistrar.removeCronTask(task);??
????}??
}??
??
return?OperationResUtils.success();??
定時任務啟動/停止狀態(tài)切換
if?(existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal()))?{??
????SchedulingRunnable?task?=?new?SchedulingRunnable(existedSysJob.getBeanName(),?existedSysJob.getMethodName(),?existedSysJob.getMethodParams());??
????cronTaskRegistrar.addCronTask(task,?existedSysJob.getCronExpression());??
}?else?{??
????SchedulingRunnable?task?=?new?SchedulingRunnable(existedSysJob.getBeanName(),?existedSysJob.getMethodName(),?existedSysJob.getMethodParams());??
????cronTaskRegistrar.removeCronTask(task);??
}??
添加實現(xiàn)了CommandLineRunner接口的SysJobRunner類,當spring boot項目啟動完成后,加載數(shù)據(jù)庫里狀態(tài)為正常的定時任務。
另外,如果您正在學習Spring Cloud,推薦一個連載多年還在繼續(xù)更新的免費教程:https://blog.didispace.com/spring-cloud-learning/
@Service??
public?class?SysJobRunner?implements?CommandLineRunner?{??
??
????private?static?final?Logger?logger?=?LoggerFactory.getLogger(SysJobRunner.class);??
??
????@Autowired??
????private?ISysJobRepository?sysJobRepository;??
??
????@Autowired??
????private?CronTaskRegistrar?cronTaskRegistrar;??
??
????@Override??
????public?void?run(String...?args)?{??
????????//?初始加載數(shù)據(jù)庫里狀態(tài)為正常的定時任務??
????????List?jobList?=?sysJobRepository.getSysJobListByStatus(SysJobStatus.NORMAL.ordinal());??
????????if?(CollectionUtils.isNotEmpty(jobList))?{??
????????????for?(SysJobPO?job?:?jobList)?{??
????????????????SchedulingRunnable?task?=?new?SchedulingRunnable(job.getBeanName(),?job.getMethodName(),?job.getMethodParams());??
????????????????cronTaskRegistrar.addCronTask(task,?job.getCronExpression());??
????????????}??
??
????????????logger.info("定時任務已加載完畢...");??
????????}??
????}??
}??
工具類SpringContextUtils,用來從spring容器里獲取bean
@Component??
public?class?SpringContextUtils?implements?ApplicationContextAware?{??
??
????private?static?ApplicationContext?applicationContext;??
??
????@Override??
????public?void?setApplicationContext(ApplicationContext?applicationContext)??
????????????throws?BeansException?{??
????????SpringContextUtils.applicationContext?=?applicationContext;??
????}??
??
????public?static?Object?getBean(String?name)?{??
????????return?applicationContext.getBean(name);??
????}??
??
????public?static??T?getBean(Class?requiredType) ?{??
????????return?applicationContext.getBean(requiredType);??
????}??
??
????public?static??T?getBean(String?name,?Class?requiredType) ?{??
????????return?applicationContext.getBean(name,?requiredType);??
????}??
??
????public?static?boolean?containsBean(String?name)?{??
????????return?applicationContext.containsBean(name);??
????}??
??
????public?static?boolean?isSingleton(String?name)?{??
????????return?applicationContext.isSingleton(name);??
????}??
??
????public?static?Class?extends?Object>?getType(String?name)?{??
????????return?applicationContext.getType(name);??
????}??
}
本文完,參考本文代碼可成功運行,親測!
關注公眾號【Java技術江湖】后回復“PDF”即可領取200+頁的《Java工程師面試指南》
強烈推薦,幾乎涵蓋所有Java工程師必知必會的知識點,不管是復習還是面試,都很實用。

