@Author:zxw
@Email:502513206@qq.com
目录
- Feign源码分析(一) - 初探Feign
- Feign源码分析(二) - builder构建
1.前言
通过前面的文章,已经分析清除了Feign代理类的生成流程。接下来就是看远程调用发起的流程Feign是如何实现的,代码还是跟之前一样,通过connect方法获取到代理对象后,直接调用Feign接口repo ```java @RequestLine(“GET /api/v5/repos/{owner}/{repo}/stargazers?access_token=xxx&page=1&per_page=20”) Listrepo(@Param(“owner”) String owner, @Param(“repo”) String repo);
// ———
Gitee connect = Gitee.connect();
List
<a name="Joj0G"></a>
# 2.源码分析
前面已经了解到Feign是使用的java接口代理的方式为我们生成了代理对象`FeignInvocationHandler`
<a name="ZiBrs"></a>
## 2.1 FeignInvocationHandler
对于java的接口代理,我们只需实现接口`InvocationHandler`即可
```java
static class FeignInvocationHandler implements InvocationHandler
要发起代理调用首先我们得有远程地址的url,以及我们接口类的Class
private String url;
private Class<T> type;
这些Feign则是封装在了HardCodedTarget
类,那么得到FeignInvocationHandler
的第一个元数据则是
private final Target target;
在生成代理对象之前,Feign为每个方法生成了一个MethodHandler
封装了调用的基本信息,那么我们还需要一个Map映射已找到对应的MethodHandler
,如下
private final Map<Method, MethodHandler> dispatch;
以上就是FeignInvocationHandler
类中的两个字段,调用只需根据当前方法从Map中拿到对应的MethodHandler
即可。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
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();
}
return dispatch.get(method).invoke(args);
通过Map拿到对象后,调用了MethodHandler
的invoke方法。Feign中提供了MethodHandler
的实现类SynchronousMethodHandler
,接下来看SynchronousMethodHandler
的invoke实现
2.2 SynchronousMethodHandler
先来回顾一下MethodHandler
的组成。Feign对接口的方法解析时会生成一个MethodMetadata
对象
private final MethodMetadata metadata;
在先前分析Feign生成代理类时说过,Feign在解析方法注解后会生成一个Request的模板工厂类,通过该类可以获取RequestTemplate
请求模板
private final RequestTemplate.Factory buildTemplateFromArgs;
对于远程调用的返回值则对应了一个解码器
private final Decoder decoder;
还需要调用的url等基本信息,上面已经提到这些Feign封装在了Target类中
private final Target<?> target;
在Feign中是以方法的维度发起调用,即每个MethodHandler中还封装了Client对象的基本信息
private final Client client;
private final Retryer retryer;
private final Options options;
这边对于MethodHandler
的元数据大致就这些。
在回过来看看接口这个方法,在发起调用前的第一步肯定是组装我们的参数了,将param的值填充到对应的{}号
@RequestLine("GET /api/v5/repos/{owner}/{repo}/stargazers?access_token=xxx&page=1&per_page=20")
List<Stargazers> repo(@Param("owner") String owner, @Param("repo") String repo);
通过RequestTemplate.Factory
工厂解析后就能得到一个RequestTemplate
对象,该对象包含了http请求的模板信息,此时还不是最终请求的Request对象。
RequestTemplate template = buildTemplateFromArgs.create(argv);
得到了请求模板后,还需要经过请求拦截器调用后才能得到真正的Request对象
Request targetRequest(RequestTemplate template) {
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
return target.apply(template);
}
最终得到了一个Request对象
Request request = targetRequest(template);
这时我们就可以使用client客户端发起我们的远程请求了。对于Client接口只提供了一个方法就是发起请求,这里也是一个扩展点,可以让我们定制化自己的client对象。
public interface Client {
/**
* Executes a request against its {@link Request#url() url} and returns a response.
*/
Response execute(Request request, Options options) throws IOException;
}
如果我们不指定的话,Feign默认使用的就是java本身提供的网络访问对象HttpURLConnection
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection, request);
}
至此远程调用就结束了,回头过看这块调用的完成逻辑
Response response;
long start = System.nanoTime();
try {
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 12
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} 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);
在请求成功拿到Response返回值,这里我的解码器就可以出场对Response响应数据进行解码了。如果我们自定义了解码器,则Feign会使用我们的解码器,如果没有则使用Feign默认提供的解码器AsyncResponseHandler
if (decoder != null)
return decoder.decode(response, metadata.returnType());
CompletableFuture<Object> resultFuture = new CompletableFuture<>();
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
metadata.returnType(),
elapsedTime);
如果我们远程方法执行失败,Feign会捕获RetryableException
异常进行重试,默认的重试次数为5。最后整个invoke
方法的调用逻辑就如同上面分析的那样。
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;
}
}
}
3.总结
在本篇文章中,主要涉及到两个对象就是FeignInvocationHandler
和SynchronousMethodHandler
。一个是我们接口的代理类,一个则包含了远程请求调用的具体逻辑。
到这里整个Feign的源码就分析的差不多了,总得来说就是解析类上的注解,生成对应的请求信息,然后通过接口生成代理对象并执行远程调用方法。