Spring Cloud OpenFeign源码解析

众所周知,Feign是一个声明式web服务客户端。它使编写web服务客户端更容易。要使用Feign,只需要创建接口并且在接口上添加注解。

Feign还支持可插拔编码器和解码器。

Spring Cloud增加了对Spring MVC注解的支持,并支持使用
与Spring Web中默认使用的 HttpMessageConverters

Spring Cloud集成了EurekaSpring Cloud CircuitBreaker以及Spring Cloud LoadBalance,在使用时提供负载均衡的Http客户端。

那么,功能如此强大的Spring Cloud OpenFeign,到底是如何实现上述的功能?本文通过源码解析,对上述功能逐一解释。

一、使用方式

在介绍源码前,先简单介绍Spring Cloud OpenFeign是如何使用的。

1.1 引入依赖

<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-openfeignartifactId>
dependency>

1.2 添加注解@EnableFeignClients

为了开启Feign,需要在被添加了@Configuration的类上面添加注解@EnableFeignClients

可以通过该注解指定FeignClient需要被扫描的包路径(比如@EnableFeignClients(basePackages = "com.example.clients")),或者列举需要被添加的FeignClient(比如@EnableFeignClients(clients = InventoryServiceFeignClient.class))。当不指定包路径时,默认为添加@EnableFeignClients注解的类所属的包。

可通过defaultConfiguration字段指定全局生效的FeignClient的配置类,比如拦截器RequestInterceptor、编解码器。详见官方文档1.2小节

@SpringBootApplication
@EnableFeignClients
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

1.3 添加FeignClient接口

注解 @FeignClient 添加到接口上,声明这是一个FeignClient,也就是Feign客户端。

  • 默认字段value(别名name)用于指定服务名称,无论是否提供url,必须为客户端提供服务的名称。可以指定为属性,比如${propertyKey}。当contextId不存在时,会将其作为bean名称的前缀。
  • 可通过contextId字段指定FeignClient的bean名称。使用场景是当存在多个FeignClient的name一样时,可以通过设置不同的contextId避免出现由于bean名称相同而导致服务启动失败的情况。
  • 可通过字段url指定服务端的URL或可解析主机名
  • 通过configuration字段指定只作用于本Feign客户端的配置。详见官方文档1.2小节
  • 通过fallbackfallbackFactory字段指定请求出现异常之后的兜底方案。详见官方文档1.7小节

负载均衡客户端会根据value(别名name)字段,发现服务端的物理地址。如果当前应用是Eureka的客户端,它会通过Eureka的服务注册中心解析服务端的地址。

@FeignClient("stores")
public interface StoreClient {
    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    List<Store> getStores();

    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    Page<Store> getStores(Pageable pageable);

    @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
    Store update(@PathVariable("storeId") Long storeId, Store store);

    @RequestMapping(method = RequestMethod.DELETE, value = "/stores/{storeId:\\d+}")
    void delete(@PathVariable Long storeId);
}

二、源码解析

2.1 @EnableFeignClients

第一章介绍到,通过注解 @EnableFeignClients开启FeignClient,并扫描指定路径/指定的FeignClient。

注解的字段第一章基本已介绍,该注解的重点是组合了另一个注解@Import(FeignClientsRegistrar.class),将FeignClientsRegistrar通过@Import的方式加载到容器中。

FeignClientsRegistrar是FeignClient非常重要的一个类,是所有FeignClient的注册器。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
   String[] value() default {};
   String[] basePackages() default {};
   Class<?>[] basePackageClasses() default {};
   Class<?>[] defaultConfiguration() default {};
   Class<?>[] clients() default {};
}

2.2 FeignClientsRegistrar

2.2.1 实现接口ImportBeanDefinitionRegistrar

FeignClientsRegistrar 实现了Spring的接口 ImportBeanDefinitionRegistrar

通过实现接口ImportBeanDefinitionRegistrar,可以在应用启动时,动态地将生成BeanDefinition,并注册到BeanDefinitionRegistry。后续ApplicationContext可以根据这些BeanDefinition实例化Bean,并且注册到容器中。(具体流程可通过Spring源码了解。挖个坑,后面单独写一篇)

简单来说,通过FeignClientsRegistrar,可以做到在应用开发者只提供接口的情况下,框架自动实现接口,并且自动注册到容器中。

