Feign 的定义

Feign 是一个声明式的 web service 客户端,它使得编写 web service 客户端更为容易。创建接口,为接口添加注解,即可使用 FeignFeign 可以使用 Feign 注解或者JAX-RS注解,还支持热插拔的编码器和解码器。Spring Cloud为 Feign 添加了Spring MVC的注解支持,并整合了 RibbonEureka 来为使用 Feign 时提供负载均衡

Feign 与 Ribbon 的不同

FeignRibbonSpring CloudNetflix 中提供的两个实现软负载均衡的组件, RibbonFeign 都是用于调用其他服务的,方式不同。 Feign 则是在 Ribbon 的基础上进行了一次改进,采用接口的方式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建 http 请求。不过要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致

Feign的入门Demo

在ribbon-demo中的feign相关分支:

  1. git clone -t feign-demo https://gitee.com/ZIB/ribbon-demo.git

Feign 源码阅读

0. Feign的主要组件

i. Encoder编码器

Encoder 接口,编码器,负责将一个对象转换成 HTTP 请求体。
spring cloud对feign的默认实现:SpringEncoder

ii. Decoder解码器

Decoder 接口,解码器,负责将 HTTP 响应转换成一个对象。
spring cloud对feign的默认实现:ResponseEntityDecoder

iii. Logger日志

Logger 抽象类,日志记录器,负责请求信息的日志打印。
spring cloud对feign的默认实现:Slf4jLogger

vi. Contract契约

Contract 接口,契约,负责解析 API 接口的方法元数据,例如说注解、方法参数、方法返回类型等等。
比如:feign本来是没法支持spring web mvc(@PathVariable、@RequestMapping、@RequestParam等)注解的,但是有Contract契约组件之后,这个组件负责解释这些注解,让feign可以跟这些注解结合起来使用。
spring cloud对feign的默认实现:SpringMvcContract

v. Feign.Builder

Feign.Builder 类,Feign 构造器,可以设置各种配置,最终构建出指定 API 接口的 HTTP “客户端”。示例代码如下:

RemoteService service = Feign.builder()
            .options(new Options(1000, 5000)) // 请求的连接和读取超时时间
            .retryer(new Retryer.Default(5000, 5000, 3)) // 重试策略
            .target(RemoteService.class, "http://localhost:8080"); // 目标 API 接口和目标地址

spring cloud对feign的默认实现:Feign.Builder,当和Hystrix整合使用: HystrixFeign.Builder

vi. FeignClient

Client 接口,定义提交 HTTP 请求的方法。它里面包含了一系列组件,比如说Encoder、Decoder、Logger、Contract等等。
spring cloud对feign的默认实现:LoadBalancerFeignClient,该实现类对 Ribbon 进行了集成。

原理图:

16f90e8706e6e4c13a29d232e8377b1d.jpg

1. 源码的一切开始 @EnableFeignClients@FeignClient

第一个入口@EnableFeignClients 注解来启动 Feigin 的核心机制来扫描,给接口中添加了 的接口生成自己 Feign 的动态代理,以及解析和处理接口上打的那些spring web mvc的注解,比如@RequestMapping@PathVarialbe , @RequestParam ,基于spring web mvc的注解,来生成接口对应的http请求
第二个入口:用@FeignClient 注解标注了一个接口,这个接口会被创建为一个REST client(发送restful请求的客户端),然后可以将这个REST client注入其他的组件(比如说ServiceBController ),如果启用了Ribbon 的话,就会采用负载均衡的方式,来进行http请求的发送,你可以用@FeignClient 标注一个配置类,在那个配置类里可以自定义自己的RibbonILoadBalancer

同样地 @RibbonClient 的名称,要跟 @FeignClient 的名称要一样,如下面代码

@FeignClient(value = "ServiceA",configuration = MyConfiguration.class)
//@RibbonClient(value = "ServiceA")
public interface ServiceAClient extends ServiceBInterface {
}

