玩转OpenFeign(下)

原创 吴就业 167 0 2020-07-08

本文为博主原创文章,未经博主允许不得转载。

本文链接:https://wujiuye.com/article/110db3db28ff49f4b7abea9a01d8c8a5

作者:吴就业
链接:https://wujiuye.com/article/110db3db28ff49f4b7abea9a01d8c8a5
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。

本篇文章写于2020年07月08日,从公众号|掘金|CSDN手工同步过来(博客搬家),本篇为原创文章。

Spring Cloud Kubernetes微服务实战与源码分析

上一篇《玩转OpenFeign》介绍了OpenFeign的一些常用配置,不过还漏了点内容。

这篇主要介绍如何为不同的Client配置不同的连接超时、读超时这类参数,并从源码角度分析配置是怎么起作用的,以及都可以配置哪些参数,内容不多。

还是从FeignAutoConfiguration这个配置类说起,该配置类使用@EnableConfigurationProperties注册了两个用于装载OpenFeign配置的Bean,分别是FeignClientPropertiesFeignHttpClientProperties

@EnableConfigurationProperties(
      { FeignClientProperties.class,
		FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
}

如果OpenFeignClient使用的是默认的Default,由于Default这个Client使用的HttpClientHttpURLConnection,所以FeignHttpClientProperties这个配置不会使用到。建议不要使用默认的Default

如果OpenFeignClient使用的是OkHttpClient,则FeignHttpClientProperties用于装载OkHttpClient的连接池、连接超时配置。

FeignClientProperties用于接收在application.yaml配置文件中为每个Client配置的连接超时、读超时、重试器、请求拦截器、编码器、解码器这类参数。

配置重试器、请求拦截器等不建议在application.yaml中配置,因为在application.yaml中配置重试器、请求拦截器的类名,OpenFeign是从Spring Boot启动应用的ApplicationContext根据类名获取Bean的,并没有使用OpenFeign提供的Client隔离的ApplicationContext

除非你想全部Client都使用相同的重试器和请求拦截器,否则不建议这样配置。既然都需要在代码中创建这些重试器、请求拦截器这些Bean,那么直接在代码中配置不是更方法吗。在上一篇已经介绍如何通过代码方式可以实现为不同Client配置不同的重试器、请求拦截器。

通过代码方式配置连接超时、读超时这些参数可通过给ClientApplicationContext注入一个Request.Options类型的Bean实现。

首先创建自动配置类Configuration,往容器中注入Request.Options,给Request.Options配置连接超时和读超时。

@AutoConfigureBefore(FeignClientsConfiguration.class)
public class DefaultFeignConfig {

    @Bean
    public Request.Options options() {
        return new Request.Options(
                // 连接超时配置
                5, TimeUnit.SECONDS,
                // 读超时配置
                6, TimeUnit.SECONDS,
                // 如果请求响应3xx,是否重定向请求
                false);
    }
    
}

不要在这个配置类上并@Configuration注解,因为这不是注册到应用的ApplicationContext,而是注册到OpenFeignClient创建的ApplicationContext

最后给@FeignClient注解的configuration属性添加这个配置类。

@FeignClient(name = "demo",
        path = "/v1",
        url = "${fegin-client.demo-url}",
        configuration = {DefaultFeignRetryConfig.class,
           // 导入DefaultFeignConfig
            DefaultFeignConfig.class})
public interface DemoService {
}

再来看下如何在application.yaml中配置Client的连接超时、读超时这些参数。

## 配置feign使用okhttp
feign:
  okhttp:
    enabled: true
## 为每个Client单独配置连接超时等
  client:
    ## 使properties配置优先
    default-to-properties: true
    config:
      service1:
        connectTimeout: 6000
        readTimeout: 5000
      service2:
        connectTimeout: 6000
        readTimeout: 5000
      ....

接收feign.client.config配置的类为FeignClientConfiguration

@ConfigurationProperties("feign.client")
public class FeignClientProperties {
	private boolean defaultToProperties = true;
	private String defaultConfig = "default";
	// key: Client --> FeignClientConfiguration
	private Map<String, FeignClientConfiguration> config = new HashMap<>();
}

config字段是个map,支持多个配置,每个Client配置的keyClient的名称(服务提供者名称),value类型为FeignClientConfiguration,支持配置连接超时(connectTimeout)、读超时(readTimeout)等参数。

从源码分析配置是怎么生效的

OpenFeign会将使用@FeignClient注解注释的接口扫描出来,并往每个Client各自的ApplicationContext注入一个FeignClientFactoryBean,该FeignClientFactoryBeangetObject方法返回的是接口的代码对象。

FeignClientFactoryBean在创建接口的代理对象时,会先生成一个Feign.Builder,然后使用这个Feign.Builder创建代理对象。

FeignClientFactoryBean在创建Feign.Builder后会读取配置,将配置写入到Feign.BuilderFeign.Builder在创建代理对象时就会使用上这些配置,最后用于创建方法拦截器SynchronousMethodHandler

FeignClientFactoryBeanFeign.Builder填充配置的源码如下。

class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
	
	private Class<?> type;
	private String name;
	private String url;
	private String contextId;
	private String path;
	private boolean decode404;
	private boolean inheritParentContext = true;
	private ApplicationContext applicationContext;
	private Class<?> fallback = void.class;
	private Class<?> fallbackFactory = void.class;
	
    protected void configureFeign(FeignContext context, Feign.Builder builder) {
		FeignClientProperties properties = this.applicationContext
				.getBean(FeignClientProperties.class);

		FeignClientConfigurer feignClientConfigurer = getOptional(context,
				FeignClientConfigurer.class);
		setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
		// 当配置不为空,且inheritParentContext为true时
		// inheritParentContext默认为true
		if (properties != null && inheritParentContext) {
		    // 配置了default-to-properties为true
			if (properties.isDefaultToProperties()) {
			    // 使用Configuration类注入的bean
				configureUsingConfiguration(context, builder);
				// 使用默认配置
				configureUsingProperties(
						properties.getConfig().get(properties.getDefaultConfig()),
						builder);
				// 使用application.yaml中添加的配置覆盖前面的配置
				configureUsingProperties(properties.getConfig().get(this.contextId),
						builder);
			}
			// 顺序不同,default-to-properties为false时,
			// 使用Configuration类注入的bean就会覆盖application.yaml中的配置
			else {
				configureUsingProperties(
						properties.getConfig().get(properties.getDefaultConfig()),
						builder);
				configureUsingProperties(properties.getConfig().get(this.contextId),
						builder);
				configureUsingConfiguration(context, builder);
			}
		}
		else {
			configureUsingConfiguration(context, builder);
		}
	}
}

