1. SpringCloud中Feign原理
1.1 Feign远程调用的基本流程
Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用者,Feign远程调用的基本流程
可以看到,Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数在应用到请求上,进而转换为真正的Request请求,通过Feign以及JAVA的动态代理机制,使得JAVA的开发人员,可以不用通过HTTP框架去封装HTTP请求报文的方式,完成远程服务的HTTP调用
1.2Feign远程调用的重要组件
在微服务启动时,Feign会进行包扫描,对加@FeignClient注解的接口,按照注解的规则,创建远程接口的本地JDK Proxy代理实例,然后将这些本地Proxy代理实例,注入到Spring IOC容器中,当远程接口的方法被调用,由proxy代理实例去完成真正的远程访问,并且返回结果
1.2.1 远程接口的本地JDK Proxy代理实例
远程接口的本地JDK Proxy代理实例,有以下特点
(1)Proxy代理实例,实现了一个加@FeignClient注解的远程调用接口
(2)Proxy代理实例,能在内部进行HTTP请求的封装,以及发送HTTP请求
(3)Proxy代理实例,能处理远程HTTP请求的响应,并且完成结果的解码,然后返回给调用者
以一个简单的远程服务的调用接口DemoClient为例,具体介绍一下远程接口的本地JDK Proxy代理实例的创建过程
DemoClient接口,有两个非常简单的远程调用抽象方法:一个为hello()抽象方法,用于完成远程URL“/api/demo/hello/v1”的HTTP请求,一个为echo抽象方法,用于完成远程URL“/api/demo/echo/{word}/v1”的HTTP请求
远程接口的本地JDK Proxy代理实例示意图
@FeignClient(
value = "seckill-provider", path = "/api/demo/",
fallback = DemoDefaultFallback.class)
public interface DemoClient {
/**
* 测试远程调用
*
* @return hello
*/
@GetMapping("/hello/v1")
Result<JSONObject> hello();
/**
* 非常简单的一个 回显 接口,主要用于远程调用
*
* @return echo 回显消息
*/
@RequestMapping(value = "/echo/{word}/v1", method = RequestMethod.GET)
Result<JSONObject> echo(
@PathVariable(value = "word") String word);
}
代码中,在DemoClient接口上加有@FeignClient注解,也就是说Feign在启动时,会为其创建一个本地JDK Proxy代理实例,并注册到Spring IOC容器
可以通过@Resource注解,按照类型匹配(这里的类型为DemoClient接口类型),从Spring IOC容器找到这个代理实例,并且装配给需要的成员变量。
DemoClient的 本地JDK Proxy 代理实例的使用的代码如下:
@Api(value = "用户信息、基础学习DEMO", tags = {"用户信息、基础学习DEMO"})
@RestController
@RequestMapping("/api/user")
public class UserController {
@Resource
DemoClient demoClient; //装配 DemoClient 的本地代理实例
@GetMapping("/say/hello/v1")
@ApiOperation(value = "测试远程调用速度")
public Result<JSONObject> hello() {
Result<JSONObject> result = demoClient.hello();
JSONObject data = new JSONObject();
data.put("others", result);
return Result.success(data).setMsg("操作成功");
}
//…
}
1.2.2 调用处理器InvocationHandler
通过JDK Proxy生成动态代理类,核心步骤就是需要定制一个调用处理器,就是实现JDK中位于java.lang.reflect包中的InvocationHandler调用处理器接口,并且实现该接口的invoke方法
为了创建Feign的远程接口的代理类,Feign提供了自己的一个默认的调用处理器,叫做FeignInvocationHandler类,该类处于feign-core核心jar包中,当然,调用处理器可以替换,如果Feign与Hystrix结合使用,则会替换成HystrixINvocationHandler调用处理器类,类处于 feign-hystrix 的jar包中
1.2.3 默认的调用处理器FeignInvocationHandler
默认的调用处理器FeignInvocationHandler是一个相对简单的类,有一个非常重要的Map类型成员dispatch映射,保存着远程接口方法到MethodHandler方法处理器的映射
前面示例中DemoClient 接口为例,其代理实现类的调用处理器 FeignInvocationHandler 的dispatch 成员的内存结构图
为什么Map类型成员dispatch映射对象中,有两个Key-Value键值对呢
默认的调用处理器FeignInvocationHandler,在处理远程方法调用的时候,会根据java反射的方法实例,在dispatch映射对象中,找到对应的MethodHandler方法处理器,然后交给MethodHandler完成实际的HTTP请求和结果的映射,前面示例中的 DemoClient 远程调用接口,有两个远程调用方法,所以,其代理实现类的调用处理器 FeignInvocationHandler 的dispatch 成员,有两个有两个Key-Value键值对。
FeignInvocationHandler关键源码
package feign;
//...省略import
public class ReflectiveFeign extends Feign {
//...
//内部类:默认的Feign调用处理器 FeignInvocationHandler
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
//方法实例对象和方法处理器的映射
private final Map<Method, MethodHandler> dispatch;
//构造函数
FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
//默认Feign调用的处理
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//...
//首先,根据方法实例,从方法实例对象和方法处理器的映射中,
//取得 方法处理器,然后,调用 方法处理器 的 invoke(...) 方法
return dispatch.get(method).invoke(args);
}
//...
}
代码的功能
(1)根据Java反射的方法实例,在dispatch映射对象中,找到对应的MethodHandler方法处理器
(2)调用MethodHandler方法处理器的invoke方法,完成实际的HTTP请求和结果的处理
1.2.4 方法处理器MethodHandler
Feign的方法处理器MethodHandler是一个独立的接口,定义在InvocationHandlerFactory接口中,仅仅拥有一个invoke()方法,源码如下
//定义在InvocationHandlerFactory接口中
public interface InvocationHandlerFactory {
//…
//方法处理器接口,仅仅拥有一个invoke(…)方法
interface MethodHandler {
//完成远程URL请求
Object invoke(Object[] argv) throws Throwable;
}
//...
}
MethodHandler的invoke方法,主要职责是完成实际远程URL请求,然后返回解码后的远程URL的响应结果,Feign提供了默认的SynchronousMethodHandler实现类,提供了基本的远程URL的同步请求处理
为了彻底了解方法处理器,来读一下SyncronousMethodHandler方法处理器的源码
package feign;
//…..省略import
final class SynchronousMethodHandler implements MethodHandler {
//…
// 执行Handler 的处理
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate requestTemplate = this.buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while(true) {
try {
return this.executeAndDecode(requestTemplate);
} catch (RetryableException var5) {
//…省略不相干代码
}
}
}
//执行请求,然后解码结果
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = this.targetRequest(template);
long start = System.nanoTime();
Response response;
try {
response = this.client.execute(request, this.options);
response.toBuilder().request(request).build();
}
}
}
SynchronousMethodHandler的invoke方法,调用了自己的executeAndDecode请求执行和结果解码方法,工作步骤
(1)首先通过RequestTemplate请求模板实例,生成远程URL请求实例request
(2)然后用自己的feign客户端client成员,excecute()执行请求,并且获取response响应
(3)对Response响应进行结果编码
1.2.5 Feign客户端组建feign.Client
客户端组件是Feign中一个非常重要的组件,负责端到端的执行URL请求,其核心的逻辑:发送request请求到服务器,并接收response响应后进行解码
feign.client类,是客户端的顶层接口,只有一个抽象方法
package feign;
/**客户端接口
* Submits HTTP {@link Request requests}.
Implementations are expected to be thread-safe.
*/
public interface Client {
//提交HTTP请求,并且接收response响应后进行解码
Response execute(Request request, Options options) throws IOException;
}
由于不同的feign.client实现类,内部完成HTTP请求的组件和技术不同,故,feign.client有多个不同的实现
(1)Client.Default类:默认的feign.Client 客户端实现类,内部使用HttpURLConnnection 完成URL请求处理;
(2)ApacheHttpClient 类:内部使用 Apache httpclient 开源组件完成URL请求处理的feign.Client 客户端实现类;
(3)OkHttpClient类:内部使用 OkHttp3 开源组件完成URL请求处理的feign.Client 客户端实现类。
(4)LoadBalancerFeignClient 类:内部使用 Ribben 负载均衡技术完成URL请求处理的feign.Client 客户端实现类。
1.3 Feign客户端实现类
1.3.1 Client.Default类
作为默认的Client接口的实现类,在Client.Default内部使用JDK自带的HTTPURLConnection类实现URL网络请求
在JKD1.8中,虽然在HttpURLConnnection 底层,使用了非常简单的HTTP连接池技术,但是,其HTTP连接的复用能力,实际是非常弱的,性能当然也很低
1.3.2 ApacheHttpClient类
ApacheHttpClient 客户端类的内部,使用 Apache HttpClient开源组件完成URL请求的处理。
从代码开发的角度而言,Apache HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性,它不仅使客户端发送Http请求变得容易,而且也方便开发人员测试接口。既提高了开发的效率,也方便提高代码的健壮性。
从性能的角度而言,Apache HttpClient带有连接池的功能,具备优秀的HTTP连接的复用能力。关于带有连接池Apache HttpClient的性能提升倍数,具体可以参见后面的对比试验。
ApacheHttpClient 类处于 feign-httpclient 的专门jar包中,如果使用,还需要通过Maven依赖或者其他的方式,倒入配套版本的专门jar包。
1.3.3 OkHttpClient类
OkHttpClient 客户端类的内部,使用OkHttp3 开源组件完成URL请求处理。OkHttp3 开源组件由Square公司开发,用于替代HttpUrlConnection和Apache HttpClient。由于OkHttp3较好的支持 SPDY协议(SPDY是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。),从Android4.4开始,google已经开始将Android源码中的 HttpURLConnection 请求类使用OkHttp进行了替换。也就是说,对于Android 移动端APP开发来说,OkHttp3 组件,是基础的开发组件之一。
1.3.4 LoadBalanceFeignClient类
LoadBalancerFeignClient 内部使用了 Ribben 客户端负载均衡技术完成URL请求处理。在原理上,简单的使用了delegate包装代理模式:Ribben负载均衡组件计算出合适的服务端server之后,由内部包装 delegate 代理客户端完成到服务端server的HTTP请求;所封装的 delegate 客户端代理实例的类型,可以是 Client.Default 默认客户端,也可以是 ApacheHttpClient 客户端类或OkHttpClient 高性能客户端类,还可以其他的定制的feign.Client 客户端实现类型。
1.4 Feign远程调用执行流程
整体流程大致分为四步
第一步 通过Spring IOC 容器实例,装配代理实例,然后进行远程调用。
Feign在启动时,会为加上了@FeignClient注解的所有远程接口(包括 DemoClient 接口),创建一个本地JDK Proxy代理实例,并注册到Spring IOC容器。在这里,暂且将这个Proxy代理实例,叫做 DemoClientProxy,稍后,会详细介绍这个Proxy代理实例的具体创建过程。
然后,在本实例的UserController 调用代码中,通过@Resource注解,按照类型或者名称进行匹配(这里的类型为DemoClient接口类型),从Spring IOC容器找到这个代理实例,并且装配给@Resource注解所在的成员变量,本实例的成员变量的名称为 demoClient。
在需要代进行hello()远程调用时,直接通过 demoClient 成员变量,调用JDK Proxy动态代理实例的hello()方法。
第二步 执行 InvokeHandler 调用处理器的invoke(…)方法
JDK Proxy动态代理实例的真正的方法调用过程,具体是通过 InvokeHandler 调用处理器完成的。故,这里的DemoClientProxy代理实例,会调用到默认的FeignInvocationHandler 调用处理器实例的invoke(…)方法。
通过前面 FeignInvocationHandler 调用处理器的详细介绍,大家已经知道,默认的调用处理器 FeignInvocationHandle,内部保持了一个远程调用方法实例和方法处理器的一个Key-Value键值对Map映射。FeignInvocationHandle 在其invoke(…)方法中,会根据Java反射的方法实例,在dispatch 映射对象中,找到对应的 MethodHandler 方法处理器,然后由后者完成实际的HTTP请求和结果的处理。
所以在第2步中,FeignInvocationHandle 会从自己的 dispatch映射中,找到hello()方法所对应的MethodHandler 方法处理器,然后调用其 invoke(…)方法。
第三步 执行 MethodHandler 方法处理器的invoke(…)方法
通过前面关于 MethodHandler 方法处理器的非常详细的组件介绍,大家都知道,feign默认的方法处理器为 SynchronousMethodHandler,其invoke(…)方法主要是通过内部成员feign客户端成员 client,完成远程 URL 请求执行和获取远程结果。
feign.Client 客户端有多种类型,不同的类型,完成URL请求处理的具体方式不同。
第四步 通过 feign.Client 客户端成员,完成远程 URL 请求执行和获取远程结果
如果MethodHandler方法处理器实例中的client客户端,是默认的 feign.Client.Default 实现类性,则使用JDK自带的HttpURLConnnection类,完成远程 URL 请求执行和获取远程结果。
如果MethodHandler方法处理器实例中的client客户端,是 ApacheHttpClient 客户端实现类性,则使用 Apache httpclient 开源组件,完成远程 URL 请求执行和获取远程结果