从重写的方法registerBeanDefinitions可以看到,这个方法主要做的事情就是将DefaultConfigurationFeignClient注册到BeanDefinitionRegistry中。方法内部做了什么事情,后续小节介绍。

class FeignClientsRegistrar
      implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
      // 省略....
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
       registerDefaultConfiguration(metadata, registry);
       registerFeignClients(metadata, registry);
    }
    // 省略....
}

2.2.2 实现接口 ResourceLoaderAware

这个接口比较简单,主要是将ApplicationContextResourceLoader传入FeignClientsRegistrar中。
ResourceLoader主要用于包扫描、资源加载。

class FeignClientsRegistrar
      implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    // 省略....
    private ResourceLoader resourceLoader;
    // 省略....
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
       this.resourceLoader = resourceLoader;
    }
    // 省略....
}

2.2.3 实现接口EnvironmentAware

这个接口也比较简单,主要是将ApplicationContextEnvironment传入FeignClientsRegistrar中。
Environment简单理解就是和配置有关的类,通过Environment可以取出配置。

class FeignClientsRegistrar
      implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    // 省略....
    private Environment environment;
    // 省略....
    @Override
    public void setEnvironment(Environment environment) {
       this.environment = environment;
    }
    // 省略....
}

至此,已对FeignClientsRegistrar实现的三个接口进行了简单介绍。接下来将介绍registerBeanDefinitions方法,分析到底是怎么将@EnableFeignClients@FeignClient的字段值作为构造FeignClient的参数的。

2.2.4 方法registerDefaultConfiguration

从2.2.1小节中,我们看到了方法registerBeanDefinitions里面有两个方法,第一个就是registerDefaultConfiguration

从方法名可以看出,这个方法主要做的事情就是将DefaultConfiguration注册到BeanDefinitionRegistry中。还记得1.2小节中介绍的@EnableFeignClients的参数defaultConfiguration,就是在这里发挥作用的。

private void registerDefaultConfiguration(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
      // 从注解EnableFeignClients中取出所有字段的值
   Map<String, Object> defaultAttrs = metadata
         .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

   if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
     // 构建bean的名称
      String name;
      if (metadata.hasEnclosingClass()) {
         name = "default." + metadata.getEnclosingClassName();
      }
      else {
         name = "default." + metadata.getClassName();
      }
      // 将BeanDefinitionRegistry、bean名称、defaultConfiguration对应的Class对象传入
      registerClientConfiguration(registry, name,
            defaultAttrs.get("defaultConfiguration"));
   }
}

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
      Object configuration) {
   // 通过 FeignClientSpecification 包装 configuration
   BeanDefinitionBuilder builder = BeanDefinitionBuilder
         .genericBeanDefinition(FeignClientSpecification.class);
   builder.addConstructorArgValue(name);
   builder.addConstructorArgValue(configuration);
   // 注册
   registry.registerBeanDefinition(
         name + "." + FeignClientSpecification.class.getSimpleName(),
         builder.getBeanDefinition());
}

2.2.5 方法registerFeignClients

这个方法看似很长,实际上就干了几件事情:

  1. 构造扫描器ClassPathScanningCandidateComponentProvider用于在包路径中找到候选组件(也就是FeignClient接口)
  2. 给扫描器添加过滤器(筛选条件),包括筛选添加了@FeignClient注解的类;筛选指定的类
  3. 扫描器找出所有候选组件,并且将注解@FeignClient对应的configuration注册到BeanDefinitionRegistry;将注解@FeignClient的参数值作为BeanDefinition的参数,将FeignClientFactoryBean注册到BeanDefinitionRegistry
