Feign 原理及源码解析

609 阅读16分钟

Feign 使用教程

Feign 基于 Netflix Feign 开发的,是一个轻量级的 声明式的 RESTful HTTP 客户端。

工作原理:

  1. 声明式,以一种简单的方法来定义和创建rest客户端,通过创建接口和注解来配置请求
  2. 集成 Ribbon负载均衡器 和 Hystrix断路器
  3. 支持注解: @FeignClient 来声明一个接口是一个 Feign 客户端
  4. 可使用不同的编码器和解码器来处理请求和响应的数据格式

这里首先介绍使用教程。这里介绍的是2.2.0.RELEASE版本

导入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>

启用feign

@EnableFeignClients
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

添加注解

@FeignClient(
        name = "demo-service",
        url = "http://localhost:8080/produce/",
        path = "/seed",
        contextId = "query",
        configuration = FeignIntecetor.class
)
public class TestService {
}

添加配置

Configuration & interceptor

在interceptor中对请求进行处理,可以在头部添加规定的数据,便于通过验证. 可以配置多个拦截器,拦截器可以链式追加,先执行配置类中指定的拦截器,然后配置文件中指定的全局拦截器,然后执行配置文件中指定的专属拦截器

public class FeignIntecetor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        requestTemplate.header("Authorizationtoken","123456");
    }
}

configuration;日志级别+重试次数

public class TestConfiguration {
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
    // 最大请求次数为5,初始间隔时间为100ms,下次间隔时间1.5倍递增,重试间最大间隔时间为1s,
    @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default();
    }
    //当feign调用返回HTTP报文时,触发此方法,获取HTTP状态码并定制处理特殊逻辑
    @Bean
    public ErrorDecoder feignError() {
        return (key, response) -> {
            if (response.status() == 400) {
                log.error("请求xxx服务400参数错误,返回:{}", response.body());
            }

            if (response.status() == 409) {
                log.error("请求xxx服务409异常,返回:{}", response.body());
            }

            if (response.status() == 404) {
                log.error("请求xxx服务404异常,返回:{}", response.body());
            }

            // 其他异常交给Default去解码处理// 这里使用单例即可,Default不用每次都去new return new ErrorDecoder.Default().decode(key, response);
        };
    }
}
client
  1. 切换client,默认的client不好用,可以使用httpclient或者okclient进行替换

导入依赖

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
    <version>10.4.0</version>
</dependency>

进行相应的配置:

feign:
  httpclient:
    enabled: true

切换okhttp

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
    <version>13.3</version>
</dependency>
feign:
  okhttp:
    enabled: true

在配置文件中,可以进行专属的配置: feignName为feignClient的name

feign:
  client:
    config:
      feignName:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: full
        errorDecoder: com.example.SimpleErrorDecoder
        retryer: com.example.SimpleRetryer
        requestInterceptors:
          - com.example.FooRequestInterceptor
          - com.example.BarRequestInterceptor
        decode404: false
        encoder: com.example.SimpleEncoder
        decoder: com.example.SimpleDecoder
压缩
feign:
    compression:
        request:
            enabled: true
            mime-types: text/xml,application/xml,application/json # 配置压缩支持的MIME TYPE
            min-request-size: 2048  # 配置压缩数据大小的下限
        response:
            enabled: true # 配置响应GZIP压缩

Openfeign 核心组件介绍

openfeign是我们常使用的组件,在使用时,通过在启动类中加入 EnableFeignClients 并在要使用的api中标明FeignClients注解进行使用。通过这一注解进行扫描,注册、生成bean和注入使用

这里主要介绍EnableFeignClients 这一注解。


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

    /**
* Alias for the { @link #basePackages()} attribute. Allows for more concise annotation
* declarations e.g.: { @code  @ComponentScan ("org.my.pkg")} instead of
* { @code  @ComponentScan (basePackages="org.my.pkg")}.
* @return the array of 'basePackages'.
*/
String[] value() default {};

    /**
* Base packages to scan for annotated components.
* <p>
* { @link #value()} is an alias for (and mutually exclusive with) this attribute.
* <p>
* Use { @link #basePackageClasses()} for a type-safe alternative to String-based
* package names.
* @return the array of 'basePackages'.
*/
String[] basePackages() default {};

    /**
* Type-safe alternative to { @link #basePackages()} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return the array of 'basePackageClasses'.
*/
Class<?>[] basePackageClasses() default {};

    /**
* A custom <code> @Configuration </code> for all feign clients. Can contain override
* <code> @Bean </code> definition for the pieces that make up the client, for instance
* { @link feign.codec.Decoder}, { @link feign.codec.Encoder}, { @link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
* @return list of default configurations
*/
Class<?>[] defaultConfiguration() default {};

    /**
* List of classes annotated with @FeignClient . If not empty, disables classpath
* scanning.
* @return list of FeignClient classes
*/
Class<?>[] clients() default {};

}

