
Spring Cloud Kubernetes微服务实战与源码分析 专栏收录该内容,点击查看专栏更多内容原创 吴就业 354 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动态配置实现的整个流程。
订阅
订阅新文章发布通知吧,不错过精彩内容!
输入邮箱,提交后我们会给您发送一封邮件,您需点击邮件中的链接完成订阅设置。