撸了一个 Feign 增强包 V2.0 升级版

前言
大概在两年前我写过一篇 撸了一个 Feign 增强包,当时准备是利用 SpringBoot + K8s 构建应用,这个库可以类似于 SpringCloud 那样结合 SpringBoot 使用声明式接口来达到服务间通讯的目的。
但后期由于技术栈发生变化(改为 Go),导致该项目只实现了基本需求后就搁置了。
巧合的时最近内部有部分项目又计划采用 SpringBoot + K8s 开发,于是便着手继续维护;现已经内部迭代了几个版本比较稳定了,也增加了一些实用功能,在此分享给大家。
https://github.com/crossoverJie/feign-plus
首先是新增了一些 features:
- 更加统一的 API。
- 统一的请求、响应、异常日志记录。
- 自定义拦截器。
- Metric 支持。
- 异常传递。
示例
结合上面提到的一些特性做一些简单介绍,统一的 API 主要是在使用层面:
在上一个版本中声明接口如下:
@FeignPlusClient(name = "github", url = "${github.url}")
public interface Github {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<GitHubRes> contributors(@Param("owner") String owner, @Param("repo") String repo);
}
其中的 @RequestLine 等注解都是使用 feign 包所提供的。
这次更新后改为如下方式:
@RequestMapping("/v1/demo")
@FeignPlusClient(name = "demo", url = "${feign.demo.url}", port = "${feign.demo.port}")
public interface DemoApi {
@GetMapping("/id")
String sayHello(@RequestParam(value = "id") Long id);
@GetMapping("/id/{id}")
String id(@PathVariable(value = "id") Long id);
@PostMapping("/create")
Order create(@RequestBody OrderCreateReq req);
@GetMapping("/query")
Order query(@SpringQueryMap OrderQueryDTO dto);
}
熟悉的味道,基本都是 Spring 自带的注解,这样在使用上学习成本更低,同时与项目中原本的接口写法保持一致。
@SpringQueryMap(top.crossoverjie.feign.plus.contract.SpringQueryMap) 是由 feign-plus 提供,其实就是从 SpringCloud 中 copy 过来的。
我这里写了两个 demo 来模拟调用:

provider: 作为服务提供者提供了一系列接口供消费方调用,并对外提供了一个 api 模块。

demo:作为服务消费者依赖 provider-api 模块,根据其中声明的接口进行远程调用。

配置文件:
server:
port: 8181
feign:
demo:
url : http://127.0.0.1
port: 8080
logging:
level:
top:
crossoverjie: debug
management:
endpoints:
web:
base-path: /actuator
exposure:
include: '*'
metrics:
distribution:
percentiles:
all: 0.5,0.75,0.95,0.99
export:
prometheus:
enabled: true
step: 1m
spring:
application:
name: demo
当我们访问 http://127.0.0.1:8181/hello/2 接口时从控制台可以看到调用结果:

日志记录
从上图中可以看出 feign-plus 会用 debug 记录请求/响应结果,如果需要打印出来时需要将该包下的日志级别调整为 debug:
logging:
level:
top:
crossoverjie: debug
由于内置了拦截器,也可以自己继承 top.crossoverjie.feign.plus.log.DefaultLogInterceptor 来实现自己的日志拦截记录,或者其他业务逻辑。
@Component
@Slf4j
public class CustomFeignInterceptor extends DefaultLogInterceptor {
@Override
public void request(String target, String url, String body) {
super.request(target, url, body);
log.info("request");
}
@Override
public void exception(String target, String url, FeignException feignException) {
super.exception(target, url, feignException);
}
@Override
public void response(String target, String url, Object response) {
super.response(target, url, response);
log.info("response");
}
}
监控 metric
feign-plus 会自行记录每个接口之间的调用耗时、异常等情况。

访问 http://127.0.0.1:8181/actuator/prometheus 会看到相关埋点信息,通过 feign_call* 的 key 可以自行在 Grafana 配置相关面板,类似于下图:

异常传递
rpc(远程调用)要使用起来真的类似于本地调用,异常传递必不可少。
// provider
public Order query(OrderQueryDTO dto) {
log.info("dto = {}", dto);
if (dto.getId().equals("1")) {
throw new DemoException("provider test exception");
}
return new Order(dto.getId());
}
// consumer
try {
demoApi.query(new OrderQueryDTO(id, "zhangsan"));
} catch (DemoException e) {
log.error("feignCall:{}, sourceApp:[{}], sourceStackTrace:{}", e.getMessage(), e.getAppName(), e.getDebugStackTrace(), e);
}
比如 provider 中抛出了一个自定义的异常,在 consumer 中可以通过 try/catch 捕获到该异常。
为了在 feign-plus 中实现该功能需要几个步骤:
- 自定义一个通用异常。
- 服务提供方需要实现一个全局拦截器,当发生异常时统一对外响应数据。
- 服务消费方需要自定义一个异常解码器的 bean。
这里我在 provider 中自定义了一个 DemoException:

