Skip to content

Latest commit

 

History

History
558 lines (466 loc) · 27.1 KB

spring-cloud-openfeign-source-note.md

File metadata and controls

558 lines (466 loc) · 27.1 KB

资料

Spring Cloud 官网文档

Spring Cloud OpenFeign 官网文档

示例代码

Spring Cloud OpenFeign 介绍

Feign 是一个声明式的 Web 服务客户端,它使 Java 编写 Web 服务客户端变得更加容易。而 OpenFeign 是通过自动装配将Feign集成到应用程序中。

整合 Spring Cache 来执行 FeignClient 接口,拦截方法的执行增加上 缓存 的设置

整合 CircuitBreaker 来执行 FeignClient 接口的方法,方法的执行委托给 CircuitBreaker 控制

整合 spring-cloud-loadbalancer 然后 Feign 使用负载均衡的HTTP客户端 来发送请求

核心功能源码分析

OpenFeign自动装配原理

spring-cloud-openfeign-core.jar!META-INF/spring.factories 的部分内容

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

FeignAutoConfiguration

主要是注册了 FeignContext、Targeter、CachingCapability、执行HTTP请求的工具(ApacheHttpClient、OkHttpClient)。

FeignContext 是用来隔离不同 FeignClient 的容器,每个 FeignClient 有单独的 IOC容器,容器中默认注册了 FeignClient 需要的 bean。

Targeter 是用来生成 FeignClient 接口的实现类的。

CachingCapability 是 用来配置 Feign.Builder 的,主要是对 InvocationHandlerFactory 进行增强,而 InvocationHandlerFactory 是用来生成 InvocationHandler 从而让方法的执行委托给 CacheInterceptor 来执行,也就是会处理 Spring Cache 相关的注解

/**
 * FeignAutoConfiguration
 *      注册三个个绑定属性的bean @EnableConfigurationProperties({ FeignClientProperties.class, FeignHttpClientProperties.class, FeignEncoderProperties.class})
 *          FeignClientProperties:记录 FeignClient 的配置信息,比如 RequestInterceptor 等等
 *          FeignHttpClientProperties:记录 HttpClient 的配置信息,最大连接数、连接的ttl等等
 *          FeignEncoderProperties: 是否从响应头 Content-Type 获取响应体的编码,会使用这个编码对响应体解码成字符串(默认是UTF-8)
 *
 *      注册 HasFeatures 是用来描述系统有 Feign 的功能,它是给 FeaturesEndpoint 使用的。
 *
 *      注册 FeignContext 其继承 NamedContextFactory。
 *        其作用是会根据 name 创建单独的IOC容器,获取bean是从单独的IOC容器中拿。IOC容器默认有两个配置类:PropertyPlaceholderAutoConfiguration、FeignClientsConfiguration
 *        并依赖 FeignClientSpecification 用来扩展配置类,创建的IOC容器会缓存到Map中。
 *
 *        FeignClientsConfiguration 其目的是注册 Decoder、Encoder、Encoder、Contract、FormattingConversionService、
 *        Retryer、FeignLoggerFactory、FeignClientConfigurer、Feign.Builder 这些bean 而且都有 @ConditionalOnMissingBean 条件,若我们想自定义
 *        这些bean,可以设置 FeignClientSpecification 扩展配置类,从而让 @ConditionalOnMissingBean 不满足,也就不会使用这些默认的bean
 *
 *        而 FeignClientSpecification 可以通过这两个注解快速配置
 *         @EnableFeignClients(defaultConfiguration={A.class}) // 这样子是注册全局的,FeignContext 创建的所有IOC容器都会使用这个配置类
 *         @FeignClient(contextId="f1",name="serviceName",configuration={A.class}) // FeignContext 为 f1 创建的IOC容器 会使用这个配置类
 *          注:contextId 为空 就会使用 name 作为缺省值
 *
 *      注册 CachingCapability 其实现 Capability 接口,依赖 CacheInterceptor。CachingCapability 是用来增强 Feign.Builder 设置的 InvocationHandlerFactory 的
 *          让方法的执行委托给 CacheInterceptor 执行,也就是支持 Spring Cache 的功能
 *
 *      注册 PageJacksonModule、SortJacksonModule 都是 Module 类型的,这两个东西是用来扩展 jackson 扩展序列化规则的,是为了支持 spring data
 *
 *      注册 Targeter 类型的bean,默认是 DefaultTargeter , 如果容器中有 CircuitBreakerFactory 类型的bean,那就会注册 FeignCircuitBreakerTargeter
 *          Targeter 是用来聚合 FeignClientFactoryBean、Feign.Builder、FeignContext、Target.HardCodedTarget,
 *                   定义了如何生成 Target.HardCodedTarget<T> 泛型的实例
 *      
 *      注册 CircuitBreakerNameResolver 是用来生成断路器name的,FeignCircuitBreakerTargeter 会依赖这个bean
 *
 *      注册 HttpClientConnectionManager 会依赖 FeignHttpClientProperties 来设置连接相关参数
 *
 *      注册 CloseableHttpClient 是 HttpClient 的实现类,其依赖 HttpClientConnectionManager、FeignHttpClientProperties 设置一些参数
 *
 *      注册 ApacheHttpClient 是 feign.Client 的实现类,依赖 HttpClient 来执行HTTP请求
 *
 *      属性 feign.okhttp.enabled == true 会注册
 *          注册 ConnectionPool 会依赖 FeignHttpClientProperties 来设置连接相关参数
 *          注册 okhttp3.OkHttpClient 其依赖 ConnectionPool、FeignHttpClientProperties 设置一些参数
 *          注册 OkHttpClient 是 feign.Client 的实现类,依赖 okhttp3.OkHttpClient 来执行HTTP请求
 * */