public void registerFeignClients(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
  // 添加扫描器
   ClassPathScanningCandidateComponentProvider scanner = getScanner();
   scanner.setResourceLoader(this.resourceLoader);

   Set<String> basePackages;
   // 从EnableFeignClients取出字段值
   Map<String, Object> attrs = metadata
         .getAnnotationAttributes(EnableFeignClients.class.getName());
   // 构造过滤器,用于过滤添加了FeignClient的类
   AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
         FeignClient.class);
   // 从从EnableFeignClients的字段值中拿出clients字段,看看有没有指定FeignClient客户端的类
   final Class<?>[] clients = attrs == null ? null
         : (Class<?>[]) attrs.get("clients");
   // 如果没有指定clients,则从EnableFeignClients取出包路径
   if (clients == null || clients.length == 0) {
      scanner.addIncludeFilter(annotationTypeFilter);
      basePackages = getBasePackages(metadata);
   }
   // 如果指定了clients,则为其添加一个特定的过滤器,只保留clients内的FeignClient客户端
   else {
      final Set<String> clientClasses = new HashSet<>();
      basePackages = new HashSet<>();
      for (Class<?> clazz : clients) {
         basePackages.add(ClassUtils.getPackageName(clazz));
         clientClasses.add(clazz.getCanonicalName());
      }
      AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
         @Override
         protected boolean match(ClassMetadata metadata) {
            String cleaned = metadata.getClassName().replaceAll("\$", ".");
            return clientClasses.contains(cleaned);
         }
      };
      // 为扫描器添加过滤器
      scanner.addIncludeFilter(
            new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
   }

   // 遍历所有包
   for (String basePackage : basePackages) {
   // 扫描器找到所有符合添加的候选组件
      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");
            // 从FeignClient中取出字段值
            Map<String, Object> attributes = annotationMetadata
                  .getAnnotationAttributes(
                        FeignClient.class.getCanonicalName());

            String name = getClientName(attributes);
            // 为FeignClient注册特定的配置类
            registerClientConfiguration(registry, name,
                  attributes.get("configuration"));
            // 注册FeignClient
            registerFeignClient(registry, annotationMetadata, attributes);
         }
      }
   }
}

上述代码64行的方法registerFeignClient,做的事情也比较简单,详见代码。

值得说明的是,当前注册的beanClass是FeignClientFactoryBean,它实现了Spring的接口FactoryBean,真正的FeignClient是通过它进行创建的。

private void registerFeignClient(BeanDefinitionRegistry registry,
      AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
   String className = annotationMetadata.getClassName();
   // FeignClientFactoryBean 作为 beanClass
   BeanDefinitionBuilder definition = BeanDefinitionBuilder
         .genericBeanDefinition(FeignClientFactoryBean.class);
   // 校验fallback和fallbackFactory不是接口
   validate(attributes);
   // 将url和path参数取出,支持从配置中取出
   definition.addPropertyValue("url", getUrl(attributes));
   definition.addPropertyValue("path", getPath(attributes));
   //将服务名取出,优先级为serviceId > name > value
   String name = getName(attributes);
   definition.addPropertyValue("name", name);
   //取出contextId,当contextId不指定时,与name字段保持一致
   String contextId = getContextId(attributes);
   definition.addPropertyValue("contextId", contextId);
   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 = contextId + "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 });
   BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

至此,完成了FeignClientsRegistrar的介绍。通过上面的介绍,我们了解了框架到底是如何通过上述几个重要的注解从包路径中找到我们创建的FeignClient接口,并将其注册到BeanDefinitionRegistry中。

那么,我们下一步要解决的是,框架到底是怎么将接口实现为具体的类的。

2.3 FeignClientFactoryBean

2.3.1 实现接口FactoryBean

对于构建一些复杂的bean,BeanDefinition不是特别方便。Spring提供了一个特殊的接口FactoryBean,可以将复杂的创建bean的逻辑写在FactoryBean实现类上。在注册BeanDefinition时,只需注册FactoryBean实现类。后续实例化bean时,ApplicationContext可以自动调用接口的getObject()方法取出真正的bean。(具体原理,再挖一个坑,后续写一篇文章分析)

因此,我们可以下一个定论,构造FeignClient的逻辑,就在FeignClientFactoryBeangetObject()方法中。

class FeignClientFactoryBean
      implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
    // 省略....
    private Class<?> type;
    // 省略....
    @Override
    public Object getObject() throws Exception {
       return getTarget();
    }
    // 省略....
    @Override
    public Class<?> getObjectType() {
       return this.type;
    }
    // 省略....
    @Override
    public boolean isSingleton() {
       return true;
    }
    // 省略....
}

2.3.2 实现接口InitializingBean

做了简单的参数校验,在这不做介绍,直接跳过

2.3.3 实现接口ApplicationContextAware

ApplicationContext传给FeignClientFactoryBean,比较简单,不作过多介绍。

2.3.4 方法getTarget()