value() 和 basePackages():用于指定要扫描的基本包,两者互斥,value() 是更简洁的语法。

basePackageClasses():类型安全的方式指定要扫描的基本包,通过类来指定包。

defaultConfiguration():指定所有 Feign 客户端的默认配置类,用于自定义编码器、解码器等组件。

clients():指定具体的 @FeignClient 类,如果指定了这个参数,则禁用类路径扫描。

这些参数共同作用于 Feign 客户端的配置和扫描机制,提供了灵活且强大的配置方式。

feign使用FeignClientsRegistrar类将FeignClient进行导入和注册

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

这一注解通过registerBeanDefinitions方法进行配置和加载,其核心为registerFeignClients方法,这里首先介绍下registerDefaultConfiguration。

registerDefaultConfiguration

其代码如下:

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

private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    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();
       }
       // 直接取自己的class name
       else {
          name = "default." + metadata.getClassName();
       }
       registerClientConfiguration(registry, name, "default", defaultAttrs.get("defaultConfiguration"));
    }
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object className,
       Object configuration) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
    builder.addConstructorArgValue(name);
    builder.addConstructorArgValue(className);
    builder.addConstructorArgValue(configuration);
    registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
          builder.getBeanDefinition());
}
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
    Assert.hasText(beanName, "Bean name must not be empty");
    Assert.notNull(beanDefinition, "BeanDefinition must not be null");
    if (beanDefinition instanceof AbstractBeanDefinition abd) {
        try {
            abd.validate();
        } catch (BeanDefinitionValidationException var8) {
            BeanDefinitionValidationException ex = var8;
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex);
        }
    }

    BeanDefinition existingDefinition = (BeanDefinition)this.beanDefinitionMap.get(beanName);
    if (existingDefinition != null) {
        if (!this.isBeanDefinitionOverridable(beanName)) {
            throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
        }

        if (existingDefinition.getRole() < beanDefinition.getRole()) {
            if (this.logger.isInfoEnabled()) {
                this.logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
            }
        } else if (!beanDefinition.equals(existingDefinition)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
            }
        } else if (this.logger.isTraceEnabled()) {
            this.logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
        }

        this.beanDefinitionMap.put(beanName, beanDefinition);
    } else {
        if (this.isAlias(beanName)) {
            String aliasedName = this.canonicalName(beanName);
            if (!this.isBeanDefinitionOverridable(aliasedName)) {
                if (this.containsBeanDefinition(aliasedName)) {
                    throw new BeanDefinitionOverrideException(beanName, beanDefinition, this.getBeanDefinition(aliasedName));
                }

                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Cannot register bean definition for bean '" + beanName + "' since there is already an alias for bean '" + aliasedName + "' bound.");
            }

            this.removeAlias(beanName);
        }

        if (this.hasBeanCreationStarted()) {
            synchronized(this.beanDefinitionMap) {
                this.beanDefinitionMap.put(beanName, beanDefinition);
                List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);
                updatedDefinitions.addAll(this.beanDefinitionNames);
                updatedDefinitions.add(beanName);
                this.beanDefinitionNames = updatedDefinitions;
                this.removeManualSingletonName(beanName);
            }
        } else {
            this.beanDefinitionMap.put(beanName, beanDefinition);
            this.beanDefinitionNames.add(beanName);
            this.removeManualSingletonName(beanName);
        }

        this.frozenBeanDefinitionNames = null;
    }

    if (existingDefinition == null && !this.containsSingleton(beanName)) {
        if (this.isConfigurationFrozen()) {
            this.clearByTypeCache();
        }
    } else {
        this.resetBeanDefinition(beanName);
    }

}

这部分代码主要对默认的配置进行注册,在最后的registerBeanDefinition里,其主要功能为确保在容器中正确注册或更新指定的bean定义

  1. 验证输入参数非空;

  2. 若新定义为AbstractBeanDefinition类型,则进行验证;

  3. 检查是否存在同名定义:

    1. 若存在且不允许覆盖,则抛出异常;
    2. 允许覆盖时,根据角色优先级更新定义,并记录日志;
  4. 若不存在,则处理别名冲突并添加新定义;

  5. 更新内部数据结构,并在必要时清除缓存或重置定义。