通常这个类应该定义在公司内部的通用包中,这里为了演示方便。
接着定义了一个 HttpStatus 的类用于统一对外响应。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class HttpStatus {
private String appName;
private int code;
private String message;
private String debugStackTrace;
}
这个也应该放在通用包中。
然后在 provider 中定义全局异常处理:

当出现异常时便会返回一个 http_code=500 的数据:

到这一步又会出现一个引战话题:HTTP 接口返回到底是全部返回 200 然后通过 code 来来判断,还是参考 http_code 进行返回?
这里不做过多讨论,具体可以参考耗子叔的文章:
“一把梭:REST API 全用 POST”
feign-plus 默认采用的 http_code !=200 才会认为发生了异常。
而这里的 http_status 也是参考了 Google 的 api 设计:

具体可以参考这个链接:
https://cloud.google.com/apis/design/errors#propagating_errors
然后定义一个异常解析器:
@Configuration
public class FeignExceptionConfig {
@Bean
public FeignErrorDecoder feignExceptionDecoder() {
return (methodName, response, e) -> {
HttpStatus status = JSONUtil.toBean(response, HttpStatus.class);
return new DemoException(status.getAppName(), status.getCode(), status.getMessage(), status.getDebugStackTrace());
};
}
}
通常这块代码也是放在基础包中。
这样当服务提供方抛出异常时,消费者便能成功拿到该异常:

实现原理
实现原理其实也比较简单,了解 rpc 原理的话应该会知道,服务提供者返回的异常调用方是不可能接收到的,这和是否由一种语言实现也没关系。
毕竟两个进程之间的栈是完全不同的,不在一台服务器上,甚至都不在一个地区。
所以 provider 抛出异常后,消费者只能拿到一串报文,我们只能根据这段报文解析出其中的异常信息,然后再重新创建一个内部自定义的异常(比如这里的 DemoException),也就是我们自定义异常解析器所干的事情。
下图就是这个异常传递的大致流程:

