XxlJob——魔改使用一个注解实现定时任务自动创建

文章目录

前言

在使用Cloud Alibaba做微服务开发,服务模块增多,每次需要配置各个微服务的定时任务,都需要手动去增加各个执行器任务的配置,很麻烦。

能否采取精简的方式,比如一个注解标记的方式,就能达到自动创建的功能呢?

梳理XxlJob相关接口

1、cookie 获取

查看xxljob-server的源码,以及在web端手动新增执行器和任务时,都能看到对应接口中会传入一个XXL_JOB_LOGIN_IDENTITY=的cookie信息。

获取方式也很简单,直接使用com.xxl.job.admin.controller.IndexController#loginDo接口。

接口名称 接口类型 接口地址 传参
获取cookie信息 POST http://127.0.0.1:8080/xxl-job-admin/login userName、password

2、执行器相关接口

执行器分为创建和查询接口,查询使用页面的pageList即可,这里注意数据过滤使用的是模糊查询。注意这些接口需要携带cookie。

接口名称 接口类型 接口地址 传参
执行器查询 POST /jobgroup/pageList appname
执行器创建 POST /jobgroup/save appname、title、addressType

3、任务相关接口

和执行器一样,也需要了解到任务列表查询和任务新增接口。注意这些接口需要携带cookie。

接口名称 接口类型 接口地址 传参
任务查询 POST /jobinfo/pageList jobGroup、jobDesc、triggerStatus(范围必传 全部为-1)
任务创建 POST /jobinfo/add jobGroup、jobDesc、author、scheduleType、glueType、scheduleConf、executorHandler、executorRouteStrategy、misfireStrategy、executorBlockStrategy、executorTimeout、executorFailRetryCount

核心思想

  • 1、定义注解,命名相关属性信息。如:任务属性点。
  • 2、编写xxljob的配置信息
  • 3、在服务启动时,识别自定义注解,查询server端是否存在指定执行器,不存在则创建。再根据执行器id和任务设定值查询任务是否已配置,未配置则需要使用接口进行自动配置。

实操

测试代码实际结构

1、依赖引入和两个实体类拷贝

这里只指出核心依赖。

xml 复制代码
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.25</version>
</dependency>
       <dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>2.4.0</version>
</dependency>

从官方源码中引入两个类至测试项目中,用于请求后信息数据的映射。

2、定义xxljob连接配置

src/main/resources下新增application.yml文件,填写xxljob的核心配置点。如下所示:

yml 复制代码
xxl:
  job:
    admin:
      addresses: http://127.0.0.1:8080/xxl-job-admin
      username: admin
      password: 123456
    executor:
      appname: xxl-job-xj
      port: 9998
      logpath: /data/applogs/xxl-job/jobhandler
      logretentiondays: 30

3、定义注解

定义如下注解

最开始是考虑到执行器信息也在注解上配置。但后来取消了这种想法。每个服务就应该所有任务在一个执行器中,直接使用全局配置信息即可!

java 复制代码
package cn.test.annotation;

import java.lang.annotation.*;
import java.util.List;

/**
 * 定义自动注册注解,包含 @XxlJob 注解的属性信息
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface XxlJobRegister {

    // ==================== 执行器属性 ========================= //

//    /**
//     * 执行器名称编号
//     * @return
//     */
//    String executeAppName() default "public";
//
//    /**
//     * 执行器名称
//     * @return
//     */
//    String executeTitle() default "deafult_execute_title";

    /**
     * 执行器注册方式 0自动注册  1手动注册
     * 此处强制自动注册
     * @return
     */
    // int executeType() default 0;

    // ==================== 任务配置属性 ========================= //

    /**
     * 任务名称
     * @return
     */
    String jobDesc() default "default_job";

    /**
     * 负责人信息
     * @return
     */
    String jobAuthor() default "-1";

    /**
     * 任务执行 JobHandler 属性
     * @return
     */
    String jobHandler() default "defaultJobHandler";

    /**
     * 仅支持调度类型为 corn,默认为 2小时执行一次
     * @return
     */
    String corn() default "0 0 2 * * ?";

    /**
     * 是否设置默认开启,默认关闭
     * @return
     */
    boolean triggerStatus() default false;

    /**
     * 路由策略
     * @return
     */
    String executorRouteStrategy() default "FIRST";

    /**
     * 调度过期策略
     * @return
     */
    String misfireStrategy() default "DO_NOTHING";
    
	 /**
     * 阻塞处理策略 这里默认使用 单机串行
     * @return
     */
    String executorBlockStrategy() default "SERIAL_EXECUTION";

    /**
     * 任务执行超时时间
     * @return
     */
    int executorTimeout() default 0;

    /**
     * 任务执行失败 重试次数
     * @return
     */
    int executorFailRetryCount() default 0;
}

