Spring Cloud Feign实战

概述

Feign是一种声明式、模板化的HTTP Client,目标是使编写Java HTTP Client变得更简单。Feign通过使用Jersey和CXF等工具实现一个HTTP Client,用于构建REST或SOAP的服务。Feign还支持用户基于常用的HTTP工具包(OkHTTP、HTTPComponents)实现自定义的HTTP Client。

Feign基于@EnableFeignClients注解的方式将HTTP请求模板化。Feign将HTTP请求参数写入Template,极大地简化HTTP请求。提供请求回放功能,使HTTP单元测试变得更加方便。

Feign应用一般依赖服务发现组件来实现远程接口调用,在并发要求不高的情况下可以作为RPC方案使用,实现服务之间的解耦。

整合Ribbon和Hystrix,从而不再需要显式地使用这两个组件。Feign还提供HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。Feign会完全代理HTTP的请求,只需要像调用方法一样调用它就可以完成服务请求。
Feign特性:

  1. 可插拔的注解支持,包括Feign注解和JAX-RS注解
  2. 支持可插拔的HTTP编码器和解码器
  3. 支持Hystrix和它的Fallback
  4. 支持Ribbon的负载均衡
  5. 支持HTTP请求和响应的压缩

常用注解
在这里插入图片描述

原理

OpenFeign使用动态代理来封装远程服务调用的过程
在这里插入图片描述
步骤 1 到 3 是在项目启动阶段加载完成的,第 4 步调用远程服务是发生在项目的运行阶段。

几个关键步骤:

  • 在项目启动阶段,OpenFeign 框架会发起一个主动的扫包流程,从指定的目录下扫描并加载所有被 @FeignClient 注解修饰的接口
  • OpenFeign 会针对每一个 FeignClient 接口生成一个动态代理对象,即FeignProxyService,在继承关系上属于 FeignClient 注解所修饰的接口的实例
  • 这个动态代理对象会被添加到 Spring 上下文中,并注入到对应的服务里,即LocalService 服务
  • LocalService 会发起底层方法调用。实际上这个方法调用会被 OpenFeign 生成的代理对象接管,由代理对象发起一个远程服务调用,并将调用的结果返回给LocalService。

OpenFeign 组件加载过程:

  • 项目加载:在项目启动阶段,EnableFeignClients 注解扮演启动开关角色,它使用 Spring 框架的 Import 注解导入 FeignClientsRegistrar 类,开始OpenFeign 组件的加载过程。
  • 扫包:FeignClientsRegistrar 负责 FeignClient 接口的加载,它会在指定的包路径下扫描所有的 FeignClients 类,并构造 FeignClientFactoryBean 对象来解析FeignClient 接口。
  • 解析 FeignClient 注解:FeignClientFactoryBean功能,解析FeignClient 接口中的请求路径和降级函数的配置信息;触发动态代理的构造过程。其中,动态代理构造是由更下一层的 ReflectiveFeign 完成的。
  • 构建动态代理对象:ReflectiveFeign 包含OpenFeign 动态代理的核心逻辑,它主要负责创建出 FeignClient 接口的动态代理对象。ReflectiveFeign 在这个过程中有两个重要任务,一个是解析 FeignClient 接口上各个方法级别的注解,将其中的远程接口URL、接口方法类型、各个请求参数等封装成元数据,并为每一个方法生成一个对应的 MethodHandler 类作为方法级别的代理;另一个重要任务是将这些MethodHandler 方法代理做进一步封装,通过 Java 标准的动态代理协议,构建一个实现 InvocationHandler 接口的动态代理对象,并将这个动态代理对象绑定到FeignClient 接口上。这样一来,所有发生在 FeignClient 接口上的调用,最终都会由它背后的动态代理对象来承接。

MethodHandler 的构建过程涉及到复杂的元数据解析,OpenFeign 组件将FeignClient 接口上的各种注解封装成元数据,并利用这些元数据把一个方法调用翻译成一个远程调用的 Request 请求。

元数据解析,依赖于 OpenFeign 组件中的Contract协议解析功能。Contract 是最顶层抽象接口,实现类如SpringMvcContract,专门用于解析 Spring MVC 标签。

实战

通过Feign上传MultipartFile

现有FS服务,有一Controller层接口:

