基于Feign使用okhttp的填坑之旅
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) publicclassOkHttpConfig{ @Bean publicokhttp3.OkHttpClientokHttpClient(){ returnnewokhttp3.OkHttpClient.Builder() //设置连接超时 .connectTimeout(10,TimeUnit.SECONDS) //设置读超时 .readTimeout(10,TimeUnit.SECONDS) //设置写超时 .writeTimeout(10,TimeUnit.SECONDS) //是否自动重连 .retryOnConnectionFailure(true) .connectionPool(newConnectionPool(10,5L,TimeUnit.MINUTES)) .build(); } }
上面这个配置类其实就是配置了一下okhttp的基本参数和连接池的基本参数
此时我们可以在配置文件中开始日志打印,看一下那些自动配置没有生效
debug=true
启动我们的项目可以在控制台搜索到如下日志输出
FeignAutoConfiguration.OkHttpFeignConfiguration: Didnotmatch: -@ConditionalOnBean(types:okhttp3.OkHttpClient;SearchStrategy:all)foundbeansoftype'okhttp3.OkHttpClient'okHttpClient(OnBeanCondition) Matched: -@ConditionalOnClassfoundrequiredclass'feign.okhttp.OkHttpClient';@ConditionalOnMissingClassdidnotfindunwantedclass'com.netflix.loadbalancer.ILoadBalancer'(OnClassCondition) -@ConditionalOnProperty(feign.okhttp.enabled)matched(OnPropertyCondition)
从日志中可以清楚的看到FeignAutoConfiguration.OkHttpFeignConfiguration没有匹配成功(Didnotmatch),原因也很简单是因为容器中已经存在了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") protectedstaticclassOkHttpFeignConfiguration{ privateokhttp3.OkHttpClientokHttpClient; @Bean @ConditionalOnMissingBean(ConnectionPool.class) publicConnectionPoolhttpClientConnectionPool( FeignHttpClientPropertieshttpClientProperties, OkHttpClientConnectionPoolFactoryconnectionPoolFactory){ IntegermaxTotalConnections=httpClientProperties.getMaxConnections(); LongtimeToLive=httpClientProperties.getTimeToLive(); TimeUnitttlUnit=httpClientProperties.getTimeToLiveUnit(); returnconnectionPoolFactory.create(maxTotalConnections,timeToLive,ttlUnit); } @Bean publicokhttp3.OkHttpClientclient(OkHttpClientFactoryhttpClientFactory, ConnectionPoolconnectionPool, FeignHttpClientPropertieshttpClientProperties){ BooleanfollowRedirects=httpClientProperties.isFollowRedirects(); IntegerconnectTimeout=httpClientProperties.getConnectionTimeout(); BooleandisableSslValidation=httpClientProperties.isDisableSslValidation(); this.okHttpClient=httpClientFactory.createBuilder(disableSslValidation) .connectTimeout(connectTimeout,TimeUnit.MILLISECONDS) .followRedirects(followRedirects).connectionPool(connectionPool) .build(); returnthis.okHttpClient; } @PreDestroy publicvoiddestroy(){ if(this.okHttpClient!=null){ this.okHttpClient.dispatcher().executorService().shutdown(); this.okHttpClient.connectionPool().evictAll(); } } @Bean @ConditionalOnMissingBean(Client.class) publicClientfeignClient(okhttp3.OkHttpClientclient){ returnnewOkHttpClient(client); } }
4)、该如何处理才能使okhttp生效
其中我们的自定义配置类中并没有做什么特别复杂的事情,仅仅是给okhttp3.OkHttpClient和它的连接池对象设置了几个参数罢了,看看上面OkHttpFeignConfiguration类中实例化的几个类对象,其中就包含了okhttp3.OkHttpClient和ConnectionPool,从代码中不难看出它们的参数值都是从FeignHttpClientProperties获取的,因此我们只需要在配置文件中配上feign.httpclient开头的相关配置就可以了生效了。
如果我们的目的不仅仅是简单的修改几个参数值,比如需要在okhttp中添加拦截器Interceptor,这也非常简单,只需要写一个Interceptor的实现类,然后将OkHttpFeignConfiguration的内容完全复制一份到我们自定义的配置类中,并设置okhttp3.OkHttpClient的拦截器即可。
importokhttp3.Interceptor; importokhttp3.Response; importorg.slf4j.Logger; importorg.slf4j.LoggerFactory; importjava.io.IOException; publicclassMyOkhttpInterceptorimplementsInterceptor{ Loggerlogger=LoggerFactory.getLogger(MyOkhttpInterceptor.class); @Override publicResponseintercept(Chainchain)throwsIOException{ logger.info("okhttpmethod:{}",chain.request().method()); logger.info("okhttprequest:{}",chain.request().body()); returnchain.proceed(chain.request()); } }
将自定义配置类中原有的内容去掉,复制一份OkHttpFeignConfiguration的代码做简单的修改,设置拦截器的代码如下
@Bean publicokhttp3.OkHttpClientclient(OkHttpClientFactoryhttpClientFactory, ConnectionPoolconnectionPool, FeignHttpClientPropertieshttpClientProperties){ BooleanfollowRedirects=httpClientProperties.isFollowRedirects(); IntegerconnectTimeout=httpClientProperties.getConnectionTimeout(); BooleandisableSslValidation=httpClientProperties.isDisableSslValidation(); this.okHttpClient=httpClientFactory.createBuilder(disableSslValidation) .connectTimeout(connectTimeout,TimeUnit.MILLISECONDS) .followRedirects(followRedirects).connectionPool(connectionPool) //这里设置我们自定义的拦截器 .addInterceptor(newMyOkhttpInterceptor()) .build(); returnthis.okHttpClient; }
3、最后上两张图,Feign的动态代理使用和处理流程
补充:springcloudfeignsentinelokhttp3gzip压缩问题
引入pomokhttp配置,okhttp使用连接池技术,相对feignhttpUrlConnection每次请求,创建一个连接,效率更高
io.github.openfeign feign-okhttp
okhttp开始压缩条件
增加拦截器动态删除Accept-Encoding参数,使okhttp压缩生效
@Slf4j publicclassHttpOkInterceptorimplementsInterceptor{ @Override publicResponseintercept(Chainchain)throwsIOException{ RequestoriginRequest=chain.request(); Responseresponse=null; if(StringUtils.isNotEmpty(originRequest.header("Accept-Encoding"))){ Requestrequest=originRequest.newBuilder().removeHeader("Accept-Encoding").build(); longdoTime=System.nanoTime(); response=chain.proceed(request); longcurrentTime=System.nanoTime(); if(response!=null){ ResponseBodyresponseBody=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{ StringencodedPath=originRequest.url().encodedPath(); LogUtil.info(log,String.format("接收响应:[%s]%n%.1fms%n", encodedPath, (currentTime-doTime)/1e6d)); } } returnresponse; } }
feign配置
feign: sentinel: #开启Sentinel对Feign的支持 enabled:true httpclient: enabled:false okhttp: enabled:true
feign配置类
@Configuration @ConditionalOnClass(Feign.class) @AutoConfigureBefore(FeignAutoConfiguration.class) publicclassFeignOkHttpConfig{ @Bean publicokhttp3.OkHttpClientokHttpClient(){ returnnewokhttp3.OkHttpClient.Builder() //设置连接超时 .connectTimeout(10,TimeUnit.SECONDS) //设置读超时 .readTimeout(10,TimeUnit.SECONDS) //设置写超时 .writeTimeout(10,TimeUnit.SECONDS) //是否自动重连 .retryOnConnectionFailure(true) .connectionPool(newConnectionPool(10,5L,TimeUnit.MINUTES)) .build(); } }
案例:feignclient
@FeignClient(name="服务名称",fallbackFactory=FeignFallBack.class,url="调试地址",configuration=FeignConfiguration.class) publicinterfaceFeignService{ @RequestMapping(value="/test/updateXx",method=RequestMethod.POST,produces=MediaType.APPLICATION_JSON_UTF8_VALUE) publicResponseEntityupdateXx(@RequestBodyXxVoxXVo); }
不知为啥sentinelfeign默认http,对压缩支持不好,使用okhttp代替实现
以上为个人经验,希望能给大家一个参考,也希望大家多多支持毛票票。如有错误或未考虑完全的地方,望不吝赐教。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。