問題描述
最近在使用
Spring Cloud
改造現有服務的工作中,在内部服務的調用方式上選擇了
Feign
元件,由于服務與服務之間有權限控制,發現通過
Feign
來進行調用時如果發生了401、407錯誤時,調用方不能夠取回被調用方傳回的錯誤資訊。
産生原因
Feign預設使用
java.net.HttpURLConnection
進行通信,通過檢視其子類
sun.net.www.protocol.http.HttpURLConnection
源碼發現代碼中在進行通信時單獨對錯誤碼為401\407的錯誤請求做了處理,當請求的錯誤碼為401\407時,會關閉請求流,由于此時還并沒有将傳回的錯誤資訊寫入響應流中,是以接收的傳回資訊中僅僅能擷取到
response.status()
,而
response.body()
為
null
。
HttpURLConnection相關資訊的源碼連結:http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/net/www/protocol/http/HttpURLConnection.java#1079
解決思路
關于此問題産生的原因已經很明顯了,就是
feign.Client
實作通信的方式選用了我們不想使用的
HttpURLConnection
。想到通常在Spring的代碼中OCP都是運用得很好的,是以基本上有解決此問題的信心了,最不濟就是自己擴充
Feign
,實作一個自己想要的
feign.Client
,當然這種事情
Spring Cloud
基本都會自己搞定,這也是
Spring Cloud
強大完善的一個地方。
通過這個思路檢視源碼,果然看到了
Spring Cloud
在使用
Feign
提前内置了三種通信方式(
feign.Client.Default
,
feign.httpclient.ApacheHttpClient
feign.okhttp.OkHttpClient
),其中預設的情況使用的就是
feign.Client.Default
,這個就是使用
HttpURLConnection
通信的方式。
源碼解析
在
Spring Cloud
項目中使用了Ribbon的元件,其會幫助我們管理使用
Feign
,檢視
org.springframework.cloud.netflix.feign.ribbon.FeignRibbonClientAutoConfiguration
源碼
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignRibbonClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory, clientFactory);
}
@Configuration
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
protected static class HttpClientFeignLoadBalancedConfiguration {
@Autowired(required = false)
private HttpClient httpClient;
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
ApacheHttpClient delegate;
if (this.httpClient != null) {
delegate = new ApacheHttpClient(this.httpClient);
}
else {
delegate = new ApacheHttpClient();
}
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
}
}
@Configuration
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty(value = "feign.okhttp.enabled", matchIfMissing = true)
protected static class OkHttpFeignLoadBalancedConfiguration {
@Autowired(required = false)
private okhttp3.OkHttpClient okHttpClient;
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
OkHttpClient delegate;
if (this.okHttpClient != null) {
delegate = new OkHttpClient(this.okHttpClient);
}
else {
delegate = new OkHttpClient();
}
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
}
}
}
- 從
方法結合其上注解我們可以很清楚的知道,當沒有feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory)
Bean的時候會預設生成feign.Client
來進行通信,這就是之前說的預設通信方式feign.Client.Default
-
、HttpClientFeignLoadBalancedConfiguration
,我們可以看到其生效的條件,當classpath中有OkHttpFeignLoadBalancedConfiguration
并且配置feign.httpclient.ApacheHttpClient
(預設為true)、feign.httpclient.enabled=true
feign.okhttp.OkHttpClient
(預設為true)feign.okhttp.enabled=true
- 當使用ApacheHttpClient或者OkHttpClient進行通信時就不會導緻發生401\407錯誤時,取不到傳回的錯誤資訊了
解決方法
通過其上的分析,解決方法已經顯而易見了,
-
檔案中新增pom.xml
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>8.18.0</version>
</dependency>
或者
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>8.18.0</version>
</dependency>
-
檔案中增加(可預設,如有切換調用方式需求可添加配置進行控制)application.properties
feign.okhttp.enabled=true
feign.httpclient.enabled=true
總結
- 由于新增的依賴沒有被starter管理,并且預設不會導緻程式啟動異常,并且傳回響應為null與此依賴沒有直接關系,是以不友善定位到問題,特此記錄下來,希望能幫助到遇到同樣問題的人,如對文章有不同的看法,望給予指正。
- 本文建立在已經搭建完成Feign的調用基礎之上,沒有講述Feign的使用,因為此類文章很多,在此就不重複了。