1。项目概览 我们首先来大概看下这个项目: 这里和定时任务相关的配置主要在config包里边,其他的都是业务类代码,换句话说其他的都是常规的CURD,所以我这里主要和小伙伴们介绍config中的代码。2。整体思路 我先来说说这个项目的整体思路,这样方便大家理解下面的内容。 在这个项目中,每一个定时任务都由一个线程去处理,负责处理每一个定时任务的线程类是SchedulingRunnable,所有的线程都跑在一个线程池中,这个线程池是ThreadPoolTaskScheduler,这是一个专为定时任务设计的线程池(支持Cron表达式),它的底层其实就是大家所熟知的ScheduledThreadPoolExecutor。当有一个新的定时任务需要执行时,创建一个SchedulingRunnable线程,然后连同Cron表达式一起扔到ThreadPoolTaskScheduler池子里去执行就行了。3。配置分析 几个配置类我们逐一来分析。3。1SpringContextUtils 首先我们提供了一个SpringContextUtils工具类,这个工具类实现了ApplicationContextAware接口,通过这个工具类,我们可以从Spring容器中查询一个Bean或者判断Spring容器中是否存在某一个Bean,工具类的代码如下(我主要列出来了有哪些方法,具体实现大家可以参考:https:github。comlenvescheduling):ComponentpublicclassSpringContextUtilsimplementsApplicationContextAware{privatestaticApplicationContextapplicationCOverridepublicvoidsetApplicationContext(ApplicationContextapplicationContext)throwsBeansException{SpringContextUtils。applicationContextapplicationC}publicstaticObjectgetBean(Stringname){}publicstaticTTgetBean(ClassTrequiredType){}publicstaticTTgetBean(Stringname,ClassTrequiredType){}publicstaticbooleancontainsBean(Stringname){}publicstaticbooleanisSingleton(Stringname){}publicstaticC?extendsObjectgetType(Stringname){}}3。2SchedulingRunnable 将来每一个定时任务执行的时候,我们都开启一个新的线程去执行这个定时任务,SchedulingRunnable就是关于这个线程的配置,我们来看下:publicclassSchedulingRunnableimplementsRunnable{privatestaticfinalLoggerloggerLoggerFactory。getLogger(SchedulingRunnable。class);privateStringbeanNprivateStringmethodNprivateSprivateObjecttargetBprivateMpublicSchedulingRunnable(StringbeanName,StringmethodName){this(beanName,methodName,null);}publicSchedulingRunnable(StringbeanName,StringmethodName,Stringparams){this。beanNamebeanNthis。methodNamemethodNthis。init();}privatevoidinit(){try{targetBeanSpringContextUtils。getBean(beanName);if(StringUtils。hasText(params)){methodtargetBean。getClass()。getDeclaredMethod(methodName,String。class);}else{methodtargetBean。getClass()。getDeclaredMethod(methodName);}ReflectionUtils。makeAccessible(method);}catch(NoSuchMethodExceptione){e。printStackTrace();}}Overridepublicvoidrun(){logger。info(定时任务开始执行bean:{},方法:{},参数:{},beanName,methodName,params);longstartTimeSystem。currentTimeMillis();try{if(StringUtils。hasText(params)){method。invoke(targetBean,params);}else{method。invoke(targetBean);}}catch(Exceptionex){logger。error(String。format(定时任务执行异常bean:s,方法:s,参数:s,beanName,methodName,params),ex);}longtimesSystem。currentTimeMillis()startTlogger。info(定时任务执行结束bean:{},方法:{},参数:{},耗时:{}毫秒,beanName,methodName,params,times);}Overridepublicbooleanequals(Objecto){if(thiso)if(onullgetClass()!o。getClass())SchedulingRunnablethat(SchedulingRunnable)o;if(paramsnull){returnbeanName。equals(that。beanName)methodName。equals(that。methodName)that。}returnbeanName。equals(that。beanName)methodName。equals(that。methodName)params。equals(that。params);}OverridepublicinthashCode(){if(paramsnull){returnObjects。hash(beanName,methodName);}returnObjects。hash(beanName,methodName,params);}} SchedulingRunnable实现了Runnable接口,这里的实现逻辑也比较简单,我们一起来看下:首先声明了beanName、methodName以及params分别作为定时任务执行的Bean的bean名称、方法名称以及方法参数。不知道小伙伴们是否记得我们上篇文章中介绍的该系统的用法,在添加一个定时任务时,我们需要传入相应的beanName、methodName以及params参数,传入后就来到这里了。另外还有targetBean和method分别表示beanName对应的对象以及methodName对应的对象,其中targetBean通过beanName从Spring容器中查找,method则通过methodName从targetBean中查找。在run方法中,通过反射去调用method方法,这也是定时任务执行时候的具体逻辑。另外,这里重写了equals和hashCode方法,这两个方法主要是比较了beanName、methodName以及params三个属性,换言之,如果这三个属性相同,则认为这是同一个对象(这三个属性相同表示这是同一个定时任务)。3。3SchedulingConfigConfigurationpublicclassSchedulingConfig{BeanpublicTaskSchedulertaskScheduler(){ThreadPoolTaskSchedulertaskSchedulernewThreadPoolTaskScheduler();taskScheduler。setPoolSize(4);taskScheduler。setRemoveOnCancelPolicy(true);taskScheduler。setThreadNamePrefix(TaskSchedulerThreadPool);returntaskS}} 这里主要是配置一下ThreadPoolTaskScheduler,这个可以很方便的对重复执行的任务进行调度管理,相比于通过Java自带的周期性任务线程池ScheduleThreadPoolExecutor,ThreadPoolTaskScheduler对象支持根据Cron表达式创建周期性任务。 既然是线程池,必然就有线程数量等问题,它的核心线程池大小就是我们配置的poolSize属性,最大线程池大小是Integer。MAXVALUE,keepAliveTime为0,这里用到的队列是DelayedWorkQueue,这个队列有一个属性privatefinalDelayQueuedqnewDelayQueue();对这个队列的操作实际是是对这个DelayQueue的操作,这个队列大小是Integer。MAXVALUE,所以线程数量肯定是够用了。 其他配置就没啥好说的。3。4ScheduledTask ScheduledTask是ScheduledFuture的包装类,这个包装类中主要多了一个future属性,这个future属性表示TaskScheduler定时任务线程池的执行结果:publicfinalclassScheduledTask{volatileScheduledF?publicvoidcancel(){ScheduledF?futurethis。if(future!null){future。cancel(true);}}}3。5CronTaskRegistrar 核心的方法都在这个里边。ComponentpublicclassCronTaskRegistrarimplementsDisposableBean{privatefinalMapRunnable,ScheduledTaskscheduledTasksnewConcurrentHashMap(16);AutowiredprivateTaskSchedulertaskSpublicTaskSchedulergetScheduler(){returnthis。taskS}publicvoidaddCronTask(Runnabletask,StringcronExpression){addCronTask(newCronTask(task,cronExpression));}publicvoidaddCronTask(CronTaskcronTask){if(cronTask!null){RunnabletaskcronTask。getRunnable();if(this。scheduledTasks。containsKey(task)){removeCronTask(task);}this。scheduledTasks。put(task,scheduleCronTask(cronTask));}}publicvoidremoveCronTask(Runnabletask){ScheduledTaskscheduledTaskthis。scheduledTasks。remove(task);if(scheduledTask!null)scheduledTask。cancel();}publicScheduledTaskscheduleCronTask(CronTaskcronTask){ScheduledTaskscheduledTasknewScheduledTask();scheduledTask。futurethis。taskScheduler。schedule(cronTask。getRunnable(),cronTask。getTrigger());returnscheduledT}Overridepublicvoiddestroy(){for(ScheduledTasktask:this。scheduledTasks。values()){task。cancel();}this。scheduledTasks。clear();}} 稍微说下这个类:首先这个类实现了DisposableBean接口,实现这个接口就重写了destroy方法,以便在Bean销毁的时候,清除所有的定时任务。addCronTask(Runnable,String)方法用来添加一个定时任务,传两个参数,第一个是Runnable,也就是我们前面所说的定时任务,第二个则是一个Cron表达式。addCronTask(CronTask)方法也用来添加定时任务,添加之前先判断这个定时任务是否已经存在,如果已经存在,就先移除。然后将定时任务存入scheduledTasks中,存储的时候,key就是那个Runnable对象,value则是一个ScheduledTask对象。ScheduledTask对象从scheduleCronTask方法中获取,这也是整个系统最最核心的一段代码,调用taskScheduler对象把定时任务添加进去。removeCronTask方法用来移除一个定时任务,移除分为两部分:1。从scheduledTasks集合中找到定时任务并移除;2。取消定时任务的执行。最后的destroy方法就是一个常规方法,该移除移除,该清空清空。3。6InitTask 这是一个处理数据库中已有定时任务的类。当系统启动时,首先从数据库中读取需要定时执行的任务,然后挨个加入定时任务执行器中:ComponentpublicclassInitTaskimplementsCommandLineRunner{AutowiredCronTaskRegistrarcronTaskRAutowiredSysJobServicesysJobSOverridepublicvoidrun(String。。。args)throwsException{ListSysJoblistsysJobService。getJobsByStatus(1);for(SysJobsysJob:list){cronTaskRegistrar。addCronTask(newSchedulingRunnable(sysJob。getBeanName(),sysJob。getMethodName(),sysJob。getMethodParams()),sysJob。getCronExpression());}}}查询所有状态为1的定时任务。遍历第一步查询出来的集合,添加定时任务。 好啦,这就是整个项目最最核心的配置了,其他的代码都是一些业务层面的代码,乏善可陈,我就不啰嗦啦。4。定时任务怎么配 有的小伙伴可能还不知道定时任务怎么配置,我这里稍微说两句。 项目中提供了如下一个测试类:Component(schedulingTaskDemo)publicclassSchedulingTaskDemo{publicvoidtaskWithParams(Stringparams){System。out。println(执行有参示例任务:params);}publicvoidtaskNoParams(){System。out。println(执行无参示例任务);}} 这是提前写好的,需要的时候我们配置的定时任务就是这里相关的参数,如下图: Bean名称、方法名称都和测试案例中的Bean一一对应。5。小结 好啦,是不是很Easy?小伙伴们赶紧去尝试下吧! 项目地址:GitHub:https:github。comlenveschedulingGitee:https:gitee。comlenvescheduling原文链接:https:mp。weixin。qq。comsskZ7uU7q1iH9QrV2EKiGOg 原作者:江南一点雨