@PostMapping(value = "/ossUploadPrivateFile")
public Response<UploadFileVO> ossUploadPrivateFile(@RequestPart(value = "file") MultipartFile multipartFile) {
}

微服务开发模式下,其他服务想要使用FS服务提供的接口,则FS服务需提供一个即jar包,即新增一个fs-client module,新增Feign接口:

@FeignClient(value = "fs-provider", configuration = FeignMultipartSupportConfig.class)
public interface RemoteFileService {
    /**
     * 上传文件
     */
    @PostMapping(value = "/fs/ossUploadFile")
    Response<UploadFileVO> upload(@RequestPart(value = "file") MultipartFile multipartFile);
}

其他服务在使用FS-client时,启动报错:

Type definition error: [simple type, class java.io.FileDescriptor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) 
(through reference chain: org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile[\"inputStream\"]->java.io.FileInputStream[\"fd\"])

添加如下依赖:

<dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form-spring</artifactId>
</dependency>

配置类:

@Configuration
public class FeignMultipartSupportConfig {
    @Bean
    @Primary
    @Scope("prototype")
    public Encoder multipartFormEncoder() {
        return new SpringFormEncoder();
    }
}

然后在RemoteFileService@FeignClient指定上述配置。

feign.FeignException: status 404 reading

报错日志:

status 404 reading RemotePaymentService#queryIsPay(String,String) 
feign.FeignException: status 404 reading RemotePaymentService#queryIsPay(String,String)
	at feign.FeignException.errorStatus(FeignException.java:78)
	at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:93)
	at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:149)
	at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:78)
	at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103)
	at com.aba.enduser.controller.UserBenefitController.getUnionUserBenefit(UserBenefitController.java)

enduser服务调用payment服务提供的Feign接口:

@FeignClient(name = "payment-provider", configuration = FeignConfig.class)
public interface RemotePaymentService {
    @RequestMapping(value = {"/pay/queryIsPay/{channel}/{userId}"}, method = {RequestMethod.GET})
    Boolean queryIsPay(@PathVariable(name = "channel") String channel, @PathVariable(value = "userId") String userId);
}

payment服务已先于enduser服务打包发布。打包会将payment-client,即RemotePaymentService所在的jar包部署到私服nexus,发布则是将payment-provider,将Feign接口对应的Controller层接口注册到Consul。enduser请求payment服务,不应该出现404报错的啊。

迷思,困惑,排查。。

好在有SkyWalking分布式调用链工具,拿到报错日志的TraceId,在ELK里搜索,发现点猫腻:
在这里插入图片描述
enduser服务请求payment时,未传参channel。payment提供的接口是/pay/queryIsPay/aa/bb,并没有提供接口/pay/queryIsPay//bb,或/pay/queryIsPay/aa/,或/pay/queryIsPay//

所以,理所当然报错404。

另外,在这个TraceId调用链里,再次看到熟悉的No message available,参考文末的链接。
在这里插入图片描述

反思

先看一下接口定义:

public @interface PathVariable {
	/**
	 * Whether the path variable is required.
	 * Defaults to true, leading to an exception being thrown if the path
	 * variable is missing in the incoming request. Switch this to false if
	 * you prefer a {@code null} or Java 8 {@code java.util.Optional} in this case.
	 * e.g. on a {@code ModelAttribute} method which serves for different requests.
	 */
	boolean required() default true;
}

上面提到的Feign接口,并没有显示标注required = true,因为是默认值。
在这里插入图片描述
请求参数缺失,为啥没有报错呢??

问题

反序列化

参考链接见文末,简单总结下:

  • 如果Controller层定义的接口:
@PostMapping(value = "/initialChannelPayGoodsList")
public Response<Boolean> initialChannelPayGoodsList(@RequestBody String channel) {
    return Response.success(Boolean.TRUE);
}
  • Feign里定义的接口:
@RequestMapping(value = "/pay/initialChannelPayGoodsList", method = {RequestMethod.POST})
Boolean initialChannelPayGoodsList(@RequestBody String channel);

两个地方的接口返回类型不一致,就会出现反序列化问题。

@RequestParam & @PathVariable

@RequestParam:用于将方法的参数与Web请求里传递的参数进行绑定。
@PathVariable:将方法中的参数绑定到请求URI中的模板变量上。可以通过@RequestMapping注解来指定URI的模板变量,然后使用@PathVariable注解将方法中的参数绑定到模板变量上。允许使用value或name属性来给参数取一个别名。