方法getObject()调用了getTarget()创建FeignClient。方法主要做了以下几件事:

  1. ApplicationContext中取出FeignContextFeignContext简单来说就是Feign的上下文对象,每个FeignClient都有自己私有的容器,这些容器通过一个map保存在FeignContext中,key为contextId。(后面的小节具体介绍,现在只需要知道可以通过FeignContext实现依赖查找即可)
  2. FeignContext中取出当前contextIdFeign.Build,并且配置编解码器、拦截器、日志打印级别等。
  3. 通过url是否为空,判断是否为RPC请求。如果是RPC请求,使用负载均衡的客户端;非RPC请求则使用默认的客户端。
  4. FeignContext取出TargeterTargetertarget()方法调用Feign.Buildertarget()方法,从而触发构建FeignClient
<T> T getTarget() {
   // 从ApplicationContext中取出FeignContext
   FeignContext context = this.applicationContext.getBean(FeignContext.class);
   // 从FeignContext取出Feign建造器
   Feign.Builder builder = feign(context);
   
   // 一系列与url有关的操作
   if (!StringUtils.hasText(this.url)) {
      if (!this.name.startsWith("http")) {
         this.url = "http://" + this.name;
      }
      else {
         this.url = this.name;
      }
      this.url += cleanPath();
      // 执行创建的方法
      return (T) loadBalance(builder, context,
            new HardCodedTarget<>(this.type, this.name, this.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) {
         // not load balancing because we have a url,
         // but ribbon is on the classpath, so unwrap
         client = ((LoadBalancerFeignClient) client).getDelegate();
      }
      if (client instanceof FeignBlockingLoadBalancerClient) {
         // not load balancing because we have a url,
         // but Spring Cloud LoadBalancer is on the classpath, so unwrap
         client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
      }
      builder.client(client);
   }
   Targeter targeter = get(context, Targeter.class);
   return (T) 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);

   // @formatter:off
   Feign.Builder builder = get(context, Feign.Builder.class)
         // required values
         .logger(logger)
         .encoder(get(context, Encoder.class))
         .decoder(get(context, Decoder.class))
         .contract(get(context, Contract.class));
   // @formatter:on

   configureFeign(context, builder);

   return builder;
}
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?");
}
class HystrixTargeter implements Targeter {

   @Override
   public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
         FeignContext context, Target.HardCodedTarget<T> target) {
      if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
         // 调用Feign.Builder的target方法创建Feign
         return feign.target(target);
      }
      // 省略
   }

至此,我们梳理完了FeignClientFactoryBeangetTarget()方法。相信读到这里的大家,一定能猜到,我们下一步就要分析 Feign了。

可能大家读到这里的时候,会有一些疑惑,FeignContext是什么,什么时候创建的?TargeterClientFeign.Builder又是什么时候创建的?有关这些信息,后面的小节会一一说明。

这里先剧透一下,FeignContextTargeterClient都是通过自动配置创建的;Feign.Builder是每个Feign私有的,是在创建Feign的子容器时创建注册到子容器的。

2.4 Feign

终于,我们来到了Feign,真正实现FeignClient接口的位置。

值得注意的是,上述几小节讲述的类、方法都是spring-cloud-openfeign这个包的。

但是从Feign开始,就不是spring-cloud-openfeign,而是单独的feign-form-spring的。

也就是说,前几节我们大费周章讲述的逻辑,都是为了将Feign封装成Spring支持的形式,使得Feign可以被Spring管理。前面介绍的一些逻辑,都是通用的将组件接入Spring的方法,具有很大的参考意义。

2.4.1 Feign.Builder

上一小节介绍到,通过Feign.Buildertarget()方法构造FeignClient

我们可以看到,这个方法实际上是通过build()方法构造了Feign,再通过FeignnewInstant()方法实现FeignClient的接口。

方法build()做的事情很简单,就是根据builder的字段值构造一个ParseHandlersByName,并与invocationHandlerFactoryqueryMapEncoder一起传进ReflectiveFeign的构造方法里面。(builder的字段逻辑是若有设置则使用设置的,没有则使用默认的)。

在这简单说明一下ParseHandlersByNameInvocationHandlerFactory作用。

ParseHandlersByName:作用是对FeignClient接口的每个方法创建一个处理器MethodHandler,该处理器是方法真正的执行逻辑。

InvocationHandlerFactory:从名字就可以看出,这是一个创建动态代理InvocationHandler实现类的工厂类,通过InvocationHandler动态代理地实现FeignClient接口。

public abstract class Feign {
    // 省略...
    public static class Builder {
        private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();
        // 省略...
        public <T> T target(Target<T> target) {
          return build().newInstance(target);
        }
        
