使用feign发送http请求解析报错的问题
作者:yangchuanan
错误如下
发送请求开始
----- [ChannelFeign#formRecog] ---> END HTTP (304117-byte body)
发送请求结束
返回开始
[ChannelFeign#formRecog] <--- HTTP/1.1 200 OK (4948ms) [ChannelFeign#formRecog] content-length: 5207 [ChannelFeign#formRecog] content-type: text/json;charset=UTF-8 [ChannelFeign#formRecog] date: Mon, 08 Oct 2018 10:47:03 GMT [ChannelFeign#formRecog] x-vcap-request-id: c323f65a-12e6-4604-7393-a4bf0ca403d5 [ChannelFeign#formRecog] [ChannelFeign#formRecog] {json格式的数据} [ChannelFeign#formRecog] <--- END HTTP (5207-byte body)
返回结束
ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.codec.DecodeException: Could not extract response: no suitable HttpMessageConverter found for response type [channel.domain.ChannelResponse<TableData>] and content type [text/json;charset=UTF-8]] with root cause
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [channel.domain.ChannelResponse<TableData>] and content type [text/json;charset=UTF-8]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:110) ~[spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.cloud.netflix.feign.support.SpringDecoder.decode(SpringDecoder.java:59) ~[spring-cloud-netflix-core-1.3.6.RELEASE.jar:1.3.6.RELEASE]
at org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder.decode(ResponseEntityDecoder.java:47) ~[spring-cloud-netflix-core-1.3.6.RELEASE.jar:1.3.6.RELEASE]
at feign.SynchronousMethodHandler.decode(SynchronousMethodHandler.java:165) ~[feign-core-9.5.0.jar:?]
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:133) ~[feign-core-9.5.0.jar:?]
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76) ~[feign-core-9.5.0.jar:?]
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103) ~[feign-core-9.5.0.jar:?]org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [channel.domain.ChannelResponse<TableData>] and content type [text/json;charset=UTF-8]
可以看到返回的类型为[ChannelFeign#formRecog] content-type: text/json;charset=UTF-8
错误原因
接口返回为JSON格式数据但却将数据表示为了[text/json]导致Feign没有采用JSON解析器来解析,从而无法将响应数据转化为对应的POJO对象;
源码分析
feign客户端发送请求入口函数invoke()
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("equals".equals(method.getName())) { try { Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null; return equals(otherHandler); } catch (IllegalArgumentException e) { return false; } } else if ("hashCode".equals(method.getName())) { return hashCode(); } else if ("toString".equals(method.getName())) { return toString(); } // 分发请求 return dispatch.get(method).invoke(args); }
decode()返回请求的解码函数
Object decode(Response response) throws Throwable { try { return decoder.decode(response, metadata.returnType()); } catch (FeignException e) { throw e; } catch (RuntimeException e) { throw new DecodeException(e.getMessage(), e); } }
进入decode.decode(),提取数据
@Override @SuppressWarnings({"unchecked", "rawtypes", "resource"}) 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)) { if (logger.isDebugEnabled()) { logger.debug("Reading [" + this.responseType + "] as \"" + contentType + "\" using [" + messageConverter + "]"); } return (T) genericMessageConverter.read(this.responseType, null, responseWrapper); } } if (this.responseClass != null) { if (messageConverter.canRead(this.responseClass, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Reading [" + this.responseClass.getName() + "] as \"" + contentType + "\" using [" + messageConverter + "]"); } 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 + "]"); }
进入genericMessageConverter.canRead(this.responseType, null, contentType)
protected boolean canRead(MediaType mediaType) { if (mediaType == null) { return true; } for (MediaType supportedMediaType : getSupportedMediaTypes()) { if (supportedMediaType.includes(mediaType)) { return true; } } return false; }
通过断点发现mediaType为接口返回的content-type:text/json类型。而supportedMediaType为application/json,所以返回false,找不到合适的转换器。
解决方案一
替代Feign的解码器,使json解析器同时解析[text/plain]的数据
// 创建一个新的转换器 解析微信的 [text/plain] public class WxMessageConverter extends MappingJackson2HttpMessageConverter { public WxMessageConverter(){ List<MediaType> mediaTypes = new ArrayList<>(); mediaTypes.add(MediaType.TEXT_PLAIN); setSupportedMediaTypes(mediaTypes); } }
注入新的Decoder Feign将自动 替换
// 解决微信返回参数为[text/plain] 无法转化为json @Bean public Decoder feignDecoder(){ WxMessageConverter wxConverter = new WxMessageConverter(); ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(wxConverter); return new SpringDecoder(objectFactory); }
解决方案二
对返回的json字符串使用fastjosn转换
String result = channelFeign.formRecogTest(channelRequest); ChannelResponse<TableData> hello = JSONObject.parseObject(result, new TypeReference<ChannelResponse<TableData>>() { });
错误2
发送请求时对象转换json会自动将属性的首字母小写
解决方法:
//@Data public class ChannelRequest { //@JSONField(name="Header") @JsonProperty private ChannelReqHead Header; //@JSONField(name="Body") @JsonProperty private ChannelReqBody Body; // 如果get方法上不加JsonIgnore,jason化时小写header也会出现 @JsonIgnore public ChannelReqHead getHeader() { return Header; } @JsonIgnore public void setHeader(ChannelReqHead header) { Header = header; } @JsonIgnore public ChannelReqBody getBody() { return Body; } @JsonIgnore public void setBody(ChannelReqBody body) { Body = body; } }
使用jsonField不起作用,不使用jsonIgnore会生成大写和小写
如:{“Header”:xxx,"header":xxx}
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。