区别:对于一个请求:https://api.com/api/user/123?name=johnny@PathVariable可绑定到userId=123@RequestParam 则用于获取name=johnny

但是在微服务开发中,不建议使用@PathVariable,两个原因:

  • 404问题,上面已经提到。看到ELK里报错404,不易排查具体是哪个参数缺失。虽然可以借助于SkyWalking这类工具进行排查。但还是会花费一定时间。
  • 不利于统计。@RequestParam@PathVariable都可以实现业务开发编码需求,但coding应该只是程序员工作内容一小部分。在微服务开发模式下,通过SkyWalking可以得知服务健康度,接口性能,在哪个环节耗时最久。
    在这里插入图片描述
    但是对于通过@PathVariable方式定义的Controller层接口,则无能为力,不同的channeluserId会组合出无数种接口统计数据:
    在这里插入图片描述

参考

  • Feign反序列化MismatchedInputException:Cannot deserialize instance of Boolean out of START_OBJECT token
  • No message available问题解决

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

全志V3S嵌入式驱动开发(开发环境再升级)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们陆陆续续开发了差不多有10个驱动&#xff0c;涉及到网口、串口、音频和视频等几个方面。但是整个开发的效率还是比较低的。每次开发调试的…

Java中查看堆里的信息

文章目录 前言1 建议无脑的做一件事2 jmp命令3 导入 hprof 文件到Visual VM 中4 查看对象属性值 前言 日常工作中&#xff0c;我们可能会遇到这样的场景&#xff1a; java项目发生了OOM&#xff1b;想知道在某种场景下&#xff0c;堆里的信息&#xff0c;从而确认一些代码功能…

JavaCV音视频开发宝典:使用JavaCV读取海康平台或海康网络摄像头sdk回调视频TS码流并解析预览图像

《JavaCV音视频开发宝典》专栏目录导航 《JavaCV音视频开发宝典》专栏介绍和目录 ​ 前言 两年前博主写了如何利用JavaCV解析各种h264裸流,《JavaCV音视频开发宝典:使用javacv读取GB28181、海康大华平台和网络摄像头sdk回调视频码流并解析预览图像》,但是随着时间变化,各…

第五章 结构化设计

结构化设计的概念 1. 设计的定义 一种软件开发活动&#xff0c;定义实现需求规约所需的软件结构。 结构化设计分为&#xff1a; (1)总体设计&#xff1a;确定系统的整体模块结构&#xff0c;即系统实现所需要的软件模块以及这些模块之间的调用关系。 (2)详细设计&#xff1a;…

【C++】类型转换

目录 一、C语言中的类型转换 二、C中的类型转换 1.static_cast 2.reinterpret_cast 3.const_cast 4.dynamic_cast 三、RTTI 1.typeid运算符 2. decltype 一、C语言中的类型转换 在 C 语言中&#xff0c;如果 赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类…

什么是域控服务器?域控服务器功能?部署域控需要考虑因素?域控组策略功能?

一、什么是域控制服务器&#xff1f; 域控制器&#xff08;Domain Controller&#xff09;是在Windows Server操作系统上运行的一个服务角色&#xff0c;它用于管理和控制一个或多个计算机的安全策略、用户身份验证和授权等任务。域控制器通常是用于企业网络中的主要身份验证和…

macOS Sonoma 14beta With OpenCore 0.9.3 and winPE双引导分区黑苹果原版镜像

镜像特点&#xff08;原文地址&#xff1a;http://www.imacosx.cn/113888.html&#xff09; 完全由黑果魏叔官方制作&#xff0c;针对各种机型进行默认配置&#xff0c;让黑苹果安装不再困难。系统镜像设置为双引导分区&#xff0c;全面去除clover引导分区&#xff08;如有需要…

华为OD机试真题 JavaScript 实现【IPv4地址转换成整数】【2023 B卷 100分】

一、题目描述 存在一种虚拟 IPv4 地址&#xff0c;由4小节组成&#xff0c;每节的范围为0~255&#xff0c;以#号间隔&#xff0c; 虚拟 IPv4 地址可以转换为一个32位的整数&#xff0c;例如&#xff1a; 128#0#255#255&#xff0c;转换为32位整数的结果为2147549183&#xff0…

vue制作自己的组件库(仿ElementUI)

