解決Feign切換client到okhttp無法生效的坑(出現原因說明)
提示:如果隻看如何解決問題,請看文章的末尾如何解決這個問題
1. 場景描述
最近項目中使用瞭feign當做http請求工具來使用、相對於httpclient、resttemplate來說,fegin用起來方便很多。
然後項目有httptrace的需求,需要輸出請求日志。
所以就開啟瞭feign自己的日志,發現它自帶的日志是debug級別才能打印。而且是逐行打印的,看日志非常的不方便。所以需要輸出json格式的日志最好。
2.解決步驟
2.1 引入feign依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${自行選擇適合項目的版本}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
這裡使用瞭spring-cloud-openfeing來避免自己手工實現feign的註入,用法上和feign一樣
2.2 配置feign
在入口類上添加 @EnableFeignClients 註解
@SpringBootApplication @EnableFeignClients public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
使用feing自己的Contract,方便使用feign自己的註解來聲明http接口。這裡使用瞭一個配置類
@Configuration public class FeignConfig { @Bean public Contract feignContract() { return new feign.Contract.Default(); } }
2.3 聲明接口
隻需要聲明一個帶有@FeignClient註解的接口,就聲明好瞭一個Feign的http請求接口
@FeignClient(name = "accessPlatform", url = "${url.access-platform}") public interface AccessPlatformFeignClient { @RequestLine("GET /access-platform/resource") List<AccessResource> queryResourceList(@QueryMap Map<String, Object> query); }
3.切換Feign的客戶端為OkHttp
由於feign自帶的http客戶端實現是HttpURLConnection,沒有連接池功能,可配置能力也比較差,因此我們使用okhttp作為底層的http客戶端的具體實現。
3.1 引入okhttp的依賴
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> <version>${feign-okhttp.version}</version> </dependency>
3.2 修改之前的FeignConfig配置類
問題就出在這裡、不過先不急,我們繼續
@Configuration @ConditionalOnClass(Feign.class) @AutoConfigureBefore(FeignAutoConfiguration.class) public class FeignConfig { // 註入feignContract @Bean public Contract feignContract() { return new feign.Contract.Default(); } // 註入自定義的okHttpClient @Bean public okhttp3.OkHttpClient okHttpClient(){ return new okhttp3.OkHttpClient.Builder() .readTimeout(60, TimeUnit.SECONDS) .connectTimeout(60, TimeUnit.SECONDS) .writeTimeout(120, TimeUnit.SECONDS) .connectionPool(new ConnectionPool()) .build(); } }
開啟okhttp作為feign的客戶端
# application.yml feign: okhttp: enabled: true
一切完美、網上的許多博客也都是這麼寫的。然後它們告訴你已經配置完瞭?呵呵,你們到底自己試過沒有??
3.3 試著請求一下
當然這裡出問題瞭,發出的請求並非來自okhttp,還是默認的JDK的HttpURLConnection,問題出在哪裡呢?接著看
4.找出問題所在
我懷疑是feing在註入配置的時候,根本就沒有運行關於okhttp的配置
4.1 查看服務啟動時feign配置過程
1.將服務根日志級別調整為debug級別
logging: level: root: debug
2.啟動服務、查看控制臺輸出
看到沒,okhttp的配置不符合配置運行條件。
3.查詢 FeignAutoConfiguration 這個配置類的細節
@Configuration @ConditionalOnClass({Feign.class}) @EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class}) public class FeignAutoConfiguration { // .....其他的配置 @Configuration @ConditionalOnClass({OkHttpClient.class}) @ConditionalOnMissingClass({"com.netflix.loadbalancer.ILoadBalancer"}) @ConditionalOnMissingBean({okhttp3.OkHttpClient.class}) @ConditionalOnProperty({"feign.okhttp.enabled"}) protected static class OkHttpFeignConfiguration { // ...okhttp的配置 } // .....其他的配置 }
就是這個自動配置類搞的鬼、當我看到 @ConditionalOnMissingBean({okhttp3.OkHttpClient.class}) 這個註解時,我就明白瞭。
翻成白話就是,隻有當容器中沒有OkHttpClient的實例時。他才會運行。
如果在 FeignAutoConfiguration之前註入瞭我們自己定義的OkHttpClient實例,那不好意思,我不幹瞭?無不註入。
5.解決問題
既然自動配置不幹,那我們自己動手幹。拷貝 FeignAutoConfiguration 配置類中的配置過程,粘貼在FeignConfig配置類中手動註入feign的client。Ok!完美解決。
@Configuration @ConditionalOnClass(Feign.class) @AutoConfigureAfter(FeignAutoConfiguration.class) public class FeignConfig { // private okhttp3.OkHttpClient okHttpClient; @Bean public Contract feignContract() { return new feign.Contract.Default(); } @Bean @ConditionalOnMissingBean({Client.class}) public Client feignClient(okhttp3.OkHttpClient client) { return new feign.okhttp.OkHttpClient(client); } @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 OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) { Boolean followRedirects = httpClientProperties.isFollowRedirects(); Integer connectTimeout = httpClientProperties.getConnectionTimeout(); Boolean disableSslValidation = httpClientProperties.isDisableSslValidation(); return httpClientFactory.createBuilder(disableSslValidation) .connectTimeout((long)connectTimeout, TimeUnit.MILLISECONDS) .followRedirects(followRedirects) .connectionPool(connectionPool) .addInterceptor(new OkHttpLogInterceptor()) // 自定義請求日志攔截器 .build(); } }
6.小結
如果你沒有耐心看完所有的過程的話。就記住一句話:用自己手動註入Feign的Client實現,來代替 Feign的自動配置所做的過程就好瞭。
具體的配置請看第5點
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。如有錯誤或未考慮完全的地方,望不吝賜教。
推薦閱讀:
- Feign 使用HttpClient和OkHttp方式
- 完美解決SpringCloud-OpenFeign使用okhttp替換不生效問題
- 淺談SpringCloud feign的http請求組件優化方案
- Feign Client 超時時間配置不生效的解決
- Spring Cloud超詳細i講解Feign自定義配置與使用