1. xxl-job源码分析-从demo开始

xxl-job是很多公司都会选用的一个定时调度平台,因为它有着可视化的web展示,使用起来也非常的简单便捷。不过在一般情况下我们都只是下载以后,按照官方文档使用一下就行。毕竟做各个业务线的我们,面对各种复杂的业务,已经够头疼了,工期还被各种压缩,能快速使用好不出错就谢天谢地了。但是,有时候我们可能会定制化的改造一些内容,这个时候就需要我们深入源码进行学习,只有了解了其原理,才能更好的去进行一些定制化的开发与改造。

首先第一步是把最新的源码下载下来,目前最新的源码发布版本是2.4.0,本次分析也是根据这个版本进行分析。

默认我认为大家已经阅读过官方文档,并对xxl-job有了一个大致的了解,能够独立配置并运行项目。

源码中的大致结构还是给大家介绍一下:

在这里插入图片描述

  1. xxl-job-admin:中心调度的web平台,可以配置多种任务和定时器
  2. xxl-job-core: 核心工程,无论是调度平台,还是我们自己写的工程都需要引入这个核心工程,使用这个核心工程的内容来完成xxl-job的集成
  3. xxl-job-executor-samples: 作者集成案例,包含两个模块,一个是非框架版的集成方式,一个是springboot版本的集成方式。

本篇的切入点就从xxl-job-executor-samples模块中的xxl-job-executor-sample-springboot分析。xxl-job-executor-sample-frameless这个非框架版本的实际上更加简单,相信理解了springboot模块的小伙伴再去看非框架版本一定能沟很快理解到位。在xxl-job-executor-sample-springboot模块中,只有一个XxlJobConfig配置类,SampleXxlJob示例代码类和XxlJobExecutorApplication的启动类。启动类是标准的springboot启动内容,没有什么特别的。SampleXxlJob示例代码告诉我们配置注解进行任务处理逻辑的开发,也不是我们的重点。我们的重点放在XxlJobConfig配置类上。看下这个配置类到底做了什么事情。

XxlJobConfig分析

XxlJobConfig的类非常简单,就是根据配置项生成了一个XxlJobSpringExecutor

@Configuration
public class XxlJobConfig {
    private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);

    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;

    @Value("${xxl.job.accessToken}")
    private String accessToken;

    @Value("${xxl.job.executor.appname}")
    private String appname;

    @Value("${xxl.job.executor.address}")
    private String address;

    @Value("${xxl.job.executor.ip}")
    private String ip;

    @Value("${xxl.job.executor.port}")
    private int port;

    @Value("${xxl.job.executor.logpath}")
    private String logPath;

    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppname(appname);
        xxlJobSpringExecutor.setAddress(address);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

        return xxlJobSpringExecutor;
    }
}

接下来就是分析XxlJobSpringExecutor

XxlJobSpringExecutor分析

先看下XxlJobSpringExecutor的类结构

在这里插入图片描述

熟悉Spring的同学可以很清晰的看到它实现了三个扩展接口,分别是ApplicationContextAwareSmartInitializingSingletonDisposableBean

ApplicationContextAware可以获取ApplicationContext也就是Spring的容器

SmartInitializingSingleton可以在类初始化完毕后进行一些后置处理

DisposableBean是销毁bean的自定义处理

具体的话可以看下代码的实现,下面我列出一些重点部分进行分析。

@Override
public void afterSingletonsInstantiated() {
    // 初始化JobHandler的仓库
    initJobHandlerMethodRepository(applicationContext);
    // 创建GlueFactory
    GlueFactory.refreshInstance(1);
    // 调用父类的start方法
    try {
        super.start();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

/**
 * 对spring容器中所有的bean进行一个过滤
 * 如果是带有@Lazy懒加载标签的,不处理
 * 找到带有注解@XxlJob的方法进行注册,即调用registJobHandler方法
 **/
private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {
    if (applicationContext == null) {
        return;
    }
    // init job handler from method
    String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true);
    for (String beanDefinitionName : beanDefinitionNames) {
        // get bean
        Object bean = null;
        Lazy onBean = applicationContext.findAnnotationOnBean(beanDefinitionName, Lazy.class);
        if (onBean!=null){
            logger.debug("xxl-job annotation scan, skip @Lazy Bean:{}", beanDefinitionName);
            continue;
        }else {
            bean = applicationContext.getBean(beanDefinitionName);
        }

        // filter method
        Map<Method, XxlJob> annotatedMethods = null;   // referred to :org.springframework.context.event.EventListenerMethodProcessor.processBean
        try {
            annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
                                                                new MethodIntrospector.MetadataLookup<XxlJob>() {
                                                                    @Override
                                                                    public XxlJob inspect(Method method) {
                                                                        return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);
                                                                    }
                                                                });
        } catch (Throwable ex) {
            logger.error("xxl-job method-jobhandler resolve error for bean[" + beanDefinitionName + "].", ex);
        }
        if (annotatedMethods==null || annotatedMethods.isEmpty()) {
            continue;
        }

        // generate and regist method job handler
        for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
            Method executeMethod = methodXxlJobEntry.getKey();
            XxlJob xxlJob = methodXxlJobEntry.getValue();
            // regist
            registJobHandler(xxlJob, bean, executeMethod);
        }
    }
}

