目 录CONTENT

文章目录

魔改xxl-job-自动注册执行器和任务

筱晶哥哥
2023-01-23 / 0 评论 / 0 点赞 / 220 阅读 / 53640 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2024-03-23,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

xxl-job是一款非常优秀的任务调度中间件,轻量级、使用简单、支持分布式等优点,让它广泛应用在我们的项目中,解决了不少定时任务的调度问题。

前言

官方文档: 分布式任务调度平台XXL-JOB

xxl-job是一款非常优秀的任务调度中间件,轻量级、使用简单、支持分布式等优点,让它广泛应用在我们的项目中,解决了不少定时任务的调度问题。

我们都知道,在使用过程中需要先到xxl-job的任务调度中心页面上,配置 执行器executor 和 具体的任务job ,这一过程如果项目中的定时任务数量不多还好说,如果任务多了的话还是挺费工夫的。

image-20230123085623749

假设项目中有上百个这样的定时任务,那么每个任务都需要走一遍绑定jobHander后端接口,填写cron表达式这个流程,填多了谁能不迷糊。

于是出于功能优化(偷懒 )这一动机,前几天我萌生了一个想法,有没有什么方法能够告别xxl-job的管理页面,能够让我不再需要到页面上去手动注册执行器和任务,实现让它们自动注册到调度中心呢。

分析

分析一下,其实我们要做的很简单,只要在项目启动时主动注册executor和各个jobHandler到调度中心就可以了,流程如下:

  1. 项目启动的时候,看执行器是否注册到调度中心了

    • 是:扫描所有添加@XxlJob的jobHandler
    • 否:自动添加新执行器,然后扫描所有添加@XxlJob的jobHandler
  2. 各个jobHandler是否注册到调度中心

    • 是:正常执行
    • 否:自动添加新任务

有的小伙伴们可能要问了,我在页面上创建执行器的时候,不是有一个选项叫做自动注册吗,为什么我们这里还要自己添加新执行器?

image-20230123091015915

其实这里有个误区,这里的自动注册指的是会根据项目中配置的xxl.job.executor.appname,将配置的机器地址自动注册到这个执行器的地址列表中。但是如果你之前没有手动创建过执行器,那么是不会给你自动添加一个新执行器到调度中心的。

既然有了想法咱们就直接开干,先到github上拉一份xxl-job的源码下来:xxl-job

整个项目导入idea后,先看一下结构:

image-20230123091227273

结合着文档和代码,先梳理一下各个模块都是干什么的:

  • xxl-job-admin:任务调度中心,启动后就可以访问管理页面,进行执行器和任务的注册、以及任务调用等功能了
  • xxl-job-core:公共依赖,项目中使用到xxl-job时要引入的依赖包
  • xxl-job-executor-samples:执行示例,分别包含了springboot版本和不使用框架的版本

为了弄清楚注册和查询executorjobHandler调用的是哪些接口,我们先从页面上去抓一个请求看看:

image-20230123091527966

好了,这样就能定位到xxl-job-admin模块中/jobgroup/save这个接口,接下来可以很容易地找到源码位置:

image-20230123091652870

按照这个思路,可以找到下面这几个关键接口:

  • /jobgroup/pageList:执行器列表的条件查询
  • /jobgroup/save:添加保存执行器
  • /jobinfo/pageList:任务列表的条件查询
  • /jobinfo/add:添加任务

但是如果直接调用这些接口,那么就会发现它会重定向跳转到xxl-job-admin的的登录页面。

其实想想也明白,出于安全性考虑,调度中心的接口也不可能允许裸调的。

那么再回头看一下刚才页面上的请求就会发现,它在Headers中添加了一条名为XXL_JOB_LOGIN_IDENTITYcookie

image-20230123092200367

至于这条cookie,则是在通过用户名和密码调用调度中心的/login接口时返回的,在返回的response可以直接拿到。只要保存下来,并在之后每次请求时携带,就能够正常访问其他接口了。