4、定义一个测试执行方法

java 复制代码
package cn.test.test;


import cn.test.annotation.XxlJobRegister;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * 测试自动注册调度方法
 */
@Component
public class HandlerTest {

    private static Logger logger = LoggerFactory.getLogger(HandlerTest.class);

    @XxlJobRegister(jobAuthor = "xj",
				    jobDesc = "香蕉测试任务自动创建",
				    jobHandler = "testXJTestJobHandler")
    public void test(){
        System.out.println("666666");
    }
}

5、定义处理器

xxljob 本身在使用前,就需要开发人员手动去注入bean,官方给出的案例xxl-job-executor-sample-springboot中默认使用的就是XxlJobSpringExecutor

这里仿照官方的逻辑,适配上述的核心思想逻辑。重新定义一个XxlJobRegisterExecutor

java 复制代码
package cn.test.config;

import cn.test.executor.XxlJobRegisterExecutor;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import lombok.extern.slf4j.Slf4j;
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;

/**
 * 自动创建执行器、任务 并注册xxljob
 */
@Configuration
public class RegisterConfig {

    private Logger logger = LoggerFactory.getLogger(RegisterConfig.class);

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

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

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

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

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

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

    @Value("${xxl.job.executor.ip:null}")
    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 XxlJobRegisterExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobRegisterExecutor xxlJobSpringExecutor = new XxlJobRegisterExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAdminUsername(adminUsername);
        xxlJobSpringExecutor.setAdminPassword(adminPassword);

        xxlJobSpringExecutor.setAppname(appname);
        xxlJobSpringExecutor.setAddress("null".equalsIgnoreCase(address) ? null : address);
        xxlJobSpringExecutor.setIp("null".equalsIgnoreCase(ip) ? null : ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken("null".equalsIgnoreCase(accessToken)?null:accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
        return xxlJobSpringExecutor;
    }
}

自定义处理器类逻辑。

java 复制代码
package cn.test.executor;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.test.annotation.XxlJobRegister;
import cn.test.domain.XxlJobGroup;
import cn.test.domain.XxlJobInfo;
import cn.test.utils.AnnotationProxyUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.xxl.job.core.executor.XxlJobExecutor;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import com.xxl.job.core.glue.GlueFactory;
import com.xxl.job.core.handler.annotation.XxlJob;
import cn.test.constant.XxlJobServerConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;

import java.lang.reflect.Method;
import java.net.HttpCookie;
import java.util.*;
import java.util.stream.Collectors;


/**
 * 设置容器启动监听,自动识别
 */
public class XxlJobRegisterExecutor extends XxlJobExecutor implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean {
    private static final Logger logger = LoggerFactory.getLogger(XxlJobSpringExecutor.class);

    /**
     * xxljob 服务cookie 缓存信息
     */
    private final Map<String,String> loginCookie = new HashMap<>();

    /** 除了 XxlJobExecutor 给定的默认属性之外,这里做扩充 **/
    private String adminUsername;
    private String adminPassword;

    // ---------------------- param ----------------------
    private String adminAddresses;
    private String accessToken;
    private String appname;
    private String address;
    private String ip;
    private int port;
    private String logPath;
    private int logRetentionDays;

    public String getAdminUsername() {
        return adminUsername;
    }

    public void setAdminUsername(String adminUsername) {
        this.adminUsername = adminUsername;
    }

    public String getAdminPassword() {
        return adminPassword;
    }