1.1 @EnableFeignClients 引用类 FeignClientsRegistrar

当spring-boot启动之后, @EnableFeignClients 注解触发 FeignClientsRegistrar 执行,而由于FeignClientsRegistrar实现了以下接口 ImportBeanDefinitionRegistrar , ResourceLoaderAware , BeanClassLoaderAware , EnvironmentAware , 所以触发FeignClientsRegistrar#registerBeanDefinitions(..) 方法执行,加载 @FeignClient 接口加载

1.2 执行FeignClientsRegistrar#registerBeanDefinitions(..)方法过程

加载执行的代码如下:

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
    }

进入 registerDefaultConfiguration(metadata, registry) 方法,该方法会获取所有@EnableFeignClients的配置项,定义了一些配置类,简略分析:

private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
    //1.这里获取@EnableFeignClients注解内的阐述
    Map<String, Object> defaultAttrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

    if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
        String name;
        if (metadata.hasEnclosingClass()) {
            name = "default." + metadata.getEnclosingClassName();
        }
        else {
            //2.默认没填写1的注解参数走这里
            name = "default." + metadata.getClassName();
        }
        //3.对应生成FeignClient的配置器
        registerClientConfiguration(registry, name,
                                    defaultAttrs.get("defaultConfiguration"));
    }
}
  1. 这里获取 @EnableFeignClients 注解中的attr,默认是没有填入配置,debug测试如下:

image.png

  1. 生成一个 name 的key为: default.com.zhss.service.b.ServiceBApplication
  2. 根据2中的name和1中的attr,生成对应FeignClient配置类到spring上下文本中,注册到spring成功

执行 registerFeignClients(..) 方法处理扫描所有包下面的 @FeignClient 注解的属性及接口,代码中的 FeignClientFactoryBean 更是后面处理feign动态代理是需要的属性数据存储及处理类,代码如下:

public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
    //1.获取了一个scanner,这个类就是复杂在指定的路径中,扫描你指定的条件的相关的bean
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);

    Set<String> basePackages;

    Map<String, Object> attrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName());
    //2. AnnotationTypeFilter,就是一个根据注解的类型进行过滤的过滤器
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
        FeignClient.class);
    final Class<?>[] clients = attrs == null ? null
        : (Class<?>[]) attrs.get("clients");
    //3.如果clients是空的,就会给组件扫描器添加一个注解过滤器,之前搞的那个专门过滤@FeignClient注解的注解过滤器,就会加入组件扫描器中
    if (clients == null || clients.length == 0) {
         //ps:默认走这里
        scanner.addIncludeFilter(annotationTypeFilter);
        //4. 如果@EnableFeignClients注解没有配置basePackages属性的(默认就没有),就会去自动生成一堆basePackages
        basePackages = getBasePackages(metadata);
    }
    else {
        //这里不走 ...代码省略
    }

    for (String basePackage : basePackages) {
        //这行代码,极为关键:使用了组件扫描器(搭载了@FeignClient注解过滤器),
        //在com.zhss.service.b包中扫描,包含了@FeignClient注解的接口
        //扫描出来了一堆BeanDefinition
        //TODO--这里会异步出发ClassPathScanningCandidateComponentProvider#isCandidateComponent()方法
        Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is an interface
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                Assert.isTrue(annotationMetadata.isInterface(),
                              "@FeignClient can only be specified on an interface");

                Map<String, Object> attributes = annotationMetadata
                    .getAnnotationAttributes(
                    FeignClient.class.getCanonicalName());

                String name = getClientName(attributes);
                registerClientConfiguration(registry, name,attributes.get("configuration"));
                //PS:主要业务就是:构造构造一个BeanDefiniction的东西,feign动态代理存储数据处理
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    //PS:当中这里definition,构建了feign动态代理中@FeignClient的相关动态代理必须使用的参数存储
    BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class);
    validate(attributes);
    definition.addPropertyValue("url", getUrl(attributes));
    definition.addPropertyValue("path", getPath(attributes));
    String name = getName(attributes);
    definition.addPropertyValue("name", name);
    definition.addPropertyValue("type", className);
    definition.addPropertyValue("decode404", attributes.get("decode404"));
    definition.addPropertyValue("fallback", attributes.get("fallback"));
    definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

    String alias = name + "FeignClient";
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

    boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null

    beanDefinition.setPrimary(primary);

    String qualifier = getQualifier(attributes);
    if (StringUtils.hasText(qualifier)) {
        alias = qualifier;
    }
    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                                                           new String[] { alias });
    //构建好的BeanDefinition存储于BeanDefinitionRegistry中
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

2. FeignClientFactoryBean 分析

  • 扫描包下面的@FeignClient 的注解,以及搞完了,扫描到内存里来了,形成了 BeanDefinition
  • 下面一步,其实就是在spring容器初始化的时候,一定是会根据扫描出来的 @FeignClient 的信息,去构造一个原生的feign的FeignClient 出来,然后基于这个FeignClient来构造一个 ServiceAClient 接口的动态代理,也就是说 ServiceAClient 接口调用的时候,一定是会走这个动态代理的
  • FeignClientFactoryBean 里面去,我们其实在这里找到了相关的一些构造 FeignClient 的过程
  • FeignClientFactoryBean 包含了 @FeignClient 注解中的所有的属性的值,所以肯定是根据你定义的@FeignClient注解的属性,来进行FeignClient的生成

    2.1 分析 Feign.Builder feign(..)getObject()configureFeign() 、两个 configureUsingConfiguration(..) 方法

    ```java @Override public Object getObject() throws Exception { //FeignContext是代表一个ServiceA服务(ServiceA)独立的spring容器, //其关联着自己独立的一些组件:比如说独立的Logger组件,独立的Decoder组件,独立的Encoder组件,都是某个服务自己独立的 FeignContext context = applicationContext.getBean(FeignContext.class); //TODO — 这里引用了feign(..)方法,从context中获取对应各个独立组件构建出Feign.Builder 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();
      //与ribbon整合了loadBalance -- 默认情况下,执行到这里!
      return loadBalance(builder, context, new HardCodedTarget<>(this.type,
                                                                 this.name, url));
    

    } if (StringUtils.hasText(this.url) && !this.url.startsWith(“http”)) {

      this.url = "http://" + this.url;
    

    } String url = this.url + cleanPath(); Client client = getOptional(context, Client.class); if (client != null) {

      if (client instanceof LoadBalancerFeignClient) {
          client = ((LoadBalancerFeignClient)client).getDelegate();
      }
      builder.client(client);
    

    } //TODO — 动态代理 Targeter targeter = get(context, Targeter.class); return targeter.target(this, builder, context, new HardCodedTarget<>(

      this.type, this.name, url));
    

    }

protected Feign.Builder feign(FeignContext context) { FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class); Logger logger = loggerFactory.create(this.type); // Feign.Builder加载了feign几个核心组件 Feign.Builder builder = get(context, Feign.Builder.class) .logger(logger) .encoder(get(context, Encoder.class)) .decoder(get(context, Decoder.class)) .contract(get(context, Contract.class)); //使用我们在application.yml中配置的参数及自定义拦截器加载,来设置Feign.Builder configureFeign(context, builder); return builder; }

protected void configureFeign(FeignContext context, Feign.Builder builder) { //application.yml中加载配置文件的读取数据,前缀为:feign.client.xxx FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class); if (properties != null) { if (properties.isDefaultToProperties()) { //默认走这里 configureUsingConfiguration(context, builder); //采用的是application.yml中针对所有feign client配置的参数 configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder); //采用的是application.yml中针对所有feign.client配置的参数,这里对应的是ServiceA服务上的配置,当前服务的配置优先级最高 configureUsingProperties(properties.getConfig().get(this.name), builder); } else { //…代码省略 } } else { //判断加载组件,以及自定义的拦截器,demo中的MyRequestInterceptor在这里加载 configureUsingConfiguration(context, builder); } }

protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) { Logger.Level level = getOptional(context, Logger.Level.class); if (level != null) { builder.logLevel(level); } Retryer retryer = getOptional(context, Retryer.class); if (retryer != null) { builder.retryer(retryer); } ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class); if (errorDecoder != null) { builder.errorDecoder(errorDecoder); } Request.Options options = getOptional(context, Request.Options.class); if (options != null) { builder.options(options); } Map requestInterceptors = context.getInstances( this.name, RequestInterceptor.class); if (requestInterceptors != null) { builder.requestInterceptors(requestInterceptors.values()); }

if (decode404) {
    builder.decode404();
}

}

protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration config, Feign.Builder builder) { if (config == null) { //默认这里直接返回,当你config有配置自定义配置,这走下面的if条件 return; }

if (config.getLoggerLevel() != null) {
    builder.logLevel(config.getLoggerLevel());
}

if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
    builder.options(new Request.Options(config.getConnectTimeout(), config.getReadTimeout()));
}

if (config.getRetryer() != null) {
    Retryer retryer = getOrInstantiate(config.getRetryer());
    builder.retryer(retryer);
}

if (config.getErrorDecoder() != null) {
    ErrorDecoder errorDecoder = getOrInstantiate(config.getErrorDecoder());
    builder.errorDecoder(errorDecoder);
}

if (config.getRequestInterceptors() != null && !config.getRequestInterceptors().isEmpty()) {
    // this will add request interceptor to builder, not replace existing
    for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) {
        RequestInterceptor interceptor = getOrInstantiate(bean);
        builder.requestInterceptor(interceptor);
    }
}

if (config.getDecode404() != null) {
    if (config.getDecode404()) {
        builder.decode404();
    }
}

}


1. `Feign.Builder` 创建需要 `Logger` 、 `Encoder` 、 `Decoder` 、 `Contract` 这些对象。这些对象可都是feign的重要组件啊!那这个Feign.Builder也是一个极为重要的东西。
1. `configureFeign()` 方法,对 `Feign.Builder` 进行配置,发现这个方法是被 `feign()` 方法来调用的
1. 两个 `configureUsingConfiguration()` 方法,这个方法的话,一看就是也是对 `Feign.Builder` 进行配置的,发现是 `configureFeign()` 方法在调用这两个方法
1. `getOrInstantiate()` 方法,是给 `configureUsingConfiguration()` 来调用的

重点: `getObject()`  方法中,内部有feign方法调用,也调用了get方法调用,当中下列代码,应该就是动态代理类
> Targeter targeter = get(context, Targeter.class);
>  return targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url));

<a name="Ilfny"></a>
### 2.2 补充: 启动时候 `FeignClientsConfiguraiton` 构建相关加载
```java
@Configuration
public class FeignClientsConfiguration {
    /**
     * 整合Hytrix框架就是使用这个Feign.Builder加载类
     **/
    @Configuration
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
        public Feign.Builder feignHystrixBuilder() {
            return HystrixFeign.builder();
        }
    }

    /**
     * 默认是Feign.Builder加载类
     **/
    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    public Feign.Builder feignBuilder(Retryer retryer) {
        return Feign.builder().retryer(retryer);
    }
    /**
     * 默认FeignLogger工厂加载类
     **/
    @Bean
    @ConditionalOnMissingBean(FeignLoggerFactory.class)
    public FeignLoggerFactory feignLoggerFactory() {
        return new DefaultFeignLoggerFactory(logger);
    }

    /**
     * 默认Decoder加载类
     **/
    @Bean
    @ConditionalOnMissingBean
    public Decoder feignDecoder() {
        return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
    }

    /**
     * 默认Encoder加载类
     **/
    @Bean
    @ConditionalOnMissingBean
    public Encoder feignEncoder() {
        return new SpringEncoder(this.messageConverters);
    }

     /**
     * 默认Contract加载类
     **/
    @Bean
    @ConditionalOnMissingBean
    public Contract feignContract(ConversionService feignConversionService) {
        return new SpringMvcContract(this.parameterProcessors, feignConversionService);
    }
}

FeignClientsConfiguration 中默认给定了预定义好的Encoder、Decoder、Contract 为 SpringEncoderResponseEntityDecoderSpringMvcContract而默认情况下Feign.Builder为 Feign.builder().retryer(retryer)

3. Feign与Ribbon整合加载

FeignClientFactoryBean#getObject() 方法内第232行执行loadBalance(..) ,这里开始是整合Feign与Ribbon的开始,下面我们具体分析一下自信流程:

//截取至FeignClientFactoryBean#getObject方法
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));
}

这里在做一下url的准备,把服务名称与http://拼接起来,在`cleanPath()`中会拼接@FeignClient注解中的path配置。

比如:@FeignClient(value = “ServiceA”, path = “/user”) 此处拼接请求URL地址的时候,就会拼接成:http://ServiceA/user

FeignClientFactoryBean@FeignClient 注解中的接口类型(com.zhss.service.ServiceAClient)、服务名称(ServiceA)、url地址(http://ServiceA)封装到 HardCodedTarget 中,然后连同 Feign.BuilderFeignContext ,一起,传入了 loadBalance() 方法里去

3.1. FeignClientFactoryBean#``loadBalance(...) 处理分析

进入分析如下代码:

    protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
            HardCodedTarget<T> target) {
        //在FeignContext中获取的。它就是用来发送feign.Request这个Http请求的
        Client client = getOptional(context, Client.class);
        if (client != null) {
            builder.client(client);
            //targeter是获取动态代理
            Targeter targeter = get(context, Targeter.class);
            //ps:这里是执行正在动态代理的生成
            return targeter.target(this, builder, context, target);
        }
        //代码省略....
    }

