SpringCloud升级之路2020.0.x版-27.OpenFeign的生命周期-创建代理

接下来,我们开始分析 OpenFeign 的生命周期,结合 OpenFeign 本身的源代码。首先是从接口定义创建 OpenFeign 代理开始。我们这里只关心同步客户端,因为异步客户端目前还在实现中,并且在我们的项目中,异步响应式的客户端不用 OpenFeign,而是用的官方的 WebClient
创建 OpenFeign 代理
创建 OpenFeign 代理,主要分为以下几步:
- 使用 Contract 解析接口的每一个方法,生成每一个方法的元数据列表:
List<MethodMetadata> metadata - 根据每一个
MethodMetadata,生成对应的请求模板工厂RequestTemplate.Factory,用于生成后面的请求。同时,使用这个模板工厂以及其他配置生成对应的方法处理器MethodHandler,对于同步的 OpenFeign,MethodHandler实现为SynchronousMethodHandler。将接口方法与MethodHandler一一对应建立映射,结果为Map<Method, MethodHandler> methodToHandler。对于 Java 8 引入的 interface default 方法,需要用不同MethodHandler,即DefaultMethodHandler,因为这种方法不用代理,不用生成对应的 http 调用,其实现为直接调用对应的 default 方法代码。 - 使用 InvocationHandlerFactory 这个工厂,创建
InvocationHandler用于代理调用。 - 调用 JDK 动态代理生成类方法使用
InvocationHandler创建代理类。
创建 OpenFeign 代理,主要基于 JDK 的动态代理实现。我们先举一个简单的例子,创建一个 JDK 动态代理,用来类比。
JDK 动态代理
使用 JDK 动态代理,需要如下几个步骤:
1. 编写接口以及对应的代理类。我们这里编写一个简单的接口和对应的实现类:
public interface TestService {
void test();
}
public class TestServiceImpl implements TestService {
@Override
public void test() {
System.out.println("TestServiceImpl#test is called");
}
}
2.创建代理类实现java.lang.reflect.InvocationHandler,并且,在核心方法中,调用实际的对象,这里即我们上面 TestService 的实现类 TestServiceImpl 的对象。
JDK 中有内置的动态代理 API,其核心是 java.lang.reflect.InvocationHandler。我们先来创建一个简单的 InvocationHandler 实现类:
public class SimplePrintMethodInvocationHandler implements InvocationHandler {
private final TestService testService;
public SimplePrintMethodInvocationHandler(TestService testService) {
this.testService = testService;
}
@Override
public Object invoke(
//代理对象
Object proxy,
//调用的方法
Method method,
//使用的参数
Object[] args)
throws Throwable {
System.out.println("Invoked method: " + method.getName());
//进行实际的调用
return method.invoke(testService, args);
}
}
3.创建代理对象,并使用代理对象调用。一般通过 Proxy 的静态方法去创建,例如:
//首先,创建要代理的对象
TestServiceImpl testServiceImpl = new TestServiceImpl();
//然后使用要代理的对象创建对应的 InvocationHandler
SimplePrintMethodInvocationHandler simplePrintMethodInvocationHandler = new SimplePrintMethodInvocationHandler(testServiceImpl);
//创建代理类,因为一个类可能实现多个接口,所以这里返回的是 Object,用户根据自己需要强制转换成要用的接口
Object proxyInstance = Proxy.newProxyInstance(
TestService.class.getClassLoader(),
testServiceImpl.getClass().getInterfaces(),
simplePrintMethodInvocationHandler
);
//强制转换
TestService proxied = (TestService) proxyInstance;
//使用代理对象进行调用
proxied.test();
这样,我们就使用了 JDK 的内置动态代理机制实现了一个简单的动态代理。在 OpenFeign 的使用中,和我们的示例有一点区别。首先,我们只需要定义要代理的接口,不用定义实现类。因为所有的 OpenFeign 接口要做的事情其实都是 HTTP 调用,其信息可以自动从接口定义中生成,我们可以使用统一的对象根据接口定义,承载 OpenFeign 接口定义的请求。在 OpenFeign 中,这个等同于实现对象的,就是根据接口生成的 MethodHandler,在同步的 OpenFeign 中,即 feign.SynchronousMethodHandler。之后,OpenFeign 创建的 InvocationHandler,其实就是将调用转发到对应的 SynchronousMethodHandler 的对应方法。
创建 OpenFeign 代理对象的流程详解
我们使用前面的例子,来看一下创建代理的流程:
interface GitHub {
/**
* 定义get方法,包括路径参数,响应返回序列化类
* @param owner
* @param repository
* @return
*/
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repository);
/**
* 响应体结构类
*/
class Contributor {
String login;
int contributions;
public Contributor() {
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public int getContributions() {
return contributions;
}
public void setContributions(int contributions) {
this.contributions = contributions;
}
}
}
/**
* 基于 FastJson 的反序列化解码器
*/
static class FastJsonDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
//读取 body
byte[] body = response.body().asInputStream().readAllBytes();
return JSON.parseObject(body, type);
}
}
public static void main(String[] args) {
//创建 Feign 代理的 HTTP 调用接口实现
GitHub github = Feign.builder()
//指定解码器为 FastJsonDecoder
.decoder(new FastJsonDecoder())
//指定代理类为 GitHub,基址为 https://api.github.com
.target(GitHub.class, "https://api.github.com");
List<GitHub.Contributor> contributors = github.contributors("OpenFeign", "feign");
}
我们这里关心的其实就是创建 Feign 代理的 HTTP 调用接口实现这一步的内部流程。首先我们来看 Feign 的 Builder 的结构,当我们初始化一个 Feign 的 Builder 也就是调用 Feign.builder() 时,会创建如下组件(同时也说明以下组件都是可以配置的,如果一些配置之前没有提到,则可以):
//请求拦截器列表,默认为空
private final List<RequestInterceptor> requestInterceptors = new ArrayList();
//日志级别,默认不打印任何日志
private Level logLevel = Level.NONE;
//负责解析类元数据的 Contract,默认是支持 OpenFeign 内置注解的默认 Contract
private Contract contract = new Contract.Default();
//承载 HTTP 请求的 Client,默认是基于 Java HttpURLConnection 的 Default Client
private Client client = new feign.Client.Default((SSLSocketFactory)null, (HostnameVerifier)null);
//重试器,默认也是 Default
private Retryer retryer = new feign.Retryer.Default();
//默认的日志 Logger,默认不记录任何日志
private Logger logger = new NoOpLogger();
//编码器解码器也是默认的
private Encoder encoder = new feign.codec.Encoder.Default();
private Decoder decoder = new feign.codec.Decoder.Default();
//查询参数编码,这个我们一般不会修改
private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
//错误编码器,默认为 Default
private ErrorDecoder errorDecoder = new feign.codec.ErrorDecoder.Default();
//各种超时的 Options 走的默认配置
private Options options = new Options();
//用来生成 InvocationHandler 的 Factory 也是默认的
private InvocationHandlerFactory invocationHandlerFactory = new feign.InvocationHandlerFactory.Default();
//是否特殊解析 404 错误,因为针对 404 我们可能不想抛出异常,默认是 false
private boolean decode404 = false;
//是否在解码后立刻关闭 Response,默认为是
private boolean closeAfterDecode = true;
//异常传播规则,默认是不传播
private ExceptionPropagationPolicy propagationPolicy = ExceptionPropagationPolicy.NONE;
//是否强制解码,这个主要为了兼容异步 Feign 引入的配置,我们直接忽略,认为他就是 false 即可
private boolean forceDecoding = false;
private List<Capability> capabilities = new ArrayList();
我们的代码中指定了指定解码器为 FastJsonDecoder,所以 Decoder 就是 FastJsonDecoder 了。最后通过 target(GitHub.class, "https://api.github.com");指定定代理类为 GitHub,基址为 https://api.github.com,这时候就会生成 Feign 代理类,其步骤是:
public <T> T target(Class<T> apiType, String url) {
//使用代理接口类型,以及基址创建 HardCodedTarget,他的意思其实就是硬编码的 Target
return target(new HardCodedTarget<T>(apiType, url));
}
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public Feign build() {
//将所有组件经过所有的 Capability,从这里我们可以看出,我们可以实现 Capability 接口来在创建 Feign 代理的时候动态修改组件
Client client = Capability.enrich(this.client, capabilities);
Retryer retryer = Capability.enrich(this.retryer, capabilities);
List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
.map(ri -> Capability.enrich(ri, capabilities))
.collect(Collectors.toList());
Logger logger = Capability.enrich(this.logger, capabilities);
Contract contract = Capability.enrich(this.contract, capabilities);
Options options = Capability.enrich(this.options, capabilities);
Encoder encoder = Capability.enrich(this.encoder, capabilities);
Decoder decoder = Capability.enrich(this.decoder, capabilities);
InvocationHandlerFactory invocationHandlerFactory =
Capability.enrich(this.invocationHandlerFactory, capabilities);
QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);
//创建 SynchronousMethodHandler 的 Factory,用于生成 SynchronousMethodHandler,SynchronousMethodHandler 是实际承载 Feign 代理请求的实现类
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
//通过方法名称来区分不同接口方法的元数据解析,用于生成并路由到对应的代理方法
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
//创建 ReflectiveFeign
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
}
创建 ReflectiveFeign 之后,会调用其中的 newInstance 方法:
public <T> T newInstance(Target<T> target) {
//使用前面提到的 ParseHandlersByName 解析元数据并生成所有需要代理的方法的 MethodHandler,我们这里分析的是同步 Feign,所以是 SynchronousMethodHandler
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
//将方法与对应的 MethodHandler 一一对应
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
//对于 Object 的方法,直接跳过
continue;
} else if (Util.isDefault(method)) {
//如果是 java 8 中接口的默认方法,就使用 DefaultMethodHandler 处理
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
//使用前面 Builder 中的 InvocationHandlerFactory 创建 InvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
//使用 InvocationHandler 创建 Proxy
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
//将代理与 DefaultMethodHandler 关联
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
对于使用前面提到的 ParseHandlersByName 解析元数据并生成所有需要代理的方法的 MethodHandler 这一步,主要就涉及到了使用 Contract 解析出方法的元数据,然后将这些元数据用对应的编码器绑定用于之后调用的编码:
public Map<String, MethodHandler> apply(Target target) {
// 使用 Contract 解析出所有方法的元数据
List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
// 对于每个解析出的方法元数据
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
//有表单的情况
buildTemplate =
new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
} else if (md.bodyIndex() != null) {
//有 body 的情况
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
} else {
//其他情况
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
}
if (md.isIgnored()) {
result.put(md.configKey(), args -> {
throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
});
} else {
// 使用 SynchronousMethodHandler 的 Factory 生成 SynchronousMethodHandler
result.put(md.configKey(),
factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
}
}
return result;
}
}
默认的 InvocationHandlerFactory 生成的 InvocationHandler 是 ReflectiveFeign.FeignInvocationHandler:
static final class Default implements InvocationHandlerFactory {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
其中的内容是:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 对于 equals,hashCode,toString 方法直接调用
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
//对于其他方法,调用对应的 SynchronousMethodHandler 进行处理
return dispatch.get(method).invoke(args);
}
从这里我们就可以看出,我们生成的 Proxy,其实就是将请求代理到了 SynchronousMethodHandler 上。

