前言
書接上文,feign接口是如何注冊到容器想必已然清楚,現在我們着重關心一個問題,feign調用服務的時候是如何抉擇的?上一篇主要是從讀源碼的角度入手,后續將會逐步從軟件構架方面進行剖析。
一、ReflectiveFeign.FeignInvocationHandler
從上文知道feign接口調用實質上是調用的對應的動態代理接口的InvocationHandler,跟蹤源碼發現默認的InvocationHandler實現就是FeignInvocationHandler。現在我們看一下這個FeignInvocationHandler.invoke(...)方法。
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();
}
// dispath 是緩存的method 以及 method對應的MethodHandler
return dispatch.get(method).invoke(args);
}
從代碼中可以看到他是直接從緩存中拿到對應的MethodHandler,然后調用的MethodHandler的invoke方法。我們看一下MethodHandler都有哪些實現:
可以看到就兩個實現, DefaultMethodHandler處理的是feign接口中的Default修飾的方法。我們調用的遠程接口用的是SynchronousMethodHandler實現。那么可以看到我們最終對feing接口的某個方法的調用實際上調用的是SynchronousMethodHandler.invoke(...)方法。跟蹤代碼發現,最終調用的是SynchronousMethodHandler持有的Client的實例的execute方法。那么我們看一下Client都有那些實現:
這里跟蹤SynchronousMethodHandler的創建過程發現Client的創建是按照如下邏輯進行的(FeignClientFactoryBean.loadBalance):
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
從上述代碼可以看到,他從context中獲取到client,然后通過client獲取執行。想必feign肯定是自動裝備了一個Client,我們看一下他的默認配置:
顯然配置必定是從FeignAutoConfiguration 或者 FeignRibbonClientAutoConfiguration進行配置的,查看這兩個類最終發現Client是通過FeignRibbonClientAutoConfiguration進行注入的(通過@Import引入的DefaultFeignLoadBalancedConfiguration進行注入):
@Configuration
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
clientFactory);
}
}
所以我們調用feign接口的某一個方法,最終調用的LoadBalancerFeignClient.execute()方法。那么負載均衡相關邏輯應該是在此接入的。
二、LoadBalancerFeignClient 做了些什么
先看核心代碼,注意注釋部分:
public Response execute(Request request, Request.Options options) throws IOException {
try {
// URL 處理
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
// 獲取調用服務配置
IClientConfig requestConfig = getClientConfig(options, clientName);
// 創建負載均衡客戶端,執行請求
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
從上面的代碼可以看到,lbClient(clientName) 創建了一個負載均衡的客戶端,它實際上就是生成的如下所述的類:
public class FeignLoadBalancer extends
AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse>
熟悉ribbon的朋友應該知道AbstractLoadBalancerAwareClient 就是Ribbon負載均衡調用的父類。具體的負載均衡實現策略,下一章在詳細描述。至此我們可以得出結論:feign集成負載均衡是通過將FeignLoadBalancer作為調用feign接口的實際執行者,從而達到負載均衡的效果。可以看到這里與Ribbon高度的解耦,相當於我們獲取了服務名、調用地址、調用參數后,最終交由一個執行器去調用。執行器並不關心參數從何而來,這里基於Ribbon提供的執行器實現只是更具傳遞的服務名找到了一個正確的實例去調用而已。
三 、 小結
至此我們可以看到初步職責划分: 代理對象、請求與響應解析、執行器三個職能部門。
- 代理對象職責是: 將feign接口中方法的調用轉接到對FeignInvocationHandler的invoke調用,在invoke函數中通過方法名稱找到對應的SynchronousMethodHandler。
- 執行器: 負責根據請求的方法解析出調用信息(調用服務名、調用地址、調用參數)然后發起調用,他不關心參數是如何來的
一個系統的好壞、可擴展性高低很大程序上取決於系統中各職能部門划分是否清晰以及各個職能部分的權限是否越界。