写点什么

【SpringCloud 技术专题】「Feign 技术专区」从源码层面让你认识 Feign 工作流程和运作机制

发布于: 12 小时前
【SpringCloud技术专题】「Feign技术专区」从源码层面让你认识Feign工作流程和运作机制

Feign 工作流程源码解析

什么是 feign:一款基于注解和动态代理的声明式 restful http 客户端。

原理

Feign 发送请求实现原理

  • 微服务启动类上标记 @EnableFeignClients 注解,然后 Feign 接口上标记 @FeignClient 注解。@FeignClient 注解有几个参数需要配置,这里不再赘述,都很简单。

  • Feign 框架会扫描注解,然后通过 Feign 类来处理注解,并最终生成一个 Feign 对象。

解析 @FeignClient 注解,生成 MethodHandler

具体的解析类是 ParseHandlerByName。这个类是 ReflectiveFeign 的内部类。


// 解析注解元数据,使用Contract解析List<MethodMetadata> metadata = this.contract.parseAndValidateMetadata(key.type());
复制代码


拿到注解元数据以后,循环处理注解元数据,创建每个方法对应的 MethodHandler,这个 MethodHandler 最终会被代理对象调用。最终 MethodHandler 都会保存到下面这个集合中,然后返回。


Map<String, MethodHandler> result = new LinkedHashMap();
复制代码
解析完成以后,调用 ReflectiveFeign.newInstance()生成代理类。

MethodHandler 是 feign 的一个接口,这个接口的 invoke 方法,是动态代理调用者 InvocationHandler 的 invoke()方法最终调用的方法。


重新表述一遍:InvocationHandler 的 invoke()方法最终回调 MethodHandler 的 invoke()来发送 http 请求。这就是 Feign 动态代理的具体实现。


ReflectiveFeign 类的 newInstance()方法的第 57 行:


// 创建动态代理调用者InvocationHandler handler = this.factory.create(target, methodToHandler);// 反射生成feign接口代理T proxy = Proxy.newProxyInstance(加载器, 接口数组, handler);
复制代码


InvocationHandler.invoke()的具体实现在 FeignInvocationHandler.invoke(),FeignInvocationHandler 也是 ReflectiveFeign 的一个内部类。里面有很多细节处理这里不再赘述,我们直接进入核心那一行代码,以免影响思路,我们是理 Feign 的实现原理的!不要在意这些细节!


// InvocationHandler 的 invoke()方法最终回调 MethodHandler 的 invoke()来发送 http 请求


ReflectiveFeign 类的 invoke()方法,第 323 行,代码的后半段,如下:


(MethodHandler)this.dispatch.get(method). invoke(args);
复制代码


  • this.dispatch:这是一个 map,就是保存所有的 MethodHandler 的集合。参考创建 InvocationHandler 的位置:ReflectiveFeign 类的 newInstance()方法的第 57 行。

  • this.dispatch.get(method):这里的 method 就是我们开发者写的 feign 接口中定义的方法的方法名!这段代码的意思就是从 MethodHandler 集合中拿到我们需要调用的那个方法。

  • this.dispatch.get(method). invoke(args):这里的 invoke 就是调用的 MethodHandler.invoke()!动态代理回调代理类,就这样完成了,oh my god,多么伟大的创举!

MethodHandler.invoke()的具体实现:SynchronousMethodHandler.invoke()

到了这里,就是发送请求的逻辑了。发送请求前,首先要创建请求模板,然后调用请求拦截器 RequestInterceptor 进行请求处理。


// 创建RequestTemplateRequestTemplate template = this.buildTemlpateFromArgs.create(argv);// 创建feign重试器,进行失败重试Retryer retryer = this.retryer.clone();while(true){    try{        // 发送请求        return this.executeAndDecode(template);    } catch(RetryableException var5) {        // 失败重试,最多重试5次        retryer.continueOrPropagate();    }}
复制代码
RequestTemplate 处理

RequestTemplate 模板需要经过一系列拦截器的处理,主要有以下拦截器:


  • BasicAuthRequestInterceptor:授权拦截器,主要是设置请求头的 Authorization 信息,这里是 base64 转码后的用户名和密码。

  • FeignAcceptGzipEncodingInterceptor:编码类型拦截器,主要是设置请求头的 Accept-Encoding 信息,默认值{gzip, deflate}。

  • FeignContextGzipEncodingInterceptor:压缩格式拦截器,该拦截器会判断请求头中 Context-Length 属性的值,是否大于请求内容的最大长度,如果超过最大长度 2048,则设置请求头的 Context-Encoding 信息,默认值{gzip, deflate}。注意,这里的 2048 是可以设置的,可以在配置文件中进行配置:


