在spring boot項目中,可以通過@EnableScheduling註解和@Scheduled註解實現定時任務,也可以通過SchedulingConfigurer接口來實現定時任務。但是這兩種方式不能動態添加、刪除、啟動、停止任務。
要實現動態增刪啟停定時任務功能,比較廣泛的做法是集成Quartz框架。但是本人的開發原則是:在滿足項目需求的情況下,儘量少的依賴其它框架,避免項目過於臃腫和複雜。
查看spring-context這個jar包中org.springframework.scheduling.ScheduledTaskRegistrar這個類的源代碼,發現可以通過改造這個類就能實現動態增刪啟停定時任務功能。
定時任務列表頁
定時任務執行日誌
添加執行定時任務的線程池配置類
<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)&¶ms.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>
定時任務數據庫表設計
定時任務數據庫表設計
添加定時任務實體類
<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>
新增定時任務
新增定時任務
<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(Class /<code>requiredType){returnapplicationContext.getBean(requiredType);}publicstatic TgetBean(Stringname,Class requiredType){returnapplicationContext.getBean(name,requiredType);}publicstaticbooleancontainsBean(Stringname){returnapplicationContext.containsBean(name);}publicstaticbooleanisSingleton(Stringname){returnapplicationContext.isSingleton(name);}publicstaticClassgetType(Stringname){returnapplicationContext.getType(name);}}
閱讀更多 儒雅程序員 的文章