到这里,我们需要的5个接口就基本准备齐了,接下来准备开始正式的改造工作。

改造

我们改造的目的是实现一个starter,以后只要引入这个starter就能实现executorjobHandler的自动注册。

image-20230123094227609

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.itjing</groupId>
	<artifactId>xxljob-autoregister-spring-boot-starter</artifactId>
	<version>1.0.0</version>
	<name>xxljob-autoregister-spring-boot-starter</name>
	<description>xxljob-autoregister-spring-boot-starter</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>

		<!-- xxl-job-core -->
		<dependency>
			<groupId>com.xuxueli</groupId>
			<artifactId>xxl-job-core</artifactId>
			<version>2.3.1</version>
		</dependency>

		<!-- spring-boot-autoconfigure -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-autoconfigure</artifactId>
		</dependency>

		<!-- hutool -->
		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.8.9</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-autoconfigure-processor</artifactId>
			<optional>true</optional>
		</dependency>

	</dependencies>
</project>

接口调用

在调用调度中心的接口前,先把xxl-job-admin模块中的XxlJobInfoXxlJobGroup这两个类拿到我们的starter项目中,用于接收接口调用的结果。

image-20230123094540990

登录接口

创建一个JobLoginService,在调用业务接口前,需要通过登录接口获取cookie,并在获取到cookie后,缓存到本地的Map中。

其他接口在调用时,直接从缓存中获取cookie,如果缓存中不存在则调用/login接口,为了避免这一过程失败,允许最多重试3次。

package com.itjing.job.service;

/**
 * @Description: 通过登录接口获取cookie
 * @Author: lijing
 * @CreateTime: 2023-01-23 09:48
 */
public interface JobLoginService {

    void login();

    String getCookie();
}
package com.itjing.job.service.impl;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.itjing.job.service.JobLoginService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.net.HttpCookie;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * @Description:
 * @Author: lijing
 * @CreateTime: 2023-01-23 09:50
 */
@Service
public class JobLoginServiceImpl implements JobLoginService {
    
    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;

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

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

    private final Map<String, String> loginCookie = new HashMap<>();

    @Override
    public void login() {
        String url = adminAddresses + "/login";
        HttpResponse response = HttpRequest.post(url)
                .form("userName", username)
                .form("password", password)
                .execute();
        List<HttpCookie> cookies = response.getCookies();
        Optional<HttpCookie> cookieOpt = cookies.stream()
                .filter(cookie -> cookie.getName().equals("XXL_JOB_LOGIN_IDENTITY"))
                .findFirst();
        if (!cookieOpt.isPresent()) {
            throw new RuntimeException("get xxl-job cookie error!");
        }
        String value = cookieOpt.get().getValue();
        loginCookie.put("XXL_JOB_LOGIN_IDENTITY", value);
    }

    @Override
    public String getCookie() {
        for (int i = 0; i < 3; i++) {
            String cookieStr = loginCookie.get("XXL_JOB_LOGIN_IDENTITY");
            if (cookieStr != null) {
                return "XXL_JOB_LOGIN_IDENTITY=" + cookieStr;
            }
            login();
        }
        throw new RuntimeException("get xxl-job cookie error!");
    }
}

执行器接口

package com.itjing.job.enums;

/**
 * @Description: 执行器地址类型枚举
 * @Author: lijing
 * @CreateTime: 2023-01-23 10:11
 */
public enum AddressTypeEnum {
    AUTO(0, "自动注册"),
    HAND(1, "手动录入"),
    ;

    private Integer code;

    private String msg;

    AddressTypeEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

创建一个JobGroupService,根据appName和执行器名称title查询执行器列表:

public List<XxlJobGroup> getJobGroup() {
    String url = adminAddresses + "/jobgroup/pageList";
    HttpResponse response = HttpRequest.post(url)
        .form("appname", appName)
        .form("title", title)
        .cookie(jobLoginService.getCookie())
        .execute();

    String body = response.body();
    JSONArray array = JSONUtil.parse(body).getByPath("data", JSONArray.class);
    List<XxlJobGroup> list = array.stream()
        .map(o -> JSONUtil.toBean((JSONObject) o, XxlJobGroup.class))
        .collect(Collectors.toList());

    return list;
}

我们在后面要根据配置文件中的appNametitle判断当前执行器是否已经被注册到调度中心过,如果已经注册过那么则跳过,而 /jobgroup/pageList 接口是一个模糊查询接口,所以在查询列表的结果列表中,还需要再进行一次精确匹配。

public boolean preciselyCheck() {
    List<XxlJobGroup> jobGroup = getJobGroup();
    Optional<XxlJobGroup> has = jobGroup.stream().filter(
        xxlJobGroup -> Objects.equals(xxlJobGroup.getAppname(), appName) && Objects.equals(xxlJobGroup.getTitle(), title)
    ).findAny();
    return has.isPresent();
}

注册新executor到调度中心:

public boolean autoRegisterGroup() {
    String url = adminAddresses + "/jobgroup/save";
    HttpRequest httpRequest = HttpRequest.post(url)
        .form("appname", appName)
        .form("title", title);

    httpRequest.form("addressType", addressType);
    if (Objects.equals(addressType, AddressTypeEnum.HAND.getCode())) {
        if (StrUtil.isBlank(addressList)) {
            throw new RuntimeException("手动录入模式下,执行器地址列表不能为空");
        }
        httpRequest.form("addressList", addressList);
    }

    HttpResponse response = httpRequest.cookie(jobLoginService.getCookie()).execute();
    Object code = JSONUtil.parse(response.body()).getByPath("code");
    return code.equals(200);
}

JobGroupServiceImpl.java 完整代码:

package com.itjing.job.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.itjing.job.enums.AddressTypeEnum;
import com.itjing.job.model.XxlJobGroup;
import com.itjing.job.service.JobGroupService;
import com.itjing.job.service.JobLoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @Description:
 * @Author: lijing
 * @CreateTime: 2023-01-23 10:00
 */
@Service
public class JobGroupServiceImpl implements JobGroupService {
    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;

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

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

    /**
     * 执行器地址类型:0=自动注册、1=手动录入
     */
    @Value("${xxl.job.executor.addressType:0}")
    private Integer addressType;

    /**
     * 执行器地址列表,多地址逗号分隔(手动录入)
     */
    @Value("${xxl.job.executor.addressList:}")
    private String addressList;

    @Autowired
    private JobLoginService jobLoginService;

    /**
     * 查询执行器列表
     * @return
     */
    @Override
    public List<XxlJobGroup> getJobGroup() {
        String url = adminAddresses + "/jobgroup/pageList";
        HttpResponse response = HttpRequest.post(url)
                .form("appname", appName)
                .form("title", title)
                .cookie(jobLoginService.getCookie())
                .execute();

        String body = response.body();
        JSONArray array = JSONUtil.parse(body).getByPath("data", JSONArray.class);
        List<XxlJobGroup> list = array.stream()
                .map(o -> JSONUtil.toBean((JSONObject) o, XxlJobGroup.class))
                .collect(Collectors.toList());

        return list;
    }

    /**
     * 注册新executor到调度中心
     * @return
     */
    @Override
    public boolean autoRegisterGroup() {
        String url = adminAddresses + "/jobgroup/save";
        HttpRequest httpRequest = HttpRequest.post(url)
                .form("appname", appName)
                .form("title", title);

        httpRequest.form("addressType", addressType);
        if (Objects.equals(addressType, AddressTypeEnum.HAND.getCode())) {
            if (StrUtil.isBlank(addressList)) {
                throw new RuntimeException("手动录入模式下,执行器地址列表不能为空");
            }
            httpRequest.form("addressList", addressList);
        }

        HttpResponse response = httpRequest.cookie(jobLoginService.getCookie()).execute();
        Object code = JSONUtil.parse(response.body()).getByPath("code");
        return code.equals(200);
    }

