原创 吴就业 148 0 2020-07-08
本文为博主原创文章,未经博主允许不得转载。
本文链接:https://wujiuye.com/article/110db3db28ff49f4b7abea9a01d8c8a5
作者:吴就业
链接:https://wujiuye.com/article/110db3db28ff49f4b7abea9a01d8c8a5
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。
本篇文章写于2020年07月08日,从公众号|掘金|CSDN手工同步过来(博客搬家),本篇为原创文章。
上一篇《玩转OpenFeign
》介绍了OpenFeign
的一些常用配置,不过还漏了点内容。
这篇主要介绍如何为不同的Client
配置不同的连接超时、读超时这类参数,并从源码角度分析配置是怎么起作用的,以及都可以配置哪些参数,内容不多。
还是从FeignAutoConfiguration
这个配置类说起,该配置类使用@EnableConfigurationProperties
注册了两个用于装载OpenFeign
配置的Bean
,分别是FeignClientProperties
、FeignHttpClientProperties
。
@EnableConfigurationProperties(
{ FeignClientProperties.class,
FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
}
FeignHttpClientProperties
:用于配置HttpClient
。HttpClient
是真正用于发起Http
请求的客户端工具类。如果OpenFeign
的Client
使用的是默认的Default
,由于Default
这个Client
使用的HttpClient
是HttpURLConnection
,所以FeignHttpClientProperties
这个配置不会使用到。建议不要使用默认的Default
。
如果OpenFeign
的Client
使用的是OkHttpClient
,则FeignHttpClientProperties
用于装载OkHttpClient
的连接池、连接超时配置。
FeignClientProperties
:为每个Client
配置连接超时、读超时、重试器、请求拦截器等。支持哪些配置参数可查看FeignClientProperties
的内部类FeignClientConfiguration
都有哪些字段。FeignClientProperties
用于接收在application.yaml
配置文件中为每个Client
配置的连接超时、读超时、重试器、请求拦截器、编码器、解码器这类参数。
配置重试器、请求拦截器等不建议在application.yaml
中配置,因为在application.yaml
中配置重试器、请求拦截器的类名,OpenFeign
是从Spring Boot
启动应用的ApplicationContext
根据类名获取Bean
的,并没有使用OpenFeign
提供的Client
隔离的ApplicationContext
。
除非你想全部Client
都使用相同的重试器和请求拦截器,否则不建议这样配置。既然都需要在代码中创建这些重试器、请求拦截器这些Bean
,那么直接在代码中配置不是更方法吗。在上一篇已经介绍如何通过代码方式可以实现为不同Client
配置不同的重试器、请求拦截器。
通过代码方式配置连接超时、读超时这些参数可通过给Client
的ApplicationContext
注入一个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
,而是注册到OpenFeign
为Client
创建的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
配置的key
为Client
的名称(服务提供者名称),value
类型为FeignClientConfiguration
,支持配置连接超时(connectTimeout
)、读超时(readTimeout
)等参数。
OpenFeign
会将使用@FeignClient
注解注释的接口扫描出来,并往每个Client
各自的ApplicationContext
注入一个FeignClientFactoryBean
,该FeignClientFactoryBean
的getObject
方法返回的是接口的代码对象。
FeignClientFactoryBean
在创建接口的代理对象时,会先生成一个Feign.Builder
,然后使用这个Feign.Builder
创建代理对象。
FeignClientFactoryBean
在创建Feign.Builder
后会读取配置,将配置写入到Feign.Builder
,Feign.Builder
在创建代理对象时就会使用上这些配置,最后用于创建方法拦截器SynchronousMethodHandler
。
FeignClientFactoryBean
为Feign.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-properties
为true
实际目的是不要让默认配置覆盖我们在application.yaml
中添加的配置。
如果项目中使用Ribbon
,那么FeignRibbonClientAutoConfiguration
会注入一个Request.Options
,当default-to-properties
配置为false
时,这个Request.Options
就会覆盖application.yaml
中添加的配置。所以要将default-to-properties
配置为true
,配置才生效。
从上面源码还发现一个参数inheritParentContext
,这个inheritParentContext
的默认值为true
。当配置inheritParentContext
为false
时,我们在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
)。在创建SynchronousMethodHandler
时Feign.Builder
会将封装了连接超时、读超时配置的Request.Options
对象传递给SynchronousMethodHandler
。在发起http
请求时,由SynchronousMethodHandler
将Request.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开发内部微服务网关,并结合注册中心实现自动服务发现路由。就在最近将项目部署测试环境的Kubernetes集群上时,发现路由失败。
本篇我们继续通过了解Spring Cloud Kubernetes实现动态加载配置接口来理解Spring Cloud动态配置实现的整个流程。
订阅
订阅新文章发布通知吧,不错过精彩内容!
输入邮箱,提交后我们会给您发送一封邮件,您需点击邮件中的链接完成订阅设置。