專欄系列文章:SpringCloud系列專欄java
系列文章:web
SpringCloud 源碼系列(1)— 註冊中心Eureka 之 啓動初始化apache
SpringCloud 源碼系列(2)— 註冊中心Eureka 之 服務註冊、續約markdown
SpringCloud 源碼系列(3)— 註冊中心Eureka 之 抓取註冊表網絡
SpringCloud 源碼系列(4)— 註冊中心Eureka 之 服務下線、故障、自我保護機制app
SpringCloud 源碼系列(5)— 註冊中心Eureka 之 EurekaServer集羣負載均衡
SpringCloud 源碼系列(6)— 註冊中心Eureka 之 總結篇ide
SpringCloud 源碼系列(7)— 負載均衡Ribbon 之 RestTemplate源碼分析
SpringCloud 源碼系列(8)— 負載均衡Ribbon 之 核心原理post
SpringCloud 源碼系列(9)— 負載均衡Ribbon 之 核心組件與配置
SpringCloud 源碼系列(10)— 負載均衡Ribbon 之 HTTP客戶端組件
SpringCloud 源碼系列(11)— 負載均衡Ribbon 之 重試與總結篇
SpringCloud 源碼系列(12)— 服務調用Feign 之 基礎使用篇
SpringCloud 源碼系列(13)— 服務調用Feign 之 掃描@FeignClient註解接口
SpringCloud 源碼系列(14)— 服務調用Feign 之 構建@FeignClient接口動態代理
前一篇文章已經分析出,最終在 Feign.Builder
的 build()
方法構建了 ReflectiveFeign
,而後利用 ReflectiveFeign 的 newInstance
方法建立了動態代理。這個動態代理的代理對象是 ReflectiveFeign.FeignInvocationHandler
。最終來講確定就會利用 Client
進行負載均衡的請求。這節就來看看 Feign 若是利用動態代理髮起HTTP請求的。
使用 FeignClient 接口時,注入的實際上是動態代理對象,調用接口方法時就會進入執行器 ReflectiveFeign.FeignInvocationHandler
,從 FeignInvocationHandler 的 invoke
方法能夠看出,就是根據 method 獲取要執行的方法處理器 MethodHandler
,而後執行方法。MethodHandler 的實際類型就是 SynchronousMethodHandler
。
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;
FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//...
// 根據 method 獲取 MethodHandler,而後執行方法
return dispatch.get(method).invoke(args);
}
}
複製代碼
接着看 SynchronousMethodHandler 的 invoke 方法,核心邏輯就兩步:
RequestTemplate
,就是處理 URI 模板、參數,好比替換掉 uri 中的佔位符、拼接參數等。executeAndDecode
執行請求,並將相應結果解碼返回。public Object invoke(Object[] argv) throws Throwable {
// 構建請求模板,例若有 url 參數,請求參數之類的
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
// 執行並解碼
return executeAndDecode(template, options);
} catch (RetryableException e) {
// 重試,默認是從不重試
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
continue;
}
}
}
複製代碼
能夠看到,通過處理後,URI 上的佔位符就被參數替換了,而且拼接了請求參數。
接着看 executeAndDecode
,主要有三步:
targetRequest
方法,主要就是遍歷 RequestInterceptor
對請求模板 RequestTemplate 定製化,而後調用 HardCodedTarget
的 target
方法將 RequestTemplate 轉換成 Request
請求對象,Request 封裝了請求地址、請求頭、body 等信息。LoadBalancerFeignClient
,這裏就進入了負載均衡請求了。decoder
來解析響應結果,將結果轉換成接口的返回類型。Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
// 處理RequestTemplate,獲得請求對象 Request
Request request = targetRequest(template);
Response response;
try {
// 調用 client 執行請求,client => LoadBalancerFeignClient
response = client.execute(request, options);
// 構建響應 Response
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
//...
}
if (decoder != null) {
// 使用解碼器解碼,將返回數據轉換成接口的返回類型
return decoder.decode(response, metadata.returnType());
}
//....
}
// 應用攔截器處理 RequestTemplate,最後使用 target 從 RequestTemplate 中獲得 Request
Request targetRequest(RequestTemplate template) {
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
// target => HardCodedTarget
return target.apply(template);
}
複製代碼
HardCodedTarget
是硬編碼寫死的,咱們沒有辦法定製化,看下它的 apply
方法,主要就是處理 RequestTemplate 模板的地址,生成完成的請求地址。最後返回 Request 請求對象。
public Request apply(RequestTemplate input) {
if (input.url().indexOf("http") != 0) {
// url() => http://demo-producer
// input.target 處理請求模板
input.target(url());
}
return input.request();
}
複製代碼
能夠看到通過 HardCodedTarget 的 apply 方法以後,就拼接上了 url 前綴了。
LoadBalancerFeignClient
是 Feign 實現負載均衡核心的組件,是 Feign 網絡請求組件 Client
的默認實現,LoadBalancerFeignClient 最後是使用 FeignLoadBalancer
來進行負載均衡的請求。
看 LoadBalancerFeignClient 的 execute
方法,從這裏到後面執行負載均衡請求,其實跟分析 Ribbon 源碼中 RestTemplate 的負載均衡請求都是相似的了。
FeignLoadBalancer.RibbonRequest
。注意 RibbonRequest 第一個參數 Client 就是設置的 LoadBalancerFeignClient 的代理對象,啓用 apache httpclient 時,就是 ApacheHttpClient
。Ribbon 的客戶端配置對 Feign 一樣生效
。FeignLoadBalancer
,而後執行負載均衡請求。public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
// 客戶端名稱:demo-producer
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
// 封裝 ClientRequest => FeignLoadBalancer.RibbonRequest
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
// 客戶端負載均衡配置 ribbon.demo-producer.*
IClientConfig requestConfig = getClientConfig(options, clientName);
// lbClient => 負載均衡器 FeignLoadBalancer,執行負載均衡請求
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
//...
}
}
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}
複製代碼
進入 executeWithLoadBalancer
方法,這就跟 Ribbon 源碼中分析的是同樣的了,最終就驗證了 Feign 基於 Ribbon 來作負載均衡請求。
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
// 負載均衡器執行命令
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
// 用Server的信息重構URI地址
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
// 實際調用 LoadBalancerFeignClient 的 execute 方法
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
//....
}
}
複製代碼
重構URI後,實際是調用 FeignLoadBalancer 的 execute
方法來執行最終的HTTP調用的。看下 FeignLoadBalancer 的 execute 方法,最終來講,就是使用代理的HTTP客戶端來執行請求。
默認狀況下,就是 Client.Default
,用 HttpURLConnection 執行HTTP請求;啓用了 httpclient 後,就是 ApacheHttpClient;啓用了 okhttp,就是 OkHttpClient。
這裏有一點須要注意的是,FeignClient 雖然能夠配置超時時間,但進入 FeignLoadBalancer 的 execute 方法後,能夠看到會用 Ribbon 的超時時間覆蓋 Feign 配置的超時時間
,最終以 Ribbon 的超時時間爲準。
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) throws IOException {
Request.Options options;
if (configOverride != null) {
// 用 Ribbon 的超時時間覆蓋了feign配置的超時時間
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options(override.connectTimeout(this.connectTimeout),
override.readTimeout(this.readTimeout));
}
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
// request.client() HTTP客戶端對象
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
複製代碼
關於Ribbon的源碼分析請看前面 Ribbon 相關的文章,Ribbon 如何從 eureka 註冊中心獲取 Server 就再也不分析了。
下面這張圖總結了 Feign 負載均衡請求的流程:
@FeignClient
註解的接口,並生成代理類注入到容器中。咱們注入 @FeignClient 接口時其實就是注入的這個代理類。ReflectiveFeign.FeignInvocationHandler
的 invoke
方法執行請求。SynchronousMethodHandler
,而後調用它的 invoke
方法執行請求。RequestTemplate
,這個時候會處理參數中的佔位符、拼接請求參數、處理body中的參數等等。Request
,在轉換的過程當中:
RequestInterceptor
處理請求模板,所以咱們能夠自定義攔截器來定製化 RequestTemplate。Target(HardCodedTarget)
處理請求地址,拼接上服務名前綴。request
方法獲取到 Request 對象。execute
方法來執行請求並獲得請求結果 Response
:
FeignLoadBalancer
,而後就執行負載均衡請求。AbstractLoadBalancerAwareClient,executeWithLoadBalancer
方法中,會先構建一個 LoadBalancerCommand,而後提交一個 ServerOperation。Response
。Decoder
解析響應結果,返回接口方法定義的返回類型。負載均衡獲取Server的核心組件是 LoadBalancerClient
,具體的源碼分析能夠參考 Ribbon 源碼分析的兩篇文章。LoadBalancerClient 負載均衡的原理能夠看下面這張圖。