實踐:SpringBoot實現定時任務的動態增刪啟停

在spring boot項目中,可以通過@EnableScheduling註解和@Scheduled註解實現定時任務,也可以通過SchedulingConfigurer接口來實現定時任務。但是這兩種方式不能動態添加、刪除、啟動、停止任務。

要實現動態增刪啟停定時任務功能,比較廣泛的做法是集成Quartz框架。但是本人的開發原則是:在滿足項目需求的情況下,儘量少的依賴其它框架,避免項目過於臃腫和複雜。

查看spring-context這個jar包中org.springframework.scheduling.ScheduledTaskRegistrar這個類的源代碼,發現可以通過改造這個類就能實現動態增刪啟停定時任務功能。

實踐:SpringBoot實現定時任務的動態增刪啟停

定時任務列表頁

實踐:SpringBoot實現定時任務的動態增刪啟停

定時任務執行日誌

添加執行定時任務的線程池配置類

<code>@ConfigurationpublicclassSchedulingConfig{@BeanpublicTaskSchedulertaskScheduler(){ThreadPoolTaskSchedulertaskScheduler=newThreadPoolTaskScheduler();//定時任務執行線程池核心線程數taskScheduler.setPoolSize(4);taskScheduler.setRemoveOnCancelPolicy(true);taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-");returntaskScheduler;}}/<code>

添加ScheduledFuture的包裝類。ScheduledFuture是ScheduledExecutorService定時任務線程池的執行結果。

<code>publicfinalclassScheduledTask{volatileScheduledFuture>future;/***取消定時任務*/publicvoidcancel(){ScheduledFuture>future=this.future;if(future!=null){future.cancel(true);}}}/<code>

添加Runnable接口實現類,被定時任務線程池調用,用來執行指定bean裡面的方法。

<code>publicclassSchedulingRunnableimplementsRunnable{privatestaticfinalLoggerlogger=LoggerFactory.getLogger(SchedulingRunnable.class);privateStringbeanName;privateStringmethodName;privateStringparams;publicSchedulingRunnable(StringbeanName,StringmethodName){this(beanName,methodName,null);}publicSchedulingRunnable(StringbeanName,StringmethodName,Stringparams){this.beanName=beanName;this.methodName=methodName;this.params=params;}@Overridepublicvoidrun(){logger.info("定時任務開始執行- bean:{},方法:{},參數:{}",beanName,methodName,params);longstartTime=System.currentTimeMillis();try{Objecttarget=SpringContextUtils.getBean(beanName);Methodmethod=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(Exceptionex){logger.error(String.format("定時任務執行異常- bean:%s,方法:%s,參數:%s ",beanName,methodName,params),ex);}longtimes=System.currentTimeMillis()-startTime;logger.info("定時任務執行結束- bean:{},方法:{},參數:{},耗時:{}毫秒",beanName,methodName,params,times);}@Overridepublicbooleanequals(Objecto){if(this==o)returntrue;if(o==null||getClass()!=o.getClass())returnfalse;SchedulingRunnablethat=(SchedulingRunnable)o;if(params==null){returnbeanName.equals(that.beanName)&&methodName.equals(that.methodName)&&that.params==null;}returnbeanName.equals(that.beanName)&&methodName.equals(that.methodName)&&params.equals(that.params);}@OverridepublicinthashCode(){if(params==null){returnObjects.hash(beanName,methodName);}returnObjects.hash(beanName,methodName,params);}}/<code>

添加定時任務註冊類,用來增加、刪除定時任務。

<code>@ComponentpublicclassCronTaskRegistrarimplementsDisposableBean{privatefinalMap<runnable>scheduledTasks=newConcurrentHashMap<>(16);@AutowiredprivateTaskSchedulertaskScheduler;publicTaskSchedulergetScheduler(){returnthis.taskScheduler;}publicvoidaddCronTask(Runnabletask,StringcronExpression){addCronTask(newCronTask(task,cronExpression));}publicvoidaddCronTask(CronTaskcronTask){if(cronTask!=null){Runnabletask=cronTask.getRunnable();if(this.scheduledTasks.containsKey(task)){removeCronTask(task);}this.scheduledTasks.put(task,scheduleCronTask(cronTask));}}publicvoidremoveCronTask(Runnabletask){ScheduledTaskscheduledTask=this.scheduledTasks.remove(task);if(scheduledTask!=null)scheduledTask.cancel();}publicScheduledTaskscheduleCronTask(CronTaskcronTask){ScheduledTaskscheduledTask=newScheduledTask();scheduledTask.future=this.taskScheduler.schedule(cronTask.getRunnable(),cronTask.getTrigger());returnscheduledTask;}@Overridepublicvoiddestroy(){for(ScheduledTasktask:this.scheduledTasks.values()){task.cancel();}this.scheduledTasks.clear();}}/<runnable>/<code>

添加定時任務示例類

<code>@Component("demoTask")publicclassDemoTask{publicvoidtaskWithParams(Stringparams){System.out.println("執行有參示例任務:"+params);}publicvoidtaskNoParams(){System.out.println("執行無參示例任務");}}/<code>

定時任務數據庫表設計

實踐:SpringBoot實現定時任務的動態增刪啟停

定時任務數據庫表設計

添加定時任務實體類

<code>publicclassSysJobPO{/***任務ID*/privateIntegerjobId;/***bean名稱*/privateStringbeanName;/***方法名稱*/privateStringmethodName;/***方法參數*/privateStringmethodParams;/***cron表達式*/privateStringcronExpression;/***狀態(1正常0暫停)*/privateIntegerjobStatus;/***備註*/privateStringremark;/***創建時間*/privateDatecreateTime;/***更新時間*/privateDateupdateTime;publicIntegergetJobId(){returnjobId;}publicvoidsetJobId(IntegerjobId){this.jobId=jobId;}publicStringgetBeanName(){returnbeanName;}publicvoidsetBeanName(StringbeanName){this.beanName=beanName;}publicStringgetMethodName(){returnmethodName;}publicvoidsetMethodName(StringmethodName){this.methodName=methodName;}publicStringgetMethodParams(){returnmethodParams;}publicvoidsetMethodParams(StringmethodParams){this.methodParams=methodParams;}publicStringgetCronExpression(){returncronExpression;}publicvoidsetCronExpression(StringcronExpression){this.cronExpression=cronExpression;}publicIntegergetJobStatus(){returnjobStatus;}publicvoidsetJobStatus(IntegerjobStatus){this.jobStatus=jobStatus;}publicStringgetRemark(){returnremark;}publicvoidsetRemark(Stringremark){this.remark=remark;}publicDategetCreateTime(){returncreateTime;}publicvoidsetCreateTime(DatecreateTime){this.createTime=createTime;}publicDategetUpdateTime(){returnupdateTime;}publicvoidsetUpdateTime(DateupdateTime){this.updateTime=updateTime;}}/<code>

新增定時任務

實踐:SpringBoot實現定時任務的動態增刪啟停

新增定時任務

<code>booleansuccess=sysJobRepository.addSysJob(sysJob);if(!success)returnOperationResUtils.fail("新增失敗");else{if(sysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())){SchedulingRunnabletask=newSchedulingRunnable(sysJob.getBeanName(),sysJob.getMethodName(),sysJob.getMethodParams());cronTaskRegistrar.addCronTask(task,sysJob.getCronExpression());}}returnOperationResUtils.success();/<code> 

修改定時任務,先移除原來的任務,再啟動新任務

<code>booleansuccess=sysJobRepository.editSysJob(sysJob);if(!success)returnOperationResUtils.fail("編輯失敗");else{//先移除再添加if(existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())){SchedulingRunnabletask=newSchedulingRunnable(existedSysJob.getBeanName(),existedSysJob.getMethodName(),existedSysJob.getMethodParams());cronTaskRegistrar.removeCronTask(task);}if(sysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())){SchedulingRunnabletask=newSchedulingRunnable(sysJob.getBeanName(),sysJob.getMethodName(),sysJob.getMethodParams());cronTaskRegistrar.addCronTask(task,sysJob.getCronExpression());}}returnOperationResUtils.success();/<code>

刪除定時任務

<code>booleansuccess=sysJobRepository.deleteSysJobById(req.getJobId());if(!success)returnOperationResUtils.fail("刪除失敗");else{if(existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())){SchedulingRunnabletask=newSchedulingRunnable(existedSysJob.getBeanName(),existedSysJob.getMethodName(),existedSysJob.getMethodParams());cronTaskRegistrar.removeCronTask(task);}}returnOperationResUtils.success();/<code>

定時任務啟動/停止狀態切換

<code>if(existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())){SchedulingRunnabletask=newSchedulingRunnable(existedSysJob.getBeanName(),existedSysJob.getMethodName(),existedSysJob.getMethodParams());cronTaskRegistrar.addCronTask(task,existedSysJob.getCronExpression());}else{SchedulingRunnabletask=newSchedulingRunnable(existedSysJob.getBeanName(),existedSysJob.getMethodName(),existedSysJob.getMethodParams());cronTaskRegistrar.removeCronTask(task);}/<code>

添加實現了CommandLineRunner接口的SysJobRunner類,當spring boot項目啟動完成後,加載數據庫裡狀態為正常的定時任務。

<code>@ServicepublicclassSysJobRunnerimplementsCommandLineRunner{privatestaticfinalLoggerlogger=LoggerFactory.getLogger(SysJobRunner.class);@AutowiredprivateISysJobRepositorysysJobRepository;@AutowiredprivateCronTaskRegistrarcronTaskRegistrar;@Overridepublicvoidrun(String...args){//初始加載數據庫裡狀態為正常的定時任務List<sysjobpo>jobList=sysJobRepository.getSysJobListByStatus(SysJobStatus.NORMAL.ordinal());if(CollectionUtils.isNotEmpty(jobList)){for(SysJobPOjob:jobList){SchedulingRunnabletask=newSchedulingRunnable(job.getBeanName(),job.getMethodName(),job.getMethodParams());cronTaskRegistrar.addCronTask(task,job.getCronExpression());}logger.info("定時任務已加載完畢...");}}}/<sysjobpo>/<code>

工具類SpringContextUtils,用來從spring容器裡獲取bean

<code>@ComponentpublicclassSpringContextUtilsimplementsApplicationContextAware{privatestaticApplicationContextapplicationContext;@OverridepublicvoidsetApplicationContext(ApplicationContextapplicationContext)throwsBeansException{SpringContextUtils.applicationContext=applicationContext;}publicstaticObjectgetBean(Stringname){returnapplicationContext.getBean(name);}publicstaticTgetBean(ClassrequiredType){returnapplicationContext.getBean(requiredType);}publicstaticTgetBean(Stringname,ClassrequiredType){returnapplicationContext.getBean(name,requiredType);}publicstaticbooleancontainsBean(Stringname){returnapplicationContext.containsBean(name);}publicstaticbooleanisSingleton(Stringname){returnapplicationContext.isSingleton(name);}publicstaticClassgetType(Stringname){returnapplicationContext.getType(name);}}/<code>


分享到:


相關文章: