目录

1. Feign 参数编码整体流程

2. Contract 方法注解及元信息解析

2.1 processAnnotationOnClass

2.2 processAnnotationOnMethod

2.3 processAnnotationsOnParameter

2.4 MethodMetadata

3. 参数解析成 Request

4. 以后只需要实现自己的 Contract,将对应的注解信息解析成 MethodMetadata,即可完成适配工作。


前面我们大致分析了一下 Feign 的工作原理,它利用jdk面向接口的动态代理机制完成了接口实现类的创建,那 Feign 到底是如何适配 Feign、JAX-RS 1/2 的 REST 声明式注解,将方法的参数解析为 Http 的请求行、请求头、请求体呢?这里就不得不提 Contract这个接口。

1. Feign 参数编码整体流程

总结: 前两步是 Feign 代理生成阶段,解析方法参数及注解元信息。后三步是调用阶段,将方法参数编码成 Http 请求的数据格式。

// feign包下
public interface Contract {//解析接口中的方法,保存成ListList<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);//实现类,abstract class BaseContract implements Contract {//提供了  parseAndValidatateMetadata 的实现 public List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType){}}//此类中提供了一系列处理类上的注解,方法上的注解,参数上的注解的 方法. class Default extends BaseContract{protected void processAnnotationOnClass(MethodMetadata data, Class<?> targetType){...}protected void processAnnotationOnMethod(MethodMetadata data,Annotation methodAnnotation,Method method) {... }protected boolean processAnnotationsOnParameter(MethodMetadata data,Annotation[] annotations,int paramIndex){  }}
}

总结: Contract 接口利用parseAndValidateMetadata()将 interface GitHub 中每个接口中的方法及其注解解析为 MethodMetadata,另外  SpringMvcContract 也是实现了 Contract 接口的一个子类,它处理了SpringMvc提供的注解.

接下来,请查看上一篇文章的   <<  2.3 MethodHandler 方法执行器 >> 这一部分,在targetToHandlersByName.apply(target);中,调用了  List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());

接着循环 metadata, 创建 BuildTemplateByResolvingArgs buildTemplate对象,

for (MethodMetadata md : metadata) {BuildTemplateByResolvingArgs buildTemplate;if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);} else if (md.bodyIndex() != null) {buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);} else {buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);}result.put(md.configKey(),factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
}

最后调用  factory.create(),创建一个MethodHandler对象.

public MethodHandler create(Target<?> target,MethodMetadata md,RequestTemplate.Factory buildTemplateFromArgs,Options options,Decoder decoder,ErrorDecoder errorDecoder) {return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger,logLevel, md, buildTemplateFromArgs, options, decoder,errorDecoder, decode404, closeAfterDecode, propagationPolicy);
}

这个 SynchronousMethodHandler 实现了 MethodHandler, 它的invoke() 方法是一个回调方法,当调用代理对象的被代理方法时,jvm会回调此方法.  以下invoke()执行过程中,由buildTemplateFromArgs.create()创建了一个RequestTemplate对象。

@Overridepublic Object invoke(Object[] argv) throws Throwable {RequestTemplate template = buildTemplateFromArgs.create(argv);Options options = findOptions(argv);Retryer retryer = this.retryer.clone();while (true) {try {return executeAndDecode(template, options);} catch (RetryableException e) {try {retryer.continueOrPropagate(e);} catch (RetryableException th) {Throwable cause = th.getCause();if (propagationPolicy == UNWRAP && cause != null) {throw cause;} else {throw th;}}if (logLevel != Logger.Level.NONE) {logger.logRetry(metadata.configKey(), logLevel);}continue;}}}

