spring cloud openFeign

fxz大约 22 分钟

spring cloud openFeign

Feign 是一个声明式的 Web 服务客户端,它使 Java 编写 Web 服务客户端变得更加容易。

OpenFeign 是通过自动装配将Feign集成到应用程序中,它在Feign的基础上进行了扩展和定制,以更好地与Spring Cloud集成,并且提供了更多的功能和特性来满足微服务架构的需求。

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

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

3: 整合 Spring Cloud locaBalancer 后 Feign 使用负载均衡的HTTP客户端 来发送请求

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
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

FeignAutoConfiguration

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

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

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

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

FeignAcceptGzipEncodingAutoConfiguration

注册 FeignAcceptGzipEncodingInterceptor , 其是 RequestInterceptor 的实现类,其目的是给 Request 增加请求头 Accept-Encoding=gzip,deflate

Accept-Encoding=gzip,deflate 是告诉服务器 客户端支持 gzip,deflate 压缩

FeignContentGzipEncodingAutoConfiguration

注册 FeignContentGzipEncodingInterceptor , 其是 RequestInterceptor 的实现类,其目的是给 Request 增加请求头 Content-Encoding=gzip,deflate

请求头有 Content-Encoding=gzip,deflate 会对请求体进行编码后(压缩)再发送给到服务器

FeignLoadBalancerAutoConfiguration

目的都是注册 Client 的实现类,根据条件会注册 FeignBlockingLoadBalancerClient 或者是 RetryableFeignBlockingLoadBalancerClient

导入的三个配置类的区别在于:

  •     HttpClientFeignLoadBalancerConfiguration  依赖 HttpClient 执行HTTP请求
    
  •     OkHttpFeignLoadBalancerConfiguration  依赖 okhttp3.OkHttpClient 执行HTTP请求
    
  •     DefaultFeignLoadBalancerConfiguration  依赖 Client.Default 执行HTTP请求
    

FeignClient扫描、注册

通过添加@EnableFeignClients 注解,开启FeignClient的扫描。

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

    /**
     * 扫描的包名
     */
    String[] value() default {};

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

    /**
     * 通过类所在的包名得到要扫描的包名
     */
    Class<?>[] basePackageClasses() default {};

    /**
     * 默认的配置类。是用来指定FeignContext要用到的配置类
     */
    Class<?>[] defaultConfiguration() default {};

    /**
     * 指定FeignClient,如果有值那就不会进行包扫描
     */
    Class<?>[] clients() default {};

}