FeignAcceptGzipEncodingAutoConfiguration

/**
 * FeignAcceptGzipEncodingAutoConfiguration
 *      注册 FeignAcceptGzipEncodingInterceptor , 其是 RequestInterceptor 的实现类,其目的是给 Request 增加请求头 Accept-Encoding=gzip,deflate
 *          注:Accept-Encoding=gzip,deflate 是告诉服务器 客户端支持 gzip,deflate 压缩
 **/
feign:
  compression:
    # 设置请求头 Accept-Encoding=gzip,deflate 用于告诉服务器 客户端支持 gzip,deflate 压缩
    response:
      enabled: true

FeignContentGzipEncodingAutoConfiguration

/**
 * FeignContentGzipEncodingAutoConfiguration
 *      注册 FeignContentGzipEncodingInterceptor , 其是 RequestInterceptor 的实现类,其目的是给 Request 增加请求头 Content-Encoding=gzip,deflate
 *          满足这两点才需要增加请求头:
 *              1. Content-Type 是属性 feign.compression.request.mimeTypes 包含的值
 *              2. Content-Length 大于 属性 feign.compression.request.minRequestSize 的值
 *
 *          请求头有 Content-Encoding=gzip,deflate 会对请求体进行编码后(压缩)再发送给到服务器
 * */
feign:
  compression:
    request:
      enabled: true
      # FeignClient 执行HTTP请求时,Content-Type 、Content-Length 满足这两个条件,就设置请求头 Content-Encoding=gzip,deflate。
      # 设置了请求头后 在发送前会对请求体进行压缩
      mimeTypes:
        - "text/xml"
        - "application/xml"
        - "application/json"
      minRequestSize: 100

FeignLoadBalancerAutoConfiguration

@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class,
      DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {}
/**
 * FeignLoadBalancerAutoConfiguration
 *
 *      目的都是注册 Client 的实现类,根据条件会注册 FeignBlockingLoadBalancerClient 或者是 RetryableFeignBlockingLoadBalancerClient
 *         导入的三个配置类的区别在于:
 *          - HttpClientFeignLoadBalancerConfiguration  依赖 HttpClient 执行HTTP请求
 *          - OkHttpFeignLoadBalancerConfiguration  依赖 okhttp3.OkHttpClient 执行HTTP请求
 *          - DefaultFeignLoadBalancerConfiguration  依赖 Client.Default 执行HTTP请求
 * */

@EnableFeignClients 和 @FeignClient

@EnableFeignClients 是用来扫描标注了 @FeignClient 的类,映射成BeanDefinition注册到BeanFactory中,而需要注意的是这个bean的实例化是 FeignClientFactoryBean.getObject() 得到的。@FeignClient 的注解值主要是映射给FeignClientFactoryBean。所以要想知道 @FeignClient 是如何实现代理的还得看FeignClientFactoryBean.getObjec()

