HTTP请求出现HttpMediaTypeNotAcceptableException报错
现象
发起HTTP请求调用,控制台报错:
org.springframework.web.HttpMediaTypeNotAcceptableException: No acceptable representation
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:322) ~[spring-webmvc-6.1.10.jar:6.1.10]
解决方式
定位报错位置
// org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
if (body != null) {
Set<MediaType> producibleMediaTypes =
(Set<MediaType>) inputMessage.getServletRequest()
.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
throw new HttpMessageNotWritableException(
"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
}
throw new HttpMediaTypeNotAcceptableException(getSupportedMediaTypes(body.getClass()));
}
由报错位置可知,是body不为空引起。同时找到该方法正常return的位置如下。可见,正常返回与body是否为空无关,而是需要存在一个能写入指定类的转换器,以及前提条件selectedMediaType != null
。
排查报错
没有消息转换器可以写入指定类
通过debug可知,实际HTTP请求的场景:

当 selectedMediaType
被设置为 "application/octet-stream"
时,这通常意味着请求或响应的内容类型被指定为二进制流,这种类型通常用于非文本数据,如图片、音频、视频等文件。也就是说,报错是因为没有消息转换器messageConvert
可以写入指定类。
项目中实际涉及的消息转换器:
- ByteArrayHttpMessageConverter 只能转换字节数组。
- StringHttpMessageConverter 只能转换字符串对象。
- ResourceHttpMessageConverter 只能转换Resource类及其子类。
- ResourceRegionHttpMessageConverter 不支持转换 非泛型类型且不是ResourceRegion类及其子类的对象。
- AllEncompassingFromHttpMessageConverter 只能转换MultiValueMap类及其子类。
- MappingJackson2HttpMessageConverter 支持 */*, application/json, application/*+json。
但是实际期望响应的内容类型为application/json
,Jackson 的主要功能是处理结构化的文本格式,例如 JSON 和 XML,响应能被MappingJackson2HttpMessageConverter
处理才对,需要继续溯源。而acceptableTypes
来源于请求,请求没有限制,那么就只能是**producibleTypes
不符合预期**。
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
protected List<MediaType> getProducibleMediaTypes(
HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
// 遍历消息转换器,查找能写入指定类的转换器
// ...
// 由于找不到能写入的messageConvert,因此producibleTypes只包含了{"*/*"}
return (result.isEmpty() ? Collections.singletonList(MediaType.ALL) : new ArrayList<>(result));
}
响应无法被MappingJackson2HttpMessageConverter
处理
那么,为什么响应无法被MappingJackson2HttpMessageConverter
处理呢?

梳理代码可知,MappingJackson2HttpMessageConverter
继承自AbstractJackson2HttpMessageConverter
。由于AbstractJackson2HttpMessageConverter
的canWrite()
方法返回false,导致无法获得Jackson2Http消息转换器的支持媒体类型。而canWrite()
的本质是有对应的序列化器就能写入指定对象,由于实际项目中没有使用注解的序列化器,因此是通过findBeanOrAddOnSerializer()
方法获得序列化器。

获取Bean序列化器,首先就是查找属性。由于从POJOPropertiesCollector
中获取到的_properties
为空,导致BeanSerializerBuilder
无法构建Bean序列化器,进而导致无法写入指定对象。
public JsonSerializer<?> build() {
// ...
if (_properties == null || _properties.isEmpty()) {
if (_anyGetter == null && _objectIdWriter == null) {
// NOTE! Caller may still call `createDummy()` later on
return null;
}
}
}
POJOPropertiesCollector
中_properties
为空
需要找到设置_properties
属性的位置,代码中仅POJOPropertiesCollector#collectAll
方法进行了设置。而collectAll
方法调用的位置很多,通过debug进行排查梳理,发现是**findSerializerByAnnotations()
方法中有调用**。

POJOPropertiesCollector
的collectAll
方法,首先会将指定对象的属性添加到props
中。重点来了!!removeUnwantedProperties()
方法会调用POJOPropertyBuilder#anyVisible
方法,将未提供get或set方法的private字段,认定为不想要的属性进行移除,进而设置_properties
属性为null。
查看指定对象的类,只有private属性和构造器…真相大白。提供get方法后,HTTP请求终于可以正常处理。
参考
这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题 - 技术角落 - 博客园