最近在开发Spring Boot项目时,遇到了一个让人头疼的异常:org.springframework.web.HttpMediaTypeNotAcceptableException: No acceptable representation。这个错误发生在调用某个RESTful接口时,控制台打印的完整堆栈信息显示问题出在消息转换环节。
从异常堆栈可以清晰看到,错误发生在AbstractMessageConverterMethodProcessor.writeWithMessageConverters()方法中。Spring框架在处理返回值时,无法找到合适的消息转换器(MessageConverter)来将Java对象转换为客户端可接受的格式。有趣的是,根据问题描述,这个错误似乎与返回对象中的@JsonProperty("pageNum")注解有关。
提示:HttpMediaTypeNotAcceptableException通常表示服务器无法生成符合客户端Accept头要求的响应格式。在REST API开发中,这往往与内容协商(Content Negotiation)机制有关。
Spring MVC处理请求时,内容协商是一个关键环节。整个过程大致如下:
当系统找不到能同时满足以下两个条件的MessageConverter时,就会抛出我们遇到的异常:
Spring Boot默认会注册以下常用的消息转换器:
| 转换器类 | 支持的媒体类型 | 处理的对象类型 |
|---|---|---|
| MappingJackson2HttpMessageConverter | application/json | 任意Java对象 |
| GsonHttpMessageConverter | application/json | 任意Java对象 |
| StringHttpMessageConverter | text/plain | String类型 |
| ByteArrayHttpMessageConverter | application/octet-stream | byte[]类型 |
根据问题描述,异常与@JsonProperty("pageNum")注解有关。这个注解来自Jackson库,用于指定JSON属性名。当出现这种问题时,通常有以下几种可能:
假设我们有一个返回分页数据的方法:
java复制@RestController
public class UserController {
@GetMapping("/users")
public PageInfo<User> getUsers() {
// 返回包含@JsonProperty注解的对象
return userService.getUsers();
}
}
@Data
class PageInfo<T> {
@JsonProperty("pageNum")
private int pageNumber;
private int pageSize;
private List<T> list;
}
当客户端请求的Accept头与可用的MessageConverter不匹配时,就会出现问题。例如:
Accept: application/xml最直接的解决方案是确保客户端和服务端使用相同的媒体类型:
客户端明确指定Accept头:
bash复制curl -H "Accept: application/json" http://localhost:8080/users
服务端强制指定produces:
java复制@GetMapping(value = "/users", produces = "application/json")
public PageInfo<User> getUsers() {
// ...
}
在pom.xml中检查是否有多个JSON库:
xml复制<!-- 推荐只保留一个JSON库 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
<!-- 如果不需要Gson,应该移除 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
如果需要更灵活的控制,可以自定义消息转换器:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 移除不必要的转换器
converters.removeIf(c -> c instanceof GsonHttpMessageConverter);
// 添加自定义Jackson转换器
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(List.of(
MediaType.APPLICATION_JSON,
MediaType.TEXT_PLAIN // 支持text/plain
));
converters.add(0, converter); // 优先使用
}
}
在application.properties中添加:
properties复制logging.level.org.springframework.web=DEBUG
logging.level.org.springframework.http=DEBUG
这样可以看到详细的内容协商过程:
code复制DEBUG - Checking if [application/json] is acceptable for [PageInfo]
DEBUG - No matching converter found for [PageInfo] to [application/json]
在Postman中测试时,注意:
在以下类中设置断点可以深入理解处理流程:
AbstractMessageConverterMethodProcessor.writeWithMessageConverters()RequestMappingHandlerAdapter.invokeHandlerMethod()HandlerMethodReturnValueHandlerComposite.handleReturnValue()现象:返回Map对象时报同样的错误
原因:Map需要特殊处理,默认转换器可能不支持
解决方案:
java复制@GetMapping(value = "/map", produces = "application/json")
public Map<String, Object> getMap() {
return Collections.singletonMap("key", "value");
}
现象:对象间循环引用导致转换失败
解决方案:
java复制@Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
return new Jackson2ObjectMapperBuilder()
.failOnEmptyBeans(false)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
}
现象:日期字段格式不符合预期
解决方案:
java复制@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
经过多次实践,我总结了以下经验:
明确指定produces:每个@RestController方法都应该明确指定produces,避免依赖默认行为
统一序列化库:项目中使用单一的JSON处理库(推荐Jackson)
版本一致性:确保所有Jackson相关依赖版本一致(jackson-core, jackson-databind, jackson-annotations)
测试覆盖:对API的多种Accept头情况进行测试,特别是:
监控配置:定期检查自动配置报告,了解生效的MessageConverter:
bash复制curl http://localhost:8080/actuator/beans | grep MessageConverter
在解决这个特定问题时,最关键的是理解Spring的内容协商机制。框架会按照以下顺序确定响应类型:
如果这三个途径都无法确定可接受的响应类型,就会使用默认配置。因此,明确指定produces是最可靠的解决方案。