首先来吐槽一波(╯‵□′)╯︵┻━┻
本来呢,我是想加一层 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 的依赖。
-
修改 application.yml
feign: hystrix: enabled: true okhttp: enabled: true httpclient: connectionTimeout: 30000 client: config: default: readTimeout: 30000
-
添加 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 的)
解析
-
什么条件下配置的 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,因此我们需要手动添加其他的配置。
但是,先不要急,因为如果使用负载均衡,这个类还不是关键。
-
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。
-
根据 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 的配置中,那么我们看看这个配置到底做了啥。
-
最后一步了~
@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 一节。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于