registerFeignClients

registerFeignClients为真正需要关注的,扫描并进行注册的部分。

首先总结下代码过程:

  1. 根据参数确定需要扫描的范围或需要加载的clients

    1. 若clients不为空,则取clients中的配置
    2. 若clients为空,则扫描value,basePackages,basePackageClasses中的包扫描存在feignclient的注解
    3. 若b中未规定包,则扫描启动类所在的包
  2. 获取name和className

    1. Name: contextId>value>name>serviceId。
  3. 注册beanName和对应的configuration

    1. beanName: name.FeignClientSpecification
  4. 注册feignClient. 注册时根据spring.cloud.openfeign.lazy-attributes-resolution 确定是懒加载还是立即加载,默认为false立即加载

    1. 立即加载:立即加载处理各个参数后,注册bean定义和可刷新的bean定义
    2. 懒加载:核心部分为设置装配模式和延迟加载 definition.setLazyInit(true);
    3. 此外,懒加载使用Supplier 延迟创建 Bean 实例,并设置懒加载。而立即加载则是直接创建实例并装配参数进行实例化
public void registerFeignClients(AnnotationMetadata metadata,
       BeanDefinitionRegistry registry) {
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);

    Set<String> basePackages;

    Map<String, Object> attrs = metadata
          .getAnnotationAttributes(EnableFeignClients.class.getName());
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
          FeignClient.class);
    final Class<?>[] clients = attrs == null ? null
          : (Class<?>[]) attrs.get("clients");
    // 优先级:clients > basepackages这些,如果配置了clients, 那么只会读取这些
    if (clients == null || clients.length == 0) {
       scanner.addIncludeFilter(annotationTypeFilter);
       // 获取真正的需要扫描的范围
       basePackages = getBasePackages(metadata);
    }
    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");

             Map<String, Object> attributes = annotationMetadata
                   .getAnnotationAttributes(
                         FeignClient.class.getCanonicalName());
             // contextId>value>name>serviceId
             String name = getClientName(attributes);
             // 注册配置
             registerClientConfiguration(registry, name,
                   attributes.get("configuration"));
            // 注册feignclient
             registerFeignClient(registry, annotationMetadata, attributes);
          }
       }
    }
}
protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
    Map<String, Object> attributes = importingClassMetadata
          .getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());

    Set<String> basePackages = new HashSet<>();
    for (String pkg : (String[]) attributes.get("value")) {
       if (StringUtils.hasText(pkg)) {
          basePackages.add(pkg);
       }
    }
    for (String pkg : (String[]) attributes.get("basePackages")) {
       if (StringUtils.hasText(pkg)) {
          basePackages.add(pkg);
       }
    }
    for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
       basePackages.add(ClassUtils.getPackageName(clazz));
    }

    if (basePackages.isEmpty()) {
       basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
    }
    return basePackages;
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object className,
       Object configuration) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
    builder.addConstructorArgValue(name);
    builder.addConstructorArgValue(className);
    builder.addConstructorArgValue(configuration);
    registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
          builder.getBeanDefinition());
}
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
       Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    // 默认情况下为立即加载 false
    if (String.valueOf(false).equals(
          environment.getProperty("spring.cloud.openfeign.lazy-attributes-resolution", String.valueOf(false)))) {
       eagerlyRegisterFeignClientBeanDefinition(className, attributes, registry);
    }
    else {
       lazilyRegisterFeignClientBeanDefinition(className, attributes, registry);
    }
}

feignClient接口

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FeignClient {
    @AliasFor("name")
    String value() default "";

    String contextId() default "";

    @AliasFor("value")
    String name() default "";

    String[] qualifiers() default {};

    String url() default "";

    boolean dismiss404() default false;

    Class<?>[] configuration() default {};

    Class<?> fallback() default void.class;

    Class<?> fallbackFactory() default void.class;

    String path() default "";

    boolean primary() default true;
}
  • name:调用的服务名称
  • Url: 服务的url
  • dismiss404: 是否允许404错误
  • contextId qualifiers: 当一个应用内部有多个同名的客户端时,使用其进行区分
  • Path: 基础路径
  • fallback:指定一个回退类,当远程调用失败时,会使用这个回退类中的方法作为替代。
  • fallbackFactory:用于指定一个回退工厂类,该工厂类可以根据具体的异常情况返回不同的回退对象。
  • primary:标记是否为主要客户端。默认 是 当存在多个时,主要的会在大部分时间使用,次要只在特定条件下使用,如主要客户端出问题,或者测试或备用环境
  • Configuration: 指定Feign客户端的配置类,可以在配置类中自定义Feign的行为,例如超时时间、重试策略等。
