FeignClient

一、介绍

一个普通的java接口,加上FeignClient注解,就可以完成整合hystrix,ribbon的http调用,使用上很是方便,也感觉很神奇,内部实现有必要探究一下。

二、问题

  1. 第一个问题:被@FeignClient标注的类,在spring中最终注入的是什么?
  2. 第二个问题:如何跟hystrix结合的?
  3. 第三个问题:如何跟ribbon结合的?
  4. 第四个问题:调用过程?

带着这四个问题,看下下面的实现

三、实现

一切都从EnableFeignClients这个注解开始

  1. 入口,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时同样会注入用户自己的配置。

  2. 工厂

    接着上面的代码段-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

  3. 调用

    这里我们看下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的人忙着其他的吧。。。)。

    到这,调用过程完毕(回答了第四个问题

  4. 这里再提一点,关于retry

    关于retry,总共有三处:

    1. feign自身的retry,默认没有开启
    2. ribbon实现的retry,经过测试,已经失效了
    3. RetryableFeignLoadBalancer即采用spirng-retry实现,这里和重试中的RestTemplate统一了。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值