在使用XXL—JOB的实现定时任务过程中,有时候可能由于部署环境的要求,就只能用Spring自带的实现方式。
所以为了通用性和灵活性,突发奇想地看看能不能实现在不修改原本Spring定时任务代码的前提下,通过配置灵活控制定时任务具体的实现,同时任务的日志的管理也要同步进行切换。
分析并列出需要解决的问题思路
根据需求背景可以初步分析实现的大致方向和实现流程。实现的思路其实不复杂,重点在于如何具体去实现落地。
图片
具体实现
判断是否启用XXl-JOB的实现方式
和大多数第三方starter包一样,我们可以利用SpringBoot的自动装配,读取配置中的某个属性值,作为是否装配我们写的类。特别注意的是SpringBoot不同版本的配置方式有所不同。
自动装配类如下:
这里我们根据xxl.job.enable
的值,决定是否启用XXl-JOB的实现方式,如果xxl.job.enable
为false,则就什么都不装配不修改实现方式,默认就是Spring自带的实现方式。
扫描并读取注解值
熟悉SpringBoot的的朋友都应该知道,SpringBoot启动的时候,会去扫描目标注解,然后去做对应的初始化操作,比如@Service
,@Component
就是使被扫描到并将对应的类注入到Spring容器中。所以我们可以按照相同的思路,可以在应用启动就绪之后,扫描@Scheduled
注解,对其进行对应的操作。
Spring中的@EventListener注解
Spring中使用@EventListener
标记某个方法为应用监听事件的处理逻辑,还能配合异步注解@Async
实现异步触发,@EventListener
通过传值的方式设置需要被监听的事件类型,比如应用启动时、应用就绪时、启动失败时等,具体有哪些监听的事件,可以参考Spring源码包org.springframework.boot.context.event
。
现在,我们可以利用Spring提供的监听注解,在应用启动就绪后,扫描对应注解,去实现我们的代码逻辑,同时为了不影响程序的正常启动速度,使用异步执行的方式。
伪代码如下:
扫描并获取被@Scheduled标记的方法和对象
我们知道,使用@Scheduled
注解对应的对象,必须是被Spring所托管的类,定时任务才会生效,所以我们可以扫描被@Component
标记的类,再定位@Scheduled
注解,获取对应的值、对象、方法等信息。
伪代码如下:
关闭Spring自带的定时任务
ScheduledAnnotationBeanPostProcessor
类是一个Spring框架的类,用于处理@Scheduled
注解,实现定时任务的功能。我们可以通过这个类,对Spring中的定时任务进行一定的操作。
通过阅读Spring源码,发现ScheduledAnnotationBeanPostProcessor
有这么一个方法postProcessBeforeDestruction
,该方法实现DestructionAwareBeanPostProcessor
接口,用于销毁一个Bean的前置操作,而在ScheduledAnnotationBeanPostProcessor
类中,这个方法的实现是取消某个Bean中的所有定时任务。具体可以看一下这个方法的源码。
由于我们上一步已经扫描获取到被@Scheduled
注解标记过方法,我们可以直接通过方法对象,获取到对应的Bean,将Bean作为入参传入postProcessBeforeDestruction
方法中,关闭Spring自带的定时任务。
读取注解信息并将任务自动注册到XXl-JOB
有使用过XXL-JOB的小伙伴都清楚,在使用方法模式时,除了使用注解标记定时任务的方法,还需要在调度中心上进行任务的配置,定时任务才会生效。
目前我们已经获取到 @Scheduled
注解的信息,我们可以将 @Scheduled
所带的信息转换为对应XXL-JOB上对应的任务类型,在启动的时候自动地注册到调度中心,简化XXl-JOB任务调度的使用配置步骤。
注册JobHandler
翻看XXl-JOB中关于@XxlJob
的源码,发现会将@XxlJob
所标记的方法,向调度中心注册一个MethodJobHandler
类型的JobHandler,表示方法模式对应的处理器。
入口代码及位置如下
❝
com.xxl.job.core.executor.impl.XxlJobSpringExecutor#initJobHandlerMethodRepository
❞
我们可以参考源码,将被@Scheduled
标记的方法,以同样的方式,注册到调度中心中去。从而实现@XxlJob
同样的效果。
自动向调度中心注册执行器和对应的任务信息
「注册执行器」
XXL-JOB没有像PowerJob一样,提供类似powerjob-client
的OpenAPI接口,但是问题不大,根据XXL-JOB的源码,我们可以自己实现一个,将获取token,添加执行器信息,添加任务信息等包装为service。
具体代码可以查看文章后的github地址,这里简单贴出向调度中心注册执行器的代码。
「添加对应任务信息」
同样的,添加任务信息的逻辑也包装为一个service。考虑到可能重复注册的问题,这里需要判断注册的任务是否已存在在调度中心中。
将定时任务中的log.info()日志输出一份到XXL-JOB的在线日志上
XXl-JOB中提供了XxlJobHelper类,用于将任务中的日志输出到调度中心,方便在调度中心上进行查看。而 lombok 生成的log.info()
依赖于Slf4j日志门面。
而我们知道,SpringBoot默认Slf4j的实现是Logback,Logback中提供类自定义Appender的接口,用于自定义日志信息的处理逻辑。我们可以在自定义的Appender中将日志打印到XXl-JOB中的调度中心。
第三方应用集成Starter使用
为了让使用方更加方便的集成使用,减少其他依赖的配置,以上的实现封装为一个Starter,使用起来将非常的方便,具体的使用步骤如下。
在POM文件中引入Starter依赖
提供的Starter对XXL-JOB没有强依赖,所以使用方还得引入XXL-JOB的依赖。
SpringBoor配置文件中添加XXL-JOB的配置
除了配置XXL-JOB的基本配置,还需要配置我们自定义实现功能所需要的配置项,具体如下:
XXL-JOB执行器组件配置
这个是XXL-JOB执行器所需要的配置。
使用SpringBoot自带的@Scheduled注解开发定时任务
新建一个Job类模拟使用定时任务的场景。
启动项目验证
先将配置文件中的xxl.job.enable设置为false,使用Spring默认的实现方式。
嗯,没啥毛病。scheduling-1 用的啥Spring自带的scheduling线程池去执行定时任务。接下来将配置文件中的xxl.job.enable
设置为true,再看看日志。
日志看起来没啥问题,注册执行器和注册任务信息的相关日志都打印了出来,定时任务的执行日志也有了。我们上调度中心看看。
图片
图片
图片
嗯,符合预期,执行器和任务详情都自动添加到调度中心了,任务中心的日志也能在调度中心中查看了。
实现过程中思考的几个问题
是否实现任务信息的更新
一开始想着是否需要监听注解上值的变化,对应地去更新XXL-JOB上的任务信息,如经常需要改变的定时任务的间隙时间或者corn表达式,后来还是决定不实现了,考虑到大多数场景下,自动注册任务只是作为应用启动的初始化工作,后续需要调整还是得上调度中心进行操作,所以任务的配置就不能一直以注解上配置为准了。
是否采用修改数据库数据的方式实现任务的注册
自动注册任务和执行器信息,其实可以直接利用ORM操作数据库去实现。不过如果XXL-JOB的数据库和当前应用不在同一台机器上,就需要配置多个数据源了,相对比较麻烦,对于第三方使用者来说,也会多出一些配置。总体看起来不够优雅,最后还是采用读取调度中心地址,利用http工具调用API的方式去实现。
是否在自动装配类上加上@Scheduled
在提供的自动装配类中,其实可以帮使用者默认加上 @Scheduled
开启SpringBoot的自动任务,但是为了尽量不影响正常的开发配置,开头说到的尽量让用户无感知,所以这个 @Scheduled
还是需要starter的使用方自己去配置,然后走默认实现的定时任务开发。
提供的Starter是否加上XXL-Job的依赖
提供的strarter包只是作为增强功能的存在,所以是可选的,不应该耦合XXL-JOB的核心依赖,就像Hutool中POI工具一样,本身并不依赖POI的核心依赖,作为Strarter包,应该只提供自己的核心功能就行。