public static class FeignClientConfiguration {
    private Logger.Level loggerLevel;
    private Integer connectTimeout;
    private Integer readTimeout;
    private Class<Retryer> retryer;
    private Class<ErrorDecoder> errorDecoder;
    private List<Class<RequestInterceptor>> requestInterceptors;
    private Class<ResponseInterceptor> responseInterceptor;
    private Map<String, Collection<String>> defaultRequestHeaders = new HashMap();
    private Map<String, Collection<String>> defaultQueryParameters = new HashMap();
    private Boolean dismiss404;
    private Class<Decoder> decoder;
    private Class<Encoder> encoder;
    private Class<Contract> contract;
    private ExceptionPropagationPolicy exceptionPropagationPolicy;
    private List<Class<Capability>> capabilities;
    private Class<QueryMapEncoder> queryMapEncoder;
    private MicrometerProperties micrometer;
    private Boolean followRedirects;
    private String url;
 }

Feign工作原理

feign方法调用详细流程

feign整体可分为两部分,第一是代理对象的创建,然后是方法的执行。

  1. 动态生成代理对象

    1. Feign.buide 注入依赖配置,生成返回值处理器,方法调用处理工厂,

      1.  public Feign internalBuild() {
             final ResponseHandler responseHandler =
                 new ResponseHandler(logLevel, logger, decoder, errorDecoder,
                     dismiss404, closeAfterDecode, decodeVoid, responseInterceptorChain());
             MethodHandler.Factory<Object> methodHandlerFactory =
                 new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors,
                     responseHandler, logger, logLevel, propagationPolicy,
                     new RequestTemplateFactoryResolver(encoder, queryMapEncoder),
                     options);
             return new ReflectiveFeign<>(contract, methodHandlerFactory, invocationHandlerFactory,
                 () -> null);
           }
         }
        
    2. 根据JDK动态代理生成接口代理类

    3.       public <T> T newInstance(Target<T> target, C requestContext) {
         TargetSpecificationVerifier.verify(target);
       //使用Contract解析接口类上的方法和注解,转换单独MethodHandler处理
         Map<Method, MethodHandler> methodToHandler =
             targetToHandlersByName.apply(target, requestContext);
             // 使用DK动态代理为接口生成代理对象,实际业务逻辑交给 InvocationHandler 处理,
             // 其实就是调用 MethodHandler
         InvocationHandler handler = factory.create(target, methodToHandler);
         T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
             new Class<?>[] {target.type()}, handler);
      
         for (MethodHandler methodHandler : methodToHandler.values()) {
           if (methodHandler instanceof DefaultMethodHandler) {
             ((DefaultMethodHandler) methodHandler).bindTo(proxy);
           }
         }
      
         return proxy;
       }
       ReflectiveFeign(
           Contract contract,
           MethodHandler.Factory<C> methodHandlerFactory,
           InvocationHandlerFactory invocationHandlerFactory,
           AsyncContextSupplier<C> defaultContextSupplier) {
         this.targetToHandlersByName = new ParseHandlersByName<C>(contract, methodHandlerFactory);
         this.factory = invocationHandlerFactory;
         this.defaultContextSupplier = defaultContextSupplier;
      
       }
      
    4.   C.生成方法处理的map

    5.  public Map<Method, MethodHandler> apply(Target target, C requestContext) {
         final Map<Method, MethodHandler> result = new LinkedHashMap<>();
      
         final List<MethodMetadata> metadataList = contract.parseAndValidateMetadata(target.type());
         for (MethodMetadata md : metadataList) {
           final Method method = md.method();
           if (method.getDeclaringClass() == Object.class) {
             continue;
           }
      
           final MethodHandler handler = createMethodHandler(target, md, requestContext);
           result.put(method, handler);
         }
      
         for (Method method : target.type().getMethods()) {
           if (Util.isDefault(method)) {
             final MethodHandler handler = new DefaultMethodHandler(method);
             result.put(method, handler);
           }
         }
      
         return result;
       }
       public static boolean isDefault(Method method) {
         // Default methods are public non-abstract, non-synthetic, and non-static instance methods
         // declared in an interface.
         // method.isDefault() is not sufficient for our usage as it does not check
         // for synthetic methods. As a result, it picks up overridden methods as well as actual default
         // methods.
         final int SYNTHETIC = 0x00001000;
         return ((method.getModifiers()
             & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC | SYNTHETIC)) == Modifier.PUBLIC)
             && method.getDeclaringClass().isInterface();
       }
      