从上述代码中可以看到,在自身对象初始化完成后,又对整个spring容器中的bean进行了一个整体的扫描。扫描了带有XxlJob注解的方法。对这些带有注解的方法(executeMethod)调用 registJobHandler(xxlJob, bean, executeMethod)进行注册。而这个注册方法是父类XxlJobExecutor的方法。在往下分析之前,我们先看下 @XxlJob注解的源代码,方面后续理解。

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface XxlJob {
    /**
     * jobhandler name
     */
    String value();
    /**
     * init handler, invoked when JobThread init
     */
    String init() default "";
    /**
     * destroy handler, invoked when JobThread destroy
     */
    String destroy() default "";

}

这个源码比较简单,说明注解是作用于方法上,且注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。这样就可以通过反射获取其中的属性。注解中一共三个属性,表示jobHandler的名字,初始化方法名和销毁的方法名。

了解了XxlJob注解后,继续分析这个重点的registJobHander方法,

protected void registJobHandler(XxlJob xxlJob, Object bean, Method executeMethod) {
    if (xxlJob == null) {
        return;
    }
    String name = xxlJob.value();
    // 获取类名
    Class<?> clazz = bean.getClass();
    // 获取方法名
    String methodName = executeMethod.getName();
    if (name.trim().length() == 0) {
        throw new RuntimeException("xxl-job method-jobhandler name invalid, for[" + clazz + "#" + methodName + "] .");
    }
    if (loadJobHandler(name) != null) {
        throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts.");
    }
	// 设置方法可访问
    executeMethod.setAccessible(true);
    Method initMethod = null;
    Method destroyMethod = null;
    if (xxlJob.init().trim().length() > 0) {
        try {
            // 获取init的方法
            initMethod = clazz.getDeclaredMethod(xxlJob.init());
            initMethod.setAccessible(true);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("xxl-job method-jobhandler initMethod invalid, for[" + clazz + "#" + methodName + "] .");
        }
    }
    if (xxlJob.destroy().trim().length() > 0) {
        try {
            // 获取destory方法
            destroyMethod = clazz.getDeclaredMethod(xxlJob.destroy());
            destroyMethod.setAccessible(true);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + clazz + "#" + methodName + "] .");
        }
    }
    // 注册 jobhandler
    registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));
}

// 注册jobhandler,也就是放入到jobHandlerRepository中
// 这个和spring容器的思想是一样的
// 先放入容器,后续需要使用的时候再取出来
public static IJobHandler registJobHandler(String name, IJobHandler jobHandler){
    logger.info(">>>>>>>>>>> xxl-job register jobhandler success, name:{}, jobHandler:{}", name, jobHandler);
    return jobHandlerRepository.put(name, jobHandler);
}

在这个方法中,根据注解@XxlJob中的属性,获取到jobHandler名称和分别得到了两个方法,分别是initMethoddestroyMethod。再拿着@XxlJob本身注解的方法,就可以构造一个MethodJobHandler了。

注意一下,MethodJobHandler已经变成一个类了。也就是说在我们写的类中,只要打上XxlJob注解的方法,实际上都会被解析成一个MethodJobHandler类。这个类中包含了三个重要的方法。当需要的时候,就调用这三个方法。这个其实和Mybatis中的Mapper有点类似,每一个Mapper中的方法,实际都是封装成了一个MapperMethod类,由这个类去单独处理一个方法。

总结

本篇介绍了Xxl-job的源码结构,从xxl-job-executor-sample-springboot模块分析了springboot如何集成Xxl-job。也从其XxlJobSpringExecutor中分析了如何采用XxlJob注解,将类中的一个个方法变成一个个的MethodJobHandler类的。但是这还只是Xxljob的冰山一角。大家心里肯定还有很多疑惑,包括Xxl-job是如何调用MethodJobHandler,它又是如何中心节点通信的呢?这些内容后续的篇章都会一一介绍,希望本篇能够引发读者的兴趣,后续我们继续分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值