feign.compression.request.enabled=truefeign.compression.request.min-request-size=2048
复制代码


min-request-size 是通过 FeignClientEncodingProperties 来解析的,默认值是 2048。


我们还可以自定义请求拦截器,我们自定义的拦截器,也会在此时进行调用,所有实现了 RequestTemplate 接口的类,都会在这里被调用。比如我们可以自定义拦截器把全局事务 id 放在请求头里。

使用 feign.Request 把 RequestTemplate 包装成 feign.Request

feign.Request 由 5 部分组成:


  • method

  • url

  • headers

  • body

  • charset


http 请求客户端


Feign 发送 http 请求支持下面几种 http 客户端:


  • JDK 自带的 HttpUrlConnection

  • Apache HttpClient

  • OkHttpClient


// 具体实现有2个类Client.Default 和LoadBalancerFeignClient
response = this.client.execute(request, this.options);
Client接口定义了execute()的接口,并且通过接口内部类实现了Client.execute()。
HttpURLConnection connection = this.convertAndSend(request, options);
return this.convertResponse(connection).toBuilder(). request(request).build();
复制代码


  • 这里的 Options 定义了 2 个参数:

  • connectTimeoutMillis:连接超时时间,默认 10 秒。

  • readTimeoutMillis:读取数据超时时间,默认 60 秒。


这种方式是最简单的实现,但是不支持负载均衡,Spring Cloud 整合了 Feign 和 Ribbon,所以自然会把 Feign 和 Ribbon 结合起来使用。也就是说,Feign 发送请求前,会先把请求再经过一层包装,包装成 RibbonRequest。


也就是发送请求的另一种实现 LoadBalancerFeignClient。


// 把Request包装成RibbonRequestRibbonRequest ribbonRequest = new   (this.delegate, request, uriWithoutHost);// 配置超时时间IClientConfig requestConfig = this.getClientConfig(options, clientName);// 以负载均衡的方式发送请求return ((RibbonResponse)this.IbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse();
复制代码


以负载均衡的方式发送请求


  • this.IbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig))的具体实现在 AbstractLoadBalancerAwareClient 类中。

  • executeWithLoaderBalancer()方法的实现也参考了响应式编程,通过 LoadBalancerCommand 提交请求,然后使用 Observable 接收响应信息。


AbstractLoadBalancerAwareClient 类的 executeWithLoadBalancer()方法的第 54 行:


Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));


AbstractLoadBalancerAwareClient 实现了 IClient 接口,该接口定义了 execute()方法,


  • AbstractLoadBalancerAwareClient.this.execute()的具体实现有很多种:

  • OkHttpLoadBalancingClient

  • RetryableOkHttpLoadBalancingClient

  • RibbonLoadBalancingHttpClient

  • RetryableRibbonLoadBalancingHttpClient


我们以 RibbonLoadBalancingHttpClient 为例来说明,RibbonLoadBalancingHttpClient.execute()


第 62 行代码:


// 组装HttpUriRequest
HttpUriRequest httpUriRequest = request.toRequest(requestConfig);
// 发送http请求
HttpResponse httpResponse = ((HttpClient)this.delegate).execute(httpUriRequest);
// 使用RibbonApacheHttpResponse包装http响应信息
return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
RibbonApacheHttpResponse由2部分组成:
httpResponse
uri
复制代码

处理 http 相应

http 请求经过上面一系列的转发以后,最终还会回到 SynchronousMethodHandler,然后 SynchronousMethodHandler 会进行一系列的处理,然后响应到浏览器。


  • 注册 Feign 客户端 bean 到 IOC 容器

  • 查看 Feign 框架源代码,我们可以发现,FeignClientsRegistar 的 registerFeignClients()方法完成了 feign 相关 bean 的注册。

Feign 架构图


  • 第一步:基于 JDK 动态代理生成代理类。

  • 第二步:根据接口类的注解声明规则,解析出底层 MethodHandler

  • 第三步:基于 RequestBean 动态生成 request。

  • 第四步:Encoder 将 bean 包装成请求。

  • 第五步:拦截器负责对请求和返回进行装饰处理。

  • 第六步:日志记录。

  • 第七步:基于重试器发送 http 请求,支持不同的 http 框架,默认使用的是 HttpUrlConnection。

发布于: 12 小时前阅读数: 5
用户头像

🏆2021年InfoQ写作平台-签约作者 🏆 2020.03.25 加入

👑【酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“】 🏅 【Java技术领域,MySQL技术领域,APM全链路追踪技术及微服务、分布式方向的技术体系等】 我们始于迷惘,终于更高水平的迷惘

评论

发布
暂无评论
【SpringCloud技术专题】「Feign技术专区」从源码层面让你认识Feign工作流程和运作机制