2018-03-29 SpringCloud Feign Decoder

如题:这篇文章主要讲的是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

推荐阅读更多精彩内容