@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
   
   String[] value() default {}; // 要扫描的包

   String[] basePackages() default {}; // 要扫描的包

   Class<?>[] basePackageClasses() default {}; // 类所在的包

   Class<?>[] defaultConfiguration() default {}; // FeignClient Context 会用到的默认配置类

   Class<?>[] clients() default {}; // 指定 FeignClient,若指定就不会根据包路径扫描
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FeignClient {
  
  String contextId() default ""; // FeignClient Context 的ID,没指定会使用 name 或者 value 的值。支持占位符解析
 
	@AliasFor("name")
	String value() default ""; // 服务名。支持占位符解析

	@AliasFor("value")
	String name() default ""; // 服务名。支持占位符解析

	String qualifier() default ""; // 别名

	String url() default ""; // 若指定这个值,那就不会使用 name,也就不会变成负载均衡请求了。支持占位符解析

	boolean decode404() default false;

	Class<?>[] configuration() default {}; // 给 FeignClient Context 设置默认配置类

	Class<?> fallback() default void.class; // 使用 {@link FeignCircuitBreaker.Builder} 构造的 FeignClient 才会用到这个属性,是用来给 CircuitBreaker 使用的,用于在执行HTTP请求时出错后 的兜底策略。需要注册到容器中才行

	Class<?> fallbackFactory() default void.class; // 和 {@link FeignClient#fallback()} 的用法类似,只不过这个是用来创建 fallback的。如果指定了 fallback ,那么这个属性就没用了。需要注册到容器中才行

	String path() default ""; // 访问路径是 url + path 。支持占位符解析

	boolean primary() default true; // bean是否是 @Primary 的

}
/**
 *
 * 举例:
 *      @EnableFeignClients(defaultConfiguration={A.class},clients={A.class})
 *      @FeignClient(name="s1",configuration={A.class})
 *      public class Config{}
 *
 * @EnableFeignClients 上有 @Import(FeignClientsRegistrar.class) 所以解析配置类解析到这个注解时会将 FeignClientsRegistrar
 * 注册到BeanFactory中,而 FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar 接口,所以其接口方法会被回调。
 * {@link FeignClientsRegistrar#registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)}
 *
 *  1. 注册 FeignClient 默认配置
 *      获取 defaultConfiguration 注解值映射成 BeanDefinition 注册到 BeanFactory 中
 *        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
 *        builder.addConstructorArgValue("default."+Config.class.getName());
 *        builder.addConstructorArgValue(defaultConfiguration);
 *        registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
 *              builder.getBeanDefinition());
 *
 *        Tips: FeignContext 继承 NamedContextFactory, 会依赖 FeignClientSpecification 类型的bean 用来配置要生成的IOC容器。
 *             FeignContext 会使用 beanName是 "default." 前缀的 FeignClientSpecification 作为默认项,用来配置要生成的IOC容器
 *
 *  2. 注册 FeignClient
 *      2.1 记录候选的组件 candidateComponents
 *          设置 clients 值那就只使用这些值作为 candidateComponents,  没有设置 clients 值,那就扫描包下的类,只会收集有 @FeignClient 的类。
 *          value + basePackages + basePackageClasses 的值作为要扫描的包路径,若这三个注解值都没设置,
 *          那就用 @EnableFeignClients 注解所在的配置类的包作为要扫描的包路径
 *
 *      2.2 遍历 candidateComponents 挨个映射成 BeanDefinition 注册到 BeanFactory 中
 *          - 校验 @FeignClient 标注的类 不是接口就报错
 *          - 注册 FeignClient 配置
 *              String name = getClientName(attributes); // 为空就依次获取属性 contextId -> value -> name -> serviceId 都没设置就报错
 *              registerClientConfiguration(registry, name, attributes.get("configuration")); // 同上映射成 FeignClientSpecification,但是没有 "default." 前缀
 *
 *          - 将注解的值映射到 FeignClientFactoryBean ,然后装饰成 BeanDefinition 注册到BeanFactory中
 *              String contextId = getContextId(beanFactory, attributes); // 获取属性值,值为空就依次获取: contextId -> serviceId -> name -> value
 *              String name = getName(attributes); // 同上,只不过获取的是:serviceId -> name -> value
 *
 *              // 定义 FeignClientFactoryBean
 *              FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
 *              factoryBean.setBeanFactory(beanFactory);
 *              factoryBean.setName(name);
 *              factoryBean.setContextId(contextId);
 *              factoryBean.setType(clazz);
 *              // 根据 属性 feign.client.refresh-enabled 设置
 *              factoryBean.setRefreshableClient(isClientRefreshEnabled());
 *
 *              // 提供 Supplier ,BeanFactory实例化会调用 Supplier 得到bean对象
 *              BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
 *                  // 根据 @FeignClient(url="")的值来设置,会解析占位符,还会补全http://
 *                  factoryBean.setUrl(getUrl(beanFactory, attributes));
 *
 *                  // 获取 @FeignClient(path="")的值来设置,会解析占位符, 会补上前缀/,移除后缀/
 *                  factoryBean.setPath(getPath(beanFactory, attributes));
 *
 *                  // 剩下的就是简单读取值然后设置给factoryBean
 *                  factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
 *                  factoryBean.setFallback(attributes.get("fallback"));
 *                  factoryBean.setFallbackFactory(attributes.get("fallbackFactory"));
 *                  return factoryBean.getObject();
 *              });
 *
 *              // 将BeanDefinition注册到BeanFactory中
 *              registry.registerBeanDefinition(beanName, definition.getBeanDefinition());
 *              
 *              如果 feign.client.refresh-enabled 是true那就多注册 OptionsFactoryBean 到容器中,而且是 refresh 作用域的
 *              当 FeignClientFactoryBean.getObject() 时会拿到 OptionsFactoryBean 用来配置 Feign.Builder
 *
 *              Tips:因为每次实例化bean都会重新设置 url、path 的值且支持使用占位符,所以我们可以
 *                    将 bean 设置成 refresh 作用域的,然后就能实现 url、path 的动态更新
 * */

