XXL-JOB执行器源码及原理分析

目录

执行全流程图

1.执行器配置

2.执行器XxlJobSpringExecutor

registJobHandler()

MethodJobHandler()

registJobHandler()

3.执行器XxlJobExecutor

start()

XxlJobFileAppender.initLogPath()

initAdminBizList()

JobLogFileCleanThread.getInstance().start()

TriggerCallbackThread.getInstance().start()

initEmbedServer()

4.嵌入式服务EmbedServer(重点)

start()

EmbedHttpServerHandler

5.业务执行器ExecutorBizImpl

run()

XxlJobExecutor.loadJobHandler()

GlueFactory.getInstance().loadNewInstance()

XxlJobExecutor.registJobThread()

jobThread.pushTriggerQueue()

newJobThread.start()

handler.execute()


执行全流程图


流程图看不清可放大看,看完这个流程图你就知道xxl-job的整体大致是怎么运行的了,图中红色线为调度中心执行任务过程,绿色线为执行器注册、回调等过程。大致分为一个调度中心,多个执行器也就是我们不同的服务都有一个不同地址的执行器,调用中心会通过远程rpc调度分派任务让执行器执行run()、kill()等。 然后执行器在启动的时候是以netty服务的方式启动的,启动会自动通过registry()远程rpc调用将本服务执行器注册到调度中心,后续执行器netty服务通过接收调度中心的http请求,执行具体的业务逻辑例如:run()。

1.执行器配置

/**
 * xxl-job config
 *
 * @author laijiangfeng 
 */
@Configuration
public class XxlJobConfig {
    private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);

    /**
     * xxl-job 定义接口地址:http://127.0.0.1:7995/digitaltwins-admin/xxl-job-admin
     */
    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;

    /**
     * xxl-job 执行器身份校验TOKEN,执行器端和调度服务端必须一致
     */
    @Value("${xxl.job.accessToken}")
    private String accessToken;

    /**
     *  xxl-job 执行器AppName,一般一个服务一个名称,自定义名称
     */
    @Value("${xxl.job.executor.appname}")
    private String appname;

    /**
     *  xxl-job 执行器地址:http://127.0.0.1:9999/,可以为空,默认会获取本机ip加端口作为地址,一般为空
     */
    @Value("${xxl.job.executor.address}")
    private String address;

    /**
     *  xxl-job 执行器ip地址:127.0.0.1,可以为空,默认会获取本机ip地址,一般为空
     */
    @Value("${xxl.job.executor.ip}")
    private String ip;

    /**
     *  xxl-job 执行器端口:9999,可以为空,默认会从9999递增直到找到一个未被使用的端口作为执行器端口,一般为空
     */
    @Value("${xxl.job.executor.port}")
    private int port;

    /**
     * xxl-job 执行器执行打印的日志路径,例如:/data/applogs/xxl-job/jobhandler,自行定义
     */
    @Value("${xxl.job.executor.logpath}")
    private String logPath;

    /**
     * xxl-job 执行器日志保留天数,例如:30,不填默认为0
     */
    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;


    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> digitaltwins 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;
    }

    /**
     * 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP;
     *
     *      1、引入依赖:
     *          <dependency>
     *             <groupId>org.springframework.cloud</groupId>
     *             <artifactId>spring-cloud-commons</artifactId>
     *             <version>${version}</version>
     *         </dependency>
     *
     *      2、配置文件,或者容器启动变量
     *          spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
     *
     *      3、获取IP
     *          String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
     */
    
}

这里也就是配置一个执行器bean,设置一些地址参数,我们往XxlJobSpringExecutor里看。

2.执行器XxlJobSpringExecutor

