文章目录
- 前言
- 正文
- 一、书接上回,从代理对象入手
- 二、ReflectiveFeign.FeignInvocationHandler#invoke()
- 三、SynchronousMethodHandler#invoke(...) 的实现原理
- 3.1 invoke(...)源码
- 3.2 executeAndDecode(...) 执行请求并解码
- 四、如何更换client 的实现
- 附录
- 附1:本系列文章链接
- 附2:比较HttpURLConnection、Apache HttpClient、OkHttp
前言
本篇是SpringCloud原理系列的 OpenFeign 模块的第四篇。
在我们启动完应用后,Spring容器也初始化好了很多我们用到的类。(什么,你不知道,烦请先看看第三篇)
那么我们下一步要做的就是,发出rest请求,然后调用FeignClient标注的接口方法。这篇文章,我们就来看看它的原理。
本文关键词:RequestTemplate
、SynchronousMethodHandler
使用java 17,spring cloud 4.0.4,springboot 3.1.4
使用项目是本系列第一篇中的项目
正文
一、书接上回,从代理对象入手
第三篇文章时,我们看到了SpringCloud将 OpenFeign的接口,映射为一个代理对象。
打个比方,使用如下接口:
@FeignClient(name = "helloFeignClient", url = "http://localhost:10080")
public interface HelloFeignClient {@PostMapping("/hello/post")HelloResponse postHello(@RequestBody HelloRequest helloRequest);
}
最终生成的代理对象是对 HelloFeignClient
接口的代理,并且绑定了handler。handler的类型是ReflectiveFeign.FeignInvocationHandler
换句话说,就是当我们调用接口HelloFeignClient
中的方法时,会触发调用ReflectiveFeign.FeignInvocationHandler
的invoke(...)
方法。
二、ReflectiveFeign.FeignInvocationHandler#invoke()
查看源码可以知道,这里invoke
方法,实际是先从 dispatch
中找到对应方法的真正的处理器,然后进行调用。
从第三篇文章,我们能知道 dispatch 是对 method 的映射。
比如接口HelloFeignClient
会被映射为dispatch
,一个方法对应为一对key、value值。dispatch
的类型是:
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
也就是说Method 只是作为一个桥梁,连接起了HelloFeignClient
内的方法和真正执行的handler实例。这里的实例真正的实现是SynchronousMethodHandler
。也就是说,当我们调用接口方法时,会执行SynchronousMethodHandler#invoke(...)
。
三、SynchronousMethodHandler#invoke(…) 的实现原理
3.1 invoke(…)源码
public Object invoke(Object[] argv) throws Throwable {// 创建请求模板,包装请求头、请求体,url等字段参数RequestTemplate template = this.buildTemplateFromArgs.create(argv);// 获取连接超时等参数Request.Options options = this.findOptions(argv);// 重试Retryer retryer = this.retryer.clone();while(true) {try {// 执行请求并解码return this.executeAndDecode(template, options);} catch (RetryableException var9) {RetryableException e = var9;try {retryer.continueOrPropagate(e);} catch (RetryableException var8) {Throwable cause = var8.getCause();if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {throw cause;}throw var8;}if (this.logLevel != Level.NONE) {this.logger.logRetry(this.metadata.configKey(), this.logLevel);}}}}
3.2 executeAndDecode(…) 执行请求并解码
Object executeAndDecode(RequestTemplate template, Request.Options options) throws Throwable {// 通过模版获取请求体,执行所有请求拦截器Request request = this.targetRequest(template);if (this.logLevel != Level.NONE) {this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);}long start = System.nanoTime();Response response;try {// 使用客户端执行请求response = this.client.execute(request, options);// 使用响应建造器构造一个响应体,包含请求和请求模板response = response.toBuilder().request(request).requestTemplate(template).build();} catch (IOException var9) {if (this.logLevel != Level.NONE) {this.logger.logIOException(this.metadata.configKey(), this.logLevel, var9, this.elapsedTime(start));}throw FeignException.errorExecuting(request, var9);}long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);// 处理响应结果&记录日志&响应解码return this.responseHandler.handleResponse(this.metadata.configKey(), response, this.metadata.returnType(), elapsedTime);}
通过分析,发现是先创建了RequestTemplate 实例,然后调用了client实例进行远程调用。而client的实现有多个,我这边看到内部实现了一个默认的:
public static class Default implements Client {public Response execute(Request request, Request.Options options) throws IOException {HttpURLConnection connection = this.convertAndSend(request, options);return this.convertResponse(connection, request);}
}
也就是说,到了这一步,就涉及到远程连接了。
这里用的是比较原始的HttpURLConnection
。每次都创建新的连接,去请求,然后断开连接。这样很多时间也就浪费在建立连接等操作上了。而且调用量一旦变大,很容易出错。
问题来了,有没有什么办法能优化下呢?
四、如何更换client 的实现
上文提到 HttpURLConnection
是默认的连接方式。那麽我们有什么优化方案吗?
可替代方案一般有两种,一种是带有连接池的Apache HttpClient
,另一种是协议上占有优势的 OkHttp
。
至于它们的更详细的优缺点,以及不同之处,请查看本文的附2。
另外,我的下一篇文打算单独将这块写一下 ===> SpringCloud实用-OpenFeign整合okHttp
戳附录中的【本系列文章链接】查看文章。
附录
附1:本系列文章链接
SpringCloud系列文章目录(总纲篇)
附2:比较HttpURLConnection、Apache HttpClient、OkHttp
参考:七大主流的HttpClient程序比较
Client | 优点 | 缺点 |
---|---|---|
HttpURLConnection | jdk自带、原始、简单 | 缺乏连接池管理、域名机械控制等特性支持,性能&效率较低,一般不建议使用 |
Apache HttpClient (已经停止开发) Apache HttpComponents HttpClient | 1. 支持连接池、多线程 2. 易用,灵活 | 安卓社区不再使用它,替换为了okHttp 需要自己做一层封装 |
java.net.http.HttpClient | java11正式启用,替代原先的HttpURLConnection | 如果使用的版本是java11以下的,用不了它 |
okHttp | 性能方面与HttpClient基本一样 链接复用 Response 缓存和 Cookie 默认 GZIP 请求失败自动重连 DNS 扩展 Http2/SPDY/WebSocket 协议支持 默认情况下,OKHttp会自动处理常见的网络问题:像二次连接、SSL的握手问题。 从Android4.4开始HttpURLConnection的底层实现采用的是okHttp. | |
一般情况下,如果使用了SpringCloud,基本都会选择 OpenFeign+okHttp的组合。