SpringCloudRPC远程调用核心原理:Feign弹性RPC客户端的重要组件,腾讯java面试有几轮

*/

@RequestMapping(value = “/echo/{word}/v1”,

method = RequestMethod.GET)

RestOut echo(

@PathVariable(value = “word”) String word);

}

注意,DemoClient远程调用接口加有@FeignClient注解,Feign在启动时会为带有@FeignClient注解的接口创建一个动态代理RPC客户端实例,并注册到Spring IOC容器,如图3-12所示。

SpringCloudRPC远程调用核心原理:Feign弹性RPC客户端的重要组件,腾讯java面试有几轮_第1张图片

图3-12 远程调用接口DemoClient的动态代理RPC客户端实例

DemoClient的本地JDK动态代理实例的创建过程比较复杂,稍后将作为重点介绍。先来看另外两个重要的Feign逻辑组件——调用处理器和方法处理器。

Feign的调用处理器InvocationHandler

=============================

大家知道,通过JDK Proxy生成动态代理类的核心步骤就是定制一个调用处理器。调用处理器实现类需要实现JDK中位于java.lang.reflect包中的InvocationHandler调用处理器接口,并且实现该接口的invoke(…)抽象方法。

Feign提供了一个默认的调用处理器,名为FeignInvocationHandler类,该类完成基本的调用处理逻辑,处于feign-core核心JAR包中。当然,Feign的调用处理器可以进行替换,如果Feign是与Hystrix结合使用的,就会被替换成HystrixInvocationHandler调用处理器类,而该类处于feign-hystrix的JAR包中。

以上两个Feign调用处理器都实现了JDK的InvocationHandler接口,如图3-13所示。

SpringCloudRPC远程调用核心原理:Feign弹性RPC客户端的重要组件,腾讯java面试有几轮_第2张图片

图3-13 两个Feign的InvocationHandler调用处理器示意图

默认的调用处理器FeignInvocationHandler是一个相对简单的类,有一个非常重要的Map类型成员dispatch映射,保存着RPC方法反射实例到Feign的方法处理器MethodHandler实例的映射。

在演示示例中,DemoClient接口的JDK动态代理实现类的调用处理器FeignInvocationHandler的某个实例的dispatch成员的内存结构图如图3-14所示。

SpringCloudRPC远程调用核心原理:Feign弹性RPC客户端的重要组件,腾讯java面试有几轮_第3张图片

图3-14 一个运行时FeignInvocationHa

《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》

【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享

ndler调用处理器实例的 dispatch成员的内存结构图

DemoClient的动态代理实例的调用处理器FeignInvocationHandler的dispatch成员映射中有两个键-值对(Key-Value Pair):一个键-值对缓存的是hello方法的方法处理器实例;另一个键-值对缓存的是echo方法的方法处理器实例。

在处理远程方法调用时,调用处理器FeignInvocationHandler会根据被调远程方法的Java反射实例在dispatch映射中找到对应的MethodHandler方法处理器,然后交给MethodHandler去完成实际的HTTP请求和结果的处理。

Feign的调用处理器FeignInvocationHandler的关键源码节选如下:

package feign;

//省略import

