剖析SpringCloud Feign中所隐藏的坑
作者:crossoverJie 发布时间:2023-11-19 05:32:03
背景
前段时间同事碰到一个问题,需要在 SpringCloud
的 Feign 调用中使用自定义的 URL;通常情况下是没有这个需求的;毕竟都用了 SpringCloud
的了,那服务之间的调用都是走注册中心的,不会需要自定义 URL 的情况。
但也有特殊的,比如我们这里碰到 ToB
场景,需要对每个商户自定义的 URL
进行调用。
虽说也可以使用原生的 Feign
甚至是自定义一个 OKHTTP Client
实现,但这些方案都得换一种写法;
打算利用现有的 SpringCloud
OpenFeign
来实现,毕竟原生的 Feign 其实是支持该功能的,而 SpringCloud OpenFeign
也只是在这基础上封装了一层。
只需要在接口声明处加上一个 URI
参数即可,这样就可以在每次调用时传递不同的 URI
来实现动态 URL
的目的。
想法很简单,但实践起来却不是那么回事了。 伪代码如下:
@FeignClient(name = "dynamic")
interface DynamicClient {
@GetMapping("/")
String get(URI uri);
}
dynamicClient.get(URI.create("https://github.com"));
执行后会抛出负载均衡的异常:
java.lang.RuntimeException: com.netflix.client.ClientException:
Load balancer does not have available server for client: github.com
这个异常也能理解,就是找不到 github 这个服务;找不到也是合理的,毕竟也不是一个内部注册的服务。
但按照 Feign
的官方介绍,只要接口中声明了 URI
这个参数就能自定义,同时我自己也用原生的 Feign 测试过确实没什么问题。
Debug
那问题只能出在 SpringCloud OpenFeign
的封装上了;经过同事的搜索在网上找到一篇博客解决了这个问题。
按照文中的说法,确实只需要加上 URL 参数同时有值就可以了,但原因不明。
本着打破砂锅问到底的精神,我个人也想知道 OpenFeign
是如何处理的,只要 url 有值就可以,这完全是个黑盒,而且在官方的注释中并没有对这种情况有特殊说明。
所以我准备从源码中找到答案。
既然是 url 有值就能正常运行,那一定是在运行过程中获取了这个值;
但我在源码中查看 url 所使用的地方,并没有在单测之外找到哪里有所应用,说明源码中并没有直接调用 url()
这个函数来获取值。
但 org.springframework.cloud.openfeign.FeignClient
这个注解总会使用吧,于是我又查询这个注解的使用情况。
最终在这里查到了使用的痕迹。
这里查阅源码时也有一些小技巧,比如如果我们直接查询时,IDEA 默认的查询范围是整个项目和所有依赖库,会有许多干扰信息。
比如我这里就需要只看项目源码,单测这些都不用看;所以在查询的时候可以过滤一下,这样干扰信息就会少很多。
左边的工具栏还有许多过滤条件,大家可以自行研究一下。
接着从源码中进行阅读,会发现是将 @FeignClient
中的所有数据都写到一个 Map
里进行使用的。
最终会发现这个 url 被写入到了 FeignClientFactoryBean
中的 url 成员变量中了。
查看哪里在使用这个 url 就知道背后的原理了。
在这里打个断点会发现:当 url 为空时会返回一个 LoadBalance
的 client
,也就是会从注册中心获取 url
的客户端,而 url
有值时则会获取一个默认的客户端,这样就不会走负载均衡了。
所以我们如果想在 OpenFeign 中使用动态 url 时就得让 @Feign 的 url 有值才行,无论是什么都可以。
Feign 的实现
既然已经看到这一步了,我也比较好奇 Feign 是如何做到只要有 URI 参数就使用指定的 URL 呢?
这里也分享一个读源码的小技巧,如果我们跟着程序执行的思路去一步步 debug
的话会非常消耗时间,毕竟这类成熟库的代码量也不小。
这里我们从官方文档中可以得知只要在接口参数中使用了 java.net.URI
便会走自定义的 url,所以我们反过来只要在源码中找到哪里在使用 java.net.URI
便能知道关键源码。
毕竟使用 java.net.URI
的场景也不会太多。
所以只需要在这个依赖的地方 cmd+shift+f
全局搜索 java.net.URI
就能查到结果,果然不多,只有两处使用。
再结合使用场景猜测大概率是判断参数中是否是有 URL.class
这样的条件,或者是 url 对象;总之我们先用 URL
这样关键字在这两个文件中搜索一下,记得勾选匹配大小写;最后会发现的确是判断了参数中是否有 URL
这个类,同时将这个索引位置记录了下来。
想必后续会通过这个索引位置读取最终的 url
信息。
最终通过这个索引的使用地方查询到了核心源码,如果有值时就取这个 URI 中所指定的地址作为 target
。
到此为止这个问题的背后原理都已经分析完毕了。
来源:https://juejin.cn/post/7100863261142155294


