天天看点

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

1.前言

从第一篇和第二篇中,可以看到Feign最终会为每个带有

@FeignClient

注解的interface生成一个JDK代理对象。那么在在通过feign进行远程调用时,一定会走到这个类的

invoke

方法中去。所以接下来我们探究一下

invoke

方法究竟在干什么~~

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

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

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

这是我自己debug出来的

dispatch

中的信息。其实就是关于接口中每个方法的信息还有

ReflectiveFeign

所包含的信息。

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

2.2 dispatch怎么来的呢?

其实就是在

ReflectiveFeign

newInstance

方法中构建并传入代理对象的

InvocationHanlder

的一组

Map<Method,MethodHandler>

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

下面是nameToHandler获取与结构图

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

这里其实我们就可以看出,每个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请求构建模板

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

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

这个方法,这个方法很关键,而且也比较比较复杂。

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

方法,看看如何选择服务的。

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

深入看一下

selectServer

方法

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

继续跟踪

getServerFromLoadBalancer

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

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
    Feign之远程JDK代理对象请求发送链路追踪(三)
    最后直接用HttpURLConnection 直接撸了消息发送。这也是我始料未及的。

3.6 补个漏 解码器解码

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

4.小结

今天看了在进行远程Feign调用时,会通过注入的JDK代理对象调用

FeignInvocationHandler

invoke

方法,然后一路最终到进行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,这些暂时都还没去深入的看。

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