Contract 解析代码的整体流程:

  1. 过滤object方法、静态方法、默认方法、标注FeignIgnore方法

  2. 剩下的方法进行解析验证获取元数据信息。元数据主要包括方法信息,返回值类型,参数信息,方法体信息等等。

    1. processAnnotationOnClass 处理类上的注解信息 这部分主要是保证类中的方法只关联一个HTTP方法

    2. processAnnotationOnMethod 处理方法上的注解信息。

      1. 注册RequestLine注解处理:解析HTTP请求行,设置方法和URI,并检查格式正确性。
      2. 注册Body注解处理:设置请求体或请求体模板,并检查内容非空。
      3. 注册Headers注解处理:将多个头部信息转换为Map并设置到模板中,并确保注解非空
    3. processAnnotationsOnParameter 处理参数上的注解信息

      1. 对于@Param注解,获取注解值,确定参数名称(优先使用注解值),配置注解指定的拓展器。拓展器可以按照不同的形式对参数进行拓展处理如添加前缀,后缀,进行编码处理等
      2. 对于@QueryMap注解,设置查询参数映射索引并初始化查询参数编码器实例。
      3. 对于@HeaderMap注解,设置头部参数映射索引。两者均检查是否有多重参数标注该注解的情况
  3. 精细化处理重写的返回值类型

默认解析代码如下,在第一部分的解析中,会将object中的方法、静态方法、默认方法、标注FeignIgnore的方法跳过,在第二部分解析中,将默认方法交给默认处理器进行处理后放入到result中。即对于静态方法,object中的方法,标注FeignIgnore的方法,都会进行过滤

public List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType) {
  checkState(targetType.getTypeParameters().length == 0, "Parameterized types unsupported: %s",
      targetType.getSimpleName());
  checkState(targetType.getInterfaces().length <= 1, "Only single inheritance supported: %s",
      targetType.getSimpleName());
  final Map<String, MethodMetadata> result = new LinkedHashMap<String, MethodMetadata>();
  for (final Method method : targetType.getMethods()) {
    if (method.getDeclaringClass() == Object.class ||
        (method.getModifiers() & Modifier.STATIC) != 0 ||
        Util.isDefault(method) || method.isAnnotationPresent(FeignIgnore.class)) {
      continue;
    }
    // 解析并验证方法元数据,在这部分会对class和方法上的类进行处理,并生成一个MethodMetadata
    // MethodMetadata实际上就是对targetType,method,方法体的编码,返回值
    final MethodMetadata metadata = parseAndValidateMetadata(targetType, method);
    // 处理方法签名冲突,主要解决重写方法的方法名相同,但返回值不同的问题,将返回值调整为子类中
    // 更加精准的类型 以及确定每个参数,是否可忽略。这部分内容见processAnnotationsOnParameter方法
    if (result.containsKey(metadata.configKey())) {
      MethodMetadata existingMetadata = result.get(metadata.configKey());
      Type existingReturnType = existingMetadata.returnType();
      Type overridingReturnType = metadata.returnType();
      Type resolvedType = Types.resolveReturnType(existingReturnType, overridingReturnType);
      if (resolvedType.equals(overridingReturnType)) {
        result.put(metadata.configKey(), metadata);
      }
      continue;
    }
    result.put(metadata.configKey(), metadata);
  }
  return new ArrayList<>(result.values());
}
protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
  final MethodMetadata data = new MethodMetadata();
  data.targetType(targetType);
  data.method(method);
  data.returnType(
      Types.resolve(targetType, targetType, method.getGenericReturnType()));
  data.configKey(Feign.configKey(targetType, method));
  if (AlwaysEncodeBodyContract.class.isAssignableFrom(this.getClass())) {
    data.alwaysEncodeBody(true);
  }

  if (targetType.getInterfaces().length == 1) {
    processAnnotationOnClass(data, targetType.getInterfaces()[0]);
  }
  processAnnotationOnClass(data, targetType);


  for (final Annotation methodAnnotation : method.getAnnotations()) {
    processAnnotationOnMethod(data, methodAnnotation, method);
  }
  if (data.isIgnored()) {
    return data;
  }
  checkState(data.template().method() != null,
      "Method %s not annotated with HTTP method type (ex. GET, POST)%s",
      data.configKey(), data.warnings());
  final Class<?>[] parameterTypes = method.getParameterTypes();
  final Type[] genericParameterTypes = method.getGenericParameterTypes();

  final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
  final int count = parameterAnnotations.length;
  for (int i = 0; i < count; i++) {
    boolean isHttpAnnotation = false;
    if (parameterAnnotations[i] != null) {
      isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
    }

    if (isHttpAnnotation) {
      data.ignoreParamater(i);
    }

    if ("kotlin.coroutines.Continuation".equals(parameterTypes[i].getName())) {
      data.ignoreParamater(i);
    }

    if (parameterTypes[i] == URI.class) {
      data.urlIndex(i);
    } else if (!isHttpAnnotation
        && !Request.Options.class.isAssignableFrom(parameterTypes[i])) {
      if (data.isAlreadyProcessed(i)) {
        checkState(data.formParams().isEmpty() || data.bodyIndex() == null,
            "Body parameters cannot be used with form parameters.%s", data.warnings());
      } else if (!data.alwaysEncodeBody()) {
        checkState(data.formParams().isEmpty(),
            "Body parameters cannot be used with form parameters.%s", data.warnings());
        checkState(data.bodyIndex() == null,
            "Method has too many Body parameters: %s%s", method, data.warnings());
        data.bodyIndex(i);
        data.bodyType(
            Types.resolve(targetType, targetType, genericParameterTypes[i]));
      }
    }
  }

  if (data.headerMapIndex() != null) {
    // check header map parameter for map type
    if (Map.class.isAssignableFrom(parameterTypes[data.headerMapIndex()])) {
      checkMapKeys("HeaderMap", genericParameterTypes[data.headerMapIndex()]);
    }
  }

  if (data.queryMapIndex() != null) {
    if (Map.class.isAssignableFrom(parameterTypes[data.queryMapIndex()])) {
      checkMapKeys("QueryMap", genericParameterTypes[data.queryMapIndex()]);
    }
  }

  return data;
}