其中@EnableFeignClients中import了 FeignClientsRegistrar,FeignClientsRegistrar中会进行客户端的扫描和注册,主要是解析注解的元信息,然后扫描或者直接注册客户端和客户端配置。我们主要看他的registerBeanDefinitions方法。

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

   	...........

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
       // 根据注解 注册默认配置
       registerDefaultConfiguration(metadata, registry);

        // 注册 FeignClient
       registerFeignClients(metadata, registry);
    }

    /**
     * 根据注解值注册默认配置
     */
    private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
       // 获取 @EnableFeignClients 注解的属性值
       Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

       if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
          String name;

          // 判断当前类是否是内部类
          if (metadata.hasEnclosingClass()) {
             name = "default." + metadata.getEnclosingClassName();
          }
          else {
             name = "default." + metadata.getClassName();
          }

          // 注册默认配置
          registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
       }
    }

    // 注册客户端
    public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
       // 要注册的 BeanDefinition
       LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
       // 获取 @EnableFeignClients 注解的属性值
       Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
       // 获取 clients 的值
       final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
        // @EnableFeignClients 没有设置 clients 值,那就扫描包下的类得到
       if (clients == null || clients.length == 0) {
          ClassPathScanningCandidateComponentProvider scanner = getScanner();
          scanner.setResourceLoader(this.resourceLoader);
            // 配置 include 规则,只会收集有 @FeignClient 的类
          scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            /**
             * @EnableFeignClients(value、basePackages、basePackageClasses) 的值作为要扫描的包路径,
             * 若这三个注解值都没设置,那就是用标注了 @EnableFeignClients 注解所在的类的包作为要扫描的包
             * */
          Set<String> basePackages = getBasePackages(metadata);
          for (String basePackage : basePackages) {
                // 开始扫描,将扫描的结果存到 candidateComponents
             candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
          }
        } else {
            // @EnableFeignClients 设置了 clients 值,那就只使用这些值
          for (Class<?> clazz : clients) {
             candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
          }
       }

        // 遍历 candidateComponents 挨个映射成 BeanDefinition 注册到 BeanFactory 中
       for (BeanDefinition candidateComponent : candidateComponents) {
          if (candidateComponent instanceof AnnotatedBeanDefinition) {
             AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
             AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
               // @FeignClient 标注的类 不是接口就报错
             Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");

               // 拿到注解的属性值
             Map<String, Object> attributes = annotationMetadata
                   .getAnnotationAttributes(FeignClient.class.getCanonicalName());

               /**
                * 为空就依次获取属性 contextId -> value -> name  都没设置就报错。
                * */
             String name = getClientName(attributes);

               /**
              * 注册客户端配置
                * */
             registerClientConfiguration(registry, name, attributes.get("configuration"));

             // 注册 FeignClient
             registerFeignClient(registry, annotationMetadata, attributes);
          }
       }
    }
	
    // 注册客户端
    private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
          Map<String, Object> attributes) {
        // 拿到标注了注解的类名
       String className = annotationMetadata.getClassName();
       Class clazz = ClassUtils.resolveClassName(className, null);
       ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
             ? (ConfigurableBeanFactory) registry : null;
        /**
         * 获取属性值,值为空就依次获取: contextId -> name -> value
         * 最后使用 environment.resolvePlaceholders(value) 解析占位符,也就是说这些注解值是支持使用 ${}、#{} 的
         * 如果都没有设置,那么 contextId 就是空字符串
         * */
       String contextId = getContextId(beanFactory, attributes);
        /**
         * 同上,只不过获取的是 name -> value
         * */
       String name = getName(attributes);
        // new 一个 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 , 也就是实例化会回调 Supplier 得到实例
        *
        * Tips:因为每次实例化bean都会重新设置 url、path 的值且支持使用占位符,会根据属性文件配置的值进行替换,所以我们可以
        *        将 bean 设置成 refresh 作用域的,然后就能实现 url、path 的动态更新
        *
        *        可以通过设置属性让bean变成refresh作用域的 spring.cloud.refresh.ExtraRefreshable=XxFeignClient
        * */
       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"))));
          Object fallback = attributes.get("fallback");
          if (fallback != null) {
             factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
                   : ClassUtils.resolveClassName(fallback.toString(), null));
          }
          Object fallbackFactory = attributes.get("fallbackFactory");
          if (fallbackFactory != null) {
             factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
                   : ClassUtils.resolveClassName(fallbackFactory.toString(), null));
          }
          return factoryBean.getObject();
       });
       definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        definition.setLazyInit(true); // 懒加载的
        // 校验  fallback、fallbackFactory 的值不能是接口
       validate(attributes);

       AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
       beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
       beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);

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

       beanDefinition.setPrimary(primary);

       // 作为别名
       String[] qualifiers = getQualifiers(attributes);
       if (ObjectUtils.isEmpty(qualifiers)) {
          qualifiers = new String[] { contextId + "FeignClient" };
       }

       BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
       // 注册到 BeanFactory 中
       BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

       /**
        * 如果 feign.client.refresh-enabled 是true那就注册 OptionsFactoryBean 到容器中
        * 而且是 refresh 作用域的
        *
        * 当 FeignClientFactoryBean.getObject() 时会拿到 OptionsFactoryBean 用来配置 Feign.Builder
        * */
       registerOptionsBeanDefinition(registry, contextId);
    }

   .........
}

可以看到,对于每个客户端其实注册的是FeignClientFactoryBean,bean的实例化是执行 FeignClientFactoryBean.getObject()得到的。

创建代理对象

FeignClientFactoryBean:

public Object getObject() {
    return getTarget();
}

/**
 * 创建客户端示例
 */