protected <T> T getOptional(FeignContext context, Class<T> type) {
    return context.getInstance(this.name, type);
}

protected <T> T get(FeignContext context, Class<T> type) {
    T instance = context.getInstance(this.name, type);
    //...代码省略
    return instance;
}
  1. 获取Client对象,在FeignContext中获取的。它就是用来发送feign.Request这个Http请求的,请注意:实现此接口的子类实现请确保是线程安全的(因为可能是多线程发送)
  2. 获取了 Targeter 对象,这里是后续根据根据 FeignClientFactoryBean 的接口类型、服务名称、url地址,生成动态代理,后面会详细分析

    3.2 在启动配置类当中,对上面Client、Targeter初始化

    i. 在Client的实体类为 LoadBalancerFeignClient ,在 DefaultFeignLoadBalancedConfiguration 为启动类,并且和Feign组件一样,在Feign.Builder构建时候已经注入
    @Configuration
    class DefaultFeignLoadBalancedConfiguration {
     @Bean
     @ConditionalOnMissingBean
     public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                               SpringClientFactory clientFactory) {
         return new LoadBalancerFeignClient(new Client.Default(null, null),
                 cachingFactory, clientFactory);
     }
    }
    
    ii. 在Targeter的实体类为 HystrixTargeter , 在 FeignAutoConfiguration 为启动类
    代码走到了Targeter targeter = get(context, Targeter.class);中,会在context中获取对应的HystrixTargeter 对象
    @Configuration
    @ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
    protected static class HystrixFeignTargeterConfiguration {
     @Bean
     @ConditionalOnMissingBean
     public Targeter feignTargeter() {
         return new HystrixTargeter();
     }
    }
    

    3.3 重点:Feign中Targeter构建动态代理的源码分析

    FeignClientFactoryBean#loadBalance(...)方法内执行targeter.target(this, builder, context, target)进去跟踪一下:
    target方法跟踪1.png
    在build()方法中,创建了ReflectiveFeign,然后ReflectiveFeign对象调用了newInstance方法。
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target)代码中:
    ReflectiveFeign方法跟踪1.png
    其实是基于我们配置的Contract、Encoder等一堆组件,加上Target对象(ServiceAClient接口),去获取ServiceAClient接口,进行spring mvc注解的解析,以及接口中各个方法的一些解析,获取了这个接口中所有需要被代理的接口,返回到一个Map中。map的key是方法名字、value是处理该方法的对象(SynchronousMethodHandler),而
    apply方法分析.png
    在apply方法中,contract(SpringMvcContract)对SpringMVC的注解进行了解析!

    比如:getById(id)这个接口 (1)方法的定义:ServiceAClient#getById(id) (2)方法的返回类型:class java.lang.Long (3)发送HTTP请求的模板:GET /get/{id} HTTP/1.1

