因此就想到了Feign,因为真的非常的方便,只需要定义一个接口就行。
但是feign默认使用的JDK的URLHttpConnection,没有连接池效率不好,从Feign的自动配置类FeignAutoConfiguration中可以看到Feign除了默认的http客户端还支持okhttp和ApacheHttpClient,我这里选择了okhttp,它是有连接池的。
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency>
feign.httpclient.enabled=false feign.okhttp.enabled=true
但网络上大部分博客还写了一个自定义配置类,在其中实例化了一个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); } }
其中我们的自定义配置类中并没有做什么特别复杂的事情,仅仅是给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; }
补充:spring cloud feign sentinel okhttp3 gzip 压缩问题
引入pom okhttp 配置,okhttp使用连接池技术,相对feign httpUrlConnection 每次请求,创建一个连接,效率更高
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency>
增加拦截器动态删除Accept-Encoding 参数,使okhttp压缩生效
@Slf4j public class HttpOkInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request originRequest = chain.request(); Response response = null; if (StringUtils.isNotEmpty(originRequest.header("Accept-Encoding"))) { Request request = originRequest.newBuilder().removeHeader("Accept-Encoding").build(); long doTime = System.nanoTime(); response = chain.proceed(request); long currentTime = System.nanoTime(); if(response != null) { ResponseBody responseBody = response.peekBody(1024 * 1024); LogUtil.info(log, String.format("接收响应: [%s] %n返回json:【%s】 %.1fms%n%s", response.request().url(), responseBody.string(), (currentTime - doTime) / 1e6d, response.headers())); }else { String encodedPath = originRequest.url().encodedPath(); LogUtil.info(log, String.format("接收响应: [%s] %n %.1fms%n", encodedPath, (currentTime - doTime) / 1e6d)); } } return response; } }
feign: sentinel: # 开启Sentinel对Feign的支持 enabled: true httpclient: enabled: false okhttp: enabled: true
@Configuration @ConditionalOnClass(Feign.class) @AutoConfigureBefore(FeignAutoConfiguration.class) public class FeignOkHttpConfig { @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(); } }
@FeignClient(name = "服务名称",fallbackFactory = FeignFallBack.class ,url = "调试地址", configuration =FeignConfiguration.class) public interface FeignService { @RequestMapping(value = "/test/updateXx", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity<byte[]> updateXx(@RequestBody XxVo xXVo); }
不知为啥 sentinel feign默认http,对压缩支持不好,使用okhttp 代替实现
以上为个人经验,希望能给大家一个参考,也希望大家多多支持呐喊教程。如有错误或未考虑完全的地方,望不吝赐教。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:notice#nhooo.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。