然后在    executeAndDecode(template,options)中,根据template生成了Request对象,并加入options等参数后发出请求,并得到响应。

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {Request request = targetRequest(template);    //****关键if (logLevel != Logger.Level.NONE) {logger.logRequest(metadata.configKey(), logLevel, request);}Response response;long start = System.nanoTime();try {response = client.execute(request, options);    //*****关键} catch (IOException e) {if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));}throw errorExecuting(request, e);}long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);boolean shouldClose = true;try {if (logLevel != Logger.Level.NONE) {response =logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);}if (Response.class == metadata.returnType()) {if (response.body() == null) {return response;}if (response.body().length() == null ||response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {shouldClose = false;return response;}// Ensure the response body is disconnectedbyte[] bodyData = Util.toByteArray(response.body().asInputStream());return response.toBuilder().body(bodyData).build();}if (response.status() >= 200 && response.status() < 300) {if (void.class == metadata.returnType()) {return null;} else {Object result = decode(response);  //****关键shouldClose = closeAfterDecode;return result;}} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {Object result = decode(response);shouldClose = closeAfterDecode;return result;} else {throw errorDecoder.decode(metadata.configKey(), response);}} catch (IOException e) {if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);}throw errorReading(request, response, e);} finally {if (shouldClose) {ensureClosed(response.body());}}}

以上是调用 Client#execute 发送 Http 请求。Client本身是一个接口,它里面有一个执行发请求的方法

Response execute(Request request, Options options) throws IOException;
public interface Client {....Response execute(Request request, Options options) throws IOException;
}

总结: Client 的具体实现有 HttpURLConnection、Apache HttpComponnets、OkHttp3 、Netty 等。

以上就是整个执行的流程。

下面我们只关注前三步:即 Feign 方法元信息解析及参数编码过程。

2. Contract 方法注解及元信息解析

以 Feign 默认的 Contract.Default 为例( 除此外还有 JAXRSContract,  HystrixDelegatingContract等):

首先回顾一下 Feign 注解的使用(@RequestLine @Headers @Body @Param @HeaderMap @QueryMap):

//假如有以下的接口
@Headers("Content-Type: application/json")
interface UserService {@RequestLine("POST /user")@Headers("Content-Type: application/json")@Body("%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D")void user(@Param("user_name") String name, @Param("password") String password, @QueryMap Map<String, Object> queryMap, @HeaderMap Map<String, Object> headerMap, User user);
}

Contract解析注解的过程

总结: Contract.BaseContract#parseAndValidatateMetadata 会遍历解析 UserService 中的每个方法,按接口类上、方法上、参数上的注解,将其解析成 MethodMetadata。

protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {MethodMetadata data = new MethodMetadata();data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));data.configKey(Feign.configKey(targetType, method));// 1. 解析类上的注解if (targetType.getInterfaces().length == 1) {processAnnotationOnClass(data, targetType.getInterfaces()[0]);}processAnnotationOnClass(data, targetType);// 2. 解析方法上的注解for (Annotation methodAnnotation : method.getAnnotations()) {processAnnotationOnMethod(data, methodAnnotation, method);}Class<?>[] parameterTypes = method.getParameterTypes();Type[] genericParameterTypes = method.getGenericParameterTypes();Annotation[][] parameterAnnotations = method.getParameterAnnotations();int count = parameterAnnotations.length;for (int i = 0; i < count; i++) {// isHttpAnnotation 表示参数上是否有注解存在boolean isHttpAnnotation = false;if (parameterAnnotations[i] != null) {isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);}// 方法参数上不存在注解if (parameterTypes[i] == URI.class) {data.urlIndex(i);} else if (!isHttpAnnotation && parameterTypes[i] != Request.Options.class) {// 已经设置过 @FormParam JAX-RS规范checkState(data.formParams().isEmpty(),"Body parameters cannot be used with form parameters.");// 已经设置过 bodyIndex,如 user(User user1, Person person) ×checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method);data.bodyIndex(i);data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));}}return data;}

这个方法也很好理解,接下来看一下 @RequestLine @Headers @Body @Param @HeaderMap @QueryMap 这些注解的具体解析过程。

2.1 processAnnotationOnClass

