Feign 使用 okhttp3 的正确姿势

本贴最后更新于 1443 天前,其中的信息可能已经时移世易

首先来吐槽一波(╯‵□′)╯︵┻━┻

本来呢,我是想加一层 feign 的 interceptor 处理 feign 里 request 请求的返回信息的,比如只提取 ResponseBody 中的 data 啥的。后来想起来 feign 默认使用的是 jdk 的 HttpURLConnection,而且 feign 本身是支持替换 okhttp 的,于是打算搞起~

可是,百毒到的都是什么鬼啊,按照别人写的文档配好,各种问题,什么 springboot 注解不行啦,需要使用 feign 默认注解,什么负载均衡失效啦,无语。(╯‵□′)╯︵┻━┻

最后,还是自己操刀,从源码看吧。

遂有此文~

百毒,淦!

首先来写一个简单的 interceptor

不需要这个的跳过看下一段吧~

//这个interceptor的目的是提取返回body中的data,并转化为json @Component public class OkHttpResponseInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Response response = chain.proceed(request); if (HttpHeaders.hasBody(response)) { if (response.code() == 200) { ResponseBody responseBody = response.body(); if (responseBody != null && responseBody.contentLength() != 0 && Objects.requireNonNull(responseBody.contentType()).type() .equals(MediaType.APPLICATION_JSON_VALUE)) { String str = responseBody.string(); JSONObject json = JSONObject.parseObject(JSON.toJSON(str)); String data = json.getString("data"); if (StringUtils.isNotBlank(data)) { ResponseBody body = ResponseBody.create(okhttp3.MediaType.get(MediaType.APPLICATION_JSON_VALUE), data); return response.newBuilder().body(body).build(); } } } } return response; } }

嗯,这样,interceptor 就定义好了,接下来就是配置 feign 了~

配置 Feign

关于 maven 依赖修改啥的我就不说了,如果使用的 spring-cloud-openfeign-dependencies,应该会包含 okhttp 的依赖。

  1. 修改 application.yml

    feign: hystrix: enabled: true okhttp: enabled: true httpclient: connectionTimeout: 30000 client: config: default: readTimeout: 30000
  2. 添加 FeignOkHttpConfig.java

    @Configuration @ConditionalOnClass(Feign.class) @AutoConfigureBefore(FeignLoadBalancerAutoConfiguration.class) public class FeignOkHttpConfig { @Autowired private OkHttpResponseInterceptor okHttpResponseInterceptor; 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 @ConditionalOnMissingBean(okhttp3.OkHttpClient.class) public okhttp3.OkHttpClient okHttpClient(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, FeignClientProperties feignClientProperties, FeignHttpClientProperties feignHttpClientProperties) { FeignClientProperties.FeignClientConfiguration defaultConfig = feignClientProperties.getConfig().get("default"); int connectTimeout = feignHttpClientProperties.getConnectionTimeout(); int readTimeout = defaultConfig.getReadTimeout(); boolean disableSslValidation = feignHttpClientProperties.isDisableSslValidation(); boolean followRedirects = feignHttpClientProperties.isFollowRedirects(); this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation) .readTimeout(readTimeout, TimeUnit.MILLISECONDS) .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS) .followRedirects(followRedirects) .connectionPool(connectionPool) .addInterceptor(okHttpResponseInterceptor) .build(); return this.okHttpClient; } @PreDestroy public void destroy() { if (this.okHttpClient != null) { this.okHttpClient.dispatcher().executorService().shutdown(); this.okHttpClient.connectionPool().evictAll(); } } }

好的~ 这样基本上就配置完了。下面我来具体解释一下,这么配置就能生效的原因。(如果不需要负载均衡,可以参考百毒到的配置方案,那个应该也是 ok 的)

解析

  1. 什么条件下配置的 okhttp 才会生效

    首先看 org.springframework.cloud.openfeign.FeignAutoConfiguration

    @Configuration(proxyBeanMethods = false) @ConditionalOnClass(OkHttpClient.class) @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer") @ConditionalOnMissingBean(okhttp3.OkHttpClient.class) @ConditionalOnProperty("feign.okhttp.enabled") protected static class OkHttpFeignConfiguration { ... }

    只有当 okhttp3.OkHttpClient 这个 Bean 不存在时,才会启用 OkHttpFeignConfiguration。

    然而我们在配置中需要修改 interceptor 必然会手动创建这个 Bean,因此我们需要手动添加其他的配置。

    但是,先不要急,因为如果使用负载均衡,这个类还不是关键。

  2. feign 负载均衡 FeignLoadBalancerAutoConfiguration

    @ConditionalOnClass(Feign.class) @ConditionalOnBean(BlockingLoadBalancerClient.class) @AutoConfigureBefore(FeignAutoConfiguration.class) @AutoConfigureAfter(FeignRibbonClientAutoConfiguration.class) @EnableConfigurationProperties(FeignHttpClientProperties.class) @Configuration(proxyBeanMethods = false) // Order is important here, last should be the default, first should be optional // see // https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653 @Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class, DefaultFeignLoadBalancerConfiguration.class }) public class FeignLoadBalancerAutoConfiguration { }

    不难发现,这个配置加载是在 FeignAutoConfiguration 之前的,因此,这个类对于我们而言更为关键。

    @Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class, DefaultFeignLoadBalancerConfiguration.class })

    通过 Import 注解的信息,我们得知需要查看 OkHttpFeignLoadBalancerConfiguration。

  3. 根据 OkHttpFeignLoadBalancerConfiguration

    @Configuration(proxyBeanMethods = false) @ConditionalOnClass(OkHttpClient.class) @ConditionalOnProperty("feign.okhttp.enabled") @ConditionalOnBean(BlockingLoadBalancerClient.class) @Import(OkHttpFeignConfiguration.class) class OkHttpFeignLoadBalancerConfiguration { @Bean @ConditionalOnMissingBean public Client feignClient(okhttp3.OkHttpClient okHttpClient, BlockingLoadBalancerClient loadBalancerClient) { OkHttpClient delegate = new OkHttpClient(okHttpClient); return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient); } }

    可以看出,这个配置类只生成了 Client 这个 Bean,对于 FeignAutoConfiguration 中需要的剩下的 Bean 显然是不够的,因此,剩下的内容应该都在

    @Import(OkHttpFeignConfiguration.class)

    这个 Import 的配置中,那么我们看看这个配置到底做了啥。

  4. 最后一步了~

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

    很显然,这个类才是我们需要替换的,而且这个配置类的加载条件很简单

    @ConditionalOnMissingBean(okhttp3.OkHttpClient.class)

    我们只要自己创建这个 Bean 就可以了。

    那么,我们要做的就是在 FeignLoadBalancerAutoConfiguration 配置类加载之前,生成这个 Bean,并根据 OkHttpFeignConfiguration 生成其他需要的 Bean 就可以了。具体参考 配置 Feign 一节。

  • Feign
    8 引用
  • OkHttp

    OkHttp 是一款 HTTP & HTTP/2 客户端库,专为 Android 和 Java 应用打造。

    16 引用 • 6 回帖 • 84 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3194 引用 • 8214 回帖 • 2 关注
  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    946 引用 • 1460 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...