1.首先自己创建个新的vue项目&#xff0c;之后更改下目录形式&#xff0c;将src文件更改为examples&#xff0c;这里是专门放组件展示的md文件&#xff0c;packages文件里是放自己写的组件代码 2.然后是开始配置vue.config.js文件 &#xff0c;其中md-loader是读取md文件的相关…

【C/C++】带你快速掌握 使用—增强for(范围for循环)

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

MySQL 存储过程

1 什么是存储过程 MySQL 5.0 版本开始支持存储过程存储过程&#xff08;Stored Procedure&#xff09;是一种在数据库中存储复杂程序&#xff0c;以便外部程序调用的一种数据 库对象。存储过程是为了完成特定功能的SQL语句集&#xff0c;经编译创建并保存在数据库中&#xff0…

【c语言进阶】深入挖掘数据在内存中的存储

深入挖掘数据在内存中的存储 数据类型介绍数据类型基本分类及其大小 整形在内存中的存储方式原码、反码、补码大小端介绍判断一个系统是大端还是小端 char与unsigned char值范围与图解整形存储相关练习题 浮点数在内存中的存储方式浮点数存储规则案列 结语 铁汁们&#xff0c;今…

访问修饰符private,default,protected,public访问等级区别

private&#xff1a;private是最严格的访问修饰符&#xff0c;它将成员声明为私有的。私有成员只能在声明它们的类内部访问&#xff0c;其他类无法直接访问私有成员。这样可以确保数据的封装性和安全性。 default&#xff08;默认&#xff09;&#xff1a;如果没有明确指定访问…

Java调用Pytorch实现以图搜图(附源码)

Java调用Pytorch实现以图搜图 设计技术栈&#xff1a; 1、ElasticSearch环境&#xff1b; 2、Python运行环境&#xff08;如果事先没有pytorch模型时&#xff0c;可以用python脚本创建模型&#xff09;&#xff1b; 1、运行效果 2、创建模型&#xff08;有则可以跳过&#xf…

AI实战营:目标检测与MMDetection

目录 目标检测的基本范式 什么是目标检测 目标检测 vs 图像分类 目标检测 in 人脸识别 目标检测 in 智慧城市 ​编辑​编辑 目标检测 in 自动驾驶 目标检测 in 下游视觉任务 目标检测技术的演进 基础知识 框、边界框&#xff08;Bounding Box&#xff09; 交并比…

计算机网络填空题

我会写下自己的答案和理解 希望自己可用在学习中体会到快乐&#xff0c;而不是麻木。 1. 网络协议三要素中语义是指 需要发出何种控制信息&#xff0c;完成何种动作以及做出何种响应 1.在计算机网络中要做到有条不紊的交换数据&#xff0c;就必须遵守一些事…

组件更新的底层逻辑

第一种更新&#xff1a;组件更新的逻辑&#xff0c;当修改了相关状态&#xff0c;组件会更新 1.触发shouldComponentUpdate 周期函数:是否允许更新 shouldComponentUpdate(nextProps, nextState) { // nextState: 存储要修改的最新状态 // this. state:存储的还是修改前的状态…

闲置APP小程序开发 你不喜欢的可能正是别人需要的

生活中我们常常会产生各种闲置物品&#xff0c;尤其是对于有宝宝的家庭来说&#xff0c;孩子小的时候可能会添置各种玩具、婴儿车或者是别的用品&#xff0c;随着孩子渐渐长大&#xff0c;这些东西都用不上了&#xff0c;但是扔了又觉得很可惜&#xff0c;留着又占地方&#xf…

RocketMQ5.x版本延迟消息被重放问题调查

一、问题 由于目标计划是将集群从4.9.x逐步升级至5.x&#xff0c;故目前先对一些不重要的集群进行升级测试。 但是在4.x的broker陆续升级至5.x的过程中&#xff0c;发现了延迟消息被重放的问题。 具体如下: 在升级时刷新后台监控&#xff0c;发现竟然有写入量&#xff1a; 即…

基于组件化开发思想的微信小程序开发框架

跨端框架的出现为小程序应用的开发带来了巨大的便利性和灵活性。它们提供了统一的开发方式、代码复用的能力&#xff0c;并且与小程序容器技术紧密结合&#xff0c;实现了一次编码、多端运行的目标。开发者可以根据项目需求和团队技术栈选择合适的跨端框架&#xff0c;从而在不…