    public void setAdminPassword(String adminPassword) {
        this.adminPassword = adminPassword;
    }

    public String getAdminAddresses() {
        return adminAddresses;
    }

    @Override
    public void setAdminAddresses(String adminAddresses) {
        super.setAdminAddresses(adminAddresses);
        this.adminAddresses = adminAddresses;
    }

    public String getAccessToken() {
        return accessToken;
    }

    @Override
    public void setAccessToken(String accessToken) {
        super.setAccessToken(accessToken);
        this.accessToken = accessToken;
    }

    public String getAppname() {
        return appname;
    }

    @Override
    public void setAppname(String appname) {
        super.setAppname(appname);
        this.appname = appname;
    }

    public String getAddress() {
        return address;
    }

    @Override
    public void setAddress(String address) {
        super.setAddress(address);
        this.address = address;
    }

    public String getIp() {
        return ip;
    }

    @Override
    public void setIp(String ip) {
        super.setIp(ip);
        this.ip = ip;
    }

    public int getPort() {
        return port;
    }

    @Override
    public void setPort(int port) {
        super.setPort(port);
        this.port = port;
    }

    public String getLogPath() {
        return logPath;
    }

    @Override
    public void setLogPath(String logPath) {
        super.setLogPath(logPath);
        this.logPath = logPath;
    }

    public int getLogRetentionDays() {
        return logRetentionDays;
    }

    @Override
    public void setLogRetentionDays(int logRetentionDays) {
        super.setLogRetentionDays(logRetentionDays);
        this.logRetentionDays = logRetentionDays;
    }

    // start
    @Override
    public void afterSingletonsInstantiated() {
        // 1、自动获取token信息,查询执行器信息,没有对应的配置则需要创建配置
        checkAndRegisterCookie();

        // 2、获取注解标记的方法,将自定义注解属性解析进行转换 XxlJob
        initJobHandlerMethodRepository(applicationContext);

        // 3、刷新获取 SpringGlueFactory
        GlueFactory.refreshInstance(1);

        // super start
        try {
            // 4、任务自动注册逻辑
            super.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private String getCookie() {
        // 组装 cookie 信息
        String mapCookie = loginCookie.get(XxlJobServerConstant.cookie_key);
        return XxlJobServerConstant.cookie_key + "=" + mapCookie;
    }

    /**
     * 判断 cookie 不存在则调用登录接口获取,并存入本地缓存
     * @return
     */
    private void checkAndRegisterCookie() {
        String mapCookie = loginCookie.get(XxlJobServerConstant.cookie_key);
        if (mapCookie == null || mapCookie.isEmpty()) {
            String url = adminAddresses + XxlJobServerConstant.login_url;
            HttpResponse response = HttpRequest.post(url)
                    .form("userName",adminUsername)
                    .form("password",adminPassword)
                    .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!");
            mapCookie = cookieOpt.get().getValue();
            loginCookie.put(XxlJobServerConstant.cookie_key,mapCookie);
        }
    }

    // destroy
    @Override
    public void destroy() {
        super.destroy();
    }

    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);
            }

            // 获取自定义注解标记的数据
            Map<Method, XxlJobRegister> xxlJobRegisterAnnotatedMethods = null;
            try {
                xxlJobRegisterAnnotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
                        new MethodIntrospector.MetadataLookup<XxlJobRegister>() {
                            @Override
                            public XxlJobRegister inspect(Method method) {
                                return AnnotatedElementUtils.findMergedAnnotation(method, XxlJobRegister.class);
                            }
                        });
            } catch (Throwable ex) {
                logger.error("xxl-job-register method-jobhandler resolve error for bean[" + beanDefinitionName + "].", ex);
            }
            if (xxlJobRegisterAnnotatedMethods !=null && !xxlJobRegisterAnnotatedMethods.isEmpty()) {
                for (Map.Entry<Method, XxlJobRegister> xxlJobRegisterEntry : xxlJobRegisterAnnotatedMethods.entrySet()) {
                    Method method = xxlJobRegisterEntry.getKey();
                    XxlJobRegister xxlJobRegister = xxlJobRegisterEntry.getValue();
                    try {
                        // 检查创建执行器 和 任务
                        registerExecutorJobIfNotExist(xxlJobRegister);
                        // 自定义注解的方法 需要进行一次注册 server
                        // com.xxl.job.core.server.EmbedServer.EmbedHttpServerHandler.channelRead0
                        // com.xxl.job.core.biz.impl.ExecutorBizImpl.run
                        // com.xxl.job.core.server.EmbedServer.start
                        // 转换自定义注解 XxlJobRegister -> XxlJob
                        XxlJob xxlJob = copyA2B(xxlJobRegister);
                        // regist
                        registJobHandler(xxlJob, bean, method);
                    } catch (JsonProcessingException e) {
                        logger.error("检查自定义注解与注册出现异常:{]",e);
                    }
                }

            }


