上一節給大家分享了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】,可以第一時間收到更新推送!