Feign之远程JDK代理对象请求发送链路追踪(三)

1.前言

从第一篇和第二篇中,可以看到Feign最终会为每个带有@FeignClient注解的interface生成一个JDK代理对象。那么在在通过feign进行远程调用时,一定会走到这个类的invoke方法中去。所以接下来我们探究一下invoke方法究竟在干什么~~
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i8jvhPrX-1627029519465)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/87e01ccc-47ac-48dd-9447-6c47d28c3234/Untitled.png)]



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是什么呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hpppz6Pq-1627029519467)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/1997d8b7-ae62-49fe-85ee-32ebf50380e2/Untitled.png)]

这是我自己debug出来的dispatch中的信息。其实就是关于接口中每个方法的信息还有ReflectiveFeign所包含的信息。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NIo53zhn-1627029519469)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/1e9b94a4-c426-4988-b690-44192787479b/Untitled.png)]

2.2 dispatch怎么来的呢?

其实就是在ReflectiveFeignnewInstance方法中构建并传入代理对象的InvocationHanlder的一组Map<Method,MethodHandler>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-la5QwnmM-1627029519470)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/5f344dc3-f993-4d39-8629-136849161a3e/Untitled.png)]

下面是nameToHandler获取与结构图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LhFLQHRk-1627029519472)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d73c53a1-76a8-4973-bf64-a5c2ba6cad62/Untitled.png)]

这里其实我们就可以看出,每个Feign的JDK代理对象的InvocationHanlder中都包含一个SynchronousMethodHandler的集合。我们想要获取接口方法的任何信息都可以通过SynchronousMethodHandler来完成。

那么我们就能知道dispatch.get(method)获取的就是一个SynchronousMethodHandler

接下来就是SynchronousMethodHandlerinvoke调用了。



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请求构建模板

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oI8XKMmC-1627029519474)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/82b7a4c9-ac0f-49d5-aad9-fb36d17230c8/Untitled.png)]

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());
      }
    }
  }

上面我们需要关注的其实就两个点:

  1. response = client.execute(request, options),将请求交给LoadBalancerFeignClient进行处理

  2. Object result = decode(response),对返回结果进行解码


3.3 LoadBalancerFeignClient的execute做了啥?

  1. 拿出服务名,清理url地址
  2. 构建FeignLoadBalancer.RibbonRequest
  3. 获取FeignLoadBalancer去执行executeWithLoadBalancer

那现在主要研究一下executeWithLoadBalancer这个方法,这个方法很关键,而且也比较比较复杂。

FeignLoadBalancerexecuteWithLoadBalancer方法在其父类AbstractLoadBalancerAwareClient中。


3.4 AbstractLoadBalancerAwareClient的executeWithLoadBalancer来自rxjava的降维打击

其实在真正看到这里之前,我一直都以为feign走的是ribbon+restTemplate的那套逻辑。直到看到了

AbstractLoadBalancerAwareClientexecuteWithLoadBalancer方法。

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);
            }
        }
        
    }

在这个方法和LoadBalancerCommandsubmit方法中,存在一些Rxjava的类的使用,这个我还没有研究过,所以暂时只能先跳过,挑重点看了。

简单看一下LoadBalancerCommand方法的submit方法,看看如何选择服务的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l9YkqnJm-1627029519475)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/7b50d8cd-ab13-41aa-9b97-e2da01d95d59/Untitled.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5gUe60Vv-1627029519477)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/8ba518aa-7436-4d91-95a4-06924e4cdbd1/Untitled.png)]

深入看一下selectServer方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iszqwtmY-1627029519479)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/6ac34dcb-8516-4b6b-b6c3-c145ccd36f72/Untitled.png)]

继续跟踪getServerFromLoadBalancer
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YswOqH6G-1627029519481)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/28b5e525-2991-48ae-b424-862fc5526625/Untitled.png)]
ok,到此结束。

ribbon按照自己的策略选择了服务。


3.5 AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig) 将通过ribbon获取的服务进行http请求发送

具体的调用链路为:

  1. AbstractLoadBalancerAwareClient.this.execute
  2. org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute
  3. feign.Client.Default#execute
  4. feign.Client.Default#convertAndSend
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4IV2Uvcf-1627029519482)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/ecf23b6c-ab33-4727-9e67-9d7dd6cb8c98/Untitled.png)]
    最后直接用HttpURLConnection 直接撸了消息发送。这也是我始料未及的。

3.6 补个漏 解码器解码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zKwRiFEd-1627029519484)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/9a20dd80-69cb-4163-8de7-25f85445b67f/Untitled.png)]



4.小结

今天看了在进行远程Feign调用时,会通过注入的JDK代理对象调用FeignInvocationHandlerinvoke方法,然后一路最终到进行http请求发送。

现在简单描述一下整个流程和核心组件:

  1. feign请求首先进入FeignInvocationHandler的invoke方法,invoke进行方法处理器的选择
  2. FeignInvocationHandler通过invoke方法选择出对应方法的SynchronousMethodHandler进行处理
  3. SynchronousMethodHandler 构建请求模板RequestTemplate和请求Request
  4. SynchronousMethodHandler 将构建的请求交给LoadBalancerFeignClient
  5. LoadBalancerFeignClient构建RibbonRequest,将请求交给FeignLoadBalancer进行处理
  6. FeignLoadBalancer构建LoadBalancerCommand
  7. LoadBalancerCommand通过submit方法去调用ILoadBalancer.chooseServer(key)去获取server
  8. LoadBalancerCommand将获取的server交给FeignLoadBalancer
  9. FeignLoadBalancer将server和请求交给Client
  10. Client 通过HttpURLConnection 去创建连接并发送请求

至此,feign的请求的发送链路就算看完了。

其实里面还有关于Encoder如果组装头信息,queryString,消息体;

Contract如果将springmvc的注解进行解析并生成对应的url,这些暂时都还没去深入的看。

后续看看有没有探讨和深入的价值。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

欢谷悠扬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值