Feign在实际项目中使用详解

Feign在实际项目中使用详解

  • 简介
  • 一 Feign客户端应该如何提供?
  • 二 Feign调用的接口要不要进行包装?
    • 2.1.问题描述
    • 2.2.问题解决
  • 三 Feign如何抓取业务生产端的业务异常?
    • 3.1.分析
    • 3.2.Feign捕获不到异常
    • 3.3.异常被额外封装
    • 3.4.解决方案
  • 案例源码

简介

我们在平时学习中简单知道调用feign接口或者做服务降级;但是在企业级项目中使用feign时会面临以下几个问题:

  1. Feign客户端应该如何提供?
  2. Feign调用的接口要不要进行包装?
  3. Feign如何抓取业务生产端的业务异常?

一 Feign客户端应该如何提供?

feign接口到底改如何对外提供?
分析:

  1. 消费者端需要引用到这些feign接口,那么feign接口直接写在消费者项目中的话,那如果另外一个也需要feign接口那是不是又得写一遍!自然而然的就会考虑到将feign接口独立出来。谁需要feign接口谁添加相应的依赖即可。

  2. feign接口中包含实体对象。那这些实体一般情况下我们都是在provider中,通过feign接口改造时我们需要将controller中用到的实体类进行提取。可以进行如下两种方式处理
    方式一将实体类提取出来放在独立模块中,provider和feign接口分别依赖实体类模块;
    方式二将实体类放在feign接口的模块中,provider依赖这个feign模块;
    项目中按照方式一来处理的情况比较多,这样不会造成依赖到不需要使用的代码;

    在这里插入图片描述

二 Feign调用的接口要不要进行包装?

2.1.问题描述