    /**
     * 在查询列表的结果列表中,还需要再进行一次精确匹配
     * @return
     */
    @Override
    public boolean preciselyCheck() {
        List<XxlJobGroup> jobGroup = getJobGroup();
        Optional<XxlJobGroup> has = jobGroup.stream().filter(
                xxlJobGroup -> Objects.equals(xxlJobGroup.getAppname(), appName) && Objects.equals(xxlJobGroup.getTitle(), title)
        ).findAny();
        return has.isPresent();
    }
}

任务接口

创建一个JobInfoService,根据执行器idjobHandler名称查询任务列表,和上面一样,也是模糊查询,然后注册一个新任务,最终返回创建的新任务的id

package com.itjing.job.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.itjing.job.model.XxlJobInfo;
import com.itjing.job.service.JobInfoService;
import com.itjing.job.service.JobLoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @Description:
 * @Author: lijing
 * @CreateTime: 2023-01-23 10:18
 */
@Service
public class JobInfoServiceImpl implements JobInfoService {

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

    @Autowired
    private JobLoginService jobLoginService;

    @Override
    public List<XxlJobInfo> getJobInfo(Integer jobGroupId, String executorHandler) {
        String url = adminAddresses + "/jobinfo/pageList";
        HttpResponse response = HttpRequest.post(url)
                .form("jobGroup", jobGroupId)
                .form("executorHandler", executorHandler)
                .form("triggerStatus", -1)
                .cookie(jobLoginService.getCookie())
                .execute();

        String body = response.body();
        JSONArray array = JSONUtil.parse(body).getByPath("data", JSONArray.class);
        List<XxlJobInfo> list = array.stream()
                .map(o -> JSONUtil.toBean((JSONObject) o, XxlJobInfo.class))
                .collect(Collectors.toList());

        return list;
    }

    @Override
    public Integer addJobInfo(XxlJobInfo xxlJobInfo) {
        String url = adminAddresses + "/jobinfo/add";
        Map<String, Object> paramMap = BeanUtil.beanToMap(xxlJobInfo);
        HttpResponse response = HttpRequest.post(url)
                .form(paramMap)
                .cookie(jobLoginService.getCookie())
                .execute();
        JSON json = JSONUtil.parse(response.body());
        Object code = json.getByPath("code");
        if (Objects.equals(code, HttpStatus.HTTP_OK)) {
            return Convert.toInt(json.getByPath("content"));
        }
        throw new RuntimeException("add jobInfo error!");
    }

}

创建注解

在创建任务时,必填字段除了执行器和jobHandler之外,还有任务描述负责人Cron表达式调度类型运行模式 。在这里,我们默认调度类型为CRON、运行模式为BEAN,另外的3个字段的信息需要用户指定。

因此我们需要创建一个新注解@XxlRegister,来配合原生的@XxlJob注解进行使用,填写这几个字段的信息,最后,额外添加了一个triggerStatus属性,表示任务的默认调度状态,0为停止状态,1为运行状态。

package com.itjing.job.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Description: 任务注册相关注解
 * @Author: lijing
 * @CreateTime: 2023-01-23 10:24
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface XxlRegister {

    /**
     * Cron表达式
     * @return
     */
    String cron();

    /**
     * 任务描述
     * @return
     */
    String jobDesc() default "default jobDesc";

    /**
     * 负责人
     * @return
     */
    String author() default "default Author";

    /**
     * 默认为 ROUND 轮询方式
     * 可选: FIRST 第一个
     * @return
     */
    String executorRouteStrategy() default "ROUND";

    /**
     * 调度状态:0-停止,1-运行
     * @return
     */
    int triggerStatus() default 0;

}

自动注册核心

基本准备工作做完后,下面实现自动注册执行器和jobHandler的核心代码。

核心类实现ApplicationListener接口,在接收到ApplicationReadyEvent事件后开始执行自动注册逻辑。

/**
 * @Description: xxl-job自动注册核心
 * @Author: lijing
 * @CreateTime: 2023-01-23 10:28
 */
@Component
public class XxlJobAutoRegister implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware {

