作用
MethodHandler从字面意思理解这是个方法管理器
/**
* Like {@link InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])}, except for a
* single method.
*/
interface MethodHandler {
Object invoke(Object[] argv) throws Throwable;
}
如注释所言,如果熟悉java.lang.reflect.InvocationHandler#invoke。大概就能明白。这是为了执行一个方法而预备的组件。
那么里面就应该包含了我们通过执行HTTP请求从而完成一个预定义的方法的逻辑。
SynchronousMethodHandler
这个类是Feign对MethodHandler的默认实现。参数太多,多以通过Factory工厂类去创建。
Factory(Client client, Retryer retryer, List<RequestInterceptor> requestInterceptors,
Logger logger, Logger.Level logLevel, boolean decode404) {
this.client = checkNotNull(client, "client");
this.retryer = checkNotNull(retryer, "retryer");
this.requestInterceptors = checkNotNull(requestInterceptors, "requestInterceptors");
this.logger = checkNotNull(logger, "logger");
this.logLevel = checkNotNull(logLevel, "logLevel");
this.decode404 = decode404;
}
这里需要为其提供6个属性,既图中红色部分的组件。大致能猜出它们的作用。
- Client
代表的是真正去执行HTTP请求的组件。他可以是OkHttpClient,也可以是Apache的HttpClient。草草观察Feign的默认实现Client.Default类可以看出其逻辑就是通过传入的Request相关参数,打开一个HttpURLConnection,执行完毕后,转换结果为Response对象,关闭连接。 - Retryer
当我们的http请求通过client执行后,如果出现RetryableException。
由Retryer这个组件负责决定是否按规定逻辑重新执行,或者抛出异常,中断执行。
因为每个客户端发起的HTTP请求执行的情况各个不同,所以我们必须为每一个的http请求单独生成一个Retryer的实例,保存相关信息。这里使用了Clone,实现原型模式。 - RequestInterceptor
为每个请求执行前加入拦截器的逻辑 - Logger
Logger虚类自己实现了如下几个方法,你可以从名字和代码知道他们大概是在什么时候会被调用,用来记录日志。
logRequest
logRetry
logAndRebufferResponse
logIOException
另外留下了一个log方法给子类自己去实现记录日志的场景逻辑。 - decode404
这是个布尔值,是否反编码404?如果没看使用说明你也能猜到对于执行HTTP请求后得到404的情况,Feign提供了特殊的选项,其实就是就是抛出异常还是返回null的区别。
总结以上几个组件的作用。他们在一起的作用是“执行HTTP请求”。所以我们传给SynchronousMethodHandler.Factory的改造参数是告诉怎么去“执行HTTP请求”。
invoke方法
这个invoke方法就是我们真正调用某个接口的某个方法的时候,去执行的完整逻辑,只不过最后这个逻辑会被嵌入动态代理生成的类中而已。准备这个方法所需要的组件简直就是山路十八弯,但是这些组件最后被使用,就这么点 代码……
@Override
public Object invoke(Object[] argv) throws Throwable {
//这个RequestTemplate可以在每次调用时创建一个Request。
RequestTemplate template = buildTemplateFromArgs.create(argv);
//Retryer对于每次调用都需要一个单独的重试计数器。
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
Object executeAndDecode(RequestTemplate template) 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);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).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);
boolean shouldClose = true;
try {
if (logLevel != Logger.Level.NONE) {
response =
logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();
}
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 {
return decode(response);
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
return decode(response);
} 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());
}
}
}