xxl-job core 客户端代码逻辑剖析

xxl-job的客户端代码逻辑相对其他框架来讲还是简单一点的,主要学习设计的思路,这里只以springboot项目,运行模式为bean类型剖析
首先,我们都知道,总体思路是xxljob服务端调用客户端的接口,然后客户端执行相应的代码,所有调度都在服务端进行控制,由服务端数据库的id保证每次任务只会执行一次,
服务端要调用客户端的代码,那必须得知道客户端的ip和端口,整体大致的思路与nacos的服务注册发现类似,
客户端主要入口为XxlJobSpringExecutor
image
触发点为SmartInitializingSingleton#afterSingletonsInstantiated方法
SmartInitializingSingleton在所有的单例bean创建完毕之后会调用afterSingletonsInstantiated,这个时候容器的bean方法处于可用状态
想要依赖SmartInitializingSingleton,就必须把XxlJobSpringExecutor注入到spring容器当中,常见的几种写法,
第一种就是新建@Configuration,将XxlJobSpringExecutor申明为bean的方式
image
当前注入为bean的方式有n种,还有比较邪修的方法,比如下面这种,也不是不可以
image
这种方法不管是spring项目还是springboot项目都适用,xxl-job原作者不知道是什么原因,像一般的框架会封装为springboot的starer,github上有好几个类似这种的pr,

@Configuration
@ConditionalOnClass({XxlJobSpringExecutor.class})
@EnableConfigurationProperties(XxlJobProperties.class)
@ConditionalOnProperty(name = "xxl.job.executor.enabled")
public class XxlJobSpringBootAutoConfiguration {
    private final XxlJobProperties xxlJobProperties;
    private Logger logger = LoggerFactory.getLogger(XxlJobSpringBootAutoConfiguration.class);

    public XxlJobSpringBootAutoConfiguration(XxlJobProperties xxlJobProperties) {
        this.xxlJobProperties = xxlJobProperties;
    }

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(xxlJobProperties.getAdmin().getAddresses());
        xxlJobSpringExecutor.setAppname(xxlJobProperties.getExecutor().getAppname());
        if (Objects.nonNull(xxlJobProperties.getExecutor().getAddress()) && !xxlJobProperties.getExecutor().getAddress().isEmpty()) {
            xxlJobSpringExecutor.setAddress(xxlJobProperties.getExecutor().getAddress());
        }
        if (Objects.nonNull(xxlJobProperties.getExecutor().getIp()) && !xxlJobProperties.getExecutor().getIp().isEmpty()) {
            xxlJobSpringExecutor.setIp(xxlJobProperties.getExecutor().getIp());
        }
        if (Objects.nonNull(xxlJobProperties.getExecutor().getPort()) && xxlJobProperties.getExecutor().getPort() > 0) {
            xxlJobSpringExecutor.setPort(xxlJobProperties.getExecutor().getPort());
        }
        xxlJobSpringExecutor.setAccessToken(xxlJobProperties.getAccessToken());
        xxlJobSpringExecutor.setLogPath(xxlJobProperties.getExecutor().getLogPath());
        xxlJobSpringExecutor.setLogRetentionDays(xxlJobProperties.getExecutor().getLogretentiondays());
        return xxlJobSpringExecutor;
    }

第一步为调用initJobHandlerMethodRepository初始化jobhandler的方法,查找全部带@XxlJob注解的方法
image
首先通过applicationContext获取所有spring容器的beanname,紧接着获取bean对象,再通过MethodIntrospector.selectMethods查询bean对象带XxlJob注解的方法,
对于大型项目来说,虽然bean的数量会很多,但是MetadataLookup的查询效率也非常高,对项目的启动速度影响很小,
image
xxljob在执行的时候可以指定init方法与destroy方法,将这些都作为参数封装为MethodJobHandler对象,最终注入到jobHandlerRepository缓存中,其中key为xxljob注解的value,也是管理端配置的JobHandler名称,value为method的对象,后续经过管理端会进行反射调用
image
到这里第一步就完成了,已经找到了项目中所有的jobhandler,
第二步GlueFactory.refreshInstance(1)会对xxljob管理端设置的glue(java)模式进行bean的注入,类似下面这种java脚本,可以处理@Resource和@Autowrird对bean进行反射注入
image
执行结果如下:
image
这里就会涉及到另一个经常问的问题了:@Autorired和@Resource注入bean的区别,
下面开始做一些准备操作,最主要的是最后一步,在客户端启动netty服务器,作为xxljob服务端调用客户端方法,然后会进行客户端的节点注册
image
首先第一步,会初始化xxljob日志的目录,根据配置的logPath,如果没有就新创建文件夹
第二步初始化服务端地址,AdminBizClient,作为后续注册服务以及回调执行结果的客户端,也是自己配置的adminAddresses,可以部署多个节点,用逗号分割
第三步创建清除日志文件线程JobLogFileCleanThread,根据配置的logRetentionDays,清除指定天数之前的日志文件。每隔一天执行一次
第四步创建回调线程TriggerCallbackThread,当客户端代码执行完之后,需要回调给客户端执行的结果, 成功或失败,通过callBackQueue队列达到解耦的目的,TriggerCallbackThread使用take()方法异步上报,通过调用第二部生成的AdminBizClient的callback方法,TriggerCallbackThread里面由很多细节最后再总结。
image
第五步,开启启动netty服务器,
image
需要留意的是EmbedHttpServerHandler为xxl-job中基于netty实现的http请求处理器,/run方法负责执行本地的bean方法,
image
服务启动完成,这时候服务端还不知道有哪些客户端,客户端通过appname和address进行注册,使用ExecutorRegistryThread线程,address为http:127.0.0.1:9999/格式,通过调用AdminBiz的registry方法进行注册,
值得注意的是这里的注册即能将当前ip+port进行注册,也能充当与客户端与服务端的心跳作用,ExecutorRegistryThread每隔30s执行一次

到这里客户端的netty的服务器已经启动了,所有准备工作都做好了,
这里假如xxljob服务端有一个调度任务需要客户端执行,并且负载已经选择完成,指定的客户端地址为http:127.0.0.1:9999/,会首先调用/run接口,
此时netty容器也正常启动,接受到run接口之后会调用ExecutorBizImpl#run方法,jobId为全局唯一表示任务id,executorHandler为@Xxljob的value属性
如果是bean模式。会从之前注册的jobHandlerRepository缓存种根据jobhandler的值取出MethodJobHandler
image
最后执行是通过JobThread,

posted @ 2026-01-21 15:29  木马不是马  阅读(0)  评论(0)    收藏  举报