“不积跬步,无以至千里。”
前面说了,FeignClientFactoryBean的getObject()方法返回的就是一个类型为FeignInvocationHandler的代理对象
所有,一旦Controller 中调用feign接口的方法,实际调用的就是这个动态代理,有点尝试的都知道,对于动态代理对象的方法调用,都会经过其绑定的InvocationHandler 的invoke()方法来处理
private final Map<Method, MethodHandler> dispatch;
@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);
}
首先经过一系列的方法判断,看看是不是“equals”,“hashCode”,“toString”方法,我们的feign接口方法当然不是这些啦
dispatch.get(method).invoke(args)
这个dispatch 是一个Map,key是方法Method对象,value就是SynchronousMethodHandler ,方法处理组件,里面包含处理方法的所有零散组件
所以这行代码最终会调用到SynchronousMethodHandler 的invoke() 方法
@Override
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) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
RequestTemplate template = buildTemplateFromArgs.create(argv);
argv,就是方法的参数
之前通过SpringMvcContract解析出来的是
GET /test_1/{param} HTTP/1.1 这种格式
GET /test_1/zhangsan HTTP/1.1 现在得到的是实际的restTemplate了,用实参替换了形参,这个create解析的过程,就不看了,体力活,一大坨代码,抓大放小
然后就去调用了executeAndDecode()方法,把这个template 作为参数传入
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 {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
}
... ...
}
}
}
首先调用了一个targetRequest()方法,把之前解析好的RestTemplate 传进去
Request targetRequest(RequestTemplate template) {
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
return target.apply(new RequestTemplate(template));
}
遍历请求拦截器,将每个请求拦截器都应用到RequestTemplate 请求模板上面去,也就是让每个请求拦截器对请求进行处理
target.apply(new RequestTemplate(template))
然后基于target 构建一个请求的Request 并把它返回
这个target 是啥,不就是HardCodedTarget 吗,里面包含了目标服务的信息(服务名),这块忘记了可以翻翻前几篇文章
response = client.execute(request, options);
这个client是一个LoadBalancerFeignClient类型的实例,在这里调用execute()方法并将请求的参数传入了进去
options,我们没有自定义这个options,使用的是默认的,连接超时时间是10000ms,读超时时间是60000ms

接着看,由LoadBalancerFeignClient 的execute 来处理代理请求,这个一看名字就是跟ribbon有一腿,还LoadBalancerXXX…
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
这个asUri 就是拿到请求的url,目前来看是这样子
http://springboot-project-2/test_1/zhangsan
也就是说还是服务名称,没有替换上ip和port
String clientName = asUri.getHost();
这里拿到服务名
URI uriWithoutHost = cleanUrl(request.url(), clientName);
这里是把上面的asUri的serviceName给扣掉了,最终得到的是这样子
http:///test_1/zhangsan
一通操作之后,IClientConfig requestConfig = getClientConfig(options, clientName);
这又是干嘛呢?实际上就是搞了一个所谓的IClientConfig ,马上要用ribbon的负载均衡器来选取目标server了,所以这里猜想,只能是跟ribbon相关的配置
lbClient(clientName)
接着就是根据服务名称,去获取对应的FeignLoadBalancer
ok,这篇文章就先到这里,下一讲来看下这个FeignLoadBalancer 是怎么获取的,然后怎么跟ribbon整合将请求url中的服务名称替换成实际的ip和port,拭目以待!
本文深入剖析了Feign客户端的工作原理,从FeignInvocationHandler的invoke方法开始,详细解释了请求的构建过程,包括RequestTemplate的创建、SynchronousMethodHandler的invoke方法调用,以及如何通过LoadBalancerFeignClient执行请求。文章还揭示了LoadBalancerFeignClient如何利用Ribbon进行负载均衡,并预告了下一步将探讨如何将服务名称替换为实际的IP和Port。
650

被折叠的 条评论
为什么被折叠?