FeignClientFactoryBean

/** 
 * @FeignClient 注解修饰的接口会注册到BeanFactory中,这种bean的实例化是执行 FeignClientFactoryBean.getObject() 得到。
 * 
 * FeignClientFactoryBean 继承 FactoryBean 实现 InitializingBean
 *      {@link FeignClientFactoryBean#afterPropertiesSet()} 会校验属性 contextId、name 都不能为空。
 *      {@link FeignClientFactoryBean#getType()} 返回的其实就是 @FeignClient 标注的接口
 *      {@link FeignClientFactoryBean#getObject()} 这个才是关键,看这里才能知道如何Feign是如何实现的
 * */

FeignClientFactoryBean#getObject

其目的是配置 Feign.Builder,配置的参数有啥作用去看 Feign 就明白了,最后将 Feign.Builder 交给 Targeter 统一使用 Feign.Bduiler 的 逻辑,我们可以自定义 Targeter 注册到容器中,从而让我们配置的生效。

可以使用 FeignClientSpecification、FeignBuilderCustomizer 用来配置 Feign.Builder 需要的参数

/**
 * {@link FeignClientFactoryBean#getObject()}
 * 
 * 1. 从容器中获取 FeignContext
 *      FeignContext context = beanFactory.getBean(FeignContext.class)
 * 
 * 2. 获取 Feign.Builder 并对其进行配置 
 *      Feign.Builder builder = feign(context);
 *          2.1 根据 contextId 从 FeignContext 中获取 Feign.Builder
 *          2.2 根据 contextId 从 FeignContext 中获取 FeignLoggerFactory、Encoder、Decoder、Contract... 设置给 Feign.Builder
 *          2.3 可以设置 feign.client.config.contextId.xx 属性 和使用 FeignBuilderCustomizer 用来对 Feign.Builder 进行配置
 *                会设置很多东西:Logger.Level、Retryer、ErrorDecoder、FeignErrorDecoderFactory、Options、RequestInterceptor、QueryMapEncoder、Contract、Encoder、Decoder、ExceptionPropagationPolicy、Capability
 *          
 *          注:FeignContext 是 NamedContextFactory 不同的 name 会有单独的IOC容器,IOC容器默认会加载的配置类是 FeignClientsConfiguration
 * 
 * 3. url 没有值,那就使用 name + path 拼接成 url,然后使用 Target 得到接口的代理对象
 *      Client client = getOptional(context, Client.class);
 *         builder.client(client);
 *         Targeter targeter = get(context, Targeter.class);
 *         
 *         return targeter.target(this, builder, context, target);
 *         
 * 4. url 有值,那就使用 url + path 拼接成 url,然后使用 Target 得到接口的代理对象(这里会对Client进行解构,使用非负载均衡的Client)
 *      Client client = getOptional(context, Client.class);
 *      if (client instanceof FeignBlockingLoadBalancerClient) {
 *         // 因为提供了Url所以不需要负载均衡的Client,所以这里解构拿到 非负载均衡的Client
 *         client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
 *      }
 *      builder.client(client);
 *      Targeter targeter = get(context, Targeter.class);
 *      return targeter.target(this, builder, context, target);   
 * 
 * */