4. Feign接收请求如何处理(与Ribbon整合处理request访问)

Feign动态代理原理是JDK动态代理实现,通过代码T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler) 创建一个代理出来

4.1 动态代理是如何处理请求的

那么就在之前看到 ReflectiveFeign.FeignInvocationHandler#invoke() 方法

@Override
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();
    }
    //TODO -- 这里执行拦截器对请求进行处理
    return dispatch.get(method).invoke(args);
}

args:你传递进来的参数
dispatch:可以看作3.3当中的methodToHandler,它是一个Map,结构为K(接口反射方法名)—V(SynchronousMethodHandler)
method通过找到方法对应的MethodHandler,将args参数交给他来处理请求
image.png
当中method为:
image.png

4.2 处理Request请求构建

// SynchronousMethodHandler#invoke方法
@Override
public Object invoke(Object[] argv) throws Throwable {
    //TODO--整合request请求整合
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
        try {
            //执行相关处理request
            return executeAndDecode(template);
        } catch (RetryableException e) {
            retryer.continueOrPropagate(e);
            if (logLevel != Logger.Level.NONE) {
                logger.logRetry(metadata.configKey(), logLevel);
            }
            continue;
        }
    }
}
  • 这里主要处理以下业务
  1. request请求url构建
  2. 执行request请求处理
  • 细节分析

    RequestTemplate template = buildTemplateFromArgs.create(argv)

  1. request url 请求如下:

image.png

  1. debug之后生成如下:

    GET /user/2 HTTP/1.1
    
  2. 执行处理的request 模板处理

    executeAndDecode(template)

代码如下:

// SynchronousMethodHandler#executeAndDecode方法
Object executeAndDecode(RequestTemplate template) throws Throwable {
    //TODO --基于RequestTemplate,创建了一个Request出来,这个Request是基于之前的那个HardCodedTarget
    Request request = targetRequest(template);
    //代码省略...
    Response response;
    long start = System.nanoTime();
    try {
      //LoadBalancerFeignClient,将请求的参数传入了进去,连接超时时间是10s,读超时时间是60s
      response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 10
      response.toBuilder().request(request).build();
    } catch (IOException e) {
      //代码省略...
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

    boolean shouldClose = true;
    try {
      if (logLevel != Logger.Level.NONE) {
        response = logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
        // ensure the request is set. TODO: remove in Feign 10
        response.toBuilder().request(request).build();
      }
      if (Response.class == metadata.returnType()) {
        if (response.body() == null) {
          return response;
        }
      //代码省略...
      }
      //代码省略...
  }

@Override
public Response execute(Request request, Request.Options options) throws IOException {
    try {
        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);
        //TODO -- 这里是request执行访问处理,当中整合了Feign与Ribbon整合处理request访问
        return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,requestConfig).toResponse();
    }
    //代码省略....
}

具体分析如下:
i. targetRequest(template) 生成一个Request出来,这个Request是基于之前的那个 HardCodedTarget
ii. IClientConfig requestConfig = getClientConfig(options, clientName) 这里是ribbon相关配置,当执行 getClientConfig(options, clientName) 方法时,会去ribbon中获取一个server, ribbon肯定在eureka中获取注册表放入serverList
iii. 重点:client为 LoadBalancerFeignClient,将请求的参数传入了进去,连接超时时间是10s,读超时时间是60s
我们其实是基于LoadBalancerFeignClient完成了请求的处理和发送,在这个里面,就是肯定是将http请求发送到了对方的ServiceA的某个server上去,
**