猜你喜欢
- 本文实例为大家分享了java日期时间操作工具类,供大家参考,具体内容如下虽然jdk1.8开始,加入了time包,里面对时区,本地化时间,格式
- 本文实例为大家分享了Android使用Retrofit上传文件的具体代码,供大家参考,具体内容如下一、封装RetrofitManagerpu
- Kotlin中的一个伟大创前举就是空指针的处理,在代码的编译阶段就能检测可能出现的空指针问题,示例代码如下:data class Perso
- 函数指针函数指针是指向函数的指针变量。通常我们说的指针变量是指向一个整型变、字符型或数组等变量,而函数指针是指向函数。函数指针可以像一般函数
- 目录一、log4j简介1、Loggers2、Appenders3、Layouts二、配置详解1、配置根Logger:2、配置日志信息输出目的
- 比如要获取打开摄像头的应用程序名称,只需要在frameworks/base/core/android/hardware/Camera.jav
- Java中数组初始化和OC其实是一样的,分为动态初始化和静态初始化,动态初始化:指定长度,由系统给出初始化值静态初始化:给出初始化值,由系统
- 在网上虽然看到了方法,但是处理感觉很复杂,我的办法,老实说,是突然试一下试到的,哈哈QWQOK,开始说明如何整的。效果如上图所示代码如下pa
- 流程如图:MainActivity 跳转至 MainActivity2 再跳转至 MainActivity3MainActivity3跳转至
- 找入口对 Spring 有一定基础的同学一定知道,请求入口是DispatcherServlet,所有的请求最终都会落到doDispatch方
- 本文实例为大家分享了C语言实现三子棋小游戏的具体代码,供大家参考,具体内容如下设计思路三子棋的C语言设计的思路:1.设计一个界面:让玩家运行
- 本文实例讲述了C#中foreach原理以及模拟的实现方法,分享给大家供大家参考。具体如下:public class Person:IEnum
- 熬夜写完,尚有不足,但仍在努力学习与总结中,而您的点赞与关注,是对我最大的鼓励!在一些本地化项目开发当中,存在这样一种需求,即开发完成的项目
- 本文实例为大家分享了Android绘制钟表的具体代码,供大家参考,具体内容如下首先要画一个表,我们要先知道步骤如何:1、仪表盘----外面最
- 前言想使用ffmpeg打开摄像头,需要输入摄像头的名称,而ffmpeg本身的枚举摄像头列表功能不是接口,所以需要用其他方式获取到设备列表。C
- HTTPclient保持长连接首先解释一下什么是长连接当我们向一台服务器发起请求时,我们需要和对方建立一条通道,去传输数据,所谓的短连接,就
- 用了多年的Visual Studio,今天才发现这个编码技巧,真是惭愧,分享出来,算是抛砖引玉吧!开发环境: vs2010+C#1、代码重构
- Java 使用getClass().getResourceAsStream()方法获取资源之前想获取一个资源文件做一些处理,使用getCla
- 1)在我们的项目中添加引用文件:TaskSchedulerEngine.dll(dll定义了一个ITask接口,定义了两个方法Initial
- java 删除链表中的元素以下实例演示了使用 Clear() 方法来删除链表中的元素:import java.util.*;public c