contract所提供的默认解析逻辑如下:

protected final void processAnnotationOnClass(MethodMetadata data, Class<?> targetType) {
  final List<GuardedAnnotationProcessor> processors = Arrays.stream(targetType.getAnnotations())
      .flatMap(annotation -> classAnnotationProcessors.stream()
          .filter(processor -> processor.test(annotation)))
      .collect(Collectors.toList());

  if (!processors.isEmpty()) {
    Arrays.stream(targetType.getAnnotations())
        .forEach(annotation -> processors.stream()
            .filter(processor -> processor.test(annotation))
            .forEach(processor -> processor.process(annotation, data)));
  } else {
    if (targetType.getAnnotations().length == 0) {
      data.addWarning(String.format(
          "Class %s has no annotations, it may affect contract %s",
          targetType.getSimpleName(),
          getClass().getSimpleName()));
    } else {
      data.addWarning(String.format(
          "Class %s has annotations %s that are not used by contract %s",
          targetType.getSimpleName(),
          Arrays.stream(targetType.getAnnotations())
              .map(annotation -> annotation.annotationType()
                  .getSimpleName())
              .collect(Collectors.toList()),
          getClass().getSimpleName()));
    }
  }
  protected final void processAnnotationOnMethod(MethodMetadata data,
                                               Annotation annotation,
                                               Method method) {
  List<GuardedAnnotationProcessor> processors = methodAnnotationProcessors.stream()
      .filter(processor -> processor.test(annotation))
      .collect(Collectors.toList());

  if (!processors.isEmpty()) {
    processors.forEach(processor -> processor.process(annotation, data));
  } else {
    data.addWarning(String.format(
        "Method %s has an annotation %s that is not used by contract %s",
        method.getName(),
        annotation.annotationType()
            .getSimpleName(),
        getClass().getSimpleName()));
  }
}

targetToHandlersByName可注入contract解析器和methodHandlerFactory方法处理工厂,去生成map,key为method, value为MethodHandler,

在processAnnotationOnClass中的test方法和process方法都为lambda方法。具体的方法如下所示:

registerMethodAnnotation(methodAnnotation -> {
// 只保留methodAnnotation上具备HttpMethod注解的方法
  final Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
  final HttpMethod http = annotationType.getAnnotation(HttpMethod.class);
  return http != null;
}, (methodAnnotation, data) -> {
// 做检查,保证每个方法只关联一个HTTP方法,避免歧义,确保一致性和正确性
  final Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
  final HttpMethod http = annotationType.getAnnotation(HttpMethod.class);
  checkState(data.template().method() == null,
      "Method %s contains multiple HTTP methods. Found: %s and %s", data.configKey(),
      data.template().method(), http.value());
  data.template().method(Request.HttpMethod.valueOf(http.value()));
});

processAnnotationsOnParameter方法具体代码如下。

protected final boolean processAnnotationsOnParameter(MethodMetadata data,
                                                      Annotation[] annotations,
                                                      int paramIndex) {
  List<Annotation> matchingAnnotations = Arrays.stream(annotations)
      .filter(
          annotation -> parameterAnnotationProcessors.containsKey(annotation.annotationType()))
      .collect(Collectors.toList());

  if (!matchingAnnotations.isEmpty()) {
    matchingAnnotations.forEach(annotation -> parameterAnnotationProcessors
        .getOrDefault(annotation.annotationType(), ParameterAnnotationProcessor.DO_NOTHING)
        .process(annotation, data, paramIndex));

  } else {
    final Parameter parameter = data.method().getParameters()[paramIndex];
    String parameterName = parameter.isNamePresent()
        ? parameter.getName()
        : parameter.getType().getSimpleName();
    if (annotations.length == 0) {
      data.addWarning(String.format(
          "Parameter %s has no annotations, it may affect contract %s",
          parameterName,
          getClass().getSimpleName()));
    } else {
      data.addWarning(String.format(
          "Parameter %s has annotations %s that are not used by contract %s",
          parameterName,
          Arrays.stream(annotations)
              .map(annotation -> annotation.annotationType()
                  .getSimpleName())
              .collect(Collectors.toList()),
          getClass().getSimpleName()));
    }
  }
  return false;
}

其主要是针对参数上的注解,筛选出已经注册过的处理器,然后进行处理,如果没有匹配的处理器,就生成警告信息返回false, 这里给一个注册的处理流程

super.registerParameterAnnotation(Param.class, (paramAnnotation, data, paramIndex) -> {
  final String annotationName = paramAnnotation.value();
  final Parameter parameter = data.method().getParameters()[paramIndex];
  final String name;
  // 如果annotationName为空,使用参数名,否则直接使用annotationName
  if (emptyToNull(annotationName) == null && parameter.isNamePresent()) {
    name = parameter.getName();
  } else {
    name = annotationName;
  }
  checkState(emptyToNull(name) != null, "Param annotation was empty on param %s.",
      paramIndex);
  nameParam(data, name, paramIndex);
  final Class<? extends Param.Expander> expander = paramAnnotation.expander();
  if (expander != Param.ToStringExpander.class) {
    data.indexToExpanderClass().put(paramIndex, expander);
  }
  if (!data.template().hasRequestVariable(name)) {
    data.formParams().add(name);
  }
});

在发起请求时的具体流程

根据不同的请求构建请求模板:

Encoder 部分

RequestTemplateFactoryResolver #resolve 默认是上面的,配置encoder可以进行编码处理

protected RequestTemplate resolve(Object[] argv,
                                  RequestTemplate mutable,
                                  Map<String, Object> variables) {
  return mutable.resolve(variables);
}
protected RequestTemplate resolve(Object[] argv,
                                    RequestTemplate mutable,
                                    Map<String, Object> variables) {
    Map<String, Object> formVariables = new LinkedHashMap<String, Object>();
    for (Map.Entry<String, Object> entry : variables.entrySet()) {
      if (metadata.formParams().contains(entry.getKey())) {
        formVariables.put(entry.getKey(), entry.getValue());
      }
    }
    try {
      encoder.encode(formVariables, Encoder.MAP_STRING_WILDCARD, mutable);
    } catch (EncodeException e) {
      throw e;
    } catch (RuntimeException e) {
      throw new EncodeException(e.getMessage(), e);
    }
    return super.resolve(argv, mutable, variables);
  }
}

