1. 概述
在本文中,我們將深入探討 Spring 異常,即“HttpMessageNotWritableException: no converter for [class …] with preset Content-Type”。
首先,我們將揭示導致該異常的主要原因。然後,我們將深入研究,通過一個實際示例來重現該異常,最後,我們將探討如何解決它。
2. 原因
在深入瞭解細節之前,讓我們先嚐試理解這個異常的含義。
異常的堆棧跟蹤説明了一切:它告訴我們 Spring 無法找到能夠將 Java 對象轉換為 HTTP 響應的合適 HttpMessageConverter。
基本上,Spring 依賴於 “Accept” 標頭來檢測它需要響應的媒體類型。
因此,使用沒有預註冊的消息轉換器,會導致 Spring 出現異常。
3. 重新觸發異常
現在我們知道導致 Spring 拋出我們自定義異常的原因,接下來讓我們通過一個實際示例來演示如何重現它。
讓我們創建一個處理方法,並假定指定一個沒有註冊的 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 拋出了 HttpMessageNotWritableException 異常:
[org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class com.baeldung.boot.noconverterfound.model.Student] with preset Content-Type 'null']因此,異常拋出是因為缺少能夠將 HttpMessageConverter 對象序列化和反序列化為 XML 中的 Student 對象的功能。
最後,讓我們創建一個測試用例,以確認 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 依賴於自動配置來註冊內置的消息轉換器。
例如,如果 classpath 中包含 Jackson 2 依賴項,它將 自動註冊 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'}"));
}如我們所見,Spring 不會拋出 HttpMessageNotWritableException,這要歸功於 MappingJackson2HttpMessageConverter,它在底層將 Student 對象轉換為 JSON。
5. 結論
在本簡短教程中,我們詳細討論了導致 Spring 拋出 <em “HttpMessageNotWritableException No converter for [class …] with preset Content-Type” 的原因。
在過程中,我們展示瞭如何製造該異常以及如何在實踐中修復它。