1. 概述
在这篇短文中,我们将深入探讨Spring中的一种异常:HttpMessageNotWritableException: no converter for [class ...] with preset Content-Type
。首先,我们会揭示这个异常的根本原因,然后通过一个实际案例来演示如何复现它,并最后讲解如何解决这个问题。
2. 原因
在深入了解之前,我们先尝试理解这个异常的含义。异常堆栈跟踪提供了关键信息:它告诉我们Spring 未能找到能够将Java对象转换为HTTP响应的合适 HttpMessageConverter。
简单来说,Spring依赖于Accept
头来检测需要响应的媒体类型。因此,使用没有预注册消息转换器的媒体类型会导致Spring抛出这个异常。
3. 复现异常
现在我们知道引发异常的原因,让我们通过一个实际例子来演示如何复现它。
创建一个处理方法,并假设我们指定一个没有注册HttpMessageConverter
的媒体类型(例如,APPLICATION_XML_VALUE
或者 "application/xml"
):
@GetMapping(value = "/student/v3/{id}", produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<Student> getV3(@PathVariable("id") int id) {
return ResponseEntity.ok(new Student(id, "Robert", "Miller", "BB"));
}
接下来,发送一个请求到http://localhost:8080/api/student/v3/1
,看看会发生什么:
curl http://localhost:8080/api/student/v3/1
该端点返回如下响应:
{"timestamp":"2022-02-01T18:23:37.490+00:00","status":500,"error":"Internal Server Error","path":"/api/student/v3/1"}
查看日志,确实可以看到Spring由于找不到将Student
对象转换为XML的HttpMessageConverter
而抛出了HttpMessageNotWritableException
:
[org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class com.baeldung.boot.noconverterfound.model.Student] with preset Content-Type 'null']
所以,异常被抛出是因为没有HttpMessageConverter
能够将Student
对象从XML反序列化并序列化。
最后,我们创建一个测试用例以确认Spring确实会抛出带有指定消息的HttpMessageNotWritableException
:
@Test
public void whenConverterNotFound_thenThrowException() throws Exception {
String url = "/api/student/v3/1";
this.mockMvc.perform(get(url))
.andExpect(status().isInternalServerError())
.andExpect(result -> assertThat(result.getResolvedException()).isInstanceOf(HttpMessageNotWritableException.class))
.andExpect(result -> assertThat(result.getResolvedException()
.getMessage()).contains("No converter for [class com.baeldung.boot.noconverterfound.model.Student] with preset Content-Type"));
}
4. 解决方案
解决这个问题的唯一方法是使用有预注册消息转换器的媒体类型。
Spring Boot依赖自动配置来注册内置消息转换器,如默认消息转换器。例如,如果类路径中存在jackson 2依赖,Spring Boot会自动注册MappingJackson2HttpMessageConverter
。
鉴于Spring Boot在Web Starter中包含了Jackson,让我们创建一个新的端点,使用APPLICATION_JSON_VALUE
媒体类型:
@GetMapping(value = "/student/v2/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Student> getV2(@PathVariable("id") int id) {
return ResponseEntity.ok(new Student(id, "Kevin", "Cruyff", "AA"));
}
现在,创建一个测试用例以确认一切按预期工作:
@Test
public void whenJsonConverterIsFound_thenReturnResponse() throws Exception {
String url = "/api/student/v2/1";
this.mockMvc.perform(get(url))
.andExpect(status().isOk())
.andExpect(content().json("{'id':1,'firstName':'Kevin','lastName':'Cruyff', 'grade':'AA'}"));
}
正如我们所见,由于MappingJackson2HttpMessageConverter
在后台处理了Student
对象到JSON的转换,Spring没有抛出HttpMessageNotWritableException
。
5. 总结
在这篇教程中,我们详细讨论了Spring抛出"HttpMessageNotWritableException No converter for [class ...] with preset Content-Type"
异常的原因。我们展示了如何产生这个异常以及在实践中如何解决。如往常一样,所有示例的完整源代码可以在GitHub上找到这里。