天天看點

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,這些暫時都還沒去深入的看。

後續看看有沒有探讨和深入的價值。