解决:Could not extract response: no suitable HttpMessageConverter found for content type [xxx]

问题

使用Spring-bootRestTemplate进行网络请求,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,可以看到,收到responseHeader里面的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 类型,是的话就会返回trueSpringBoot 就会调用 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;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值