code message 模式
由于 feign-plus 默认是采用 http_code != 200 的方式来抛出异常的,所以采用 http_code=200, code message 的方式响应数据将不会传递异常,依然会任务是一次正常调用。
不过基于该模式传递异常也是可以实现的,但没法做到统一,比如有些团队习惯 code !=0 表示异常,甚至字段都不是 code;再或者异常信息有些是放在 message 或 msg 字段中。
每个团队、个人习惯都不相同,所以没法抽象出一个标准,因此也就没做相关适配。
这也印证了使用国际标准所带来的好处。
限于篇幅,如果有相关需求的朋友也可以在评论区沟通,实现上会比现在稍微复杂一点点。
总结
项目源码:
https://github.com/crossoverJie/feign-plus
基于2022年云原生这个背景,当然更推荐大家使用 gRPC 来做服务间通信,这样也不需要维护类似于这样的库了。
不过在一些调用第三方接口而对方也没有提供 SDK 时,这个库也有一定用武之地,虽然使用原生 feign 也能达到相同目的,但使用该库可以使得与 Spring 开发体验一致,同时内置了日志、metric 等功能,避免了重复开发。
你的点赞与分享是对我最大的支持
撸了一个 Feign 增强包 V2.0 升级版的更多相关文章
- 撸了一个 Feign 增强包
前言 最近准备将公司的一个核心业务系统用 Java 进行重构,大半年没写 Java ,JDK 都更新到 14 了,考虑到稳定性等问题最终还是选择的 JDK11. 在整体架构选型时,由于是一个全新的系统 ...
- 请设计实现一个商城系统开发v2.0【代码优化】
#!/usr/bin/env python 优化的部分:1.改用字典取键,来调用函数[原来是用if-else判断] [补充]:也可以用列表,按索引取,可以在列表最前面加一个“”任意元素,凑成一个.就和 ...
- 剪贴板增强---Kawvin增强剪贴板_V2.0
#Persistent SetWorkingDir,%A_ScriptDir% ;设置工作目录 #MaxThreadsPerHotkey ;最大热键数量 #NoEnv ;#Warn #SingleIn ...
- 从零開始制作H5应用(2)——V2.0版,多页单张图片滑动,透明过渡及交互指示
上一次.我们制作了我们第一个H5场景应用的V1.0版,这次我们趁热打铁.在上一版的基础上对层序进行改动和扩展. 任务 1.页面数量由3张增加至9张: 2.每张页面中放入一张全屏自适应的图片. 3.修复 ...
- 痞子衡嵌入式:MCUBootUtility v2.0来袭,i.MXRT1010哪里逃
-- 恩智浦半导体从2017年10月开始正式推出业内首款跨界处理器-i.MX RT系列,如今距离该系列第一款i.MXRT1050发布已过去近2年,i.MX RT系列在行业里应用越来越广泛,i.MX R ...
- 性能监视器PerfMon v2.0 是一个流氓的汉化版
最近在部署一台新设备时,由于懒得翻墙用google下载软件,由一次中了坑.百度搜索出来的这个<性能监视器 v2.0 汉化版>,安装了之后,设备会时不时自动弹出广告.反编译分析了一下,的确就 ...
- EasyPlayer RTSP播放器:一个适用于安防行业的工具利器(EasyPlayer Windows v2.0.17.0709)
本文转自EasyDarwin开源团队成员Sword的博客:http://blog.csdn.net/swordtwelve EasyPlayer(Windows) v2.0.17.0709版本又更新发 ...
- virtualbox安装增强包及配置共享文件夹
因为需要在host及虚拟机间传输数据,想使用共享文件夹.但是单独设置了共享文件夹后在centos里找不到共享文件夹,看了下要安装增强包.好吧,顺 便也解决下鼠标切换的问题,省的老是按右CTL切换 ...
- ArcGIS Runtime for Android开发教程V2.0(3)基础篇---Hello World Map
原文地址: ArcGIS Runtime for Android开发教程V2.0(3)基础篇---Hello World Map - ArcGIS_Mobile的专栏 - 博客频道 - CSDN.NE ...
随机推荐
- Mybaits 的优点?
1.基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任 何影响,SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理:提供 XML 标签,支持编写动态 SQL ...
- 学习Puppet(一)
puppet的入门 1.简介 puppet是一种采用C/S星状结构的linux.Unix平台的集中配置管理系统. puppet拥有自己的语言,可管理配置文件.用户.cron任务.软件包.系统服务等. ...
- SQL数据库之“TIMESTAMPDIFF(unit,datetime_expr1,datetime_expr2)”
一.介绍 样本:TIMESTAMPDIFF(unit,datetime_expr1,datetime_expr2) 解析:TIMESTAMPDIFF(格式,开始时间,结束时间) 二.参数解析 格式: ...
- kali Linux 渗透测试 | ettercap图形界面(ARP 欺骗 + DNS欺骗)
上次我们使用 arpspoof 工具在命令行中完成了 arp 欺骗实验,今天我们用另一种工具 ettercap 工具来实现.ettercap支持图形化操作,对新手非常友好,并且操作非常简单, ette ...
- 在linux环境下安装VMtools(成功)
想在主机和虚拟机之间互相复制文件吗? 想更加方便的联系主机和虚拟机吗? 就安装VMtools吧 其实,在linux下安装VMtools 是非常的简单,只要简单地几步就行了! 第一步:打开虚拟机,在左 ...
- 设计一个基于svg的涂鸦组件(一)
基于svg写了一个涂鸦组件,说项目之前先附上几张效果图: 项目地址:SVGraffiti 由于篇幅问题,本文先总体介绍一下项目的大概情况,重点介绍一下组件间的通信方式. 一.项目说明 该项目是基于we ...
- Android Studio安装问题
安装问题可以参考:https://blog.csdn.net/y74364/article/details/96121530 但是gradle安装缓慢,需要FQ.有加速器FQ的可以开加速器安装,没有的 ...
- IDEA中 Debug 调试工具(图文详解)
DEBUG调试工具 一. Debug 调试工具 1. Debug的作用 2. Debug的使用步骤 3. IDEA中Debug按钮详解 总结 参考博文:https://blog.csdn.net/qq ...
- 关于webpack,你想知道的都在这;
咱也标题党一回 哈哈哈 要使用webpack优化项目打包构建速度,首先得知道问题出在哪, 要知道问题出在哪,首先得知道webpack 打包的基本原理才能针对性的去做优化,下面首先了解webpack基本 ...
- numpy---(精简)
numpy get started 导入numpy库, 并查看版本 import numpy as np np.__version__ '1.14.3' # pyplot显示画图, 数据分析与可视化 ...