        public Feign build() {
          SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
              new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
                  logLevel, decode404, closeAfterDecode, propagationPolicy);
          ParseHandlersByName handlersByName =
              new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
                  errorDecoder, synchronousMethodHandlerFactory);
          return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
        }
    // 省略...
    }
}

2.4.2 方法newInstance()

上一节提到Feign通过newInstance()返回接口的实现类,那么这一小节就介绍newInstant()的干了什么事情。

从方法Feign.Builder.build()可以看出ReflectiveFeign是Feign的实现类,因此我们就来分析ReflectiveFeign的接口newInstance()

方法newInstance()主要干了几件事:

  1. 为接口的每个方法创建一个处理类MethodHandler。在方法被调用时,通过MethodHandler进行处理。
  2. 将所有方法的处理类作为入参,构造一个动态代理的InvocationHandler
  3. 通过动态代理,实现FeignClient接口,并将该接口的实现类返回。
public class ReflectiveFeign extends Feign {
    private final ParseHandlersByName targetToHandlersByName;
    private final InvocationHandlerFactory factory;
    // 省略..
    @Override
    public <T> T newInstance(Target<T> target) {
      // 为接口的每个方法创建一个MethodHandler
      Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
      Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
      List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

      for (Method method : target.type().getMethods()) {
        if (method.getDeclaringClass() == Object.class) {
          continue;
        } else if (Util.isDefault(method)) {
          DefaultMethodHandler handler = new DefaultMethodHandler(method);
          defaultMethodHandlers.add(handler);
          methodToHandler.put(method, handler);
        } else {
          // 将MethodHandler放到另一个map里面
          methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
        }
      }
      // 创建动态代理的InvocationHandler
      InvocationHandler handler = factory.create(target, methodToHandler);
      // 通过动态代理实现接口
      T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
          new Class<?>[] {target.type()}, handler);
      // 忽略下面这段
      for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
        defaultMethodHandler.bindTo(proxy);
      }
      return proxy;
    }
}

至此,我们终于找到了实现接口的位置,梳理清楚了FeignClient接口是怎么被实现的。

但是,有一些细节我们暂时还不是太清楚,包括InvocationHandler的逻辑是怎么样的,每个方法的处理类MethodHandler是怎么处理方法调用的。

2.4.3 ReflectiveFeign.FeignInvocationHandler

我们从Feign.Builder可以看出,InvocationHandlerFactory默认的实现类就是InvocationHandlerFactory.Default。创建InvocationHandler的方法create()创建的就是ReflectiveFeign.FeignInvocationHandler

因此想知道InvocationHandler的逻辑,分析ReflectiveFeign.FeignInvocationHandler即可。

public abstract class Feign {
    public static class Builder {
        private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();
    }    
}
public interface InvocationHandlerFactory {

  InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);
  interface MethodHandler {
    Object invoke(Object[] argv) throws Throwable;
  }
  
  // 默认实现类
  static final class Default implements InvocationHandlerFactory {
    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }
  }
}

FeignInvocationHandler的逻辑是根据方法,从dispatch的一个map中取出方法的处理器MethodHandler,并且调用MethodHandlerinvoke()方法进行处理。

public class ReflectiveFeign extends Feign {
    static class FeignInvocationHandler implements InvocationHandler {
      // 省略...
      private final Map<Method, MethodHandler> dispatch;
      // 省略...
      @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();
        }
        // 根据方法,分发到具体的处理器进行处理
        return dispatch.get(method).invoke(args);
      }
      // 省略...
}

2.4.4 ReflectiveFeign.ParseHandlersByName

从方法ReflectiveFeign.newInstance()的第一行可以知道,MethodHandler是通过ParseHandlersByName的方法apply()创建的。

那么,我们分析一下apply()到底做了哪几件事:

  1. 取出接口各方法的元信息。包括方法的入参、出参、注解等。我们可以在这里画个重点,之所以FeignClient支持Spring MVC的那一套注解,都是因为这个方法做了解析。由于篇幅有限,具体细节在这里不再详细介绍,详见org.springframework.cloud.openfeign.support.SpringMvcContract
  2. 创建用于构造RequestTemplateBuildTemplateByResolvingArgs
  3. 通过SynchronousMethodHandler.Factory创建方法处理器MethodHandler
public class ReflectiveFeign extends Feign {
    
    private final Contract contract;
    
    private final SynchronousMethodHandler.Factory factory;
    
