背景

前段时间同事碰到一个问题,需要在 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 的封装上了;经过同事的搜索在网上找到一篇博客解决了这个问题。

https://www.cnblogs.com/syui-terra/p/14386188.html

按照文中的说法,确实只需要加上 URL 参数同时有值就可以了,但原因不明。

本着打破砂锅问到底的精神,我个人也想知道 OpenFeign 是如何处理的,只要 url 有值就可以,这完全是个黑盒,而且在官方的注释中并没有对这种情况有特殊说明。

所以我准备从源码中找到答案。

既然是 url 有值就能正常运行,那一定是在运行过程中获取了这个值;



但我在源码中查看 url 所使用的地方,并没有在单测之外找到哪里有所应用,说明源码中并没有直接调用 url() 这个函数来获取值。

org.springframework.cloud.openfeign.FeignClient 这个注解总会使用吧,于是我又查询这个注解的使用情况。



最终在这里查到了使用的痕迹。

这里查阅源码时也有一些小技巧,比如如果我们直接查询时,IDEA 默认的查询范围是整个项目和所有依赖库,会有许多干扰信息。

比如我这里就需要只看项目源码,单测这些都不用看;所以在查询的时候可以过滤一下,这样干扰信息就会少很多。

左边的工具栏还有许多过滤条件,大家可以自行研究一下。


接着从源码中进行阅读,会发现是将 @FeignClient 中的所有数据都写到一个 Map 里进行使用的。



最终会发现这个 url 被写入到了 FeignClientFactoryBean 中的 url 成员变量中了。

查看哪里在使用这个 url 就知道背后的原理了。

在这里打个断点会发现:当 url 为空时会返回一个 LoadBalanceclient,也就是会从注册中心获取 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

到此为止这个问题的背后原理都已经分析完毕了。

总结

其实本文重点是分析了一些 debug 和阅读源码的一些小技巧,特别是在读关于 Spring 相关的代码时一定不能 debug 跟踪到细节中,因为调用链通常是很长的,稍不留神就把自己都绕晕了,只需要知道核心、关键源码是如何处理的即可。

最后对于 OpenFeign 处理动态 url 的方案确实也有些疑惑,是一个典型的约定大于配置的场景,但问题就在于我们并不知道这个约定是 @Feign 的 url 得有值。

所以我也提了一个 PROpenFeign,感兴趣的朋友也可以查看一下:

https://github.com/spring-cloud/spring-cloud-openfeign/pull/713

分享一个 SpringCloud Feign 中所埋藏的坑的更多相关文章

  1. 分享一个移动项目中消除click事件点击延迟的方法

    对于前端工程师来说,apicloud无疑给我们提供了很好的平台,有各种各样的模块供我们使用,但是在实际项目的时候,很大部分的代码,还是需要我们用html css js来实现的.但是呢,移动端页面对于c ...

  2. 分享一个在js中判断数据是undefined,NaN,null,的技巧

    教大家如何在js中判断一个值是否是undefined,null,NaN,以及如何单独判断 平常开发过程中大家可能遇到一种问题,就是取页面某个值的时候获取不到这个var就是undefined了,如果是数 ...

  3. 分享一个vueui axios-mock-adapter 中的用法

    import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { LoginUsers, Users ...

  4. 分享一个linux系统中采用嵌套for循环比较两个数组内容,并输出相同值的shell脚本

    #!/bin/bash array1=(1 3 5 6 7 9) array2=(3 4 9) echo array1=${array1[@]} echo array2=${array2[@]} fo ...

  5. 分享一个linux系统中循环遍历两个数组内容,并输出数组中的不同内容的shell脚本

    cat diffarray.sh #!/bin/bash arry_list1=(1 2 3 4 5 6 7 8 9) arry_list2=(3 5 8) declare -a diff_list ...

  6. SpringCloud Feign context-path踩到的坑

    最近在使用SpringCloud的context-path时,遇到了一些坑,记录一下. server.context-path(上下文) 服务提供者的application配置文件中有一个属性叫ser ...

  7. 分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间)

    分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间) 很多时候我们都需要计算数据库中各个表的数据量和每行记录所占用空间 这里共享一个脚本 CREATE TABLE #tab ...

  8. 分享一个安卓中异步获取网络图片并自适应大小的第三方程序(来自github)

    安卓中获取网络图片,生成缓存 用安卓手机,因为手机流量的限制,所以我们在做应用时,要尽量为用户考虑,尽量少耗点用户的流量,而在应用中网络图片的显示无疑是消耗流量最大的,所以我们可以采取压缩图片或者将图 ...

  9. (转)分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间)

    分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间) 很多时候我们都需要计算数据库中各个表的数据量和每行记录所占用空间 这里共享一个脚本 CREATE TABLE #tab ...

随机推荐

  1. web音频流转发之AudioNode

    前言 上一章地址: web音频流转发之音频源下一张地址:web音频流转发之音视频直播在这一章我说几个我们需要用到的音频处理模块也就3个吧,包括我们转发流是需要用到的核心模块.更多模块请看MDN,或者看 ...

  2. 从八道面试题看JavaScript DOM事件机制

    As we all know,事件机制其实很简单,无非冒泡和捕获这两点,笔者不再赘述,网上相关文章一大堆,下面让我们直接看面试题 题目一到七,统一设置css .test2 { height: 50px ...

  3. 前端面试题整理——Javascript基础

    常见值类型: let a; //undefined let s = 'abc'; let n = 100; let b = true; let sb = Symbol('s'); let nn = N ...

  4. CCF201712-2游戏

    问题描述 有n个小朋友围成一圈玩游戏,小朋友从1至n编号,2号小朋友坐在1号小朋友的顺时针方向,3号小朋友坐在2号小朋友的顺时针方向,--,1号小朋友坐在n号小朋友的顺时针方向. 游戏开始,从1号小朋 ...

  5. CCF201903-2二十四点

    思路描述:最开始的思路是拿一个栈来存储数据和符号,在动手实践的过程中发现行不通,单个数字的char和int转换可以,但是加起来的数据两位数字就很难处理了. 然后就去看了看别人的思路,给了我一个很好的启 ...

  6. Jar 包下载以及 maven jar 包配置

    学习内容: jar包下载是我们必须掌握的一个内容,不管是使用Maven项目还是其他项目,一般都需要引入外部的 jar 包 jar包下载 下载地址(打不开网址的直接百度搜索 maven reposito ...

  7. Blazor组件自做一 : 使用JS隔离封装viewerjs库

    Viewer.js库是一个实用的js库,用于图片浏览,放大缩小翻转幻灯片播放等实用操作 本文相关参考链接 JavaScript 模块中的 JavaScript 隔离 Viewer.js工程 Blazo ...

  8. IDM调用参数

    cmd调用IDM 基本命令: IDMan /d URL [/p 本地文件路径] [/f 本地文件名] [/q] [/h] [/n] [/a] 参数: 名称 作用 /d 下载文件的url /s 开始任务 ...

  9. asp.net core启动源码以及监听,到处理请求响应的过程

    摘要 asp.net core发布至今已经将近6年了,很多人对于这一块还是有些陌生,或者说没接触过:接触过的,对于asp.net core整个启动过程,监听过程,以及请求过程,响应过程也是一知半解,可 ...

  10. Java学习day10

    在类内定义的类就称为内部类.内部类可以访问外部类的所有成员,外部类要访问内部类,必须先建立对象 内部类分为成员内部类和局部内部类 外界创建public修饰的成员内部类对象的格式:外部类名.内部类名 对 ...