Feign详解4-Contract 源码 淡淡的烟草味﹌ 2022-11-21 04:24 188阅读 0赞 **目录** 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 参数编码整体流程 # ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW5neWluZ2NoZW5ncWk_size_16_color_FFFFFF_t_70][] **总结:** 前两步是 `Feign` 代理生成阶段,解析方法参数及注解元信息。后三步是调用阶段,将方法参数编码成 Http 请求的数据格式。 // feign包下 public interface Contract { //解析接口中的方法,保存成List List<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 方法执行器][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对象。 @Override public 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 disconnected byte[] 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解析注解的过程 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW5neWluZ2NoZW5ncWk_size_16_color_FFFFFF_t_70 1][] **总结:** `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 clear data.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 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW5neWluZ2NoZW5ncWk_size_16_color_FFFFFF_t_70 2][] ## 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时,为 urlIndex private Integer bodyIndex; // 方法参数没有任务注解,默认为 bodyIndex private 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. @QueryMap if (metadata.queryMapIndex() != null) { // add query map parameters after initial resolve so that they take // precedence over any predefined values Object value = argv[metadata.queryMapIndex()]; Map<String, Object> queryMap = toQueryMap(value); template = addQueryMapQueryParameters(queryMap, template); } // 5. @HeaderMap if (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 提供了支持 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW5neWluZ2NoZW5ncWk_size_16_color_FFFFFF_t_70]: /images/20221120/491e0638b8fd4599852524ef5a420c7f.png [2.3 MethodHandler]: https://blog.csdn.net/zhangyingchengqi/article/details/109387215 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW5neWluZ2NoZW5ncWk_size_16_color_FFFFFF_t_70 1]: /images/20221120/04d2c585b3d44cb8aa955a034e029ce5.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW5neWluZ2NoZW5ncWk_size_16_color_FFFFFF_t_70 2]: /images/20221120/d4a7234fbea8483f9d2869704cad779c.png
还没有评论,来说两句吧...