4.3 Feign 与 ribbon 及 Eureka 整合处理Request请求访问

下面分析整合该代码入口return``lbClient(clientName)``.executeWithLoadBalancer(ribbonRequest,requestConfig).toResponse() 分析:

//1. LoadBalancerFeignClient#lbClient(String clientName)
private FeignLoadBalancer lbClient(String clientName) {
    //TODO -- ILoadBalancer构建及client生成入口
    return this.lbClientFactory.create(clientName);
}

//2. CachingSpringLoadBalancerFactory#create(String clientName)
public FeignLoadBalancer create(String clientName) {
    //a. 缓存中有构建clientName()名称,则从缓存中获取client
    if (this.cache.containsKey(clientName)) {
        return this.cache.get(clientName);
    }
    //b. 初始化时候,缓存还没构建时,这里构建配置文件
    IClientConfig config = this.factory.getClientConfig(clientName);
    //TODO--这里是构建ILoadBalancer设置
    ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
    ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
    FeignLoadBalancer client = enableRetry ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
    this.cache.put(clientName, client);
    return client;
}

归纳如下:

  1. lbClient(String clientName) 入口,进入 CachingSpringLoadBalancerFactory#create(String clientName) 方法后
  2. 开始时候cache为null,需要执行加载配置对应config配置获取,生成Feign与Ribbon整合的FeignLoadBalance为client,当中client为Eureka中获取了注册服务表信息封装到server list注入到client当中
  3. client 与服务名(这里是“ServiceA”)封装到cache当中,下次请求时,通过服务名直接从缓存中获取client(也就是 FeignLoadBalancer)
  • DEBUG如图:

image.png

  • cache缓存如图:cache为一个K(服务接口名,这里是“ServiceA”) - V( FeignLoadBalancer )

image.png

4.3 补充 4.2 相关加载的Ribbon所加载的类及分析

在项目启动之后,配置类 RibbonClientConfiguraiton 加载了以4.2中 ILoadBalancer 实现类为 ZoneAwareLoadBalancer ,内部是持有的跟eureka进行整合的 DomainExtractingServerList

//Feign与Ribbon整合的配置类
@SuppressWarnings("deprecation")
@Configuration
@EnableConfigurationProperties
@Import({HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {
    //Ribbon启动加载ILoadBalancer实现类
    @Bean
    @ConditionalOnMissingBean
    public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
            ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
            IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
        if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
            return this.propertiesFactory.get(ILoadBalancer.class, config, name);
        }
        return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
                serverListFilter, serverListUpdater);
    }
}

//Ribbon与Eureka整合的ServerList加载
@Configuration
public class EurekaRibbonClientConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public ServerList<?> ribbonServerList(IClientConfig config, Provider<EurekaClient> eurekaClientProvider) {
        if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {
            return this.propertiesFactory.get(ServerList.class, config, serviceId);
        }
        DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
                config, eurekaClientProvider);
        DomainExtractingServerList serverList = new DomainExtractingServerList(
                discoveryServerList, config, this.approximateZoneFromHostname);
        return serverList;
    }
}

当中处理这里处理Requst请求之间的联动,可查看 3. Feign 请求拦截处理业务逻辑

4.4 Feign与Robbin 源码整合分析

下面主要分析4.2最后一行这个代码 return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,requestConfig).toResponse() 的,进入 executeWithLoadBalancer(..) 方法如下:

