1、由于項目需要遠程調(diào)用http請求
因此就想到了Feign,因為真的非常的方便,只需要定義一個接口就行。
但是feign默認使用的JDK的URLHttpConnection,沒有連接池效率不好,從Feign的自動配置類FeignAutoConfiguration中可以看到Feign除了默認的http客戶端還支持okhttp和ApacheHttpClient,我這里選擇了okhttp,它是有連接池的。
2、看看網(wǎng)絡(luò)上大部分博客中是怎么使用okhttp的
1)、引入feign和okhttp的maven坐標
- <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)、在配置文件中禁用默認的URLHttpConnection,啟動okhttp
- feign.httpclient.enabled=false
- feign.okhttp.enabled=true
3)、其實這個時候就可以使用okhttp了
但網(wǎng)絡(luò)上大部分博客還寫了一個自定義配置類,在其中實例化了一個okhttp3.OkHttpClient,就是這么一個配置類導(dǎo)致了大坑啊,有了它之后okhttp根本不會生效,不信咱們就是來試一下
- @Configuration
- @ConditionalOnClass(Feign.class)
- @AutoConfigureBefore(FeignAutoConfiguration.class)
- public class OkHttpConfig {
- @Bean
- public okhttp3.OkHttpClient okHttpClient(){
- return new okhttp3.OkHttpClient.Builder()
- //設(shè)置連接超時
- .connectTimeout(10 , TimeUnit.SECONDS)
- //設(shè)置讀超時
- .readTimeout(10 , TimeUnit.SECONDS)
- //設(shè)置寫超時
- .writeTimeout(10 , TimeUnit.SECONDS)
- //是否自動重連
- .retryOnConnectionFailure(true)
- .connectionPool(new ConnectionPool(10 , 5L, TimeUnit.MINUTES))
- .build();
- }
- }
上面這個配置類其實就是配置了一下okhttp的基本參數(shù)和連接池的基本參數(shù)
此時我們可以在配置文件中開始日志打印,看一下那些自動配置沒有生效
- 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),原因也很簡單是因為容器中已經(jīng)存在了okhttp3.OkHttpClient對象,我們?nèi)タ纯催@個配置類的源碼,其中類上標注了@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生效
其中我們的自定義配置類中并沒有做什么特別復(fù)雜的事情,僅僅是給okhttp3.OkHttpClient和它的連接池對象設(shè)置了幾個參數(shù)罷了,看看上面OkHttpFeignConfiguration類中實例化的幾個類對象,其中就包含了okhttp3.OkHttpClient和ConnectionPool,從代碼中不難看出它們的參數(shù)值都是從FeignHttpClientProperties獲取的,因此我們只需要在配置文件中配上feign.httpclient開頭的相關(guān)配置就可以了生效了。
如果我們的目的不僅僅是簡單的修改幾個參數(shù)值,比如需要在okhttp中添加攔截器Interceptor,這也非常簡單,只需要寫一個Interceptor的實現(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的代碼做簡單的修改,設(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的動態(tài)代理使用和處理流程
補充:spring cloud feign sentinel okhttp3 gzip 壓縮問題
引入pom okhttp 配置,okhttp使用連接池技術(shù),相對feign httpUrlConnection 每次請求,創(chuàng)建一個連接,效率更高
- <dependency>
- <groupId>io.github.openfeign</groupId>
- <artifactId>feign-okhttp</artifactId>
- </dependency>
okhttp 開始壓縮條件
增加攔截器動態(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對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è)置連接超時
- .connectTimeout(10, TimeUnit.SECONDS)
- //設(shè)置讀超時
- .readTimeout(10, TimeUnit.SECONDS)
- //設(shè)置寫超時
- .writeTimeout(10, TimeUnit.SECONDS)
- //是否自動重連
- .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默認http,對壓縮支持不好,使用okhttp 代替實現(xiàn)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持我們。如有錯誤或未考慮完全的地方,望不吝賜教。
原文鏈接:https://blog.csdn.net/eric520zenobia/article/details/103547552