如题:这篇文章主要讲的是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;
}
前文说的泛型的返回值类型,这里responseType
为 ParameterizedTypeImpl
类型
Debug发现
这里Instaceof Class
是false
,则 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