目录
2.3 processAnnotationsOnParameter
4. 以后只需要实现自己的 Contract,将对应的注解信息解析成 MethodMetadata,即可完成适配工作。
前面我们大致分析了一下 Feign
的工作原理,它利用jdk面向接口的动态代理机制完成了接口实现类的创建,那 Feign
到底是如何适配 Feign、JAX-RS 1/2 的 REST 声明式注解,将方法的参数解析为 Http 的请求行、请求头、请求体呢?这里就不得不提 Contract
这个接口。
1. Feign 参数编码整体流程
总结: 前两步是 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 方法执行器 >> 这一部分,在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解析注解的过程
总结: 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);
}
}
总结: 类上只有一个注解:
- @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));
}
}
总结: 方法上可能有三个注解:
- @RequestLine -> data.template().method + data.template().uri
- @Body -> data.template().body
- @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;
}
总结: 参数上可能有三个注解:
-
@Param-> data.indexToName
-
@QueryMap-> data.queryMapIndex
-
@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时,为 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,即可完成适配工作。
jaxrs
Feign 原生支持,感兴趣的可以看一下其实现:feign.jaxrs.JAXRSContract
Spring Web MVC
Spring Cloud OpenFeign 提供了支持