*/
@RequestMapping(value = “/echo/{word}/v1”,
method = RequestMethod.GET)
RestOut echo(
@PathVariable(value = “word”) String word);
}
注意,DemoClient远程调用接口加有@FeignClient注解,Feign在启动时会为带有@FeignClient注解的接口创建一个动态代理RPC客户端实例,并注册到Spring IOC容器,如图3-12所示。
图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所示。
图3-13 两个Feign的InvocationHandler调用处理器示意图
默认的调用处理器FeignInvocationHandler是一个相对简单的类,有一个非常重要的Map类型成员dispatch映射,保存着RPC方法反射实例到Feign的方法处理器MethodHandler实例的映射。
在演示示例中,DemoClient接口的JDK动态代理实现类的调用处理器FeignInvocationHandler的某个实例的dispatch成员的内存结构图如图3-14所示。
图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
//构造函数
FeignInvocationHandler(Target target, Map
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所示
图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所示。
图3-16 Feign客户端组件的UML图
下面对以上4个常用的客户端实现类进行简要介绍。
1.Client.Default默认实现类
作为默认的Client接口的实现类,Client.Default内部使用JDK自带的HttpURLConnnection类提交HTTP请求。
Client.Default默认实现类的方法如图3-17所示。
图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}