public class ReflectiveFeign extends Feign {

//内部类:默认的Feign调用处理器FeignInvocationHandler

static class FeignInvocationHandler implements InvocationHandler {

private final Target target;

//RPC方法反射实例和方法处理器的映射

private final Map dispatch;

//构造函数

FeignInvocationHandler(Target target, Map 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 {

//首先,根据方法反射实例从dispatch中取得MethodHandler方法处理器实例

//然后,调用方法处理器的invoke(…) 方法

return dispatch.get(method).invoke(args);

}

}

以上源码很简单,重点在于invoke(…)方法,虽然核心代码只有一行,但有两个功能:

(1)根据被调RPC方法的Java反射实例在dispatch映射中找到对应的MethodHandler方法处理器。

(2)调用MethodHandler方法处理器的invoke(…)方法完成实际的RPC远程调用,包括HTTP请求的发送和响应的解码。

Feign的方法处理器MethodHandler

========================

Feign的方法处理器MethodHandler接口和JDK动态代理机制中的InvocationHandler调用处理器接口没有任何的继承和实现关系。

Feign的MethodHandler接口是Feign自定义接口,是一个非常简单的接口,只有一个invoke(…)方法,并且定义在InvocationHandlerFactory工厂接口的内部,MethodHandler接口源码如下:

//定义在InvocationHandlerFactory接口中

public interface InvocationHandlerFactory {

//方法处理器接口,仅仅拥有一个invoke(…)方法

interface MethodHandler {

//完成远程URL请求

Object invoke(Object[] argv) throws Throwable;

}

}

MethodHandler的invoke(…)方法的主要目标是完成实际远程URL请求,然后返回解码后的远程URL的响应结果。Feign内置提供了SynchronousMethodHandler和DefaultMethodHandler两种方法处理器的实现类,如图3-15所示

SpringCloudRPC远程调用核心原理:Feign弹性RPC客户端的重要组件,腾讯java面试有几轮_第4张图片

图3-15 Feign的MethodHandler方法处理器及其实现类

内置的SynchronousMethodHandler同步方法处理实现类是Feign的一个重要类,提供了基本的远程URL的同步请求响应处理。

SynchronousMethodHandler方法处理器的源码如下:

package feign;

//省略import

final class SynchronousMethodHandler implements MethodHandler {

private static final long MAX_RESPONSE_BUFFER_SIZE = 8192L;

private final MethodMetadata metadata; //RPC远程调用方法的元数据

private final Target target; //RPC远程调用Java接口的元数据

private final Client client; //Feign客户端实例:执行REST请求和处理响应

private final Retryer retryer;

private final List requestInterceptors; //请求拦截器

private final Decoder decoder; //结果解码器

private final ErrorDecoder errorDecoder;

private final boolean decode404; //是否反编码404

private final boolean closeAfterDecode;

//执行Handler的处理

public Object invoke(Object[] argv) throws Throwable {

RequestTemplate requestTemplate = this.buildTemplateFromArgs

.create(argv);

… while(true) {

try {

return this.executeAndDecode(requestTemplate); //执行REST请求和处理响应

} catch (RetryableException var5) {

}

}

}

//执行RPC远程调用,然后解码结果

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(…)方法首先生成请求模板requestTemplate实例,然后调用内部成员方法executeAndDecode()执行RPC远程调用。

SynchronousMethodHandler的成员方法executeAndDecode()执行流程如下:

(1)通过请求模板requestTemplate实例生成目标request请求实例,主要完成请求的URL、请求参数、请求头等内容的封装。

(2)通过client(Feign客户端)成员发起真正的RPC远程调用。

(3)获取response响应,并进行结果解码。

SynchronousMethodHandler的主要成员如下:

(1)Target<?>target:RPC远程调用Java接口的元数据,保存了RPC接口的类名称、服务名称等信息,换句话说,远程调用Java接口的@FeignClient注解中配置的主要属性值都保存在target实例中。

(2)MethodMetadata metadata:RPC方法的元数据,该元数据首先保存了RPC方法的配置键,格式为“接口名#方法名(形参表)”;其次保存了RPC方法的请求模板(包括URL、请求方法等);再次保存了RPC方法的returnType返回类型;另外还保存了RPC方法的一些其他的属性。

(3)Client client:Feign客户端实例是真正执行RPC请求和处理响应的组件,默认实现类为Client.Default,通过JDK的基础连接类HttpURLConnection发起HTTP请求。Feign客户端有多种实现类,比如封装了Apache HttpClient组件的

feign.httpclient.HttpClient客户端实现类,稍后详细介绍。

(4)ListrequestInterceptors:每个请求执行前加入拦截器的逻辑。

(5)Decoder decoder:HTTP响应的解码器。

同步方法处理器SynchronousMethodHandler的属性较多,这里不一一介绍了。其内部有一个Factory工厂类,负责其实例的创建。创建一个SynchronousMethodHandler实例的源码如下:

package feign;

//同步方法调用器

final class SynchronousMethodHandler implements MethodHandler {

//同步方法调用器的创建工厂

static class Factory {

private final Client client; //Feign客户端:负责RPC请求和处理响应

private final Retryer retryer;

private final List requestInterceptors; //请求拦截器

private final Logger logger;

private final Level logLevel;

private final boolean decode404; //是否解码404错误响应

private final boolean closeAfterDecode;

//省略Factory创建工厂的全参构造器

//工厂的默认创建方法:创建一个方法调用器

public MethodHandler create(Target target, MethodMetadata md,

feign.RequestTemplate.Factory buildTemplateFromArgs,

Options options, Decoder decoder, ErrorDecoder errorDecoder) {

//返回一个新的同步方法调用器

return new SynchronousMethodHandler(target, this.client,

this.retryer, this.requestInterceptors, this.logger, this.logLevel, md,

buildTemplateFromArgs, options, decoder,

errorDecoder, this.decode404, this.closeAfterDecode);

}

}

}

Feign的客户端组件

============

客户端组件是Feign中一个非常重要的组件,负责最终的HTTP(包括REST)请求的执行。它的核心逻辑:发送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客户端实现类如下:

(1)Client.Default类:默认的实现类,使用JDK的HttpURLConnnection类提交HTTP请求。

(2)ApacheHttpClient类:该客户端类在内部使用ApacheHttpClient开源组件提交HTTP请求。

(3)OkHttpClient类:该客户端类在内部使用OkHttp3开源组件提交HTTP请求。

(4)LoadBalancerFeignClient类:内部使用Ribbon负载均衡技术完成HTTP请求处理。Feign客户端组件的UML图如图3-16所示。

SpringCloudRPC远程调用核心原理:Feign弹性RPC客户端的重要组件,腾讯java面试有几轮_第5张图片

图3-16 Feign客户端组件的UML图

下面对以上4个常用的客户端实现类进行简要介绍。

1.Client.Default默认实现类

作为默认的Client接口的实现类,Client.Default内部使用JDK自带的HttpURLConnnection类提交HTTP请求。

Client.Default默认实现类的方法如图3-17所示。

SpringCloudRPC远程调用核心原理:Feign弹性RPC客户端的重要组件,腾讯java面试有几轮_第6张图片

图3-17 Client.Default默认实现类的方法

在JDK 1.8中,虽然HttpURLConnnection底层使用了非常简单的HTTP连接池技术,但是其HTTP连接的复用能力实际上是非常弱的,所以其性能也比较低,不建议在生产环境中使用。

2.ApacheHttpClient实现类

ApacheHttpClient客户端类的内部使用Apache HttpClient开源组件提交HTTP请求。

和JDK自带的HttpURLConnnection连接类比,Apache HttpClient更加易用和灵活,它不仅使客户端发送HTTP请求变得容易,而且方便开发人员测试接口,既可以提高开发的效率,又可以提高代码的健壮性。从性能的角度而言,ApacheHttpClient带有连接池的功能,具备优秀的HTTP连接的复用能力。客户端实现类ApacheHttpClient处于feign-httpclient独立JAR包中,如果使用,还需引入配套版本的JAR包依赖。疯狂创客圈的脚手架crazy-springcloud使用了ApacheHttpClient客户端,在各Provider微服务提供者模块中加入了feign-httpclient和httpclient两个组件的依赖坐标,具体如下:

io.github.openfeign

feign-httpclient

${feign-httpclient.version}

你可能感兴趣的:(程序员,面试,java,后端)