@Override
protected void processAnnotationOnClass(MethodMetadata data, Class<?> targetType) {if (targetType.isAnnotationPresent(Headers.class)) {String[] headersOnType = targetType.getAnnotation(Headers.class).value();checkState(headersOnType.length > 0, "Headers annotation was empty on type %s.",targetType.getName());Map<String, Collection<String>> headers = toMap(headersOnType);headers.putAll(data.template().headers());data.template().headers(null); // to cleardata.template().headers(headers);}
}

总结: 类上只有一个注解:

  1. @Headers -> data.template().headers

2.2 processAnnotationOnMethod

protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {Class<? extends Annotation> annotationType = methodAnnotation.annotationType();if (annotationType == RequestLine.class) {String requestLine = RequestLine.class.cast(methodAnnotation).value();checkState(emptyToNull(requestLine) != null,"RequestLine annotation was empty on method %s.", method.getName());Matcher requestLineMatcher = REQUEST_LINE_PATTERN.matcher(requestLine);if (!requestLineMatcher.find()) {throw new IllegalStateException(String.format("RequestLine annotation didn't start with an HTTP verb on method %s",method.getName()));} else {data.template().method(HttpMethod.valueOf(requestLineMatcher.group(1)));data.template().uri(requestLineMatcher.group(2));}data.template().decodeSlash(RequestLine.class.cast(methodAnnotation).decodeSlash());data.template().collectionFormat(RequestLine.class.cast(methodAnnotation).collectionFormat());} else if (annotationType == Body.class) {String body = Body.class.cast(methodAnnotation).value();checkState(emptyToNull(body) != null, "Body annotation was empty on method %s.",method.getName());if (body.indexOf('{') == -1) {data.template().body(body);} else {data.template().bodyTemplate(body);}} else if (annotationType == Headers.class) {String[] headersOnMethod = Headers.class.cast(methodAnnotation).value();checkState(headersOnMethod.length > 0, "Headers annotation was empty on method %s.",method.getName());data.template().headers(toMap(headersOnMethod));}
}

总结: 方法上可能有三个注解:

  1. @RequestLine -> data.template().method + data.template().uri
  2. @Body -> data.template().body
  3. @Headers -> data.template().headers

2.3 processAnnotationsOnParameter

protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations,int paramIndex) {boolean isHttpAnnotation = false;for (Annotation annotation : annotations) {Class<? extends Annotation> annotationType = annotation.annotationType();if (annotationType == Param.class) {Param paramAnnotation = (Param) annotation;String name = paramAnnotation.value();checkState(emptyToNull(name) != null, "Param annotation was empty on param %s.",paramIndex);nameParam(data, name, paramIndex);Class<? extends Param.Expander> expander = paramAnnotation.expander();if (expander != Param.ToStringExpander.class) {data.indexToExpanderClass().put(paramIndex, expander);}data.indexToEncoded().put(paramIndex, paramAnnotation.encoded());isHttpAnnotation = true;// 即不是@Headers和@Body上的参数,只能是formParams了if (!data.template().hasRequestVariable(name)) {data.formParams().add(name);}} else if (annotationType == QueryMap.class) {checkState(data.queryMapIndex() == null,"QueryMap annotation was present on multiple parameters.");data.queryMapIndex(paramIndex);data.queryMapEncoded(QueryMap.class.cast(annotation).encoded());isHttpAnnotation = true;} else if (annotationType == HeaderMap.class) {checkState(data.headerMapIndex() == null,"HeaderMap annotation was present on multiple parameters.");data.headerMapIndex(paramIndex);isHttpAnnotation = true;}}return isHttpAnnotation;
}

总结: 参数上可能有三个注解:

  1. @Param-> data.indexToName

  2. @QueryMap-> data.queryMapIndex

  3. @HeaderMap-> data.headerMapIndex

2.4 MethodMetadata

上面解析方法的元信息,目的就是为了屏蔽 Feign、JAX-RS 1/2、Spring Web MVC 等 REST 声明式注解的差异,那 MethodMetadata 到底有那些信息呢?

public final class MethodMetadata implements Serializable {private static final long serialVersionUID = 1L;private String configKey;   // 方法签名,类全限名+方法全限名private transient Type returnType;   // 方法返回值类型private Integer urlIndex;   // 方法参数为url时,为 urlIndexprivate Integer bodyIndex;  // 方法参数没有任务注解,默认为 bodyIndexprivate Integer headerMapIndex;private Integer queryMapIndex;private boolean queryMapEncoded;private transient Type bodyType;private RequestTemplate template = new RequestTemplate();      // 核心private List<String> formParams = new ArrayList<String>();private Map<Integer, Collection<String>> indexToName =new LinkedHashMap<Integer, Collection<String>>();private Map<Integer, Class<? extends Expander>> indexToExpanderClass =new LinkedHashMap<Integer, Class<? extends Expander>>();private Map<Integer, Boolean> indexToEncoded = new LinkedHashMap<Integer, Boolean>();private transient Map<Integer, Expander> indexToExpander;}

总结: 到目前为至,Method 的方法的参数已经解析成 MethodMetadata,当方法调用时,会根据 MethodMetadata 的元信息将 argv 解析成 Request。

3. 参数解析成 Request

以 BuildTemplateByResolvingArgs 为例。

public RequestTemplate create(Object[] argv) {RequestTemplate mutable = RequestTemplate.from(metadata.template());// 1. 解析url参数if (metadata.urlIndex() != null) {int urlIndex = metadata.urlIndex();checkArgument(argv[urlIndex] != null,"URI parameter %s was null", urlIndex);mutable.target(String.valueOf(argv[urlIndex]));}// 2. 解析参数argv成对应的对象Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {int i = entry.getKey();Object value = argv[entry.getKey()];if (value != null) { // Null values are skipped.if (indexToExpander.containsKey(i)) {value = expandElements(indexToExpander.get(i), value);}for (String name : entry.getValue()) {varBuilder.put(name, value);}}}// 3. @Body中的参数占位符RequestTemplate template = resolve(argv, mutable, varBuilder);// 4. @QueryMapif (metadata.queryMapIndex() != null) {// add query map parameters after initial resolve so that they take// precedence over any predefined valuesObject value = argv[metadata.queryMapIndex()];Map<String, Object> queryMap = toQueryMap(value);template = addQueryMapQueryParameters(queryMap, template);}// 5. @HeaderMapif (metadata.headerMapIndex() != null) {template =addHeaderMapHeaders((Map<String, Object>) argv[metadata.headerMapIndex()], template);}return template;
}

总结: 将方法的参数解析成 RequestTemplate 后就简单了,只需要调用 request 即可最终解析成 Request。可以看到 Request 包含了 Http 请求的全部信息。到此,Feign 的参数解析全部完成。

public Request request() {if (!this.resolved) {throw new IllegalStateException("template has not been resolved.");}return Request.create(this.method, this.url(), this.headers(), this.requestBody());
}

4. 以后只需要实现自己的 Contract,将对应的注解信息解析成 MethodMetadata,即可完成适配工作。

  1. jaxrs Feign 原生支持,感兴趣的可以看一下其实现:feign.jaxrs.JAXRSContract
  2. Spring Web MVC Spring Cloud OpenFeign 提供了支持

Feign详解4-Contract 源码相关推荐

  1. android WebView详解,常见漏洞详解和安全源码(下)

    上篇博客主要分析了 WebView 的详细使用,这篇来分析 WebView 的常见漏洞和使用的坑.  上篇:android WebView详解,常见漏洞详解和安全源码(上)  转载请注明出处:http ...

  2. android WebView详解,常见漏洞详解和安全源码(上)

    这篇博客主要来介绍 WebView 的相关使用方法,常见的几个漏洞,开发中可能遇到的坑和最后解决相应漏洞的源码,以及针对该源码的解析.  由于博客内容长度,这次将分为上下两篇,上篇详解 WebView ...

  3. FPGA学习之路—接口(3)—SPI详解及Verilog源码分析

    FPGA学习之路--SPI详解及Verilog源码分析 概述 SPI = Serial Peripheral Interface,是串行外围设备接口,是一种高速,全双工,同步的通信总线. 优点 支持全 ...

  4. faster rcnn fpn_Faster-RCNN详解和torchvision源码解读(三):特征提取

    我们使用ResNet-50-FPN提取特征 model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=True) ...

  5. 【Python】基金/股票 最大回撤率计算与绘图详解(附源码和数据)

    如果你想找的是求最大回撤的算法,请跳转:[Python] 使用动态规划求解最大回撤详解 [Python]基金/股票 最大回撤率计算与绘图详解(附源码和数据) 0. 起因 1. 大成沪深300指数A 5 ...

  6. 生成对抗网络入门详解及TensorFlow源码实现--深度学习笔记

    生成对抗网络入门详解及TensorFlow源码实现–深度学习笔记 一.生成对抗网络(GANs) 生成对抗网络是一种生成模型(Generative Model),其背后最基本的思想就是从训练库里获取很多 ...

  7. CNN入门详解及TensorFlow源码实现--深度学习笔记

    CNN入门详解及TensorFlow源码实现–深度学习笔记 ##一.卷积神经网络 ###1.简介 卷积神经网络是一种前馈神经网络,它的人工神经元可以响应一部分覆盖范围内的周围单元,对于大型图像处理有出 ...

  8. EKF SLAM Matlab仿真实践详解(附源码)

    EKF SLAM Matlab仿真实践详解(附源码) 为提供更好的阅读体验,详细内容及源码请移步https://github.com/Nrusher/EKF_SLAM 或 https://gitee. ...

  9. aes js加密php解密实例,基于PHP和JS的AES相互加密解密方法详解(CryptoJS)_PHP_JS_AES源码...

    [实例简介] 基于PHP和JS的AES相互加密解密方法详解(CryptoJS)_PHP_JS_AES源码 [实例截图] [核心代码] 基于PHP和JS的AES相互加密解密方法详解(CryptoJS)_ ...

  10. JAVA文件上传详解(附源码)

    文章目录 JAVA文件上传详解(附源码) 1.准备工作 2.使用类介绍 FileItem类 ServletFileUpload类 3.代码编写 JAVA文件上传详解(附源码) 在web应用中,文件上传 ...

最新文章

  1. 《Microduino实战》——第2章 Microduino
  2. html禁止f12键代码,网站禁用f12 禁止调试代码方法
  3. SQLServer服务的运行与登录
  4. prometheus实战:
  5. 51 NOD 1363 最小公倍数之和 (欧拉函数思维应用)
  6. 题目1457:非常可乐(广度优先遍历BFS)
  7. Android组件化方案及组件消息总线modular-event实战
  8. Matlab二维曲线之fplot函数
  9. cesium 页面截图_Cesium开发入门篇 | 02Cesium开发环境搭建及第一个示例
  10. 【转】Java杂谈(九)--Struts
  11. 超详细的springBoot学习教程
  12. Java性能调优全攻略来了
  13. python拦截游戏封包_【求助】关于pygame封包问题
  14. 关于iOS 报Command failed with exit 128: git错误额解决方案
  15. 推荐几个全网最全的程序员接私活地方法或完整攻略或常用平台以及接单的注意事项(以免被雇主坑),比如国内的程序员客栈、CODING 码市,国外的Upwork、Freelancer、Dribbble等。
  16. auto.js B0013 查找父控件子控件进入阅读文章视频学习每日答题2021-10-03
  17. php破解referer防盗链解析,Referer原理与图片防盗链实现方法详解
  18. 将SSH多次登录失败的IP加入黑名单
  19. 一级计算机浏览器题加收藏夹,2016年计算机一级考试上机操作重难点突破
  20. 基于javaweb的宠物商城系统(java+jsp+javascript+servlet+mysql)

热门文章

  1. 职场语言的特征3p1A7C指什么,商务礼仪选择题和答案
  2. 创建MFC对话框Dlg工程项目
  3. Android Hawk的源码解析,一款基于SharedPreferences的存储框架
  4. 云南艺术学院艺考开考 万人追逐艺术梦
  5. 2009世界500强:《财富》全球五百家公司排名
  6. 管理经济学知识点汇总
  7. android 播放器 音乐,音乐我最行 七款Android播放器大比拼
  8. 华为荣耀4X使用eRecovery恢复系统
  9. CLIP: 打通文本图像迁移模型的新高度
  10. Java毕设项目艾灸减肥管理网站计算机(附源码+系统+数据库+LW)