基于Feign使用okhttp的填坑之旅
1、由于項(xiàng)目需要遠(yuǎn)程調(diào)用http請(qǐng)求
因此就想到了Feign,因?yàn)檎娴姆浅5姆奖悖恍枰x一個(gè)接口就行。
但是feign默認(rèn)使用的JDK的URLHttpConnection,沒有連接池效率不好,從Feign的自動(dòng)配置類FeignAutoConfiguration中可以看到Feign除了默認(rèn)的http客戶端還支持okhttp和ApacheHttpClient,我這里選擇了okhttp,它是有連接池的。
2、看看網(wǎng)絡(luò)上大部分博客中是怎么使用okhttp的
1)、引入feign和okhttp的maven坐標(biāo)
<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>
2)、在配置文件中禁用默認(rèn)的URLHttpConnection,啟動(dòng)okhttp
feign.httpclient.enabled=false feign.okhttp.enabled=true
3)、其實(shí)這個(gè)時(shí)候就可以使用okhttp了
但網(wǎng)絡(luò)上大部分博客還寫了一個(gè)自定義配置類,在其中實(shí)例化了一個(gè)okhttp3.OkHttpClient,就是這么一個(gè)配置類導(dǎo)致了大坑啊,有了它之后okhttp根本不會(huì)生效,不信咱們就是來試一下
@Configuration @ConditionalOnClass(Feign.class) @AutoConfigureBefore(FeignAutoConfiguration.class) public class OkHttpConfig { @Bean public okhttp3.OkHttpClient okHttpClient(){ return new okhttp3.OkHttpClient.Builder() //設(shè)置連接超時(shí) .connectTimeout(10 , TimeUnit.SECONDS) //設(shè)置讀超時(shí) .readTimeout(10 , TimeUnit.SECONDS) //設(shè)置寫超時(shí) .writeTimeout(10 , TimeUnit.SECONDS) //是否自動(dòng)重連 .retryOnConnectionFailure(true) .connectionPool(new ConnectionPool(10 , 5L, TimeUnit.MINUTES)) .build(); } }
上面這個(gè)配置類其實(shí)就是配置了一下okhttp的基本參數(shù)和連接池的基本參數(shù)
此時(shí)我們可以在配置文件中開始日志打印,看一下那些自動(dòng)配置沒有生效
debug=true
啟動(dòng)我們的項(xiàng)目可以在控制臺(tái)搜索到如下日志輸出
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),原因也很簡(jiǎn)單是因?yàn)槿萜髦幸呀?jīng)存在了okhttp3.OkHttpClient對(duì)象,我們?nèi)タ纯催@個(gè)配置類的源碼,其中類上標(biāo)注了@ConditionalOnMissingBean(okhttp3.OkHttpClient.class),意思上當(dāng)容器中不存在okhttp3.OkHttpClient對(duì)象時(shí)才生效,然后我們卻在自定義的配置類中畫蛇添足的實(shí)例化了一個(gè)該對(duì)象到容器中。
@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生效
其中我們的自定義配置類中并沒有做什么特別復(fù)雜的事情,僅僅是給okhttp3.OkHttpClient和它的連接池對(duì)象設(shè)置了幾個(gè)參數(shù)罷了,看看上面OkHttpFeignConfiguration類中實(shí)例化的幾個(gè)類對(duì)象,其中就包含了okhttp3.OkHttpClient和ConnectionPool,從代碼中不難看出它們的參數(shù)值都是從FeignHttpClientProperties獲取的,因此我們只需要在配置文件中配上feign.httpclient開頭的相關(guān)配置就可以了生效了。
如果我們的目的不僅僅是簡(jiǎn)單的修改幾個(gè)參數(shù)值,比如需要在okhttp中添加攔截器Interceptor,這也非常簡(jiǎn)單,只需要寫一個(gè)Interceptor的實(shí)現(xiàn)類,然后將OkHttpFeignConfiguration的內(nèi)容完全復(fù)制一份到我們自定義的配置類中,并設(shè)置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()); } }
將自定義配置類中原有的內(nèi)容去掉,復(fù)制一份OkHttpFeignConfiguration的代碼做簡(jiǎn)單的修改,設(shè)置攔截器的代碼如下
@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) //這里設(shè)置我們自定義的攔截器 .addInterceptor(new MyOkhttpInterceptor()) .build(); return this.okHttpClient; }
3、最后上兩張圖,F(xiàn)eign的動(dòng)態(tài)代理使用和處理流程
補(bǔ)充:spring cloud feign sentinel okhttp3 gzip 壓縮問題
引入pom okhttp 配置,okhttp使用連接池技術(shù),相對(duì)feign httpUrlConnection 每次請(qǐng)求,創(chuàng)建一個(gè)連接,效率更高
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency>
okhttp 開始?jí)嚎s條件
增加攔截器動(dòng)態(tài)刪除Accept-Encoding 參數(shù),使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("接收響應(yīng): [%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("接收響應(yīng): [%s] %n %.1fms%n", encodedPath, (currentTime - doTime) / 1e6d)); } } return response; } }
feign 配置
feign: sentinel: # 開啟Sentinel對(duì)Feign的支持 enabled: true httpclient: enabled: false okhttp: enabled: true
feign 配置類
@Configuration @ConditionalOnClass(Feign.class) @AutoConfigureBefore(FeignAutoConfiguration.class) public class FeignOkHttpConfig { @Bean public okhttp3.OkHttpClient okHttpClient(){ return new okhttp3.OkHttpClient.Builder() //設(shè)置連接超時(shí) .connectTimeout(10, TimeUnit.SECONDS) //設(shè)置讀超時(shí) .readTimeout(10, TimeUnit.SECONDS) //設(shè)置寫超時(shí) .writeTimeout(10, TimeUnit.SECONDS) //是否自動(dòng)重連 .retryOnConnectionFailure(true) .connectionPool(new ConnectionPool(10, 5L, TimeUnit.MINUTES)) .build(); } }
案例:feign client
@FeignClient(name = "服務(wù)名稱",fallbackFactory = FeignFallBack.class ,url = "調(diào)試地址", 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默認(rèn)http,對(duì)壓縮支持不好,使用okhttp 代替實(shí)現(xiàn)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
java高并發(fā)情況下高效的隨機(jī)數(shù)生成器
這篇文章主要介紹了java高并發(fā)情況下高效的隨機(jī)數(shù)生成器,對(duì)于性能有要求的同學(xué),可以參考下2021-04-04java聯(lián)系人管理系統(tǒng)簡(jiǎn)單設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了java聯(lián)系人管理系統(tǒng)簡(jiǎn)單設(shè)計(jì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10Spring AbstractRoutingDatasource 動(dòng)態(tài)數(shù)據(jù)源的實(shí)例講解
本文介紹如何使用 Spring AbstractRoutingDatasource 基于上下文動(dòng)態(tài)切換數(shù)據(jù)源,因此我們會(huì)讓查找數(shù)據(jù)源邏輯獨(dú)立于數(shù)據(jù)訪問之外2021-07-07Mybatis之a(chǎn)ssociation和collection用法
這篇文章主要介紹了Mybatis之a(chǎn)ssociation和collection用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02Java實(shí)現(xiàn)簡(jiǎn)單日歷小程序 Java圖形界面小日歷開發(fā)
這篇文章主要介紹了Java實(shí)現(xiàn)簡(jiǎn)單日歷小程序,如何用Java swing開發(fā)一款簡(jiǎn)單的小日歷,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02java 利用反射機(jī)制,獲取實(shí)體所有屬性和方法,并對(duì)屬性賦值
這篇文章主要介紹了 java 利用反射機(jī)制,獲取實(shí)體所有屬性和方法,并對(duì)屬性賦值的相關(guān)資料,需要的朋友可以參考下2017-01-01SpringBoot實(shí)現(xiàn)Server-Sent Events(SSE)的使用完整指南
使用SpringBoot實(shí)現(xiàn)Server-Sent Events(SSE)可以有效處理實(shí)時(shí)數(shù)據(jù)推送需求,具有單向通信、輕量級(jí)和高實(shí)時(shí)性等優(yōu)勢(shì),本文詳細(xì)介紹了在SpringBoot中創(chuàng)建SSE端點(diǎn)的步驟,并通過代碼示例展示了客戶端如何接收數(shù)據(jù),適用于實(shí)時(shí)通知、數(shù)據(jù)展示和在線聊天等場(chǎng)景2024-09-09