FeignClientBuilder

这是OpenFeign提供的工具类,用于快速生成 FeignClient 接口的实例对象。其实就是通过配置 FeignClientFactoryBean 然后执行 getObject 得到实例对象

示例代码

Targeter

public interface Targeter {
	<T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
			Target.HardCodedTarget<T> target);
}

其目的是统一使用Feign的逻辑 Feign.BuilderTarget.HardCodedTarget 描述的接口生成代理对象。这属于Feign 的知识了。

我们可以注册 Targeter 到容器中,自定义逻辑,比如 FeignCircuitBreakerTargeter

FeignCircuitBreakerTargeter

断路器可以看:

spring-cloud-openfeign-core.jar!META-INF/spring.factories 的部分内容

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
       org.springframework.cloud.openfeign.FeignAutoConfiguration,\

属性feign.circuitbreaker.enabled是true,且容器中有 CircuitBreakerFactory 类型的bean,就会注册 FeignCircuitBreakerTargeter 到容器中

public class FeignAutoConfiguration {
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnProperty("feign.circuitbreaker.enabled")
	protected static class CircuitBreakerPresentFeignTargeterConfiguration {

		@Bean
		@ConditionalOnMissingBean
		@ConditionalOnBean(CircuitBreakerFactory.class)
		public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory) {
			return new FeignCircuitBreakerTargeter(circuitBreakerFactory);
		}

	}
}

FeignCircuitBreakerTargeter 使用 Feign.Builder 的逻辑。其实就是配置 invocationHandlerFactory 属性,从而能够将方法的执行委托给 CircuitBreaker 执行

// 伪代码如下
Feign.builder().invocationHandlerFactory(
                        (target, dispatch) -> new FeignCircuitBreakerInvocationHandler(circuitBreakerFactory,
                                feignClientName, target, dispatch, nullableFallbackFactory
                        ));
class FeignCircuitBreakerTargeter implements Targeter {
  
	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
			Target.HardCodedTarget<T> target) {
		// 不是 FeignCircuitBreaker 类型的就不做处理
		if (!(feign instanceof FeignCircuitBreaker.Builder)) {
			return feign.target(target);
		}
		FeignCircuitBreaker.Builder builder = (FeignCircuitBreaker.Builder) feign;
		String name = !StringUtils.hasText(factory.getContextId()) ? factory.getName() : factory.getContextId();
		/**
		 * 前置知识:Feign 其实是通过JDK动态代理 为接口创建出代理对象,所以要想拦截方法的执行只需要配置 InvocationHandler 即可。
		 *
		 * 下面的几行代码的最终目都是设置 FeignCircuitBreakerInvocationHandler 作为代理对象的 InvocationHandler,
		 * 所以关键还得看 FeignCircuitBreakerInvocationHandler
		 *
		 * {@link FeignCircuitBreakerInvocationHandler#invoke(Object, Method, Object[])}
		 * 		大致流程是方法的执行交给 CircuitBreaker 执行 {@link CircuitBreaker#run(Supplier, Function)}
		 * 		CircuitBreaker 可以拿到 fallback 或者 fallbackFactory。可以决定什么时候回调 fallback 的逻辑
		 * */
		Class<?> fallback = factory.getFallback();
		if (fallback != void.class) {
			// 存在 fallback 的情况
			return targetWithFallback(name, context, target, builder, fallback);
		}
		Class<?> fallbackFactory = factory.getFallbackFactory();
		if (fallbackFactory != void.class) {
			// 存在 fallbackFactory 的情况
			return targetWithFallbackFactory(name, context, target, builder, fallbackFactory);
		}
		return builder(name, builder).target(target);
	}
}

FeignCircuitBreakerInvocationHandler

