Spring Cloud Feign实战

news2025/2/1 0:56:17

概述

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问题解决

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

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

相关文章

2023网安面试题170道,轻松应对面试

最近有不少小伙伴跑来咨询&#xff1a; 想找网络安全工作&#xff0c;应该要怎么进行技术面试准备&#xff1f; 工作不到 2 年&#xff0c;想跳槽看下机会&#xff0c;有没有相关的面试题呢&#xff1f; 为了更好地帮助大家高薪就业&#xff0c;今天就给大家分享两份网络安全工…

仙境传说ro:如何在地图上刷怪教程

仙境传说ro&#xff1a;如何在地图上刷怪教程 大家好我是艾西&#xff0c;在仙境传说这个游戏中我们知道了怎么创建NPC添加商品售卖后&#xff0c;那么对于游戏的怪肯定也得有自己的想法以及对游戏的设定以及理解&#xff0c;今天我跟大家分享的是怎么在地图中刷怪教程。 我们…

STL——string、vector、deque容器

初识STL **STL的基本概念****vector容器存放内置数据类型****vector容器中存放自定义数据类型****vector容器嵌套vector容器****string容器——构造函数****string容器——赋值操作****string容器——字符串拼接****string容器——字符串的查找和替换****string容器——字符串比…

人工智能该如何学习?详细的AI学习

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 1.TomChat 地址&#xff1a;https://www.ridderchat.com/ 该网站非常简介好看&#xff0c;界面看着很舒服&#xff0c;可以帮助你快速编写、编辑和讨论代码。 2.强大的AI辅助 链接…

模拟实现strstr函数,通俗易懂!!!

函数介绍 函数声明 函数声明&#xff1a;char *strstr(const char *str1, const char *str2) 头 文 件&#xff1a;#include <string.h> 返 回 值&#xff1a; 返回值为char * 类型&#xff08; 返回指向 str1 中第一次出现的 str2 的指针&#xff09;&#xff1b…

Webots R2021a教程

文章目录 Windows安装设置中文打开世界添加贴图 为外部控制器配置Anaconda解决报错&#xff1a;CondaSSLError: Encountered an SSL error. Most likely a certificate verification issue.调用Python API Windows 安装 进入下载页面 https://github.com/cyberbotics/webots/r…

CSS实现几种常见布局

CSS实现几种常见布局 两列左窄右宽型布局 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" co…

【面试题】2023前端面试系列-- Vue 篇

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 web前端面试题库 VS java后端面试题库大全 Vue 常见面试题总结 MVVM模型&#xff1f; MVVM&#xff0c;是Model-View-ViewModel的简写…

001docker架构介绍

docker 官网Choose the best method for you to install Docker Engine. This client-server application is available on Linux, Mac, Windows, and as a static binary.https://docs.docker.com/engine/install/ 介绍 Docker用Go编程语言编写&#xff0c;并利用Linux内核的…

算法刷题-链表-两两交换链表中的节点

两两交换链表中的节点 24. 两两交换链表中的节点思路其他语言版本 24. 两两交换链表中的节点 力扣题目链接 给定一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后的链表。 你不能只是单纯的改变节点内部的值&#xff0c;而是需要实际的进行节点交换。 思…

黄金回收小程序开发功能有哪些?

一、用户端&#xff1a; 1、实时查询&#xff1a;通过对接三方接口实现实时金价动态查看&#xff1b; 2、多种类珠宝实时回收&#xff1a;小程序支持多品类珠宝的实时回收包含黄金饰品、金条、铂金、K金、白银等&#xff0c;同步实现价格实时更新&#xff1b; …

计算机内核态、用户态和零拷贝技术详解

存储介质的性能 话不多说&#xff0c;先看一张图&#xff0c;下图左边是磁盘到内存的不同介质&#xff0c;右边形象地描述了每种介质的读写速率。一句话总结就是越靠近cpu&#xff0c;读写性能越快。了解了不同硬件介质的读写速率后&#xff0c;你会发现零拷贝技术是多么的香&a…

JDBC 事务和批处理 详解(通俗易懂)

目录 一、前言 二、事务 1.事务介绍 : 2.事务处理 : Δ准备工作 Δ不使用事务的情况 Δ使用事务的情况 三、批处理 1.介绍 : 2.常用方法 : 3.应用 : 4.源码分析(JDK17.0版本&#xff09; : 四、总结 一、前言 第四节内容&#xff0c;up主要和大家…

阿里背调,征信不好也会被pass

大厂背调&#xff0c;我一直认为是唬人的&#xff0c;走下流程而已&#xff0c;没想到这么严格。这次提供的背调信息&#xff0c;我填写了上家公司三个联系人&#xff0c;HR、领导、同事&#xff1b;上上家公司三个联系人&#xff0c;HR、领导、同事。根据朋友的反馈来看&#…

python 第五章 列表list [ ]

系列文章目录 第一章 初识python 第二章 变量 第三章 基础语句 第四章 字符串str 文章目录 5.1列表的应用场景5.2列表的格式5.3列表的常用操作查找下标函数查找函数index()count()len() 判断是否存在innot in 增加append()extend()insert() 删除delpop()remove()clear() 清空列…

Java并发回顾

树叶柔和爽朗的呼吸 诗人一路吹着口哨回家 一路踢着石子妙想连篇 感到夕阳和晚风自古多情 自己现在和将来 都是个幸福的人 系列文章目录 Java常见知识点汇总Java集合回顾Java并发回顾… 文章目录 系列文章目录什么是线程和进程?线程与进程的关系,区别及优缺点&#xff1f;图解…

Tcp的三次握手及netty和实际开发如何设置全连接队列参数

上图 第一次握手&#xff0c;client 发送 SYN 到 server&#xff0c;状态修改为 SYN_SEND&#xff0c;server 收到&#xff0c;状态改变为 SYN_REVD&#xff0c;并将该请求放入 sync queue 队列 第二次握手&#xff0c;server 回复 SYN ACK 给 client&#xff0c;client 收到…

【Prometheus】mysqld_exporter采集+Grafana出图+AlertManager预警

前提环境&#xff1a;已经安装和配置好prometheus server 所有组件对应的版本&#xff1a; prometheus-2.44.0 mysqld_exporter-0.14.0 grafana-enterprise-9.1.2-1.x86_64.rpm alertmanager-0.25.0 prometheus-webhook-dingtalk-2.1.0 简介 mysql_exporter是用来收集MysQL或…

spring 事务超时

Transactional(timeout 10) 表示设置事务的超时时间为10秒 表示超过10秒如果该事务中所有的DML语句还没有执行完毕的话&#xff0c;最终结果会选择回滚 默认值-1&#xff0c;表示没有时间限制。 如果最后一条DML语句后面还有很多业务逻辑&#xff0c;这些业务代码执行的时间不…

setState详解

this. setState( [partialState], [callback]) 1.[partialState] :支持部分状态更改 this, setState({ x:100 //不论总共有多少状态&#xff0c;我们只修改了x&#xff0c;其余的状态不动 });callback :在状态更改/视图更新完毕后触发执行&#xff0c;也可以说只要执行了setS…