1、由于项目需要远程调用http请求,因此就想到了Feign,因为真的非常的方便,只需要定义一个接口就行。但是feign默认使用的JDK的URLHttpConnection,没有连接池效率不好,从Feign的自动配置类FeignAutoConfiguration中可以看到Feign除了默认的http客户端还支持okhttp和ApacheHttpClient,我这里选择了okhttp,它是有连接池的。
2、看看网络上大部分博客中是怎么使用okhttp的
1)、引入feign和okhttp的maven坐标
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.cloud
spring-cloud-starter-openfeign
io.github.openfeign
feign-okhttp
2)、在配置文件中禁用默认的URLHttpConnection,启动okhttp
feign.httpclient.enabled=false
feign.okhttp.enabled=true
3)、其实这个时候就可以使用okhttp了,但网络上大部分博客还写了一个自定义配置类,在其中实例化了一个okhttp3.OkHttpClient,就是这么一个配置类导致了大坑啊,有了它之后okhttp根本不会生效,不信咱们就是来试一下
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class OkHttpConfig {
@Bean
public okhttp3.OkHttpClient okHttpClient(){
return new okhttp3.OkHttpClient.Builder()
//设置连接超时
.connectTimeout(10 , TimeUnit.SECONDS)
//设置读超时
.readTimeout(10 , TimeUnit.SECONDS)
//设置写超时
.writeTimeout(10 , TimeUnit.SECONDS)
//是否自动重连
.retryOnConnectionFailure(true)
.connectionPool(new ConnectionPool(10 , 5L, TimeUnit.MINUTES))
.build();
}
}
上面这个配置类其实就是配置了一下okhttp的基本参数和连接池的基本参数
此时我们可以在配置文件中开始日志打印,看一下那些自动配置没有生效
debug=true
启动我们的项目可以在控制台搜索到如下日志输出
FeignAutoConfiguration.OkHttpFeignConfiguration:
Did not match:
- @ConditionalOnBean (types: okhttp3.OkHttpClient; SearchStrategy: all) found beans of type 'okhttp3.OkHttpClient' okHttpClient (OnBeanCondition)
Matched:
- @ConditionalOnClass found required class 'feign.okhttp.OkHttpClient'; @ConditionalOnMissingClass did not find unwanted class 'com.netflix.loadbalancer.ILoadBalancer' (OnClassCondition)
- @ConditionalOnProperty (feign.okhttp.enabled) matched (OnPropertyCondition)
从日志中可以清楚的看到FeignAutoConfiguration.OkHttpFeignConfiguration没有匹配成功(Did not match),原因也很简单是因为容器中已经存在了okhttp3.OkHttpClient对象,我们去看看这个配置类的源码,其中类上标注了@ConditionalOnMissingBean(okhttp3.OkHttpClient.class),意思上当容器中不存在okhttp3.OkHttpClient对象时才生效,然后我们却在自定义的配置类中画蛇添足的实例化了一个该对象到容器中。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
protected static 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();
Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation)
.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();
}
}
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(okhttp3.OkHttpClient client) {
return new OkHttpClient(client);
}
}
4)、该如何处理才能使okhttp生效
其中我们的自定义配置类中并没有做什么特别复杂的事情,仅仅是给okhttp3.OkHttpClient和它的连接池对象设置了几个参数罢了,看看上面OkHttpFeignConfiguration类中实例化的几个类对象,其中就包含了okhttp3.OkHttpClient和ConnectionPool,从代码中不难看出它们的参数值都是从FeignHttpClientProperties获取的,因此我们只需要在配置文件中配上feign.httpclient开头的相关配置就可以了生效了。如果我们的目的不仅仅是简单的修改几个参数值,比如需要在okhttp中添加拦截器Interceptor,这也非常简单,只需要写一个Interceptor的实现类,然后将OkHttpFeignConfiguration的内容完全复制一份到我们自定义的配置类中,并设置okhttp3.OkHttpClient的拦截器即可。
import okhttp3.Interceptor;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class MyOkhttpInterceptor implements Interceptor {
Logger logger = LoggerFactory.getLogger(MyOkhttpInterceptor.class);
@Override
public Response intercept(Chain chain) throws IOException {
logger.info("okhttp method:{}",chain.request().method());
logger.info("okhttp request:{}",chain.request().body());
return chain.proceed(chain.request());
}
}
将自定义配置类中原有的内容去掉,复制一份OkHttpFeignConfiguration的代码做简单的修改,设置拦截器的代码如下
@Bean
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
ConnectionPool connectionPool,
FeignHttpClientProperties httpClientProperties) {
Boolean followRedirects = httpClientProperties.isFollowRedirects();
Integer connectTimeout = httpClientProperties.getConnectionTimeout();
Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation)
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
.followRedirects(followRedirects).connectionPool(connectionPool)
//这里设置我们自定义的拦截器
.addInterceptor(new MyOkhttpInterceptor())
.build();
return this.okHttpClient;
}
3、最后上两张图,Feign的动态代理使用和处理流程