            // 官方注解的数据
            Map<Method, XxlJob> xxlJobAnnotatedMethods = null;   // referred to :org.springframework.context.event.EventListenerMethodProcessor.processBean
            try {
                xxlJobAnnotatedMethods = 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 (xxlJobAnnotatedMethods !=null && !xxlJobAnnotatedMethods.isEmpty()) {
                // generate and regist method job handler
                for (Map.Entry<Method, XxlJob> methodXxlJobEntry : xxlJobAnnotatedMethods.entrySet()) {
                    Method executeMethod = methodXxlJobEntry.getKey();
                    XxlJob xxlJob = methodXxlJobEntry.getValue();
                    // regist
                    registJobHandler(xxlJob, bean, executeMethod);
                }
            }

        }
    }

    /**
     * 将注解 A 的属性拷贝到注解 B 的实例
     * @param xxlJobRegister 源注解 A 实例
     * @return XxlJob 注解 B 实例
     */
    public static XxlJob copyA2B(XxlJobRegister xxlJobRegister) {
        // 1. 构建注解 B 的属性映射(自定义属性对应关系)
        Map<String, Object> bAttributeMap = Map.of(
                "value", xxlJobRegister.jobHandler()
        );

        // 2. 生成注解 B 的代理实例
        return AnnotationProxyUtil.createAnnotationInstance(XxlJob.class, bAttributeMap);
    }

    /**
     * 注册执行器
     * @param xxlJobRegister
     */
    private void registerExecutorJobIfNotExist(XxlJobRegister xxlJobRegister) throws JsonProcessingException {
        // 1. 查询已存在的执行器  执行器不放自定义注解中,直接读取配置
        String executorListUrl = adminAddresses + XxlJobServerConstant.execute_list;
        Map<String, Object> param = new HashMap<>();
        param.put("appname", getAppname());
        String executorListResp = post(executorListUrl, param);

        // 为空时  直接创建
        JSONArray array = JSONUtil.parse(executorListResp).getByPath("data", JSONArray.class);
        List<XxlJobGroup> jobGroups = array.stream()
                .map(o -> JSONUtil.toBean((JSONObject) o, XxlJobGroup.class))
                .collect(Collectors.toList());
        // 这里是模糊查询 还需要程序上进行完全匹配
        Optional<XxlJobGroup> jobGroup = jobGroups.stream()
                .filter(xxlJobGroup -> xxlJobGroup.getAppname().equals(getAppname()))
                .findAny();

        if (!jobGroup.isPresent()) {
            // 不存在则新增
            String executorSaveUrl = adminAddresses + XxlJobServerConstant.execute_add;
            Map<String, Object> saveParams = new HashMap<>();
            saveParams.put("appname", getAppname());
            saveParams.put("title", getAppname());
            saveParams.put("addressType",0);

            String executeSave = post(executorSaveUrl, saveParams);
            JSON executeSaveJson = JSONUtil.parse(executeSave);
            Object code = executeSaveJson.getByPath("code");
            if (code.equals(200)) {
                logger.info("执行器[" + getAppname() + "]注册成功!");
            } else {
                logger.error("执行器[" + getAppname() + "]注册失败!失败日志:"+executeSave);
                return;
            }
        }

        // 2、获取注解中的执行任务信息,不存在则自动创建
        // 再次查询完整的执行器配置,获取 groupId
        Map<String, Object> executorListParam2 = new HashMap<>();
        executorListParam2.put("appname", getAppname());
        //executorListParam2.put("title", xxlJobRegister.executeTitle());
        String executeList2 = post(executorListUrl, executorListParam2);
        JSONArray executeList2Array = JSONUtil.parse(executeList2).getByPath("data", JSONArray.class);
        List<XxlJobGroup> jobGroups2 = executeList2Array.stream()
                .map(o -> JSONUtil.toBean((JSONObject) o, XxlJobGroup.class))
                .collect(Collectors.toList());
        XxlJobGroup xxlJobGroup2 = jobGroups2.stream()
                .filter(xxlJobGroup -> xxlJobGroup.getAppname().equals(getAppname()))
                .findAny().get();
        int jobGroupId = xxlJobGroup2.getId();

        String jobInfoListUrl = adminAddresses + XxlJobServerConstant.job_list;
        Map<String, Object> jobInfoParams = new HashMap<>();
        jobInfoParams.put("jobGroup",jobGroupId);
        jobInfoParams.put("jobDesc",xxlJobRegister.jobDesc());
        jobInfoParams.put("executorHandler",xxlJobRegister.jobHandler());
        jobInfoParams.put("triggerStatus",-1); // -1 表示全部
        String jobInfoLists = post(jobInfoListUrl, jobInfoParams);
        JSONArray jobInfoDatas = JSONUtil.parse(jobInfoLists).getByPath("data", JSONArray.class);
        List<XxlJobInfo> xxlJobInfos = jobInfoDatas.stream()
                .map(o -> JSONUtil.toBean((JSONObject) o, XxlJobInfo.class))
                .collect(Collectors.toList());
        // 这里是模糊查询 还需要程序上进行完全匹配
        Optional<XxlJobInfo> jobInfo = xxlJobInfos.stream()
                .filter(xxlJobInfo -> xxlJobInfo.getJobDesc().equals(xxlJobRegister.jobDesc())
                        && xxlJobInfo.getExecutorHandler().equals(xxlJobRegister.jobHandler()))
                .findAny();
        if (!jobInfo.isPresent()) {
            // 不存在指定的任务 创建任务
            String jobInfoAddUrl = adminAddresses + XxlJobServerConstant.job_add;
            Map<String, Object> jobInfoAddParams = new HashMap<>();
            jobInfoAddParams.put("jobGroup",jobGroupId);
            jobInfoAddParams.put("jobDesc",xxlJobRegister.jobDesc());
            jobInfoAddParams.put("author",xxlJobRegister.jobAuthor());
            jobInfoAddParams.put("scheduleType","CRON"); // 暂时只支持 cron 表单式方式
            jobInfoAddParams.put("glueType","BEAN"); // 运行模式暂时定义bean
            jobInfoAddParams.put("scheduleConf",xxlJobRegister.corn());
            jobInfoAddParams.put("executorHandler",xxlJobRegister.jobHandler());
            jobInfoAddParams.put("executorRouteStrategy",xxlJobRegister.executorRouteStrategy()); // 路由策略 第一个
            jobInfoAddParams.put("misfireStrategy",xxlJobRegister.misfireStrategy());
            jobInfoAddParams.put("executorBlockStrategy",xxlJobRegister.executorBlockStrategy()); // 阻塞处理策略 单机串行
            jobInfoAddParams.put("executorTimeout",xxlJobRegister.executorTimeout()); // 任务超时时间
            jobInfoAddParams.put("executorFailRetryCount",xxlJobRegister.executorFailRetryCount()); // 失败重试次数
            String jonInfoAddRes = post(jobInfoAddUrl, jobInfoAddParams);
            JSON jobInfoSaveJson = JSONUtil.parse(jonInfoAddRes);
            Object code = jobInfoSaveJson.getByPath("code");
            if (code.equals(200)) {
                logger.info("任务[" + xxlJobRegister.jobDesc() + "]注册成功!");
            } else {
                logger.error("任务[" + xxlJobRegister.jobDesc() + "]注册失败!失败日志:"+jonInfoAddRes);
            }
        }
    }

    /**
     * 通用POST请求方法(调用XXL-Job Admin API)
     */
    private String post(String url, Map<String, Object> param) {
        String cookie = getCookie();
        HttpResponse response = HttpRequest.post(url).form(param).cookie(cookie).execute();
        return response.body();
    }

    // ---------------------- applicationContext ----------------------
    private static ApplicationContext applicationContext;

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

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

}

