2018-03-29 SpringCloud Feign Decoder

weixin_34249678發表於2018-03-29

如題:這篇文章主要講的是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

相關文章