<T> T getTarget() {
    // 从容器中获取 FeignContext
    FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
          : applicationContext.getBean(FeignContext.class);

    /**
     * 根据配置创建 Feign.Builder
     * */
    Feign.Builder builder = feign(context);

    /**
     * url 没有值,那就使用 name + path 拼接成 url,然后构造出 负载均衡的实例对象
     * */
    if (!StringUtils.hasText(url)) {

       if (LOG.isInfoEnabled()) {
          LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");
       }
       if (!name.startsWith("http")) {
          url = "http://" + name;
       }
       else {
          url = name;
       }
       url += cleanPath();

       // 从容器中获取 Targeter 实例,执行接口方法得到 type 的实现类
       return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
    }

    // 补上协议名
    if (StringUtils.hasText(url) && !url.startsWith("http")) {
       url = "http://" + url;
    }
    // 拼接上 path
    String url = this.url + cleanPath();
    // 从容器中获取 Client 实例(会从父容器中找)
    Client client = getOptional(context, Client.class);
    if (client != null) {
    
       if (client instanceof FeignBlockingLoadBalancerClient) {
          client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
       }
       if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
          client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
       }
       builder.client(client);
    }
    
    /**
     * 根据 contextId 从 FeignContext 获取 FeignBuilderCustomizer 用来加工 builder
     * */
    applyBuildCustomizers(context, builder);

    // 从容器中获取 Targeter 实例
    Targeter targeter = get(context, Targeter.class);
    
    /**
     * 执行方法得到 type 的实现类
     * */
    return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}


	/**
	 * 根据配置 创建 Feign.Builder
	 */
	protected Feign.Builder feign(FeignContext context) {
		FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
		Logger logger = loggerFactory.create(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;
	}

// 应用配置到Feign.Builder
protected void configureFeign(FeignContext context, Feign.Builder builder) {
		FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class)
				: applicationContext.getBean(FeignClientProperties.class);

        // 会尝试从父容器中获取
		FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class);
		// 设置 inheritParentContext 属性
		setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());

		/**
		 * 有 FeignClientProperties 且继承父上下文(默认是true)
		 *
		 * inheritParentContext 为 true 那么获取bean时,会先找当前容器找不到会从父容器中找
		 * */
		if (properties != null && inheritParentContext) {
			if (properties.isDefaultToProperties()) {
				// 根据 contextId 从 context 中获取相应的bean用来配置 builder
				configureUsingConfiguration(context, builder);
				// 属性文件配置的 FeignClientConfiguration 来对 builder 进行配置
				configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
				configureUsingProperties(properties.getConfig().get(contextId), builder);
			}
			else {
				configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
				configureUsingProperties(properties.getConfig().get(contextId), builder);
				configureUsingConfiguration(context, builder);
			}
		}
		else {
			configureUsingConfiguration(context, builder);
		}
	}

可以看到最终执行的是targeter.target方法,我们看一下FeignCircuitBreakerTargeter实现,其实就是配置 invocationHandlerFactory 属性,从而能够将方法的执行委托给 CircuitBreaker 执行。

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);
}

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);
         }
      };
   }
}

最终feign调用newInstance方法创建feign的动态代理。

  • 利用contract解析方法上的注解信息为MethodMetadata,封装MethodHandler使之与method对应。

  • 将MethodHandler与方法的映射封装进InvocationHandler,这里是FeignInvocationHandler。

  • 创建jdk动态代理对象。

ReflectiveFeign:

public <T> T newInstance(Target<T> target) {
  // 利用contract解析方法上的注解信息为MethodMetadata。 封装MethodHandler使之与method对应
  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 {
      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;
}

需要注意的是在open feign中Contract实现类SpringMvcContract添加了对spring mvc注解的支持。

SpringMvcContractprivate List<AnnotatedParameterProcessor> getDefaultAnnotatedArgumentsProcessors() {

    List<AnnotatedParameterProcessor> annotatedArgumentResolvers = new ArrayList<>();

    annotatedArgumentResolvers.add(new MatrixVariableParameterProcessor());
    annotatedArgumentResolvers.add(new PathVariableParameterProcessor());
    annotatedArgumentResolvers.add(new RequestParamParameterProcessor());
    annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor());
    annotatedArgumentResolvers.add(new QueryMapParameterProcessor());
    annotatedArgumentResolvers.add(new RequestPartParameterProcessor());
    annotatedArgumentResolvers.add(new CookieValueParameterProcessor());

    return annotatedArgumentResolvers;
}

Feign调用

执行的是代理对象的FeignInvocationHandler。

FeignInvocationHandler:

@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();
  }

  // 通过方法和handle的映射处理
  return dispatch.get(method).invoke(args);
}

MethodHandler的默认实现:SynchronousMethodHandler

SynchronousMethodHandler@Override
public Object invoke(Object[] argv) throws Throwable {
  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;
    }
  }
}

