【OpenFeign 】OpenFeign 的常用配置以及连接池的配置方式
1 前言
上节我们看了下 OpenFeign 里的重试,在从源码的角度看它的执行原理的时候,又意外的遇到了一个【OpenFeign 】OpenFeign 下未开启重试,服务却被调用了两次 的问题的分析,后面我们又看了重试器的入场和执行时机,那么本节我们看看 OpenFeign 的一些常用配置,以及全局配置和想对某个 Feign 单独配置的方法。
2 环境准备
在前面的 Feign 下,我增加了一个 StockFeign,这样来测测单独针对某个 Feign 的配置:
3 配置相关
3.1 配置优先级
在了解配置之前,要先知道配置的关系。
配置也是分层次的,比如有一个全局的配置,有可以针对某个 Feign 单独的配置,配置取值优先级采用的是就近策略,也就是这个 Feign 有自己的配置了,就用自己的,没有的话就用默认的。
这块逻辑体现在:
// FeignClientFactoryBean 在初始化 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); setInheritParentContext(feignClientConfigurer.inheritParentConfiguration()); if (properties != null && inheritParentContext) { if (properties.isDefaultToProperties()) { configureUsingConfiguration(context, builder); // 先设置 default 的 configureUsingProperties( properties.getConfig().get(properties.getDefaultConfig()), builder); // 再设置自己特有的 properties.getConfig().get(contextId) configureUsingProperties(properties.getConfig().get(contextId), builder); } else { // 先设置 default 的 configureUsingProperties( properties.getConfig().get(properties.getDefaultConfig()), builder); // 再设置自己特有的 properties.getConfig().get(contextId) configureUsingProperties(properties.getConfig().get(contextId), builder); configureUsingConfiguration(context, builder); } } else { configureUsingConfiguration(context, builder); } }
配置设置参考:
# 设置默认的配置的名称 默认是 default 一般我们都不会动它 feign.client.default-config = default # 设置默认的属性值 # 格式:feign.client.config.default.属性名 = 属性值 feign.client.config.default.read-timeout=1000 # 设置某个 Feign 特有的 contextId 就是@FeignClient 的 contextId,contextId为空的话取 name属性的值 feign.client.config.feign的contextId.read-timeout=1000
3.2 配置的相关类
(1)FeignClientProperties
// Feign客户端的基础配置选项,比如日志级别、重试策略、编码器和解码器的选择等。 @ConfigurationProperties("feign.client") public class FeignClientProperties {
(2)FeignClientEncodingProperties
// 请求压缩相关 mimeTypes 支持的mime类型默认text/xml,application/xml,application/json minRequestSize 边界超过多少进行请求压缩 默认2048 @ConfigurationProperties("feign.compression.request") public class FeignClientEncodingProperties {
(3)FeignHttpClientProperties
// 这个配置类可能包含了一系列与HTTP客户端相关配置,如连接池大小、连接超时时间、读取超时时间等。使用Apache HttpClient或其他HTTP客户端时,这类配置是非常有用的。例如,当使用Apache HttpClient作为Feign的HTTP客户端时,可以通过此类配置来优化连接管理和性能。 @ConfigurationProperties(prefix = "feign.httpclient") public class FeignHttpClientProperties {
(4)HTTP 连接池相关的两个
# feign.httpclient.enabled # feign.okhttp.enabled @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true) class HttpClientFeignLoadBalancedConfiguration { @ConditionalOnProperty("feign.okhttp.enabled") class OkHttpFeignLoadBalancedConfiguration { // 熔断相关 feign.hystrix.enabled protected static class HystrixFeignConfiguration { @ConditionalOnProperty(name = "feign.hystrix.enabled") public Feign.Builder feignHystrixBuilder() { return HystrixFeign.builder(); } }
4 常用配置
4.1 feign.client 相关的
设置默认配置的名称,默认就是 default 这个不建议设置,就用默认的即可。
feign.client.default-config = default
feign.client 可以设置默认的配置,也可以对某个 feign 设置最后都是保存在 FeignClientProperties 的 config 属性中。
private Map<String, FeignClientConfiguration> config = new HashMap<>();
设置默认的:feign.client.config.default.属性 = 属性值
设置某个的:feign.client.config.contextId.属性 = 属性值
FeignClientConfiguration 是 FeignClientProperties 的子类,是针对 feignClient 可以设置的一些属性,我们看一些常见的:
4.1.1 日志
(1)日志相关的:loggerLevel Level有 NONE、BASIC、HEADERS、FULL 四个等级,不设置的话默认为 null
比如:feign.client.config.default.logger-level = full
它的作用点就是在执行过程中,判断当前的日志级别,来打印相应的信息:
// 摘自 SynchronousMethodHandler Object executeAndDecode(RequestTemplate template, Options options) throws Throwable { Request request = targetRequest(template); // 判断日志级别 打印相应信息 if (logLevel != Logger.Level.NONE) { logger.logRequest(metadata.configKey(), logLevel, request); } try { response = client.execute(request, options); ... } catch (IOException e) { if (logLevel != Logger.Level.NONE) { logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start)); } ... }
4.1.2 连接、响应超时时间
connectTimeout 连接超时时间、readTimeout 响应超时时间(单位时间:毫秒)比如:
# 设置默认的响应时间 1 秒 feign.client.config.default.read-timeout=1000 # 设置 stockFeign 的响应时间 5 秒 feign.client.config.stockFeign.read-timeout=5000
它的作用点在:
在初始化 Feign.Builder 的时候取出配置的连接、响应超时时间并用 Request.Options 封装起来:
// Request.Options() public Options() { // 默认连接超时 10 秒,响应超时 60 秒 this(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true); } // 默认值来自于 Request.Options() private int readTimeoutMillis = new Request.Options().readTimeoutMillis(); private int connectTimeoutMillis = new Request.Options().connectTimeoutMillis(); private boolean followRedirects = new Request.Options().isFollowRedirects(); // 如果配置了,取配置的没有的话取 Request.Options() 上边默认的 connectTimeoutMillis = config.getConnectTimeout() != null ? config.getConnectTimeout() : connectTimeoutMillis; readTimeoutMillis = config.getReadTimeout() != null ? config.getReadTimeout() : readTimeoutMillis; followRedirects = config.isFollowRedirects() != null ? config.isFollowRedirects() : followRedirects; // 构建 new Request.Options builder.options(new Request.Options(connectTimeoutMillis, TimeUnit.MILLISECONDS, readTimeoutMillis, TimeUnit.MILLISECONDS, followRedirects));
默认情况下请求是通过 HttpURLConnection 发送的,根据你的 Request.Options() 建立请求:
@Override public Response execute(Request request, Options options) throws IOException { HttpURLConnection connection = convertAndSend(request, options); return convertResponse(connection, request); } HttpURLConnection convertAndSend(Request request, Options options) throws IOException { ... connection.setConnectTimeout(options.connectTimeoutMillis()); connection.setReadTimeout(options.readTimeoutMillis()); }
而对于 ApacheHttpClient 或者 okHttp 他们都是由 FeignHttpClientProperties即(feign.httpclient)来管理。
你看 HttpClient,最大连接数、连接超时等都是从 FeignHttpClientProperties 获取的:
@Bean @ConditionalOnMissingBean(HttpClientConnectionManager.class) public HttpClientConnectionManager connectionManager( ApacheHttpClientConnectionManagerFactory connectionManagerFactory, FeignHttpClientProperties httpClientProperties) { final HttpClientConnectionManager connectionManager = connectionManagerFactory .newConnectionManager(httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(), httpClientProperties.getMaxConnectionsPerRoute(), httpClientProperties.getTimeToLive(), httpClientProperties.getTimeToLiveUnit(), this.registryBuilder); this.connectionManagerTimer.schedule(new TimerTask() { @Override public void run() { connectionManager.closeExpiredConnections(); } }, 30000, httpClientProperties.getConnectionTimerRepeat()); return connectionManager; } private CloseableHttpClient createClient(HttpClientBuilder builder, HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) { // 从 FeignHttpClientProperties 获取 连接超时时间 默认 2秒 // public static final int DEFAULT_CONNECTION_TIMEOUT = 2000; RequestConfig defaultRequestConfig = RequestConfig.custom() .setConnectTimeout(httpClientProperties.getConnectionTimeout()) .setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build(); CloseableHttpClient httpClient = builder .setDefaultRequestConfig(defaultRequestConfig) .setConnectionManager(httpClientConnectionManager).build(); return httpClient; }
你看 okHttp 一样的都是从 FeignHttpClientProperties 获取的:
@Bean public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) { Boolean followRedirects = httpClientProperties.isFollowRedirects(); Integer connectTimeout = httpClientProperties.getConnectionTimeout(); this.okHttpClient = httpClientFactory .createBuilder(httpClientProperties.isDisableSslValidation()) .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS) .followRedirects(followRedirects).connectionPool(connectionPool).build(); return this.okHttpClient; }
有一点比较好奇的是 FeignHttpClientProperties 这些没有响应超时时间,连接池不能设置超时时间么?
其实连接池的响应超时时间、连接超时时间都是从 Request.Options 获取的,我们来看下:
ApacheHttpClient、OkHttpClient 都实现了 feign core包里的 Client 接口:
public interface Client { Response execute(Request request, Options options) throws IOException; }
对于 ApacheHttpClient:
public final class ApacheHttpClient implements Client { @Override public Response execute(Request request, Request.Options options) throws IOException { HttpUriRequest httpUriRequest; try { httpUriRequest = toHttpUriRequest(request, options); ... } } } HttpUriRequest toHttpUriRequest(Request request, Request.Options options) throws URISyntaxException { RequestBuilder requestBuilder = RequestBuilder.create(request.httpMethod().name()); // per request timeouts 从 Request.Options 获取连接超时和响应超时 RequestConfig requestConfig = (client instanceof Configurable ? RequestConfig.copy(((Configurable) client).getConfig()) : RequestConfig.custom()) .setConnectTimeout(options.connectTimeoutMillis()) .setSocketTimeout(options.readTimeoutMillis()) .build(); requestBuilder.setConfig(requestConfig); URI uri = new URIBuilder(request.url()).build(); ... }
对于 OkHttpClient:
public Response execute(feign.Request input, Options options) throws IOException { okhttp3.OkHttpClient requestScoped; // 也是从 Options 来获取的 if (this.delegate.connectTimeoutMillis() == options.connectTimeoutMillis() && this.delegate.readTimeoutMillis() == options.readTimeoutMillis() && this.delegate.followRedirects() == options.isFollowRedirects()) { requestScoped = this.delegate; } else { requestScoped = this.delegate.newBuilder().connectTimeout((long)options.connectTimeoutMillis(), TimeUnit.MILLISECONDS).readTimeout((long)options.readTimeoutMillis(), TimeUnit.MILLISECONDS).followRedirects(options.isFollowRedirects()).build(); } Request request = toOkHttpRequest(input); okhttp3.Response response = requestScoped.newCall(request).execute(); return toFeignResponse(response, input).toBuilder().request(input).build(); }
我这里拿 ApacheHttpClient 调试如下:
虽然 ApacheHttpClient 默认连接超时是 2秒,但是由于 Options 默认是 10秒,所以 ApacheHttpClient 被重置为了 10秒,并且我配置的响应超时 1秒也生效了:
所以不管对于连接池方式的还是默认的 HttpURLConnection,连接超时或者响应超时的配置都可以通过 feign.client.config 来做:
feign.client.config.default.read-timeout=1000 feign.client.config.default.connect-timeout=1000 或者 feign.client.config.某个contextId.read-timeout=1000 feign.client.config.某个contextId.connect-timeout=1000
4.1.3 Retryer 重试器
重试器我们这里就不看了吧,之前都看过了。
4.1.4 RequestInterceptor 拦截器
OpenFeign 在执行请求的时候,给我们提供了一个拦截器,来做一些自定义的处理。
public interface RequestInterceptor { void apply(RequestTemplate template); }
它的执行时机如图,是在 SynchronousMethodHandler 的 executeAndDecode 执行请求时的 targetRequest 方法中执行拦截器的:
Request targetRequest(RequestTemplate template) { for (RequestInterceptor interceptor : requestInterceptors) { interceptor.apply(template); } return target.apply(template); }
它的场景主要有:
- 比如我们服务之间互相调用,要传递用户信息,通过从上下文取到当前用户标志塞到 RequestTemplate 的 header 中,下游服务再从 Header中解析获得。
- 比如我们要看服务调用时的一些请求参数等信息,可以通过拦截器打印。
- 比如服务之间的链路监控,也是通过将 TraceId 放置到 header 中,跟第一点方式原理类似。
4.1.5 默认请求头、请求参数
defaultRequestHeaders 默认的请求头、defaultQueryParameters 默认的路径参数
他俩的实现原理都是通过拦截器来实现的:
protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration config, Feign.Builder builder) { ... if (Objects.nonNull(config.getDefaultRequestHeaders())) { // lambda 放置一个拦截器 builder.requestInterceptor(requestTemplate -> requestTemplate .headers(config.getDefaultRequestHeaders())); } if (Objects.nonNull(config.getDefaultQueryParameters())) { // lambda 放置一个拦截器 builder.requestInterceptor(requestTemplate -> requestTemplate .queries(config.getDefaultQueryParameters())); } ... }
4.1.6 编码器、解码器
encode 编码:就是在请求发出之前对参数进行编码
decode 解码:在接收到结果数据后对其进行解码
说实话,这块还真没研究过,这里就看一下这两者的执行时机:
4.1.6.1 encoder 编码时机
我们的方法处理器 SynchronousMethodHandler 有一个这样的属性:
private final RequestTemplate.Factory buildTemplateFromArgs;
它来源于 ReflectiveFeign 解析你的 Feign 的方法的时候会根据你方法请求方式以及参数来创建不同的 BuildTemplateByResolvingArgs(它实现了 RequestTemplate.Factory),并且放置编码器在里边:
public Map<String, MethodHandler> apply(Target target) { List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type()); Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>(); // 遍历每个方法的原始信息 for (MethodMetadata md : metadata) { BuildTemplateByResolvingArgs buildTemplate; // 当是存在 form 表单形式的提交 if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) { buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target); // 其次是有请求体的时候 } else if (md.bodyIndex() != null) { buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target); } else { // 剩余的走这里 buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target); } ... } return result; }
先看下类图关系:
然后 SynchronousMethodHandler 在执行的第一步就是构建 RequestTemplate,就会调用 buildTemplateFromArgs 的 create 方法:
@Override public Object invoke(Object[] argv) throws Throwable { RequestTemplate template = buildTemplateFromArgs.create(argv); ... }
接着就会先进入父类 BuildTemplateByResolvingArgs 的 create 方法:
@Override public RequestTemplate create(Object[] argv) { ... // 解析 RequestTemplate template = resolve(argv, mutable, varBuilder); ... }
解析方法 resolve 就会进入上边的两个编码过程:
// BuildFormEncodedTemplateFromArgs 的: private static class BuildFormEncodedTemplateFromArgs extends BuildTemplateByResolvingArgs { private final Encoder encoder; @Override protected RequestTemplate resolve(Object[] argv, RequestTemplate mutable, Map<String, Object> variables) { Map<String, Object> formVariables = new LinkedHashMap<String, Object>(); for (Entry<String, Object> entry : variables.entrySet()) { if (metadata.formParams().contains(entry.getKey())) { formVariables.put(entry.getKey(), entry.getValue()); } } try { encoder.encode(formVariables, Encoder.MAP_STRING_WILDCARD, mutable); } catch (EncodeException e) { throw e; } catch (RuntimeException e) { throw new EncodeException(e.getMessage(), e); } return super.resolve(argv, mutable, variables); } } } // BuildEncodedTemplateFromArgs 的: private static class BuildEncodedTemplateFromArgs extends BuildTemplateByResolvingArgs { private final Encoder encoder; @Override protected RequestTemplate resolve(Object[] argv, RequestTemplate mutable, Map<String, Object> variables) { Object body = argv[metadata.bodyIndex()]; checkArgument(body != null, "Body parameter %s was null", metadata.bodyIndex()); try { encoder.encode(body, metadata.bodyType(), mutable); } catch (EncodeException e) { throw e; } catch (RuntimeException e) { throw new EncodeException(e.getMessage(), e); } return super.resolve(argv, mutable, variables); } }
4.1.6.1 decoder 解码时机
解码就是在 SynchronousMethodHandler 的 executeAndDecode 执行方法中,当响应结果回来后:
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable { Request request = targetRequest(template); Response response; try { // 发送请求 response = client.execute(request, options); response = response.toBuilder() .request(request) .requestTemplate(template) .build(); } catch (IOException e) { throw errorExecuting(request, e); } // 解码 if (decoder != null) return decoder.decode(response, metadata.returnType()); ... }
4.2 feign.httpclient 相关
feign.httpclient 其实我们上边看到了,它是作用给开启连接池的时候用到的,我们这里简单看下都能配置哪些内容以及每个的默认值:
# 开启 httpclient连接池 前提要引入依赖才能生效 默认 false feign.httpclient.enabled = false # 连接超时时间 默认 20秒 feign.httpclient.connection-timeout = 2000 # 多久检查一次连接池中的空闲连接是否仍然可用 默认30秒 feign.httpclient.connection-timer-repeat = 3000 # 用于控制Feign客户端在遇到HTTP重定向(如301、302状态码)时是否自动跟随重定向 默认 true feign.httpclient.follow-redirects = true # 用于限制Feign客户端能够建立的总的并发连接数 默认 200 feign.httpclient.max-connections = 200 # 这个配置项用于限制针对特定目标服务器(路由)的并发连接数 默认 50 # 路由是指到特定目标服务器的连接路径,通常对应于一个特定的主机和端口组合。 feign.httpclient.max-connections-per-route = 50 # 用于定义连接在连接池中闲置多长时间后会被关闭 默认 900 feign.httpclient.time-to-live = 900 # 配合上边的闲置单位 默认秒 feign.httpclient.time-to-live-unit = seconds # 用于控制是否禁用SSL证书验证。当使用HTTPS协议时,默认情况下,Feign客户端会验证服务器的SSL证书以确保通信的安全性 # 然而,在某些开发或测试环境中,可能需要禁用这一验证,以绕过自签名证书或其他非正式证书的验证问题 # 默认 false feign.httpclient.disable-ssl-validation = false
还记得上边的连接池连接超时时间的优先级么,比如我设置了如下:
feign.client.config.default.connect-timeout=1000 feign.httpclient.connection-timeout=2000
那连接超时时间时多少呢?应该是 10秒 对吧,虽然连接池设置的是20秒,但最后会被 Feign,Builder 里的 Request.Options 覆盖为 10秒,我们调试看看:
4.3 feign.compression.request 相关
# 是否开启请求压缩 默认 false feign.compression.request.enabled = false # 请求压缩支持的 mime 类型 feign.compression.request.mime-types = text/xml,application/xml,application/json # 设置最小请求大小,只有当请求体的大小超过这个阈值时才启用压缩 默认 2048 feign.compression.request.min-request-size = 2048 # 设置是否启用响应压缩 如果启用,Feign客户端会处理来自服务器的压缩响应 默认 false feign.compression.response.enabled = false # 设置响应开启 gzip 解码器 默认 false feign.compression.response.useGzipDecoder = false
5 连接池
关于 OpenFeign 的请求,我们知道它是基于 Http 的,并且默认的情况下,它是不开启连接池的:
@Configuration(proxyBeanMethods = false) class DefaultFeignLoadBalancedConfiguration { @Bean // 当没有别的 Client 的话走这里,也就是默认走这里 @ConditionalOnMissingBean public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) { // 客户端默认用的是 Client.Default (Client 是 feign 核心包里的 Default 是默认的实现) return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory, clientFactory); } } // Client.Default class Default implements Client { ... @Override public Response execute(Request request, Options options) throws IOException { // 每次请求都是用的 HttpURLConnection 当没有keep-alive的情况下,其实每次请求都会经历建立连接发送接收数据断开连接 影响性能 HttpURLConnection connection = convertAndSend(request, options); return convertResponse(connection, request); } }
5.1 Apache-HttpClient
如果要开启 Apache 的 HttpClient 作为 HTTP 客户端,首先引入依赖:
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>
并且开启配置即可:
feign.httpclient.enabled = true
原理来源于两个配置类:
@Configuration(proxyBeanMethods = false) @ConditionalOnClass(ApacheHttpClient.class) @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true) @Conditional(HttpClient5DisabledConditions.class) // 引入 HttpClientFeignConfiguration @Import(HttpClientFeignConfiguration.class) class HttpClientFeignLoadBalancedConfiguration { @Bean @ConditionalOnMissingBean(Client.class) public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory, HttpClient httpClient) { // httpClient 来源于下面的配置类 ApacheHttpClient delegate = new ApacheHttpClient(httpClient); return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory); } } // HttpClientFeignConfiguration @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(CloseableHttpClient.class) public class HttpClientFeignConfiguration { @Bean @ConditionalOnProperty(value = "feign.compression.response.enabled", havingValue = "false", matchIfMissing = true) public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory, HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) { // 默认走这里创建 HttpClient this.httpClient = createClient(httpClientFactory.createBuilder(), httpClientConnectionManager, httpClientProperties); return this.httpClient; } private CloseableHttpClient createClient(HttpClientBuilder builder, HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) { RequestConfig defaultRequestConfig = RequestConfig.custom() .setConnectTimeout(httpClientProperties.getConnectionTimeout()) .setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build(); CloseableHttpClient httpClient = builder .setDefaultRequestConfig(defaultRequestConfig) .setConnectionManager(httpClientConnectionManager).build(); return httpClient; } ... }
5.2 OkHttp
如果要开启 okHttp 作为 HTTP 客户端,也是先引入依赖:
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency>
并且开启配置即可:
feign.okhttp.enabled = true
原理也是来源于两个配置类:
@Configuration(proxyBeanMethods = false) @ConditionalOnClass(OkHttpClient.class) @ConditionalOnProperty("feign.okhttp.enabled") // 引入 OkHttpFeignConfiguration @Import(OkHttpFeignConfiguration.class) class OkHttpFeignLoadBalancedConfiguration { @Bean @ConditionalOnMissingBean(Client.class) public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) { OkHttpClient delegate = new OkHttpClient(okHttpClient); return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory); } } @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(okhttp3.OkHttpClient.class) public class OkHttpFeignConfiguration { private okhttp3.OkHttpClient okHttpClient; @Bean @ConditionalOnMissingBean(ConnectionPool.class) public ConnectionPool httpClientConnectionPool( FeignHttpClientProperties httpClientProperties, OkHttpClientConnectionPoolFactory connectionPoolFactory) { Integer maxTotalConnections = httpClientProperties.getMaxConnections(); Long timeToLive = httpClientProperties.getTimeToLive(); TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit(); return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit); } @Bean public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) { Boolean followRedirects = httpClientProperties.isFollowRedirects(); Integer connectTimeout = httpClientProperties.getConnectionTimeout(); this.okHttpClient = httpClientFactory .createBuilder(httpClientProperties.isDisableSslValidation()) .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS) .followRedirects(followRedirects).connectionPool(connectionPool).build(); return this.okHttpClient; } @PreDestroy public void destroy() { if (this.okHttpClient != null) { this.okHttpClient.dispatcher().executorService().shutdown(); this.okHttpClient.connectionPool().evictAll(); } } }
另外要注意的是,如果你的依赖中既包含 httpClient 又包含 okHttpClient 的话,默认是用的 httpClient
即使你设置了 feign.okhttp.enabled = true,也没用还是会用 httpClient,我调试发现是这样的。
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true) class HttpClientFeignLoadBalancedConfiguration {
6 小结
好啦,本节就看到这里,有理解不对的地方欢迎指点。