文章目录
前言
在使用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指定方法手动执行一次,查看效果。
