2018-03-29 SpringCloud Feign Dec

作者: 一杯半盏 | 来源:发表于2018-03-29 18:55 被阅读92次

    如题:这篇文章主要讲的是Spring Cloud Feign的Decoder

    前提:使用FeignClient
    并且返回值 使用泛型,例如 PhpResponse<Model>。
    SpringCloud版本:Brixton (Spring Core 4.2.3)

    在HTTP协议不是很规范的情况下,需要配置Decoder

    例如PHP写的服务,就不跟你区分 ContentType 是不是JSON了。即便是大厂腾讯,相信他们的接口例如微信支付等等,是不区分的。

    具体来说:就是返回数据是JSON,而ContentType 为 text/html;charset=UTF-8
    这不影响你读取他的文本内容。

    对于SpringCloud Feign来说:

    在没有写Fallback的情况下:

    Could not extract response: no suitable HttpMessageConverter found 
    

    如果写了Fallback,则即便返回了正常的JSON内容,因为Decoder无法decode这种Response类型,则进入了Fallback降级类了。你看不到相关的错误日志,警告都没有。

    问题现状

    如果是自己的公司,要求PHP传 正确的ContentType ,不难,就是要说服他们改改。
    也就一句话

    header("ContentType", "application/json;charset=UTF-8");
    

    而如果对方很调皮,表示怎么就你JAVA的要求这么高? Customer端也没有要求这个?

    而如果对方是腾讯的微信支付接口,你让人家改?

    所以,还是要自己用一些方法处理的。
    如果因此放弃Feign ,改用HTTPClient,这也不甘心啊。

    解决方案

    贡献出代码:

    @Configuration
    public class FeignConfig {
    
        @Bean
        Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL;
        }
    
    
        @Bean
        public Decoder feignDecoder() {
            return new ResponseEntityDecoder(new SpringDecoder(feignHttpMessageConverter()));
        }
    
        public ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
            final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(new PhpMappingJackson2HttpMessageConverter());
            return new ObjectFactory<HttpMessageConverters>() {
                @Override
                public HttpMessageConverters getObject() throws BeansException {
                    return httpMessageConverters;
                }
            };
        }
    
        public class PhpMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
            PhpMappingJackson2HttpMessageConverter(){
                List<MediaType> mediaTypes = new ArrayList<>();
                mediaTypes.add(MediaType.valueOf(MediaType.TEXT_HTML_VALUE + ";charset=UTF-8")); //关键
                setSupportedMediaTypes(mediaTypes);
            }
        }
    
    }
    

    这里关键位置,就添加了对这种Response的支持。

    RestTemplate的配置,我还不会,这里就不写了。

    思考

    好像配置过FastJSON的MessageConverter的?
    下面的内容将说明 我所用的FastJsonHttpMessageConverter没有起作用的原因。

    原理

    下面看看源码:

    //SpringDecoder.java
    
    @Override
        public Object decode(final Response response, Type type) throws IOException,
                FeignException {
            if (type instanceof Class || type instanceof ParameterizedType) {
                @SuppressWarnings({ "unchecked", "rawtypes" })
                HttpMessageConverterExtractor<?> extractor = new HttpMessageConverterExtractor(
                        type, this.messageConverters.getObject().getConverters());
    
                return extractor.extractData(new FeignResponseAdapter(response));
            }
            throw new DecodeException(
                    "type is not an instance of Class or ParameterizedType: " + type);
        }
    

    这里构造了一个HttpMessageConverterExtractor,跟进构造函数:

    HttpMessageConverterExtractor(Type responseType, 
            List<HttpMessageConverter<?>> messageConverters, Log logger) {
            Assert.notNull(responseType, "'responseType' must not be null");
            Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
            this.responseType = responseType;
            this.responseClass = (responseType instanceof Class) ? (Class<T>) responseType : null;
            this.messageConverters = messageConverters;
            this.logger = logger;
        }
    

    前文说的泛型的返回值类型,这里responseTypeParameterizedTypeImpl类型
    Debug发现
    这里Instaceof Classfalse,则 this.responseClass = null

    再看extractData

    //HttpMessageConverterExtractor.java
    
    public T extractData(ClientHttpResponse response) throws IOException {
            MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
            if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
                return null;
            }
            MediaType contentType = getContentType(responseWrapper);
    
            for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
                if (messageConverter instanceof GenericHttpMessageConverter) {
                    GenericHttpMessageConverter<?> genericMessageConverter = (GenericHttpMessageConverter<?>) messageConverter;
                    if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
                        ......
                        return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
                    }
                }
                if (this.responseClass != null) {
                    if (messageConverter.canRead(this.responseClass, contentType)) {
                        ......
                        return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
                    }
                }
            }
    
            throw new RestClientException("Could not extract response: no suitable HttpMessageConverter found " +
                    "for response type [" + this.responseType + "] and content type [" + contentType + "]");
        }
    

    如果添加的是FastJsonHttpMessageConverter,这里他非 GenericHttpMessageConverter接口的实现类(注:可能是当前FastJson版本问题。),不是Spring自带的,不是亲儿子。
    直接因为 this.responseClass = null 进入下面的内容,抛异常。

    因为没有一个HttpMessageConverter 是 CanRead这种类型的。

    所以还是找他:MappingJackson2HttpMessageConverter

    相关文章

      网友评论

      本文标题:2018-03-29 SpringCloud Feign Dec

      本文链接:https://www.haomeiwen.com/subject/lgkvcftx.html