平前后端分离项目中,后端给前端返回接口数据时一般会统一返回格式;我们的Controller基本上会是这样的:

    @GetMapping("getTest")public Result<TestVO> getTest() {TestVO testVO = new TestVO("1", "测试标题", "无内容", "小明");return Result.success(testVO);

而Feign的接口定义需要跟实现类保持一致;

在这里插入图片描述
所以我们在使用这个方法的feign接口时,情况是这样的。

    @GetMapping("getContent")public Result<String> getContent() {String content=null;Result<TestVO> test = commentRestApi.getTest();if (test.isSuccess()) {TestVO data = test.getData();content = data.getContent();}else {throw new  RuntimeException(test.getMessage());}return Result.success(content);}

这里要先获取到​​Result​​​包装类,再通过判断返回结果解成具体的​​TestVO ​​对象,很明显这段代码有两个问题:

  • 每个Controller接口都需要手动使用Result.success对结果进行包
  • Feign调用时又需要从包装类解装成需要的实体对象

那项目中的接口有很多很多个,不断的做这种操作是不是太鸡肋了!!!无疑是增加了不必要的开发负担。

2.2.问题解决

优化的目标也很明确:​

  • 当我们通过Feign调用时,直接获取到实体对象,不需要额外的解装。
  • 前端通过网关直接调用时,返回统一的包装体。

这里我们可以借助​​ResponseBodyAdvice​​来实现,通过对Controller返回体进行增强,如果识别到是Feign的调用就直接返回对象,否则给我们加上统一包装结构。(SpringBoot统一封装controller层返回的结果)

新的问题: 如何识别出是Feign的调用还是网关直接调用呢?

基于自定义注解实现和基于Feign拦截器实现。

  • ​基于自定义注解实现​

    自定义一个注解,比如@ResponseNotIntercept​​,给Feign的接口标注上此注解,这样在使用ResponseBodyAdvice匹配时可以通过此注解进行匹配。
    在这里插入图片描述
    不过这种方法有个弊端,就是前端和feign没法公用,如一个接口​​user/get/{id}​​既可以通过feign调用也可以通过网关直接调用,采用这种方法就需要写2个不同路径的接口。

  • ​基于Feign拦截器实现​

    对于Feign的调用,在Feign拦截器上加上特殊标识,在转换对象时如果发现是feign调用就直接返回对象。

在这里插入图片描述
在这里插入图片描述

第二种方式具体实现步骤:

  1. 在feign拦截器中给feign请求添加特定请求头​​T_REQUEST_ID

/*** @ClassName: OpenFeignConfig Feign拦截器* @Description: 对于Feign的调用,在请求头中加上特殊标识* @Author: wang xiao le* @Date: 2023/08/25 23:13**/
@ConditionalOnClass(Feign.class)
@Configuration
public class OpenFeignConfig implements RequestInterceptor {/*** Feign请求唯一标识*/public static final String T_REQUEST_ID = "T_REQUEST_ID";/*** get请求标头** @param request 请求* @return {@link Map }<{@link String }, {@link String }>* @Author wxl* @Date 2023-08-27**/private Map<String, String> getRequestHeaders(HttpServletRequest request) {Map<String, String> map = new HashMap<>(16);Enumeration headerNames = request.getHeaderNames();while (headerNames.hasMoreElements()) {String key = (String) headerNames.nextElement();String value = request.getHeader(key);map.put(key, value);}return map;}@Overridepublic void apply(RequestTemplate requestTemplate) {ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if (null != attributes) {HttpServletRequest request = attributes.getRequest();Map<String, String> headers = getRequestHeaders(request);// 传递所有请求头,防止部分丢失for (Map.Entry<String, String> entry : headers.entrySet()) {requestTemplate.header(entry.getKey(), entry.getValue());}// 微服务之间传递的唯一标识,区分大小写所以通过httpServletRequest获取if (request.getHeader(T_REQUEST_ID) == null) {String sid = String.valueOf(UUID.randomUUID());requestTemplate.header(T_REQUEST_ID, sid);}}}}
  1. 自定义CommonResponseResult并实现ResponseBodyAdvice​​
/*** 如果引入了swagger或knife4j的文档生成组件,这里需要仅扫描自己项目的包,否则文档无法正常生成** @RestControllerAdvice(basePackages = "com.wxl52d41")* @ClassName: CommonResponseResult* @Description: controller返回结果统一封装* @Author wxl* @Date 2023-08-27* @Version 1.0.0**/
@RestControllerAdvice
public class CommonResponseResult implements ResponseBodyAdvice<Object> {/*** 支持注解@ResponseNotIntercept,使某些方法无需使用Result封装** @param returnType    返回类型* @param converterType 选择的转换器类型* @return true 时会执行beforeBodyWrite方法,false时直接返回给前端*/@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {if (returnType.getDeclaringClass().isAnnotationPresent(ResponseNotIntercept.class)) {//若在类中加了@ResponseNotIntercept 则该类中的方法不用做统一的拦截return false;}if (returnType.getMethod().isAnnotationPresent(ResponseNotIntercept.class)) {//若方法上加了@ResponseNotIntercept 则该方法不用做统一的拦截return false;}return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,Class<? extends HttpMessageConverter<?>> selectedConverterType,ServerHttpRequest request, ServerHttpResponse response) {if (request.getHeaders().containsKey(OpenFeignConfig.T_REQUEST_ID)) {//Feign请求时通过拦截器设置请求头,如果是Feign请求则直接返回实体对象return body;}if (body instanceof Result) {// 提供一定的灵活度,如果body已经被包装了,就不进行包装return body;}if (body instanceof String) {//解决返回值为字符串时,不能正常包装return JSON.toJSONString(Result.success(body));}return Result.success(body);}
}
  1. 修改provider后端接口返回对象以及feign接口
    如果为Feign请求,则不做转换,否则通过Result进行包装。
    /*** 对象返回值测试,是否能正常封装返回体*/@GetMapping("getOne")public TestVO getOne() {TestVO testVO = new TestVO("1", "测试标题", "无内容", "小明");return testVO;}

在这里插入图片描述

  1. 修改consumer模块中feign调用逻辑
    不需要在接口上返回封装体ResultData,经由ResponseBodyAdvice实现自动增强。
   @GetMapping("getOne")public TestVO getOne() {TestVO one = commentRestApi.getOne();return one;}
  1. 测试
    在消费者端调用。发现控制台中调用feign接口返回的方法并没有被统一封装。
    在这里插入图片描述

直接通过postman调用provider层方法。发现方法被统一封装了。

在这里插入图片描述

在​正常情况下​达到了我们优化目标,通过Feign调用直接返回实体对象,通过网关调用返回统一包装体。看上去很完美,但是实际很糟糕,这又导致了第三个问题,Feign如何处理异常?

三 Feign如何抓取业务生产端的业务异常?

3.1.分析

生产者对于提供的接口方法会进行业务规则校验,对于不符合业务规则的调用请求会抛出业务异常​​BusinessException​​,而正常情况下项目上会有个全局异常处理器,他会捕获业务异常BusinessException,并将其封装成统一包装体返回给调用方,现在让我们来模拟这种业务场景:

  1. 生产者抛出业务异常
    模拟业务中名称为空
    /*** 对象返回值测试,是否能正常封装返回体*/@GetMapping("getOne")public TestVO getOne() {TestVO testVO = new TestVO("1", "测试标题", "无内容", "小明");if (true) {throw new BusinessException(ResultEnum.VALIDATE_FAILED.getCode(), "名称为空");}return testVO;}
  1. 全局异常拦截器捕获业务异常
   /*** 捕获 自定 异常*/@ExceptionHandler({BusinessException.class}public Result<?> handleBusinessException(BusinessException ex) {log.error(ex.getMessage(), ex);return Result.failed(ex.getCode(),ex.getMessage());}
  1. 消费者端调用异常的feign接口
    @ResourceCommentRestApi commentRestApi;@GetMapping("getOne")public TestVO getOne() {TestVO one = commentRestApi.getOne();System.out.println("one = " + one);return one;}

3.2.Feign捕获不到异常

  1. 观察结果
    调用consumer中getOne()方法发现返回的信息中并没有异常,data中对象字段全部设置为null,如下:
    在这里插入图片描述
    查看provider端日志确实抛出了自定义异常:
    在这里插入图片描述
    将Feign的日志级别设置为FULL查看返回结果:
    @BeanLogger.Level feginLoggerLevel(){return Logger.Level.FULL;}

在这里插入图片描述
通过日志可以看到Feign其实获取到了全局异常处理器转换后的统一对象Result,并且响应码为200,正常响应。而消费者接受对象为TestVO,属性无法转换,全部当作NULL值处理。

很显然,这不符合我们正常业务逻辑,我们应该要直接返回生产者抛出的异常,​那如何处理呢?​

很简单,我们只需要给全局异常拦截器中​业务异常设置一个非200的响应码​即可,如:
在这里插入图片描述

    /*** 捕获 自定 异常*/@ExceptionHandler({BusinessException.class})@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Result<?> handleBusinessException(BusinessException ex) {log.error(ex.getMessage(), ex);return Result.failed(ex.getCode(),ex.getMessage());}

这样消费者就可以正常捕获到生产者抛出的业务异常,如下图所示:在这里插入图片描述

3.3.异常被额外封装

虽然能获取到异常,但是Feign捕获到异常后又在业务异常的基础上再进行了一次封装。

原因是​当feign调用结果为非200的响应码时就触发了Feign的异常解析,Feign的异常解析器会将其包装成FeignException,即在我们业务异常的基础上再包装一次​。

可以在​​feign.codec.ErrorDecoder#decode()​​方法上打上断点观察执行结果,如下:
在这里插入图片描述
很显然,这个包装后的异常我们并不需要,我们应该直接将捕获到的生产者的业务异常直接抛给前端,那这又该如何解决呢?

3.4.解决方案

很简单,​我们只需要重写Feign的异常解析器,重新实现decode逻辑,返回正常的BusinessException即可,而后全局异常拦截器又会捕获BusinessException!​(感觉有点无限套娃的感觉)

代码如下:

  1. 重写Feign异常解析器
/*** @ClassName: OpenFeignErrorDecoder* @Description: 解决Feign的异常包装,统一返回结果* @Author wxl* @Date 2023-08-26* @Version 1.0.0**/
@Configuration
public class OpenFeignErrorDecoder implements ErrorDecoder {/*** Feign异常解析** @param methodKey 方法名* @param response  响应体* @return {@link Exception }* @Author wxl* @Date 2023-08-26**/@SneakyThrows@Overridepublic Exception decode(String methodKey, Response response) {//获取数据String body = Util.toString(response.body().asReader(Charset.defaultCharset()));Result<?> result = JSON.parseObject(body, Result.class);if (!result.isSuccess()) {return new BusinessException(result.getStatus(), result.getMessage());}return new BusinessException(500, "Feign client 调用异常");}}
  1. 再次调用
    provider层抛出的异常信息能够被consumer层捕获,并通过自定义的异常解析器处理成自定义异常,不再被默认的feign异常包装;抛出的自定义异常被统一返回封装处理。在这里插入图片描述在这里插入图片描述
    在这里插入图片描述

案例源码

案例源码传送带

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/1620599.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

<C++> STL_list

1.list的介绍 list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中通过指针指向 其前一个元素和后一个元素。list与…

RabbitMQ+springboot用延迟插件实现延迟消息的发送

延迟队列&#xff1a;其实就是死信队列中消息过期的特殊情况 延迟队列应用场景&#xff1a; 可以用死信队列来实现&#xff0c;不过死信队列要等上一个消息消费成功&#xff0c;才会进行下一个消息的消费&#xff0c;这时候就需要用到延迟插件了&#xff0c;不过要线在docker上…

飞桨中的李宏毅课程中的第一个项目——PM2.5的预测

所谓的激活函数&#xff0c;就是李宏毅老师讲到的sigmoid函数 和 hard sigmoid函数 &#xff0c;ReLU函数那些 现在一点点慢慢探索&#xff0c;会成为日后想都做不到的经历&#xff0c;当你啥也不会的时候&#xff0c;才是慢慢享受探索的过程。 有一说一&#xff0c;用chatGP…

Linux elementary-ubuntu 使用笔记

Linux elementary-ubuntu 使用笔记 有很多功能对应多个命令&#xff0c;在下只记录对于本人而言最容易记住的 有些电脑安装双系统时&#xff0c;会出现进入不了界面的情况&#xff0c;一般是由于独显的影响&#xff0c;所以需要在启动命令中添加屏蔽独显命令 acpi_osiLinux no…

Spring Boot进阶(59):【实战教程】使用Spring Boot集成InfluxDB时序数据库,打造高效数据监控系统!

1. 前言&#x1f525; 前几期我们陆续讲解了Mysql、Redis、PostgreSQL、MongoDB等数据库集成及使用案例&#xff0c;接下来&#xff0c;bug菌继续给同学们介绍一种特殊的数据库&#xff0c;到底是什么数据库呢&#xff1f;它就是InfluxDB(时序数据库)&#xff0c;InfluxDB是一款…

带你启用window10专业版系统自带的远程桌面

启用window10专业版系统自带的远程桌面 文章目录 启用window10专业版系统自带的远程桌面前言1.找到远程桌面的开关2. 找到“应用”项目3. 打开需要远程操作的电脑远程桌面功能 总结 前言 Windows操作系统作为应用最广泛的个人电脑操作系统&#xff0c;在我们身边几乎随处可见。…

网站安全检测、漏洞检测、dedecms本地文件包含

可以在“站长工具”或“360网站安全检测”进行网站安全检测 以下是个人遇到的网站安全检测问题&#xff1a; 这是修改后的检测返回的信息 之前是存在高危漏洞1个页面 【高危】dedecms本地文件包含 找到Include/payment/alipay.php Include/payment/yeepay.php 从下载补丁的地…

网站漏洞检测服务 URL跳转漏洞的检测与修复

网站渗透测试是指在没有获得网站源代码以及服务器的情况下&#xff0c;模拟入侵者的攻击手法对网站进行漏洞检测&#xff0c;以及渗透测试&#xff0c;可以很好的对网站安全进行全面的安全检测&#xff0c;把安全做到最大化。在挖掘网站漏洞的时候我们发现很多网站存在域名跳转…

Web自动化漏洞检测工具:Xray

** Web自动化漏洞检测工具&#xff1a;Xray ** 下载地址: https : //github.com/chaitin/xray/releases Xray是一款功能强大的安全评估工具&#xff0c;有以下特性&#xff1a; 1.检测速度快。发包速度快; 漏洞检测算法高效。 支持范围广。大至 OWASP Top 10 通用漏洞检测&…

php网站漏洞检测对sql注入漏洞防护

近日&#xff0c;我们SINE安全对metinfo进行网站安全检测发现&#xff0c;metinfo米拓建站系统存在高危的sql注入漏洞&#xff0c;攻击者可以利用该漏洞对网站的代码进行sql注入攻击&#xff0c;伪造恶意的sql非法语句&#xff0c;对网站的数据库&#xff0c;以及后端服务器进行…

网站漏洞检测

除了可以用nagios、nessus、cacti检测网站漏洞外&#xff0c;我们还可以利用webscan.360.cn在线检测网站漏洞&#xff0c;检测出漏洞外还提供详细的解决方案&#xff0c;实在值得一试。因为webscan.360.cn要提供ftp等账户信息来验证用户&#xff0c;然后再检测漏洞&#xff0c;…

漏洞挖掘 | 通用型漏洞挖掘思路技巧

搜索公众号&#xff1a;白帽子左一&#xff0c;每天更新技术干货&#xff01;作者&#xff1a;ajie 转自地址&#xff1a;https://xz.aliyun.com/t/10539 0x01 前言 大概是在上半年提交了某个CMS的命令执行漏洞&#xff0c;现在过了那么久&#xff0c;也想通这次现挖掘通用型漏…

WEB漏洞挖掘——思路指南

本篇主要记录了WEB漏洞挖掘学习过程中的信息收集部分&#xff0c;web渗透最重要的便是信息收集&#xff0c;希望以下内容能够给予同在漏洞挖掘学习中的小伙伴一些帮助&#xff0c;若有不足之处可以告诉我&#xff0c;大家一起努力进步。大佬路过也请多多指点&#xff01; 目录…

常见的Web漏洞——XSS

目录 XSS简介 XSS原理及分类 反射型XSS 存储型XSS 基于DOM的XSS XSSer的使用 至少有一个的参数&#xff1a; 可选的参数&#xff1a; 检查选项 选择攻击向量 绕过防火墙选项 绕过器选项 特殊技术 最后注入选项 特殊最后注入选项 报告导出 XSSer演示 BeEF-XSS…

【burpsuite安全练兵场-服务端6】信息泄露漏洞-5个实验(全)

前言&#xff1a; 介绍&#xff1a; 博主&#xff1a;网络安全领域狂热爱好者&#xff08;承诺在CSDN永久无偿分享文章&#xff09;。 殊荣&#xff1a;CSDN网络安全领域优质创作者&#xff0c;2022年双十一业务安全保卫战-某厂第一名&#xff0c;某厂特邀数字业务安全研究员&…

XSS攻击是怎么回事?记录一下

title: XSS攻击 date: 2023-08-27 19:15:57 tags: [XSS, 网络安全] categories: 网络安全 今天学习了一个网络攻击的手段&#xff0c;XSS攻击技术&#xff0c;大家自建网站的朋友&#xff0c;记得看看是否有此漏洞。 &#x1f388; XSS 攻击 全称跨站脚本攻击 Cross Site Sc…

基于JAVA_Swing的图形化GUI漏洞扫描工具开发的编程思维

文章目录 前言一、工具外观整体设计二、漏洞检测类模块总结2022年6月27日更新此篇 前言 CSDN博客有很长一段时间没有更新&#xff0c;漏洞复现的文章也没有经常发了&#xff0c;原因是因为我这段时间在做一件大事&#xff0c;并且这件大事已经做完啦&#xff01;&#xff01;&…

Xray工具~(Web自动化漏洞检测)

Web自动化漏洞检测~Xray工具 下载地址: GitHub&#xff1a;https : //github.com/chaitin/xray/releases Xray 是一款功能强大的安全评估工具,主要特性有: 检测速度快。发包速度快; 漏洞检测算法高效。 支持范围广。大至 OWASP Top 10 通用漏洞检测&#xff0c;小至各种 CMS 框…

Arp欺骗截cookies入侵“中国黑客”网站(图)

放假回家&#xff0c;暂时没有自己的电脑&#xff0c;只能跑网吧。打开ie就看见网吧主页。。。这个网吧是我家附近作的最好的。在这里玩过几次&#xff0c;也提醒过老板一些漏洞。不过漏洞天天更新么~ 是个2级域名111.lzbiz.com。换成www&#xff0c;竟然是个黑客网站。。。名…

目标URL存在跨站漏洞

URL存在跨站漏洞 &#x1f696;漏洞复现&#x1f696;漏洞描述&#x1f696;解决方法&#x1f696;对于开发&#x1f696;对于安全操作&#x1f696;对于质量保证 &#x1f696;漏洞复现 记录学习中遇到的问题 该页面存在反射型XSS payload citynull&accountnull&pa…