@Author:zxw
@Email:502513206@qq.com
目录
1.前言
通过前面的文章,已经分析清除了Feign代理类的生成流程。接下来就是看远程调用发起的流程Feign是如何实现的,代码还是跟之前一样,通过connect方法获取到代理对象后,直接调用Feign接口repo
@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);
// ------
Gitee connect = Gitee.connect();
List<Stargazers> star = connect.repo("xiaowei_zxw", "JSDX-JwSystem");
2.源码分析
前面已经了解到Feign是使用的java接口代理的方式为我们生成了代理对象FeignInvocationHandler
2.1 FeignInvocationHandler
对于java的接口代理,我们只需实现接口InvocationHandler
即可
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的源码就分析的差不多了,总得来说就是解析类上的注解,生成对应的请求信息,然后通过接口生成代理对象并执行远程调用方法。