5、补充一个注解属性映射的工具类

注解是个很特殊的存在,不能采取new的方式进行实例属性值的转换,只能采取动态代理方式进行。

java 复制代码
package cn.test.utils;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Map;

/**
 * 注解动态代理工具类(Java 官方规范方式生成注解实例)
 */
public class AnnotationProxyUtil {

    /**
     * 根据属性映射生成注解实例
     * @param annotationClass 目标注解类(如 AnnotationB.class)
     * @param attributeMap    注解属性名 -> 属性值的映射
     * @param <T>             注解类型
     * @return 注解实例(通过动态代理生成)
     */
    @SuppressWarnings("unchecked")
    public static <T extends Annotation> T createAnnotationInstance(Class<T> annotationClass,
                                                                    Map<String, Object> attributeMap) {
        // 校验:必须是注解类型
        if (!annotationClass.isAnnotation()) {
            throw new IllegalArgumentException(annotationClass.getName() + " 不是注解类型");
        }

        // 动态代理调用处理器:拦截注解的属性方法,返回对应值
        InvocationHandler handler = (proxy, method, args) -> {
            String methodName = method.getName();
            // 1. 处理 annotationType() 方法(注解接口的默认方法,必须实现)
            if ("annotationType".equals(methodName)) {
                return annotationClass;
            }
            // 2. 处理属性方法:从映射中获取值,无则返回注解默认值
            if (attributeMap.containsKey(methodName)) {
                return attributeMap.get(methodName);
            }
            // 3. 返回注解的默认值
            return method.getDefaultValue();
        };

        // 生成注解代理实例(Java 官方唯一合法的注解实例化方式)
        return (T) Proxy.newProxyInstance(
                annotationClass.getClassLoader(),
                new Class<?>[]{annotationClass},
                handler
        );
    }
}