    static final class ParseHandlersByName {
        public Map<String, MethodHandler> apply(Target key) {
          // 取出方法的元信息(包括入参、出参,注解等信息)
          List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
          Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
          for (MethodMetadata md : metadata) {
            // 创建用于构造RequestTemplate的BuildTemplateByResolvingArgs
            BuildTemplateByResolvingArgs buildTemplate;
            if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
              buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
            } else if (md.bodyIndex() != null) {
              buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
            } else {
              buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
            }
            result.put(md.configKey(),
                // 创建方法处理器MethodHandler
                factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
          }
          return result;
        }
    }
}

2.4.5 SynchronousMethodHandler

到这里,其实基本上已经分析地差不多了。我们最后再来看一眼,FeignClient的方法最后到底是怎么被执行的。

final class SynchronousMethodHandler implements MethodHandler {
    @Override
    public Object invoke(Object[] argv) throws Throwable {
      // 构造RequestTemplate
      RequestTemplate template = buildTemplateFromArgs.create(argv);
      Options options = findOptions(argv);
      Retryer retryer = this.retryer.clone();
      while (true) {
        try {
          // 发送请求并且接收结果
          return executeAndDecode(template, options);
        } catch (RetryableException e) {
          try {
            retryer.continueOrPropagate(e);
          } catch (RetryableException th) {
            Throwable cause = th.getCause();
            if (propagationPolicy == UNWRAP && cause != null) {
              throw cause;
            } else {
              throw th;
            }
          }
          if (logLevel != Logger.Level.NONE) {
            logger.logRetry(metadata.configKey(), logLevel);
          }
          continue;
        }
      }
    }
}

到这里,可能有人会问,负载均衡的逻辑呢?

可以在executeAndDecode()方法继续往下看,负载均衡的逻辑在feign.Client#execute()的实现里。

由于篇幅有限,本文暂不介绍这部分的内容。

至此,终于把FeignClient如何从接口变成实现类讲完了。前面几节,为了逻辑的连贯性,忽略了一些Spring Cloud OpenFeign的一些配置相关的内容,下面再介绍一下。

2.5 FeignAutoConfiguration

还记得2.3.4 中,突然冒出来了几个组件。一开始看源码的时候,可能会有疑问,这些组件啥时候被加载进ApplicationContext的?直觉告诉我,应该是自动配置干的。

简单翻了一下spring-cloud-openfeign-core-2.2.1.RELEASE.jar,找到了一个和自动配置密切相关的文件META-INF/spring.factories。在应用启动的时候,Spring会从META-INF/spring.factories读入需要被自动配置的类,并且将符合条件的配置注册到容器中。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration

可以看到,刚刚用到的几个bean,FeignContextTargeter都是通过自动配置加载的。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
      FeignHttpClientProperties.class })
@Import(DefaultGzipDecoderConfiguration.class)
public class FeignAutoConfiguration {

   @Autowired(required = false)
   private List<FeignClientSpecification> configurations = new ArrayList<>();

   @Bean
   public HasFeatures feignFeature() {
      return HasFeatures.namedFeature("Feign", Feign.class);
   }

   @Bean
   public FeignContext feignContext() {
      FeignContext context = new FeignContext();
      context.setConfigurations(this.configurations);
      return context;
   }

   @Configuration(proxyBeanMethods = false)
   @ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
   protected static class HystrixFeignTargeterConfiguration {

      @Bean
      @ConditionalOnMissingBean
      public Targeter feignTargeter() {
         return new HystrixTargeter();
      }

   }

   @Configuration(proxyBeanMethods = false)
   @ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
   protected static class DefaultFeignTargeterConfiguration {

      @Bean
      @ConditionalOnMissingBean
      public Targeter feignTargeter() {
         return new DefaultTargeter();
      }
   }
}

那么,可能又有人要问了,这才几个bean啊,其他的bean的?这就要提到FeignContext

2.6 FeignContext

上一节看到,FeignContext在自动配置的时候创建并且注册到ApplicationContext中。在创建之后,还将configurations设置到FeignContext中。

帮大家回忆一下,这些FeignClientSpecification就是在2.2节注册BeanDefinition时,注册的FeignClient的全局配置类、私有配置类

我们先来简单介绍一下FeignContext的功能。FeignContext持有所有FeignClient的私有容器,可以做到每个FeignClient可以拥有自己独有的bean,相互之间隔离。

FeignClient需要取出私有容器时,可以通过FeignContext,将自己的唯一标识contextId传入。

