问题
使用Spring-boot
的RestTemplate
进行网络请求,RestTemplate
把数据从 HttpResponse
转换成Object
的时候找不到合适的HttpMessageConverter
报错详情
org.springframework.web.client.UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.zhubayi.emp.entity.ResponseData] and content type [application/octet-stream]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:124)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1132)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1115)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:865)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:764)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:512)
at com.zhubayi.emp.service.EmpService.getEmpInfo(EmpService.java:49)
at com.zhubayi.emp.Test01.test(Test01.java:18)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
源码分析
点进框住的部分,然后就会进入extractData
方法
public T extractData(ClientHttpResponse response) throws IOException {
IntrospectingClientHttpResponse responseWrapper = new IntrospectingClientHttpResponse(response);
if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
return null;
}
//得到返回值的contentType
MediaType contentType = getContentType(responseWrapper);
try {
//拿到messageConverters集合,然后进行遍历
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
//如果是GenericHttpMessageConverter接口的实例,继承AbstractHttpMessageConverter会走这个if。
if (messageConverter instanceof GenericHttpMessageConverter genericMessageConverter) {
//判断这个转换器能不能转换这个contentType类型
if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
if (logger.isDebugEnabled()) {
ResolvableType resolvableType = ResolvableType.forType(this.responseType);
logger.debug("Reading to [" + resolvableType + "]");
}
//走到这代表当前的HttpMessageConverter能进行转换,则调用read并返回
return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
}
}
if (this.responseClass != null) {
//还是判断这个转换器能不能进行contentType转换
if (messageConverter.canRead(this.responseClass, contentType)) {
if (logger.isDebugEnabled()) {
String className = this.responseClass.getName();
logger.debug("Reading to [" + className + "] as \"" + contentType + "\"");
}
//走到这代表当前的HttpMessageConverter能进行转换,则调用read并返回
return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
}
}
}
}
catch (IOException | HttpMessageNotReadableException ex) {
throw new RestClientException("Error while extracting response for type [" +
this.responseType + "] and content type [" + contentType + "]", ex);
}
走到这抛出异常,所有的消息转换器都不能进行处理。
throw new UnknownContentTypeException(this.responseType, contentType,
responseWrapper.getStatusCode(), responseWrapper.getStatusText(),
responseWrapper.getHeaders(), getResponseBody(responseWrapper));
}
messageConverters
集合中就保存着
在 RestTemplate
构造方法中添加的 HttpMessageConverter
实现类
debug分析
进行debug
,可以看到,收到response
的Header
里面的Content-Type
值为application/octet-stream
继续往下走,通用的http
消息转换器匹配到自己的子类MappingJackson2HttpMessageConverter,但是可以看到的是supportedMediaTypes 只支持:application/json 的 MediaType。并不能转换text/html
未匹配到转换器,走到方法最后,就会抛出异常
throw new UnknownContentTypeException(this.responseType, contentType,
responseWrapper.getStatusCode(), responseWrapper.getStatusText(),
responseWrapper.getHeaders(), getResponseBody(responseWrapper));
解决方法
1. 修改 mappingJackson2HttpMessageConverter
配置
重新设置 MappingJackson2HttpMessageConverter
能处理的 MediaType
@Bean
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate();
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_HTML));
restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);
return restTemplate;
}
MappingJackson2HttpMessageConverter
也是一个 HttpMessageConverter
转换类,但是他不能处理 text/html
的数据,原因是他的父类 AbstractHttpMessageConverter
中的 supportedMediaTypes
集合中没有 text/html
类型,如果有的话就能处理了,通过 setSupportedMediaTypes
可以给他指定一个新的 MediaType
集合
上面的写法会导致 MappingJackson2HttpMessageConverter
只能处理 text/html
类型的数据
2. 继承 mappingJackson2HttpMessageConverter
使用MappingJackson2HttpMessageConverter
,只需要给他能处理的MediaType
public class MyHttpMessageConverter extends MappingJackson2HttpMessageConverter {
public MyHttpMessageConverter() {
setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM));
}
}
然后将这个消息转换器追加到RestTemplate
中的 messageConverters
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MyHttpMessageConverter()); // 兼容 application/octet-stream
return restTemplate;
}
3.实现 HttpMessageConverter
直接继承HttpMessageConverter
(当然更推荐的是继承 Abstract HttpMessageConverter
)来实现
public interface HttpMessageConverter<T> {
/**
* 根据mediaType判断clazz是否可读
*/
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
/**
* 根据mediaType判断clazz是否可写
*/
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
/**
* 获取支持的mediaType
*/
List<MediaType> getSupportedMediaTypes();
/**
* 将HttpInputMessage流中的数据绑定到clazz中
*/
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
/**
* 将t对象写入到HttpOutputMessage流中
*/
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
canWrite,write
方式是不需要处理的,只管 canRead和read 就行
在 canRead
方法中判断了是不是 application/octet-stream
类型,是的话就会返回true
,SpringBoot
就会调用 read
,用来将字节流中的数据转换成具体实体,Class
就是我们最终想要得到的实例对象的Class
StreamUtils
这个工具类是SpringBoot自带的一个,用来读取InputStream
中的数据并返回String
字符串,SpringBoot
内部很多地方都用到了这个工具类
public class MyHttpMessageConverter2 implements HttpMessageConverter<Object> {
/**
* 根据mediaType判断clazz是否可读
*/
@Override
public boolean canRead(Class<?> aClass, MediaType mediaType) {
if (mediaType != null) {
return mediaType.isCompatibleWith(MediaType.APPLICATION_OCTET_STREAM);
}
return false;
}
/**
* 根据mediaType判断clazz是否可写
*/
@Override
public boolean canWrite(Class<?> aClass, MediaType mediaType) {
return false;
}
/**
* 获取支持的mediaType
*/
@Override
public List<MediaType> getSupportedMediaTypes() {
return Arrays.asList(MediaType.APPLICATION_OCTET_STREAM);
}
/**
* 将HttpInputMessage流中的数据绑定到clazz中
*/
@Override
public Object read(Class<?> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
String json = StreamUtils.copyToString(httpInputMessage.getBody(), Charset.forName("UTF-8"));
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper.readValue(json, aClass);
}
/**
* 将t对象写入到HttpOutputMessage流中
*/
@Override
public void write(Object o, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
}
}
最后需要进行配置,getMessageConverters()
会返回现有的 HttpMessageConverter
集合,我们在这个基础上加入我们自定义的HttpMessageConverter
即可
@Bean
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MyHttpMessageConverter2());
return restTemplate;
}
4.继承 AbstractHttpMessageConverter
public class MyHttpMessageConverter3 extends AbstractHttpMessageConverter<Object> {
public MyHttpMessageConverter3() {
super(MediaType.APPLICATION_OCTET_STREAM);
}
@Override
protected boolean supports(Class<?> aClass) {
return true;
}
@Override
protected Object readInternal(Class<?> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
String json = StreamUtils.copyToString(httpInputMessage.getBody(), Charset.forName("UTF-8"));
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper.readValue(json, aClass);
}
@Override
protected void writeInternal(Object o, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
}
}
}
然后同上面,加入我们自定义的 HttpMessageConverter
即可
@Bean
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MyHttpMessageConverter3());
return restTemplate;
}