RequestInterceptor

路径 executeAndDecode -> targetRequest

private Request targetRequest(RequestTemplate template) {
  for (RequestInterceptor interceptor : methodHandlerConfiguration.getRequestInterceptors()) {
    interceptor.apply(template);
  }
  return methodHandlerConfiguration.getTarget().apply(template);
}

在拦截器中可以配置,对请求头进行加内容等等,从而通过调用方的校验限制

public interface RequestInterceptor {

  /**
* Called for every request. Add data using methods on the supplied { @link RequestTemplate}.
*/
void apply(RequestTemplate template);
}

Log

如果不是不输出就进行对应的输出

public enum Level {
  /**
* No logging.
*/
NONE,
  /**
* Log only the request method and URL and the response status code and execution time.
*/
BASIC,
  /**
* Log the basic information along with request and response headers.
*/
HEADERS,
  /**
* Log the headers, body, and metadata for both requests and responses.
*/
FULL
}
if (methodHandlerConfiguration.getLogLevel() != Logger.Level.NONE) {
  methodHandlerConfiguration.getLogger().logRequest(
      methodHandlerConfiguration.getMetadata().configKey(),
      methodHandlerConfiguration.getLogLevel(), request);
}
protected void logRequest(String configKey, Level logLevel, Request request) {
  String protocolVersion = resolveProtocolVersion(request.protocolVersion());
  // 先打印基本信息
  log(configKey, "---> %s %s %s", request.httpMethod().name(), request.url(),
      protocolVersion);
   // 判断是否打印头部
  if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {

    for (String field : request.headers().keySet()) {
      if (shouldLogRequestHeader(field)) {
        for (String value : valuesOrEmpty(request.headers(), field)) {
          log(configKey, "%s: %s", field, value);
        }
      }
    }

    int bodyLength = 0;
    if (request.body() != null) {
      bodyLength = request.length();
      // 打印全部
      if (logLevel.ordinal() >= Level.FULL.ordinal()) {
        String bodyText =
            request.charset() != null
                ? new String(request.body(), request.charset())
                : null;
        log(configKey, ""); // CRLF
        log(configKey, "%s", bodyText != null ? bodyText : "Binary data");
      }
    }
    log(configKey, "---> END HTTP (%s-byte body)", bodyLength);
  }
}