public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean {
    private static final Logger logger = LoggerFactory.getLogger(XxlJobSpringExecutor.class);

    /**
     * 容器对象applicationContext
     */
    private static ApplicationContext applicationContext;

    /**
     * 初始bean会调用这个方法
     */
    @Override
    public void afterSingletonsInstantiated() {

        // 初始化获取被@XxlJob注解的bean,根据bean获取执行方法注册到ConcurrentMap中,方便后续获取到bean的方法直接通过反射调用
        initJobHandlerMethodRepository(applicationContext);

        // 刷新脚工厂GlueFactory,分为 0:普通模式,1:spring模式,区别在于spring模式会将有@Autowired、@Resource标识的属性注入容器的bean
        // 这边会根据你写的脚本代码动态生成处理类来执行逻辑,这个写的感觉很牛。
        GlueFactory.refreshInstance(1);

        // 调用父类start方法启动执行器服务
        try {
            super.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * bean销毁时调用
     */
    @Override
    public void destroy() {
        super.destroy();
    }

    /**
     * 初始化方法仓库,找到所有的bean过滤出带@XxlJob注解的,存储到ConcurrentMap
     * @param applicationContext  容器对象
     */
    private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {
        if (applicationContext == null) {
            return;
        }
        //获取所有是Object类型、是单例bean、且获取懒加载的bean的定义名称
        String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true);
        for (String beanDefinitionName : beanDefinitionNames) {

            // 获取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);
            }

            // 获取bean中所有方法,过滤出带有@XxlJob注解的方法
            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);
            }
            // 没有找到带有@XxlJob注解的方法,则跳过
            if (annotatedMethods==null || annotatedMethods.isEmpty()) {
                continue;
            }

            // 循环带有@XxlJob注解的方法,注册到ConcurrentMap中
            for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
                Method executeMethod = methodXxlJobEntry.getKey();
                XxlJob xxlJob = methodXxlJobEntry.getValue();
                // 注册
                registJobHandler(xxlJob, bean, executeMethod);
            }
        }
    }

    /**
     * bean初始化前会调用这个方法,并且传入容器对象
     * @param applicationContext 容器对象
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        XxlJobSpringExecutor.applicationContext = applicationContext;
    }

    /**
     * 获取容器对象
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

bean初始化前调用setApplicationContext将spring容器applicationContext设置值,初始化调用afterSingletonsInstantiated将注册所有带有xxl-job注解的方法及bean注册到执行器保存到ConcurrentMap里,方便后续反射调用,刷新脚本工厂GlueFactory设置为1表示spring类型,0表示普通类型,区别在于spring模式会将有@Autowired、@Resource标识的属性注入容器的bean,脚本工厂让我们可以将外部编写的类继承IJobHandler(xxl-job处理器) 重写execute()方法后续将类代码转成 string字符串配置在glueSource即可实现由脚本工厂动态帮我们生成类,调用我们类的execute()方法执行逻辑,而不需要重新在项目中写代码在部署服务,非常方便。最后就是调用super.start(),我们往下看。

registJobHandler()

protected void registJobHandler(XxlJob xxlJob, Object bean, Method executeMethod){
        //判空
        if (xxlJob == null) {
            return;
        }
        //获取注解的value值,也就是执行器名称。
        String name = xxlJob.value();
        //获取bean的class对象,方便后续反射获取类方法
        Class<?> clazz = bean.getClass();
        //获取执行方法,也就是注解中定义的execute方法。
        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 {
                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 {
                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,校验方法名称、获取执行job任务时调用的初始化方法、执行job任务时调用的销毁方法,后续调用registJobHandler()注册JobHandler。

MethodJobHandler()

/**
 * 方法反射执行任务的处理器
 */
public class MethodJobHandler extends IJobHandler {

    /**
     * 任务方法执行实例bean对象
     */
    private final Object target;
    /**
     * 任务方法执行实例bean对象的执行方法,反射调用
     */
    private final Method method;
    /**
     * 任务初始化方法执行实例bean对象的初始化方法,反射调用
     */
    private Method initMethod;

    /**
     * 任务销毁方法执行实例bean对象的销毁方法,反射调用
     */
    private Method destroyMethod;

    /**
     * 构造方法
     * @param target  任务方法执行实例bean对象
     * @param method 任务方法执行实例bean对象的执行方法,反射调用
     * @param initMethod 任务初始化方法执行实例bean对象的初始化方法,反射调用
     * @param destroyMethod  任务销毁方法执行实例bean对象的销毁方法,反射调用
     */
    public MethodJobHandler(Object target, Method method, Method initMethod, Method destroyMethod) {
        this.target = target;
        this.method = method;
        this.initMethod = initMethod;
        this.destroyMethod = destroyMethod;
    }

    /**
     * 执行任务invoke对类下的方法名进行反射调用
     * @throws Exception
     */
    @Override
    public void execute() throws Exception {
        Class<?>[] paramTypes = method.getParameterTypes();
        if (paramTypes.length > 0) {
            method.invoke(target, new Object[paramTypes.length]);       // method-param can not be primitive-types
        } else {
            method.invoke(target);
        }
    }

    /**
     * 初始化任务init对类下的方法名进行反射调用
     * @throws Exception
     */
    @Override
    public void init() throws Exception {
        if(initMethod != nu
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值