FeignCircuitBreakerTargeter 会配置 FeignCircuitBreakerInvocationHandler 作为 FeignClient 接口代理对象的 InvocationHandler,作用是 将方法的执行 和 fallback的执行 交给 CircuitBreaker 来决定,比如方法执行出错了就执行 fallback。

class FeignCircuitBreakerInvocationHandler implements InvocationHandler {

   @Override
   public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
      String circuitName = this.feignClientName + "_" + method.getName();
      // 通过 CircuitBreakerFactory 得到 CircuitBreaker 实例
      CircuitBreaker circuitBreaker = this.factory.create(circuitName);
      // 定义方法的执行
      Supplier<Object> supplier = asSupplier(method, args);
      /**
       * 存在 nullableFallbackFactory 就使用
       *
       * 比如这两种情况 nullableFallbackFactory 才会有值
       *     @FeignClient(fallback=A.class)
       *     @FeignClient(fallbackFactory=A.class)
       *     @FeignClient(fallback=A.class, fallbackFactory=A.class) // 两个都有的情况 只会使用 fallback
       * */
      if (this.nullableFallbackFactory != null) {
         // 使用 nullableFallbackFactory 构造出 fallbackFunction
         Function<Throwable, Object> fallbackFunction = throwable -> {
            // 通过 nullableFallbackFactory 得到 fallback
            Object fallback = this.nullableFallbackFactory.create(throwable);
            try {
               // 使用 fallback 执行当前出错的方法
               return this.fallbackMethodMap.get(method).invoke(fallback, args);
            }
            catch (Exception e) {
               throw new IllegalStateException(e);
            }
         };
         // 使用 circuitBreaker 执行方法
         return circuitBreaker.run(supplier, fallbackFunction);
      }
      // 使用 circuitBreaker 执行方法
      return circuitBreaker.run(supplier);
   }

   private Supplier<Object> asSupplier(final Method method, final Object[] args) {
      return () -> {
         try {
            return this.dispatch.get(method).invoke(args);
         }
         catch (RuntimeException throwable) {
            throw throwable;
         }
         catch (Throwable throwable) {
            throw new RuntimeException(throwable);
         }
      };
   }
}

FeignClientsConfiguration

FeignContext 中为name创建的IOC容器都会使用 FeignClientsConfiguration 作为默认的配置类,这个配置类中定义了配置 Feign.Builder 的参数,说白了就对 Feign 的功能做实现,让 Feign 支持 SpringMVC 的注解子类的。

列举最关键的几个参数对象,并不是全部的代码

@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {

   /**
    * 依赖IOC容器配置的 List<HttpMessageConverter> ,其作用是将 执行 FeignClient 接口的响应体 转成 方法的参数类型
    * @return
    */
   @Bean
   @ConditionalOnMissingBean
   public Decoder feignDecoder() {
      return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
   }

   /**
    * 依赖IOC容器配置的 List<HttpMessageConverter> ,其作用是将 FeignClient 接口的参数 设置到请求体中 
    * @param formWriterProvider
    * @return
    */
   @Bean
   @ConditionalOnMissingBean
   @ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
   public Encoder feignEncoder(ObjectProvider<AbstractFormWriter> formWriterProvider) {
      return springEncoder(formWriterProvider);
   }

   /**
    * 扩展 FeignClient 接口 支持的特殊注解,然后再执行接口方法时 将特殊注解标注的内容 映射到Request对象中,
    * 比如设置 请求头、请求路径、查询参数、请求体 等等
    * @param feignConversionService
    * @return
    */
   @Bean
   @ConditionalOnMissingBean
   public Contract feignContract(ConversionService feignConversionService) {
      boolean decodeSlash = feignClientProperties == null || feignClientProperties.isDecodeSlash();
      return new SpringMvcContract(this.parameterProcessors, feignConversionService, decodeSlash);
   }
   
   /**
   * 生成 FeignClient 的 Builder 对象,FeignCircuitBreaker OpenFeign 定义的,
   * 用来使用 FeignCircuitBreaker 来执行   FeignClient 接口的方法
   * @return
   */
   @Bean
   @Scope("prototype")
   @ConditionalOnMissingBean
   @ConditionalOnBean(CircuitBreakerFactory.class)
   public Feign.Builder circuitBreakerFeignBuilder() {
       return FeignCircuitBreaker.builder();
   }

}