client 执行命令

由client执行最终的请求,

默认为Default

@Override
public Response execute(Request request, Options options) throws IOException {
  HttpURLConnection connection = convertAndSend(request, options);
  return convertResponse(connection, request);
}

负载均衡 -> RibbonClient

public Response execute(Request request, Request.Options options) throws IOException {
    URI originalUri = URI.create(request.url());
    String serviceId = originalUri.getHost();
    Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
    String hint = this.getHint(serviceId);
    DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest(new RequestDataContext(LoadBalancerUtils.buildRequestData(request), hint));
    Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator.getSupportedLifecycleProcessors(this.loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class), RequestDataContext.class, ResponseData.class, ServiceInstance.class);
    supportedLifecycleProcessors.forEach((lifecycle) -> {
        lifecycle.onStart(lbRequest);
    });
    ServiceInstance instance = this.loadBalancerClient.choose(serviceId, lbRequest);
    org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(instance);
    String message;
    if (instance == null) {
        message = "Load balancer does not contain an instance for the service " + serviceId;
        if (LOG.isWarnEnabled()) {
            LOG.warn(message);
        }

        supportedLifecycleProcessors.forEach((lifecycle) -> {
            lifecycle.onComplete(new CompletionContext(Status.DISCARD, lbRequest, lbResponse));
        });
        return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value()).body(message, StandardCharsets.UTF_8).build();
    } else {
        message = this.loadBalancerClient.reconstructURI(instance, originalUri).toString();
        Request newRequest = this.buildRequest(request, message, instance);
        return LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing(this.delegate, options, newRequest, lbRequest, lbResponse, supportedLifecycleProcessors);
    }
}

这段代码主要是从可使用的负载均衡对象中选择服务实例并重构请求url返回请求。

Ribbon的负载均衡策略有:

  1. 轮询
  2. 随机
  3. 重试
  4. 加权响应时间策略
  5. 连接数最少
  6. 可用性过滤

总结

本文主要介绍feign的使用教程、openfeign的扫描注册、feign的工作原理三个部分

相关介绍

Supplier接口

Supplier是 Java 中的一个功能性接口(Functional Interface),位于 java.util.function 包中。它只有一个抽象方法 get(),返回一个结果但不抛出异常。Supplier 接口的主要作用是提供对象,而不需要知道对象是如何创建的。

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

这里的 <T> 是一个类型参数,表示 Supplier 返回的对象类型。

Supplier 接口可以用在多种场景中,例如:

  1. 懒加载:需要延迟对象的创建直到真正需要它的时候,可以使用 Supplier 来实现懒加载。
  2. 依赖注入:在一些依赖注入框架中,Supplier 可以用来提供依赖对象的实例。
  3. 函数式编程:在 Java 8 引入的函数式编程特性中,Supplier 可以作为参数传递给函数,或者作为返回值。