FeignContext继承了NamedContextFactoryNamedContextFactory有两个重要的方法getContext()createContext()

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
   public FeignContext() {
      // 将FeignClientsConfiguration传入,作为默认的配置类
      super(FeignClientsConfiguration.class, "feign", "feign.client.name");
   }
}

下面分别介绍这两个方法。

2.6.1 getContext()

这个方法比较简单,就是根据名称从Map取出对应的容器,将将其返回。若该名称的容器还不存在,则立即创建一个,然后返回。

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
      implements DisposableBean, ApplicationContextAware {
    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
    
    protected AnnotationConfigApplicationContext getContext(String name) {
           // 每个名称空间有一个单独的容器,若还不存在则触发创建。
           if (!this.contexts.containsKey(name)) {
              synchronized (this.contexts) {
                 if (!this.contexts.containsKey(name)) {
                    this.contexts.put(name, createContext(name));
                 }
              }
           }
           return this.contexts.get(name);
        }
    }
}

2.6.2 createContext()

该方法主要的作用为指定名称创建容器,在创建容器时,主要做了以下几件事:

  1. 将本名称空间独有的配置类(也就是@FeignClient指定的)注册到子容器中
  2. 将全局的配置类(也就是@EnableFeignClients指定的)注册到子容器中
  3. 将其他自动配置类、配置类注册到子容器中。FeignClientsConfiguration就是这个时候被注册到子容器的,ContractFeign.Builder等组件也被顺带注册到子容器中。
  4. 设置子容器的父容器。将应用的容器作为父容器,这样做的好处是,当需要找某个bean时,若子容器不存在,会自动在父容器中查找。
  5. 触发刷新子容器。
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
      implements DisposableBean, ApplicationContextAware

    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
    // 父容器
    private ApplicationContext parent;
    // 默认的配置类
    private Class<?> defaultConfigType;

    public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
          String propertyName) {
       this.defaultConfigType = defaultConfigType;
       this.propertySourceName = propertySourceName;
       this.propertyName = propertyName;
    }
    
    // 当前spring容器为父容器
    @Override
    public void setApplicationContext(ApplicationContext parent) throws BeansException {
       this.parent = parent;
    }
    
    public void setConfigurations(List<C> configurations) {
       // 保存每个名称空间的配置
       for (C client : configurations) {
          this.configurations.put(client.getName(), client);
       }
    }

    protected AnnotationConfigApplicationContext createContext(String name) {
       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
       // 将当前名称空间的配置类注册到容器中
       if (this.configurations.containsKey(name)) {
          for (Class<?> configuration : this.configurations.get(name)
                .getConfiguration()) {
             context.register(configuration);
          }
       }
       // 将全局的配置类注册到容器中
       for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
          if (entry.getKey().startsWith("default.")) {
             for (Class<?> configuration : entry.getValue().getConfiguration()) {
                context.register(configuration);
             }
          }
       }
       // 注册配置类 FeignClientsConfiguration
       context.register(PropertyPlaceholderAutoConfiguration.class,
             this.defaultConfigType);
       context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
             this.propertySourceName,
             Collections.<String, Object>singletonMap(this.propertyName, name)));
       if (this.parent != null) {
          // 将Spring容器作为父容器
          context.setParent(this.parent);
          // jdk11 issue
          // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
          context.setClassLoader(this.parent.getClassLoader());
       }
       context.setDisplayName(generateDisplayName(name));
       // 刷新子容器
       context.refresh();
       return context;
    }
}

三、总结

至此,我们终于把Spring Cloud OpenFeign的源码的大概脉络梳理完了。我们来简单总结一下:

  1. 通过@EnableFeignClients@Import(FeignClientsRegistrar.class),实现了动态地将指定范围内的@FeignClient的接口的BeanDefinition注册到容器中。
  2. 基于META-INF/spring.factories配置文件,自动将一些OpenFeig的组件注册到容器中。
  3. 基于FeignContext,实现了不同FeignClient的组件隔离的效果。
  4. 基于FeignClientFactoryBean,封装了对Feign使用。通过调用Feign,实现了以动态代理的方式实现@FeignClient接口。

参考资料

  1. Spring Cloud OpenFeign 官方文档
  2. Spring Cloud OpenFeign 源码
  3. Spring Core Technologies 官方文档

你可能感兴趣的:(spring,cloud,spring,后端)