Spring Cloud OpenFeign呼叫流程

馮文議發表於2022-11-22

上一節給大家分享了Spring Cloud OpenFeign的啟動流程,接下來給大家分享一下呼叫流程。話不多說,我們們直接開始。

影片:https://www.bilibili.com/video/BV1A84y1C7XD/

呼叫流程

xxxFeignClient

→ feign.ReflectiveFeign.FeignInvocationHandler#invoke

→ feign.InvocationHandlerFactory.MethodHandler#invoke

→ feign.SynchronousMethodHandler#invoke

→ feign.SynchronousMethodHandler#executeAndDecode

→ org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient#execute

→ feign.Client.Default#execute

→ feign.AsyncResponseHandler#handleResponse

動態代理

feign.ReflectiveFeign.FeignInvocationHandler#invoke

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  return dispatch.get(method).invoke(args);
}

這裡說一下 dispatch 屬性,它的型別是Map<Method, MethodHandler>意思是,可以透過方法找到對應的Handler,這樣就可以進入到 SynchronousMethodHandler#invoke。

feign.SynchronousMethodHandler#executeAndDecode

從這個方法的名稱也能看出來,這個是執行請求,並且實現解碼的功能,這是一個核心的方法。

負載均衡

org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient#execute

這個是實現均衡,實現將URL中服務名轉成 真實的IP。

下面我們看看它是如何被自動注入的。

首先在 spring.factories 檔案中,做了配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration

FeignLoadBalancerAutoConfiguration 中引入 DefaultFeignLoadBalancerConfiguration

@ConditionalOnClass(Feign.class)
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@AutoConfigureBefore(FeignAutoConfiguration.class)
@AutoConfigureAfter({ BlockingLoadBalancerClientAutoConfiguration.class, LoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties(FeignHttpClientProperties.class)
@Configuration(proxyBeanMethods = false)
// Order is important here, last should be the default, first should be optional
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class,
        HttpClient5FeignLoadBalancerConfiguration.class, DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {

}

new FeignBlockingLoadBalancerClient,並且注入到 Spring Bean 中

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
class DefaultFeignLoadBalancerConfiguration {

    @Bean
    @ConditionalOnMissingBean
    @Conditional(OnRetryNotEnabledCondition.class)
    public Client feignClient(LoadBalancerClient loadBalancerClient,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient,
                loadBalancerClientFactory);
    }

}

Http請求

下面我們看看 feign 是如何實現 Http 請求的。

feign.Client.Default#execute

@Override
public Response execute(Request request, Options options) throws IOException {
  HttpURLConnection connection = convertAndSend(request, options);
  return convertResponse(connection, request);
}

主要就是在這個方法中,預設使用 jdk 實現 http請求。

convertAndSend,這個方法做了兩件事,一是,開啟 Http 連線,獲取到 HttpURLConnection ,並設定相關屬性;二是,如果有引數,就透過輸出流(OutputStream)寫入引數。

convertResponse,這個方法返回的是 feign.Response ,我們它有哪些屬性:

public final class Response implements Closeable {

  private final int status;
  private final String reason;
  private final Map<String, Collection<String>> headers;
  private final Body body;
  private final Request request;
  private final ProtocolVersion protocolVersion;

}

首先,這裡實現 Closeable 介面,所以必然有 close 方法,我們看一下:

@Override
public void close() {
  Util.ensureClosed(body);
}

好了,明白了,body實際上是寫入流(InputStream)。

總結一下:這裡實現了Http請求,上傳了引數,或獲得了輸入流。

Http響應處理

看完了請求,我們再回到 feign.SynchronousMethodHandler#executeAndDecode,看下面的程式碼

CompletableFuture<Object> resultFuture = new CompletableFuture<>();
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
    metadata.returnType(),
    elapsedTime);

try {
  if (!resultFuture.isDone())
    throw new IllegalStateException("Response handling not done");

  return resultFuture.join();
} catch (CompletionException e) {
  Throwable cause = e.getCause();
  if (cause != null)
    throw cause;
  throw e;
}

這裡是透過 CompletableFuture,來裝配響應結果。

feign.AsyncResponseHandler#handleResponse,這個方法就也就是處理Http響應結果的入口。

比如要判斷狀態碼,獲取結果,關閉輸入流等。

響應結果解碼

解碼流程如下:

feign.AsyncResponseHandler#decode

→ org.springframework.cloud.openfeign.support.ResponseEntityDecoder#decode

→ org.springframework.cloud.openfeign.support.SpringDecoder#decode

→ org.springframework.cloud.openfeign.support.SpringDecoder.FeignResponseAdapter#FeignResponseAdapter

→ org.springframework.web.client.HttpMessageConverterExtractor#extractData

→ org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#read

為什麼需要解碼呢?

http響應的結果型別是String,而我們需要的是一個物件,比如:

@FeignClient(
        value = "openfeign-goods-service",
        path = "/goods"
)
public interface IGoodsFeignClient {

    @GetMapping("/list")
    ResultTemplate<ListTemplate<GoodsModel>> list();

}

我是 Erwin Feng,一個專注於高質量程式設計的開發者。如果你對我內容感興趣,可以關注我的微信公眾號【Erwin Feng】,可以第一時間收到更新推送!

相關文章