一、介紹
一個普通的java接口,加上FeignClient註解,就可以完成整合hystrix,ribbon的http調用,使用上很是方便,也感覺很神奇,內部實現有必要探究一下。
二、問題
- 第一個問題:被@FeignClient標註的類,在spring中最終注入的是什麼?
- 第二個問題:如何跟hystrix結合的?
- 第三個問題:如何跟ribbon結合的?
- 第四個問題:調用過程?
帶着這四個問題,看下下面的實現
三、實現
一切都從EnableFeignClients這個註解開始
-
入口,EnableFeignClients的部分代碼如下:
@Import(FeignClientsRegistrar.class) public @interface EnableFeignClients {
也就是說,EnableFeignClients會Import FeignClientsRegistrar,Import註解是spring3.0就有的註解,其作用是引入配置類。
參考FeignClientsRegistrar定義:
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware {
FeignClientsRegistrar實現了ImportBeanDefinitionRegistrar,而ImportBeanDefinitionRegistrar從spring3.1就有了,其作用是增加額外的bean定義,同時可以使用@Configuration的配置項。
也就是說FeignClientsRegistrar能夠使用相關配置項向spring註冊被@FeignClient標註的bean,其重要的方法如下:
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerDefaultConfiguration(metadata, registry); registerFeignClients(metadata, registry); }
上面的代碼就是整個入口了,首先注入默認配置,接着注入FeignClient,注入FeignClient時同樣會注入用戶自己的配置。
-
工廠
接着上面的代碼段-registerFeignClients,其部分簡化如下:
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); ...中間省略... BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }
其中關鍵的部分就是向spring注入了一個工廠bean-FeignClientFactoryBean,bean的類型就是被@FeignClient標註的我們自己定義的業務類。
也就是說,被@FeignClient標註的類,在使用@Autowired注入時,其實注入的是FeignClientFactoryBean生成的一個實例。
說起工廠,其關鍵作用只有一個,就是創建對象,那麼它創建的具體對象是什麼呢?代碼如下:
public Object getObject() throws Exception { FeignContext context = applicationContext.getBean(FeignContext.class); Feign.Builder builder = feign(context); if (!StringUtils.hasText(this.url)) { String url; if (!this.name.startsWith("http")) { url = "http://" + this.name; } else { url = this.name; } url += cleanPath(); return loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url)); } ...其餘省略... }
FeignContext即包含有feign配置的上下文,其被FeignAutoConfiguration自動注入,這裏不再多說,只要知道其內包括了feign的各種配置項即可。
而feign(context)則是根據配置項配置Feign.Builder對象。那麼Feign.Builder是在哪注入的呢?
這個可以參考FeignClientsConfiguration可以發現如下代碼:
@Configuration @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class }) protected static class HystrixFeignConfiguration { @Bean @Scope("prototype") @ConditionalOnMissingBean @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true) public Feign.Builder feignHystrixBuilder() { return HystrixFeign.builder(); } } @Bean @Scope("prototype") @ConditionalOnMissingBean public Feign.Builder feignBuilder(Retryer retryer) { return Feign.builder().retryer(retryer); }
也就是說如果發現hystrix依賴,則使用HystrixFeign.builder()進行注入,如果沒有hystrix依賴,則使用Feign.builder()進行注入。在這裏,feign完成了與hystrix的結合(回答了第二個問題)。注:Camden版本中該參數feign.hystrix.enabled默認是打開的,但是到了Dalston中該參數已經默認關閉了,也就是說需要單獨配置打開。
接着再來看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-ribbon?"); }
此方法中會構建一個feign.Client的實例,在依賴ribbon的情況下,默認會注入LoadBalancerFeignClient,它爲feign提供了負載均衡的能力,在這裏,feign完成了和ribbon的結合(回答了第三個問題)。
再往下看,targeter.target(this, builder, context, target)最終會調用到Feign.Builder的build方法:
public <T> T target(Target<T> target) { return build().newInstance(target); } 其中build方法如下: public Feign build() { SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, errorDecoder, synchronousMethodHandlerFactory); return new ReflectiveFeign(handlersByName, invocationHandlerFactory); }
到了這裏我們可以知道,其實工廠構造的對象就是ReflectiveFeign.newInstance返回的對象,接着查看newInstance方法:
public <T> T newInstance(Target<T> target) { ...省略其餘方法... T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler); return proxy; }
到了這裏可以看到,真正的實例其實就是我們的業務接口(被@FeignClient標註的接口),採用jdk動態代理生成的一個代理對象(回答了第一個問題)。
處理反射調用時,觸發的其實是SynchronousMethodHandler
-
調用
這裏我們看下SynchronousMethodHandler的invoke方法,即實際調用我們接口的某個方法時,會觸發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) { retryer.continueOrPropagate(e); if (logLevel != Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } continue; } } }
從上面的代碼中看到一個Retryer,feign默認情況下是Retryer.NEVER_RETRY,即不重試。
而executeAndDecode最終會委託給LoadBalancerFeignClient的execute來發起調用(參考第三個問題的答案處注入的LoadBalancerFeignClient),代碼如下:
public Response execute(Request request, Request.Options options) throws IOException { try { ...省略其他代碼... return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse(); } catch (ClientException e) { IOException io = findIOException(e); if (io != null) { throw io; } throw new RuntimeException(e); } }
核心的代碼是委託給一個LoadBalancer來執行調用。那麼LoadBalancer來自那裏,跟蹤lbClient方法可以看到如下代碼:
public FeignLoadBalancer create(String clientName) { if (this.cache.containsKey(clientName)) { return this.cache.get(clientName); } IClientConfig config = this.factory.getClientConfig(clientName); ILoadBalancer lb = this.factory.getLoadBalancer(clientName); ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class); FeignLoadBalancer client = enableRetry ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector, loadBalancedRetryPolicyFactory) : new FeignLoadBalancer(lb, config, serverIntrospector); this.cache.put(clientName, client); return client; }
這裏可以看到,使用的負載均衡器ILoadBalancer lb就是負載均衡器所介紹的。
- 如果沒有啓用retry,那麼使用的是FeignLoadBalancer。
- 如果啓用了retry,使用RetryableFeignLoadBalancer,RetryableFeignLoadBalancer中的retry使用的是spirng-retry實現的,這裏不再多說。
那麼,現在有了LoadBalancer,其核心方法executeWithLoadBalancer,這個方法存在與其父類中AbstractLoadBalancerAwareClient(該類是ribbon-loadbalancer jar中的,即是ribbon的實現)。
經過查看,該方法executeWithLoadBalancer使用RxJava來進行相關調用,代碼比較複雜,這裏不再列出。
值得一說的是executeWithLoadBalancer裏面有相關的retry邏輯,但是經過測試,這些retry邏輯已經失效,不再使用(不知道爲啥netflix不把retry邏輯移除,也許在別的地方使用?查看github代碼,更新時間是3年前,也行netflix的人忙着其他的吧。。。)。
到這,調用過程完畢(回答了第四個問題)
-
這裏再提一點,關於retry
關於retry,總共有三處:
- feign自身的retry,默認沒有開啓
- ribbon實現的retry,經過測試,已經失效了
- RetryableFeignLoadBalancer即採用spirng-retry實現,這裏和重試中的RestTemplate統一了。