基于重试以及负载均衡的客户端实现:

RetryableFeignBlockingLoadBalancerClient:

public Response execute(Request request, Request.Options options) throws IOException {
    final URI originalUri = URI.create(request.url());
    String serviceId = originalUri.getHost();
    Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
    final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryFactory.createRetryPolicy(serviceId,
          loadBalancerClient);
    RetryTemplate retryTemplate = buildRetryTemplate(serviceId, request, retryPolicy);
    
    return retryTemplate.execute(context -> {
       Request feignRequest = null;
       ServiceInstance retrievedServiceInstance = null;
        
       Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
             .getSupportedLifecycleProcessors(
                   loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
                   RetryableRequestContext.class, ResponseData.class, ServiceInstance.class);
        
       String hint = getHint(serviceId);
       DefaultRequest<RetryableRequestContext> lbRequest = new DefaultRequest<>(
             new RetryableRequestContext(null, buildRequestData(request), hint));
       
       if (context instanceof LoadBalancedRetryContext) {
          LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
          ServiceInstance serviceInstance = lbContext.getServiceInstance();
          if (serviceInstance == null) {
             if (LOG.isDebugEnabled()) {
                LOG.debug("Service instance retrieved from LoadBalancedRetryContext: was null. "
                      + "Reattempting service instance selection");
             }
              
             ServiceInstance previousServiceInstance = lbContext.getPreviousServiceInstance();
             lbRequest.getContext().setPreviousServiceInstance(previousServiceInstance);
             supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
              
             // 负载均衡选择服务实例
             retrievedServiceInstance = loadBalancerClient.choose(serviceId, lbRequest);
             if (LOG.isDebugEnabled()) {
                LOG.debug(String.format("Selected service instance: %s", retrievedServiceInstance));
             }
             lbContext.setServiceInstance(retrievedServiceInstance);
          }

          if (retrievedServiceInstance == null) {
             if (LOG.isWarnEnabled()) {
                LOG.warn("Service instance was not resolved, executing the original request");
             }
             org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
                   retrievedServiceInstance);
             supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
                   .onComplete(new CompletionContext<ResponseData, ServiceInstance, RetryableRequestContext>(
                         CompletionContext.Status.DISCARD, lbRequest, lbResponse)));
             feignRequest = request;
          }
          else {
             if (LOG.isDebugEnabled()) {
                LOG.debug(String.format("Using service instance from LoadBalancedRetryContext: %s",
                      retrievedServiceInstance));
             }
             String reconstructedUrl = loadBalancerClient.reconstructURI(retrievedServiceInstance, originalUri)
                   .toString();
             feignRequest = buildRequest(request, reconstructedUrl);
          }
       }
        
       org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
             retrievedServiceInstance);
       LoadBalancerProperties loadBalancerProperties = loadBalancerClientFactory.getProperties(serviceId);
        
       // 执行请求调用
       Response response = LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing(delegate, options,
             feignRequest, lbRequest, lbResponse, supportedLifecycleProcessors, retrievedServiceInstance != null,
             loadBalancerProperties.isUseRawStatusCodeInResponseData());
       int responseStatus = response.status();
       if (retryPolicy != null && retryPolicy.retryableStatusCode(responseStatus)) {
          if (LOG.isDebugEnabled()) {
             LOG.debug(String.format("Retrying on status code: %d", responseStatus));
          }
          byte[] byteArray = response.body() == null ? new byte[] {}
                : StreamUtils.copyToByteArray(response.body().asInputStream());
          response.close();
          throw new LoadBalancerResponseStatusCodeException(serviceId, response, byteArray,
                URI.create(request.url()));
       }
       return response;
    }, new LoadBalancedRecoveryCallback<Response, Response>() {
       @Override
       protected Response createResponse(Response response, URI uri) {
          return response;
       }
    });
}

总结

@EnableFeignClients注解开启扫描、注册FeignClient。

@FeignClient可以包含了客户端的信息。会为每个客户端生成单独的配置。

注册到容器中FeignClientFactoryBean 类型的bean。通过调用FeignClientFactoryBean 的getObject方法,获取客户端相应的配置,来创建相应的feign客户端。

执行调用是通过代理对象的MethodHandler。

通过整合spring cloud loadbalancer实现了负载均衡的客户端。

通过整合spring cloud circuitbreaker实现了熔断的客户端。