前言
最近有一个需求,看似简单,就是增加一个xxl-job的定时任务,要求手动执行的时候可以传参数,通过参数去定时跑不同的数据。本来以为没什么,xxl-job也经常用,不过是新的项目新的服务,先部署了一下单节点的xxl-job服务,改改配置,启动!
一个小时不到,很快搞定。
java服务加两个注解方法,手动在xxl-job里面加两个定时任务
点下测试,一切正常,能成功执行。到这一步都一切顺利,用时不到两小时。
不过,干了这么多年开发,哪有这么一帆风顺的事,作妖的开始了。
问题
然后通过手动入参跑历史的数据
结果,看了看数据库,发现并没有任何反应。
本来以为是个小问题,第一时间就想到,是不是代码实现有问题?
于是翻来覆去看了半个小时代码逻辑,没看出什么问题。
再后来,debug手动执行方法,结果数据竟然按我想要的方式生成了。
这时候马上想到了xxl-job参数那里出了问题。
于是写了一个测试定时任务
手动执行了一次
嗯!?
这时候有点懵了,感觉也没什么特殊的点,为什么这次没有把参数传回来???
因为以前经常这样用,代码都是参考的原来项目的,实在搞不懂什么原理。
于是只能场外求助了...
问了问通义千问,仔细看了看他的回复,也没看出来什么问题。
难道是没有加 throws Exception 没有识别到方法???
总觉得这不是正确答案,但是又看了半个小时也没有头绪,不管怎样先试试再说。
不出所料还是没有...
分析
这时候已经过去大半天了,说实话心里有点恼火,又有其他的事情要做,而且还毫无头绪。
到底哪里出了问题?
于是又仔细比较了以前项目和现在这个的区别,发现除了用法之外没有任何不同。
想了半天,突然一个念头在心里想起。
虽然新旧代码没什么区别,但是有个关键点不一样,那就是xxl-job core包的版本不一样,老项目用的2.x的版本,新项目因为和新部署的xxl-job服务配套,和它一样用的3.0.0的版本。
这时,一个隐隐不好的念头在心中产生。
难道3.0.0的版本获取外部参数已经和2.x的版本不一样了?
这种实现有什么弊端吗?为什么会舍弃这种获取参数的方式?
虽然有点难以相信,心想这么通用的方式,xxl-job没必要舍弃吧,再怎么说兼容一下也可以的吧。
于是,再次请求场外援助(ps:现在AI有个很大的好处就是不用再像以前一样全网搜答案了) 向AI提问【xxljob 版本3.0.0通过注解如何实现方法入参?】
不过它并没有给我想要的答案,回复和上一次的实现没什么不同。
看来AI也不是万能的,还是得自己上手。
验证猜想
想要验证猜想,又只有走上读xxl-job源码的路上,因为线上xxl-job调不了本地服务,只有本地起一套服务。
改了配置,需要起一个管理服务和一个执行器服务,数据库就用开发环境了,也省了配账号的步骤。
怎么入手?
当然从手动执行那个接口开始,一步一步看看他怎么调到我们本地方法的。
F12打开,发现调了一个这个接口
一步步跟着debug发现它先做了鉴权-新增线程执行执行trigger方法,最后执行ExecutorBizClient类下的run方法,其中TriggerParam类则记录了我们传递的参数、执行器等信息
我之所以停在这里,因为addressUrl这个字段很明显是我们本地服务的注册节点,那么这里很大概率就是我们本地服务
于是进去看了眼,这个地方确实是通过http请求调的本地服务,而且调的是一个/run的接口。
于是又去本地服务的xxl-job包里面查了查,发现xxl-job并不是复用的SpringBoot的链接执行的http请求解析,而是自己新开了一个netty,自己解析的http协议。
可以看到它默认新增的线程池,核心线程数0,最大线程数200,2000的阻塞队列(嗯,面试有题可以问问应聘者了)。不过这不是这次的重点,继续看他的实现。
可以看到解析uri之后执行了不同的方法,继续断点跟踪,发现一大坨找job执行线程的逻辑,先不细看了。
最后定格在pushTriggerQueue方法上,看情况是将任务参数添加进阻塞队列里面。
这点也很好明白,估计怕任务执行不过来所以解耦操作。既然有入队的地方就应该有出队的地方,通过搜索队列引用找到了这个方法。
在JobThread类里,该类继承了Thread,应该是xxl-job自己实现的一个线程类,用于执行任务。
中间一大坨逻辑就先不看了
handler.execute()看到这个方法基本也就确认了执行器的实现。
点进去看,发现是一个接口
三个实现类,一眼就看中了中间那个MethodJobHandler的实现,因为我的方法就是通过@XxlJob注解实现的方法,看看里面在干嘛...
???
上来就是三个问号?
看看这,你在做什么?我的参数呢,执行我方法的时候明明有输入参数,但是这里直接就是根据我入参的数量创建一个object的数组,我实际的入参为什么们没有传过来???
到这里大概也看出来了,难怪怎么折腾执行方法那都获取不到值,因为压根没传。
愣神之后,还是冷静了下来,既然xxl-job提供了可以动态入参,那么他会把我的参数藏在哪里?
于是重新往回翻代码,通过TriggerParam这个类终于看到用它的地方
没错,在刚才run那个线程方法上,他把传入的参数设置到了threadlocal里面,好嘛,搞半天原来是通过上线文获取。
虽然不明白为什么这么做,不过既然知道了原理,那就好办了,于是将方法修改成下面的模样。
传参没有了,所以直接删除方法上的入参,通过XxlJobContext.getXxlJobContext().getJobParam();这个上线文获取。
嗯,有值了,TM的,一行代码写一天,领导估计又说我摸鱼了。
总结
本来不是一个什么大问题,但真赶时间的时候是真要命,最主要没想到AI竟然不知道3.0.0版本的xxl-job获取参数的方式变了,真让我失望!亏我现在这么多问题都问它,搞的我不得不自己翻代码。
其实这个问题要不了多久时间,主要还是开始懒,不想本地部署,也不想看源码,左右纠结了半天,最后没办法还是只能自己上了。
另外这里发散一下,如果从旧的项目的xxl-job版本升级到新的3.0.0版本,要实现兼容,还需要自己去实现IJobHandler这个接口,让它能正确的将参数注入到方法里。