//AbstractLoadBalancerAwareClient#executeWithLoadBalancer(..)
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
    //LoadBalancerCommand是结合Hytrix相关,可以作为集合、熔断相关的,分析buildLoadBalancerCommand如下:
    //1.在FeignLoadBalancer内部执行执行工作的核心组件是LoadBalancerCommand,buildLoadBalancerCommand方法是对LoadBalancerCommand构建
    LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

    try {
        //2.command.submit方法这里rxjava中订阅模式,主要由下面的call()去执行http请求,结果提交到command对象中,
        //调用了一个toBlocking().single()方法,看着就是阻塞式同步执行
        return command.submit(
            new ServerOperation<T>() {
                @Override
                public Observable<T> call(Server server) {
                    URI finalUri = reconstructURIWithServer(server, request.getUri());
                    S requestForServer = (S) request.replaceUri(finalUri);
                    try {
                        //5.这里AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig)后面通过RibbonResponse#execute(..)执行http请求,返回结果
                        return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                    } 
                    catch (Exception e) {
                        return Observable.error(e);
                    }
                }
            })
            //设置为阻塞同步处理
            .toBlocking()
            .single();
    } catch (Exception e) {
        //代码省略......
    }
}

//AbstractLoadBalancerAwareClient#buildLoadBalancerCommand(..)
//buildLoadBalancerCommand方法中需要通过request、config去构建其LoadBalancerCommand对象
protected LoadBalancerCommand<T> buildLoadBalancerCommand(final S request, final IClientConfig config) {
    RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, config);
    LoadBalancerCommand.Builder<T> builder = LoadBalancerCommand.<T>builder()
        .withLoadBalancerContext(this)
        .withRetryHandler(handler)
        .withLoadBalancerURI(request.getUri());
    customizeLoadBalancerCommandBuilder(request, config, builder);
    return builder.build();
}

//3.LoadBalancerCommand代码中与Ribbon的整合
private Observable<Server> selectServer() {
    return Observable.create(new OnSubscribe<Server>() {
        @Override
        public void call(Subscriber<? super Server> next) {
            try {
                //4.这里关键就是就是ribbon中赛选serverlist的入口
                Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);   
                next.onNext(server);
                next.onCompleted();
            } catch (Exception e) {
                //代码省略。。。。
            }
        }
    });
}

//进入LoadBalancerContext#getServerFromLoadBalancer方法中,截取其中代码如下:
//通过Server svc = lb.chooseServer(loadBalancerKey)进行赛选server,IRule由ribbon中介绍的规则进行
 public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
        String host = null;
        int port = -1;
        if (original != null) {
            host = original.getHost();
        }
        if (original != null) {
            Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original);        
            port = schemeAndPort.second();
        }

        ILoadBalancer lb = getLoadBalancer();
        if (host == null) {
            if (lb != null){
                //TODO -- 5.这里loadBalancer中进行筛选server,IRule由ribbon中介绍的规则进行
                Server svc = lb.chooseServer(loadBalancerKey);
                host = svc.getHost();
                return svc;
            } else {
                //代码省略
            }
        } 
       //代码省略
        return new Server(host, port);
    }

//RibbonResponse#execute(..)执行http请求返回response结果
@Override
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
    throws IOException {
    Request.Options options;
    //代码省略....
    Response response = request.client().execute(request.toRequest(), options);
    return new RibbonResponse(request.getUri(), response);
}
  1. 在FeignLoadBalancer内部执行执行工作的核心组件是LoadBalancerCommand,buildLoadBalancerCommand方法是对LoadBalancerCommand构建
  2. 当执行 command.submit 方法这里rxjava中订阅模式,主要由下面的 call() 去执行http请求,结果提交到command对象中,调用了一个 toBlocking().single() 方法,看着就是阻塞式同步执行
  3. 进入 LoadBalancerCommand 代码中,当中与Ribbon整合代码入口 loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey) 进入后其执行 lb.chooseServer(loadBalancerKey) 方法筛选server,IRule由ribbon中介绍的规则进行(具体细则回顾Ribbon源码阅读)
  4. 执行request请求生成、反问接口由 AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig) 开始,通过上面的系列业务处理,以知道筛选的Server、requestConfig后,进入 RibbonResponse#execute(..) 方法,执行http访问