    private static final Log log = LogFactory.get();

    private ApplicationContext applicationContext;

    @Autowired
    private JobGroupService jobGroupService;

    @Autowired
    private JobInfoService jobInfoService;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        //注册执行器
        addJobGroup();
        //注册任务
        addJobInfo();
    }
}

自动注册执行器的代码非常简单,根据配置文件中的appNametitle精确匹配查看调度中心是否已有执行器被注册过了,如果存在则跳过,不存在则新注册一个:

/**
     * 自动注册执行器
     */
private void addJobGroup() {
    if (jobGroupService.preciselyCheck()) {
        return;
    }

    if (jobGroupService.autoRegisterGroup()) {
        log.info("auto register xxl-job group success!");
    }
}

自动注册任务的逻辑则相对复杂一些,需要完成:

  • 通过applicationContext拿到spring容器中的所有bean,再拿到这些bean中所有添加了@XxlJob注解的方法
  • 对上面获取到的方法进行检查,是否添加了我们自定义的@XxlRegister注解,如果没有则跳过,不进行自动注册
  • 对同时添加了@XxlJob@XxlRegister的方法,通过执行器idjobHandler的值判断是否已经在调度中心注册过了,如果已存在则跳过
  • 对于满足注解条件且没有注册过的jobHandler,调用接口注册到调度中心

具体代码如下:

private void addJobInfo() {
    List<XxlJobGroup> jobGroups = jobGroupService.getJobGroup();
    XxlJobGroup xxlJobGroup = jobGroups.get(0);

    String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true);
    for (String beanDefinitionName : beanDefinitionNames) {
        Object bean = applicationContext.getBean(beanDefinitionName);

        Map<Method, XxlJob> annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
                                                                                new MethodIntrospector.MetadataLookup<XxlJob>() {
                                                                                    @Override
                                                                                    public XxlJob inspect(Method method) {
                                                                                        return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);
                                                                                    }
                                                                                });
        for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
            Method executeMethod = methodXxlJobEntry.getKey();
            XxlJob xxlJob = methodXxlJobEntry.getValue();

            // 自动注册
            if (executeMethod.isAnnotationPresent(XxlRegister.class)) {
                XxlRegister xxlRegister = executeMethod.getAnnotation(XxlRegister.class);
                List<XxlJobInfo> jobInfo = jobInfoService.getJobInfo(xxlJobGroup.getId(), xxlJob.value());
                if (!jobInfo.isEmpty()) {
                    // 因为是模糊查询,需要再判断一次
                    Optional<XxlJobInfo> first = jobInfo.stream()
                        .filter(xxlJobInfo -> xxlJobInfo.getExecutorHandler().equals(xxlJob.value()))
                        .findFirst();
                    if (first.isPresent()) {
                        continue;
                    }
                }

                XxlJobInfo xxlJobInfo = createXxlJobInfo(xxlJobGroup, xxlJob, xxlRegister);
                Integer jobInfoId = jobInfoService.addJobInfo(xxlJobInfo);
            }
        }
    }
}

private XxlJobInfo createXxlJobInfo(XxlJobGroup xxlJobGroup, XxlJob xxlJob, XxlRegister xxlRegister) {
    XxlJobInfo xxlJobInfo = new XxlJobInfo();
    xxlJobInfo.setJobGroup(xxlJobGroup.getId());
    xxlJobInfo.setJobDesc(xxlRegister.jobDesc());
    xxlJobInfo.setAuthor(xxlRegister.author());
    xxlJobInfo.setScheduleType("CRON");
    xxlJobInfo.setScheduleConf(xxlRegister.cron());
    xxlJobInfo.setGlueType("BEAN");
    xxlJobInfo.setExecutorHandler(xxlJob.value());
    xxlJobInfo.setExecutorRouteStrategy(xxlRegister.executorRouteStrategy());
    xxlJobInfo.setMisfireStrategy("DO_NOTHING");
    xxlJobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");
    xxlJobInfo.setExecutorTimeout(0);
    xxlJobInfo.setExecutorFailRetryCount(0);
    xxlJobInfo.setGlueRemark("GLUE代码初始化");
    xxlJobInfo.setTriggerStatus(xxlRegister.triggerStatus());
    return xxlJobInfo;
}

