一、介绍
一个普通的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统一了。