1.前言
从第一篇和第二篇中,可以看到Feign最终会为每个带有@FeignClient
注解的interface生成一个JDK代理对象。那么在在通过feign进行远程调用时,一定会走到这个类的invoke
方法中去。所以接下来我们探究一下invoke
方法究竟在干什么~~
2.FeignInvocationHandler的invoke方法
@Override
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);
}
2.1 那么这个dispatch是什么呢?
这是我自己debug出来的dispatch
中的信息。其实就是关于接口中每个方法的信息还有ReflectiveFeign
所包含的信息。
2.2 dispatch怎么来的呢?
其实就是在ReflectiveFeign
的newInstance
方法中构建并传入代理对象的InvocationHanlder
的一组Map<Method,MethodHandler>
。
下面是nameToHandler获取与结构图
这里其实我们就可以看出,每个Feign的JDK代理对象的InvocationHanlder中都包含一个SynchronousMethodHandler
的集合。我们想要获取接口方法的任何信息都可以通过SynchronousMethodHandler
来完成。
那么我们就能知道dispatch.get(method)
获取的就是一个SynchronousMethodHandler
。
接下来就是SynchronousMethodHandler
的invoke
调用了。
3.SynchronousMethodHandler的invoke方法
public Object invoke(Object[] argv) throws Throwable {
// 这个就是通过参数和方法的信息构建请求链接和请求参数信息
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
// (核心) 执行请求并解码结果
return executeAndDecode(template);
} catch (RetryableException e) {
try {
// 重试间隔计算与睡眠(sleep),如果超过次数,直接抛出异常
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.1 RequestTemplate http请求构建模板
RequestTemplate中包含头信息,queryString参数,请求方法,请求体,uri信息等等。这基本都是发送http请求需要的基础信息。
3.2 executeAndDecode 执行请求并解码返回结果
Object executeAndDecode(RequestTemplate template) throws Throwable {
// 将RequestTemplate储存的请求信息转化为Request
// 对了SynchronousMethodHandler中的拦截器也得加到request中
// 其实这里的拦截器并不是http请求的拦截器,这个拦截器的作用其实是对RequestTemplate的
// 毕竟这个Request是通过RequestTemplate创建的
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
// 通过LoadBalancerFeignClient来继续往下执行
response = client.execute(request, options);
} catch (IOException 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()) {
// 返回结果直接为Reponse的
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());
}
}
}
上面我们需要关注的其实就两个点:
-
response = client.execute(request, options)
,将请求交给LoadBalancerFeignClient
进行处理 -
Object result = decode(response)
,对返回结果进行解码
3.3 LoadBalancerFeignClient的execute做了啥?
- 拿出服务名,清理url地址
- 构建
FeignLoadBalancer.RibbonRequest
- 获取
FeignLoadBalancer
去执行executeWithLoadBalancer
那现在主要研究一下executeWithLoadBalancer
这个方法,这个方法很关键,而且也比较比较复杂。
FeignLoadBalancer
的executeWithLoadBalancer
方法在其父类AbstractLoadBalancerAwareClient
中。
3.4 AbstractLoadBalancerAwareClient的executeWithLoadBalancer来自rxjava的降维打击
其实在真正看到这里之前,我一直都以为feign走的是ribbon+restTemplate的那套逻辑。直到看到了
AbstractLoadBalancerAwareClient
的executeWithLoadBalancer
方法。
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
// 构建一个LoadBalancerCommand,将LoadBalancerContext,request,requestConfig都丢到
// 这个对象中,而LoadBalancerContext中就包含了ILoadBalancer,也就是
// DyanmicServerListLoadBalancer
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
// 传递进去一个匿名对象
new ServerOperation<T>() {
@Override
// 这个是在LoadBalancerCommand中会被调用
// 在LoadBalancerCommand中,会通过ILoadBalancer去选择一个服务
// 然后传递到这个方法中去
// 所以feign与ribbon的结合,以及ribbon的服务选择在
// LoadBalancerCommand中
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}
}
在这个方法和LoadBalancerCommand
的submit
方法中,存在一些Rxjava的类的使用,这个我还没有研究过,所以暂时只能先跳过,挑重点看了。
简单看一下LoadBalancerCommand
方法的submit
方法,看看如何选择服务的。
深入看一下selectServer
方法
继续跟踪getServerFromLoadBalancer
ok,到此结束。
ribbon按照自己的策略选择了服务。
3.5 AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig) 将通过ribbon获取的服务进行http请求发送
具体的调用链路为:
- AbstractLoadBalancerAwareClient.this.execute
- org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute
- feign.Client.Default#execute
- feign.Client.Default#convertAndSend
最后直接用HttpURLConnection 直接撸了消息发送。这也是我始料未及的。
3.6 补个漏 解码器解码
4.小结
今天看了在进行远程Feign调用时,会通过注入的JDK代理对象调用FeignInvocationHandler
的invoke
方法,然后一路最终到进行http请求发送。
现在简单描述一下整个流程和核心组件:
- feign请求首先进入
FeignInvocationHandler
的invoke方法,invoke进行方法处理器的选择 FeignInvocationHandler
通过invoke方法选择出对应方法的SynchronousMethodHandler
进行处理SynchronousMethodHandler
构建请求模板RequestTemplate
和请求Request
SynchronousMethodHandler
将构建的请求交给LoadBalancerFeignClient
LoadBalancerFeignClient
构建RibbonRequest
,将请求交给FeignLoadBalancer
进行处理FeignLoadBalancer
构建LoadBalancerCommand
LoadBalancerCommand
通过submit
方法去调用ILoadBalancer.chooseServer(key)
去获取serverLoadBalancerCommand
将获取的server交给FeignLoadBalancer
FeignLoadBalancer
将server和请求交给Client
Client
通过HttpURLConnection
去创建连接并发送请求
至此,feign的请求的发送链路就算看完了。
其实里面还有关于Encoder如果组装头信息,queryString,消息体;
Contract如果将springmvc的注解进行解析并生成对应的url,这些暂时都还没去深入的看。
后续看看有没有探讨和深入的价值。