自动装配

创建一个配置类,用于扫描bean

package com.itjing.job.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @Description:
 * @Author: lijing
 * @CreateTime: 2023-01-23 10:39
 */
@Configuration
@ComponentScan(basePackages = "com.itjing.job")
public class XxlJobPlusConfig {
}

将它添加到META-INF/spring.factories文件:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.itjing.job.config.XxlJobPlusConfig

到这里starter的编写就完成了,可以通过maven发布jar包到本地或者私服

mvn clean install/deploy

测试

新建一个springboot项目,引入我们在上面打好的包:

<!-- 晶哥哥魔改xxl-job -->
<dependency>
    <groupId>com.itjing</groupId>
    <artifactId>xxljob-autoregister-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

application.properties中配置xxl-job的信息,首先是原生的配置内容

# 原生xxl-job配置
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
xxl.job.accessToken=default_token
xxl.job.executor.appname=xxl-job-executor-test
xxl.job.executor.address=
xxl.job.executor.ip=127.0.0.1
xxl.job.executor.port=9999
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
xxl.job.executor.logretentiondays=30

此外还要额外添加我们自己的starter要求的新配置内容:

# 新增配置项,必须项
# admin用户名
xxl.job.admin.username=admin
# admin 密码
xxl.job.admin.password=123456
# 执行器名称,数据库定义长度为 varchar(12),建议不要超过此长度 ,否则可以修改数据库字段长度
xxl.job.executor.title=test-exec

# 新增配置项,可选项
# 执行器地址类型:0=自动注册、1=手动录入,默认为0
xxl.job.executor.addressType=0
# 在上面为1的情况下,手动录入执行器地址列表,多地址逗号分隔
xxl.job.executor.addressList=http://127.0.0.1:9999

完成后在代码中配置一下XxlJobSpringExecutor,然后在测试接口上添加原生@XxlJob注解和我们自定义的@XxlRegister注解:

package com.itjing.job.config;

import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Description: xxl-job配置
 * @Author: lijing
 * @CreateTime: 2023-01-23 11:03
 */
@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;
    }

}
package com.itjing.job.service;

import com.itjing.job.annotation.XxlRegister;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.springframework.stereotype.Service;

/**
 * @Description: 测试业务类
 * @Author: lijing
 * @CreateTime: 2023-01-23 11:06
 */
@Service
public class TestService {

    @XxlJob(value = "testJob")
    @XxlRegister(cron = "0 0 0 * * ? *",
            author = "lijing",
            jobDesc = "测试job")
    public void testJob() {
        System.out.println("#公众号:程序员阿晶");
    }


    @XxlJob(value = "testJob222")
    @XxlRegister(cron = "59 1-2 0 * * ?",
            triggerStatus = 1)
    public void testJob2() {
        System.out.println("#作者:lijing");
    }

    @XxlJob(value = "testJob444")
    @XxlRegister(cron = "59 59 23 * * ?")
    public void testJob4() {
        System.out.println("hello xxl job");
    }

}

启动项目,可以看到执行器自动注册成功:

image-20230123113556922

再打开调度中心的任务管理页面,可以看到同时添加了两个注解的任务也已经自动完成了注册:

image-20230123113414712

image-20230123113529749

从页面上手动执行任务进行测试,可以执行成功:

image-20230123113743060

image-20230123113716402

到这里,starter的编写和测试过程就算基本完成了,项目中引入后,以后也能省出更多的时间来摸鱼学习了~

0

评论区