我们这一节详细介绍了 OpenFeign 创建代理的详细流程,可以看出,对于同步 Feign 生成的 Proxy,其实就是将接口 HTTP 请求定义的方法请求代理到了 SynchronousMethodHandler 上。下一节我们会详细分析 SynchronousMethodHandler 做实际 HTTP 调用的流程,来搞清楚 Feign 所有组件是如何协调工作的。
微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer:

SpringCloud升级之路2020.0.x版-27.OpenFeign的生命周期-创建代理的更多相关文章
- SpringCloud升级之路2020.0.x版-28.OpenFeign的生命周期-进行调用
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 接下来,我们开始分析 OpenFeign 同步环境下的生命周期的第二部分,使用 Synch ...
- SpringCloud升级之路2020.0.x版-26.OpenFeign的组件
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 首先,我们给出官方文档中的组件结构图: 官方文档中的组件,是以实现功能为维度的,我们这里是 ...
- SpringCloud升级之路2020.0.x版-25.OpenFeign简介与使用
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent OpenFeign 的由来和实现思路 在微服务系统中,我们经常会进行 RPC 调用.在 S ...
- SpringCloud升级之路2020.0.x版-1.背景
本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ Spring ...
- SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解(1)
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 接下来,将进入我们升级之路的又一大模块,即网关模块.网关模块我们废弃了已经进入维护状态的 ...
- SpringCloud升级之路2020.0.x版-6.微服务特性相关的依赖说明
本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford spring-cl ...
- SpringCloud升级之路2020.0.x版-10.使用Log4j2以及一些核心配置
本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Log4 ...
- SpringCloud升级之路2020.0.x版-43.为何 SpringCloudGateway 中会有链路信息丢失
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在开始编写我们自己的日志 Filter 之前,还有一个问题我想在这里和大家分享,即在 Sp ...
- SpringCloud升级之路2020.0.x版-7.从Bean到SpringCloud
本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ 在理解 Spr ...
随机推荐
- swiper tabs综合示例
html部分: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <m ...
- [leetcode]1109. 航班预订统计(击败100%用户算法-差分数组的详解)
执行用时2ms,击败100%用户 内存消耗52.1MB,击败91%用户 这也是我第一次用差分数组,之前从来没有碰到过,利用差分数组就是利用了差分数组在某一区间内同时加减情况,只会改变最左边和最右边+1 ...
- TensorFlow-Slim 简介+Demo
github介绍:https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/slim 基于slim实现的yolo- ...
- Mybatis-技术专区-如何清晰的解决出现「多对一模型」和「一对多模型」的问题
前提介绍 在mybatis如何进行多对一.一对多(一对一)的多表查询呢?本章带你认识如何非常顺滑的解决! 基础使用篇 一对一 association association通常用来映射一对一的关系,例 ...
- 前后端数据交互(二)——原生 ajax 请求详解
一.ajax介绍 ajax 是前后端交互的重要手段或桥梁.它不是一个技术,是一组技术的组合. ajax :a:异步:j:js:a:和:x:服务端的数据. ajax的组成: 异步的 js 事件 其他 j ...
- Spring Security进阶
Spring Security进阶 1.连接数据库进行数据的验证 Spring Security进行身份验证或者权限控制时,用户名和密码应该要和数据库的进行比较才行,用户的各种信息我们从数据库中去获取 ...
- K8s 系列(四) - 浅谈 Informer
1. 概述 进入 K8s 的世界,会发现有很多的 Controller,它们都是为了完成某类资源(如 pod 是通过 DeploymentController, ReplicaSetControlle ...
- noip模拟32
\(\color{white}{\mathbb{山高而青云冷,池深而蛟穴昏,行以慎步,援以轻身,名之以:落石}}\) 开题发现 \(t1\) 80分特别好写,于是先写了 但是这个做法没有任何扩展性,导 ...
- C#取汉字首字母,汉字全拼
使用类库为 https://gitee.com/kuiyu/dotnetcodes/tree/master/DotNet.Utilities/%E6%B1%89%E5%AD%97%E8%BD%AC%E ...
- python库--pandas--DataFrame
转换 索引,迭代 运算符 功能应用,分组及窗口 计算/描述统计 重新索引/选择/标签操作 缺失数据处理 形状变换/排序/转置 组合/加入/合并 时间序列相关 ...