由于xxljob在com.xxl.job.core.executor.XxlJobExecutor#registJobHandler方法中限定传参类型为XxlJob,会将对应注解的一些具体调度方法做一个注册和缓存配制,这里最好是不动他原本的逻辑,所以需要使用上面的工具类进行转换处理。

java 复制代码
/**
 * 将注解 A 的属性拷贝到注解 B 的实例
 * @param xxlJobRegister 源注解 A 实例
 * @return XxlJob 注解 B 实例
 */
public static XxlJob copyA2B(XxlJobRegister xxlJobRegister) {
    // 1. 构建注解 B 的属性映射(自定义属性对应关系)
    Map<String, Object> bAttributeMap = Map.of(
            "value", xxlJobRegister.jobHandler()
    );

    // 2. 生成注解 B 的代理实例
    return AnnotationProxyUtil.createAnnotationInstance(XxlJob.class, bAttributeMap);
}

测试效果

服务未启动时


client 服务启动后



点击 xxljob-server指定方法手动执行一次,查看效果。

相关推荐
瑶山1 个月前
Spring Cloud微服务搭建三、分布式任务调度XXL-JOB
java·spring cloud·微服务·xxljob
闫小甲8 个月前
jobrunr xxljob 怎么选?
springboot·xxljob·jobrunr
seasugar1 年前
xxljob
xxljob
LXMXHJ2 年前
Jenkins-zookeeper-docker-xxljob-rancher
docker·zookeeper·jenkins·rancher·xxljob
鬼钺魂2 年前
集成xxljob项目如何迁移到K8S
java·kubernetes·xxljob
way_more2 年前
XXL-Job详解(一):组件架构
java·spring boot·spring·xxljob
软件科学研究院2 年前
xxl-job项目集成实战,全自动项目集成,可以直接使用到项目中
定时器·xxl-job·xxljob·分布式任务·xxl job·xxl job项目实战
爪蛙毁一生3 年前
java面试题-微服务面试题
java·开发语言·微服务·面试·xxljob