注解中其实不能使用类型为函数的成员
你是否也曾想在注解中使用函数变量,像这样
public @interface HeadSpecialSet {
BiConsumer<ReqHead, String> setter();
String param();
}
而事实上会报编译错误。
需求
在我工作的过程中,不止一次希望java能支持这样的功能,之前碰到该问题,都没有使用函数的方式去解决该问题,而是使用反射,线程变量之类的方式解决的该问题。下面我们看看一个例子,看是否能使用函数相关的功能解决一些实际问题。
这里是一个分布式情况下通讯的需求场景,A系统会调用BCDE..系统:
A系统(本系统) -> B系统
-> C系统
-> D系统
1、RPC框架是架构组写的,提供jar包,子系统引用RPC框架,调用RpcApi.send(t)即可。
public class RpcApi {
public static <T> String send(T body) {
Request<T> objectRequest = generateReq(body);
return sendNative(objectRequest);
}
private static <T> Request<T> generateReq(T t) {
Request<T> req = new Request<>();
req.body = t;
// head都是由架构组写的,passwd,username都是通过认证体系获取,ip为执行机ip
req.head = new ReqHead();
req.head.setIp("本地IP");
req.head.setPassword("登录用户密码");
req.head.setUsername("登录用户名");
return req;
}
private static <T> String sendNative(Request<?> request) {
return "对方系统返回结果";
}
}
public class Request<T> {
public ReqHead head;
public T body;
}
2、给B,C,D系统的req, resp对象由业务系统发布,如下是a系统查B系统的资源的入参结构
public class QueryBResource {
// 按条件查询
Date conditionTimeBegin;
}
3、A本可以愉快地像下面代码一样调用B,C,D系统
SendApi.send(new QueryBResource());
4、忽然一天,需求上线E系统,且E系统不支持统一认证,并给A系统一个用户名eadmin,要求访问E系统的时候统使用eadmin替换Head里的当前用户发送到E系统,假设E系统接收的参数为QueryEResource
解决方案
5、业务系统调用api时,只能封装body,无法封装Request<T>,如何让业务系统代码能通知到api代码执行的时候去修改Head里的值呢,我的第一首选肯定是使用注解+函数,但上面已经看到注解中不能使用函数
6、注解虽然不支持函数,但支持Class,Enum等高级类型,这里先使用Class与method字面量注解完成需求内容。需要架构组提供HeadSpecialSet 注解,并在api代码中获取body 的注解内容,然后使用反射完成值的重赋予。如下,在QueryResource(Request body)上使用注解通知api模块,需要将Head里的username设置为固定值,api模块代码解析注解,并使用反射设置req.head
@HeadSpecialSet(clazz = Request.class, setMethod = "setUsername", param = "eadmin ")
public class QueryEResource {
// 按条件查询
Date conditionTimeBegin;
}
private static <T> Request<T> generateReq(T t) {
Request<T> req = new Request<>();
req.body = t;
// head都是由架构组写的,passwd,username都是通过认证体系获取,ip为执行机ip
req.head = new ReqHead();
/*req.head.setIp("本地IP");
req.head.setPassword("登录用户密码");
req.head.setUsername("登录用户名");*/
// 反射对head属性进行设置
HeadSpecialSet setMethod = t.getClass().getAnnotation(HeadSpecialSet.class);
Method method = null;
try {
method = setMethod.clazz().getMethod(setMethod.setMethod(), String.class);
method.invoke(req.head, setMethod.param());
} catch (Exception e) {
}
return req;
}
7、这种方式我现在一般不使用了,因为反射不能检查编译错误。
8、但如何使用函数呢,这里只需要将函数与某一个变量建立映射关系即可以,可以使用map, 数组,enum,这里我喜欢使用枚举enum
// 加入枚举类
public enum BiConsumerEnum {
SET_IP(ReqHead::setIp),
SET_USERNAME(ReqHead::setUsername),
SET_PASSWD(ReqHead::setPassword);
BiConsumer<ReqHead, String> bi;
BiConsumerEnum(BiConsumer<ReqHead, String> bi) {
this.bi = bi;
}
}
// 业务端代码与框架端代码修改
public @interface HeadSpecialSet {
BiConsumerEnum setter();
String param();
}
public class SendApi {
......
private static <T> Request<T> generateReq(T t) {
Request<T> req = new Request<>();
req.body = t;
// head都是由架构组写的,passwd,username都是通过认证体系获取,ip为执行机ip
req.head = new ReqHead();
/*req.head.setIp("本地IP");
req.head.setPassword("登录用户密码");
req.head.setUsername("登录用户名");*/
HeadSpecialSet setMethod = t.getClass().getAnnotation(HeadSpecialSet.class);
setMethod.setter().bi.accept(req.head, setMethod.param());
return req;
}
}
@HeadSpecialSet(setter = BiConsumerEnum.SET_USERNAME, param = "eadmin")
public class QueryEResource {
// 按条件查询
Date conditionTimeBegin;
}
9、如果还想支持多个属性的设置,只需要把@HeadSpecialSet设置为可重复即可,具体可以学习一下@Repeat的注解
10、使用线程变量也是可以的,有兴趣的可以思考看看