作者:jessehua
来源:/p/0f68936393fd
在spring boot项目中,可以通过@EnableScheduling注解和@Scheduled注解实现定时任务,也可以通过SchedulingConfigurer接口来实现定时任务。但是这两种方式不能动态添加、删除、启动、停止任务。要实现动态增删启停定时任务功能,比较广泛的做法是集成Quartz框架。但是本人的开发原则是:在满足项目需求的情况下,尽量少的依赖其它框架,避免项目过于臃肿和复杂。查看spring-context这个jar包中org.springframework.scheduling.ScheduledTaskRegistrar这个类的源代码,发现可以通过改造这个类就能实现动态增删启停定时任务功能。
定时任务列表页
定时任务执行日志
添加执行定时任务的线程池配置类
publicclassSchedulingConfig{ @Bean publicTaskSchedulertaskScheduler(){ ThreadPoolTaskSchedulertaskScheduler=newThreadPoolTaskScheduler(); //定时任务执行线程池核心线程数 taskScheduler.setPoolSize(4); taskScheduler.setRemoveOnCancelPolicy(true); taskScheduler.setThreadNamePrefix("TaskSchedulerThreadPool-"); returntaskScheduler; } }@Configuration
添加ScheduledFuture的包装类。ScheduledFuture是ScheduledExecutorService定时任务线程池的执行结果。
volatileScheduledFuture>future; /** *取消定时任务 */ publicvoidcancel(){ ScheduledFuture>future=this.future; if(future!=null){ future.cancel(true); } } }publicfinalclassScheduledTask{
添加Runnable接口实现类,被定时任务线程池调用,用来执行指定bean里面的方法。
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; } @Override publicvoidrun(){ 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); } @Override publicbooleanequals(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); } @Override publicinthashCode(){ if(params==null){ returnObjects.hash(beanName,methodName); } returnObjects.hash(beanName,methodName,params); } }publicclassSchedulingRunnableimplementsRunnable{
添加定时任务注册类,用来增加、删除定时任务。
publicclassCronTaskRegistrarimplementsDisposableBean{ privatefinalMapscheduledTasks=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(); } }@Component
添加定时任务示例类
publicclassDemoTask{ publicvoidtaskWithParams(Stringparams){ System.out.println("执行有参示例任务:"+params); } publicvoidtaskNoParams(){ System.out.println("执行无参示例任务"); } }@Component("demoTask")
定时任务数据库表设计
定时任务数据库表设计
添加定时任务实体类
/** *任务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; } }publicclassSysJobPO{
新增定时任务
新增定时任务
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();booleansuccess=sysJobRepository.addSysJob(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();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); } } returnOperationResUtils.success();booleansuccess=sysJobRepository.deleteSysJobById(req.getJobId());
定时任务启动/停止状态切换
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); }if(existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())){
添加实现了CommandLineRunner接口的SysJobRunner类,当spring boot项目启动完成后,加载数据库里状态为正常的定时任务。
publicclassSysJobRunnerimplementsCommandLineRunner{ privatestaticfinalLoggerlogger=LoggerFactory.getLogger(SysJobRunner.class); @Autowired privateISysJobRepositorysysJobRepository; @Autowired privateCronTaskRegistrarcronTaskRegistrar; @Override publicvoidrun(String...args){ //初始加载数据库里状态为正常的定时任务 ListjobList=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("定时任务已加载完毕..."); } } }@Service
工具类SpringContextUtils,用来从spring容器里获取bean
publicclassSpringContextUtilsimplementsApplicationContextAware{ privatestaticApplicationContextapplicationContext; @Override publicvoidsetApplicationContext(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); }publicstaticClassextendsObject>getType(Stringname){returnapplicationContext.getType(name); } }@Component