接口改造
背景
现有旧订单接口 orderDetail
,该接口会返回全量节点,部分节点不会使用因此造成了冗余,给数据库造成了较大压力,因此改造新接口 basicOrderDetail(xxx)
支持传入需要赋值的节点,方便赋值。
有如下改造方案:
- 现有多个其他部分接口调用当前
orderDetail
接口获取订单详情,并且再调用订单详情的getter()
方法,统计所有调用订单接口的方法,以及这些方法内部调用订单详情getter()
方法; - 将旧接口替换为新接口,并传入需要赋值的节点。
难点
难点在于调用 getter()
方法的地方太多,无法手动一个个统计,工作量巨大。
解决方案
新接口如何设计?
// 调用旧接口伪代码
Response resp = orderDetail(request.orderId(), "");
// Response 中返回了全量节点值// 新接口设计
// 1.各服务自定义封装 Request,并设置需要赋值的节点信息
class NewRequest {int orderId;List<ItemFlag> flags; // ItemFlag 为枚举类,对应 Response 中各字段信息
}
// 2.服务调用时构造 Request,然后发起请求,这样依赖就同意了调用格式,各服务以统一形式调用新接口,同时可以设置需要赋值的节点
NewResponse resp = basicOrderDetail(new NewRequest(1, Arrays.asList(ItemFlag.xxx, ItemFlag.xxx)));
其他服务中获取字段信息 getter()
方法如何识别替换?
这是一类对方法增强的功能,所以很自然联想到动态代理,SpringBoot 中可以通过 注解+AOP切面实现。
方案一:注解+AOP
- 在不修改原代码逻辑的基础上,新增自定义注解 @ItemRecord;
- 将注解标注在获取
OrderDetail
结果的方法上; - 定义对应切面,以
@Pointcut("@annotation(xxx.xxx.xxx.ItemRecord)")
为切点,并定义切点的AfterReturning
通知,这样在运行时就能获取到方法返回的结果中哪些字段非空,但是无法确定各接口实际需要的节点值。
方案二:编译时注解+Processor
- 定义两个编译时注解,一个用于获取 OrderDetail 类型变量,并统计获取代码中
getter()
方法; - 再定义一个编译时注解,用于替换 OrderDetail 的获取方法,替换为 BasicOrderDetail 获取方法。
但是这种方式解析语法树过于麻烦,实行起来复杂。
方案三:Cglib 动态代理
初步思路:
- 对原始接口返回的对象 OrderDetail 创建动态代理对象,每当调用该对象的
getter()
方法时,触发增强后的getter()
方法,记录下对应的执行线路、当前方法调用所在的外层方法名、该服务方法需要getter()
的字段信息,记录上述信息存储到内存 HashMap 中; - 在第一步操作记录完毕后,针对调用老接口
orderDetail
的服务对象创建动态代理对象,之后再调用orderDetail
老接口时,方法内部会代替调用增强后的代理对象执行增强后的方法,方法内只针对部分目标属性赋值,避免了全量赋值。
复现 Demo:
https://gitee.com/loserii/proxy_demo
重点步骤:
- 创建 App 对象的动态代理类,收集
getter()
方法所需的属性,并存储 Map。
-
创建非全量新接口,修改老接口逻辑,老接口执行时判断代理类是否为空,不为空调用代理方法,否则调用旧方法。新接口构造代理方法所需参数,调用代理方法。
-
代理方法会根据请求参数中枚举的信息,针对指定字段赋值。
结果
用镜像集群测试,接口返回数据正确,同时调用新接口,性能提升。
实现无需手动统计和替换自动完成接口替换。