从上面源码可以看出,我们在application.yaml中配置default-to-propertiestrue实际目的是不要让默认配置覆盖我们在application.yaml中添加的配置。

如果项目中使用Ribbon,那么FeignRibbonClientAutoConfiguration会注入一个Request.Options,当default-to-properties配置为false时,这个Request.Options就会覆盖application.yaml中添加的配置。所以要将default-to-properties配置为true,配置才生效。

从上面源码还发现一个参数inheritParentContext,这个inheritParentContext的默认值为true。当配置inheritParentContextfalse时,我们在application.yaml中添加的配置就都不会生效。

如果想要将inheritParentContext设置为false,该如何设置呢?

@FeignClient注解的configuration属性指定的配置类中注入一个FeignClientConfigurer类型的Bean,实现FeignClientConfigurer接口的inheritParentConfiguration方法,在方法中返回false即可。

@FeignClient(name = "demo",
        path = "/v1",
        url = "${fegin-client.demo-url}",
        configuration = {DefaultFeignConfig.class})
public interface DemoService {
}

DefaultFeignConfig配置类中注册FeignClientConfigurer

@AutoConfigureBefore(FeignClientsConfiguration.class)
public class DefaultFeignConfig {

    @Bean
    public FeignClientConfigurer feignClientConfigurer() {
        return new FeignClientConfigurer() {
            @Override
            public boolean inheritParentConfiguration() {
                return false;
            }
        };
    }
}

实际我们关心的还是创建出来的代理对象的方法拦截器(SynchronousMethodHandler)。在创建SynchronousMethodHandlerFeign.Builder会将封装了连接超时、读超时配置的Request.Options对象传递给SynchronousMethodHandler。在发起http请求时,由SynchronousMethodHandlerRequest.Options配置对象传给Client,如OkHttpClient

public final class OkHttpClient implements Client {
   // 这是HttpClient,不要与OpenFeign的Client搞混
   private final okhttp3.OkHttpClient delegate;
  
  // 由SynchronousMethodHandler调用发起请求
  @Override
  public feign.Response execute(feign.Request input, feign.Request.Options options)
      throws IOException {
    okhttp3.OkHttpClient requestScoped;
    // 当全局配置的连接超时与当前Client配置的连接超时不同时,重新创建OkHttpClient,
    // 使用的还是相同的连接池
    if (delegate.connectTimeoutMillis() != options.connectTimeoutMillis()
        || delegate.readTimeoutMillis() != options.readTimeoutMillis()
        || delegate.followRedirects() != options.isFollowRedirects()) {
      requestScoped = delegate.newBuilder()
          .connectTimeout(options.connectTimeoutMillis(), TimeUnit.MILLISECONDS)
          .readTimeout(options.readTimeoutMillis(), TimeUnit.MILLISECONDS)
          .followRedirects(options.isFollowRedirects())
          .build();
    } else {
      requestScoped = delegate;
    }
    Request request = toOkHttpRequest(input);
    Response response = requestScoped.newCall(request).execute();
    return toFeignResponse(response, input).toBuilder().request(input).build();
  }
 }

每种Client实现的方式不同,OkHttpClient的实现是,当全局配置的连接超时与当前Client配置的连接超时不同时,重新创建HttpClient,即重新创建用于真正发起Http请求的OkHttpClient,使用的还是相同的连接池。

#后端

声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。

文章推荐

Spring Cloud Gateway结合注册中心使用,自定义路由功能

我们基于Spring Cloud Gateway开发内部微服务网关,并结合注册中心实现自动服务发现路由。就在最近将项目部署测试环境的Kubernetes集群上时,发现路由失败。

Sentinel与OpenFeign整合实现熔断降级源码分析

本篇我们从源码分析Sentinel与OpenFeign整合实现熔断降级的原理。

OpenFeign整合Sentinel实现熔断降级

本篇先介绍如何将Sentinel与OpenFeign整合使用,并且熔断降级策略使用动态配置,将配置存储在配置中心。

玩转OpenFeign(上)

使用OpenFeign不仅能够简化调用接口的步骤,也能顺便使用OpenFeign提供的重试机制,不需要再编写一个HttpUtils工具类。

Spring Cloud Kubernetes动态配置实现原理与源码分析

本篇我们继续通过了解Spring Cloud Kubernetes实现动态加载配置接口来理解Spring Cloud动态配置实现的整个流程。

Spring Boot与Spring Cloud应用启动流程

本篇我们一起学习Spring Boot与Spring Cloud应用的启动流程。