问题描述
   
    最近在使用
    
     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)
 
 方法结合其上注解我们可以很清楚的知道,当没有
 
 feign.Client
 
 Bean的时候会默认生成
 
 feign.Client.Default
 
 来进行通信,这就是之前说的缺省通信方式
- 
     从
 
 HttpClientFeignLoadBalancedConfiguration
 
 、
 
 OkHttpFeignLoadBalancedConfiguration
 
 ,我们可以看到其生效的条件,当classpath中有
 
 feign.httpclient.ApacheHttpClient
 
 并且配置
 
 feign.httpclient.enabled=true
 
 (缺省为true)、
 
 feign.okhttp.OkHttpClient
 
 并且配置
 
 feign.okhttp.enabled=true
 
 (缺省为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的使用,因为此类文章很多,在此就不重复了。
    作者:vincent_ren
    
    链接:https://www.jianshu.com/p/44e877e395a9
    
    来源:简书
    
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
   
 
