Перейти к основному содержимому

HttpMessageNotWritableException: нет преобразователя для [класса …] с предустановленным типом содержимого

· 3 мин. чтения

1. Обзор

В этой короткой статье мы подробно рассмотрим исключение Spring «HttpMessageNotWritableException: нет преобразователя для [класса…] с предустановленным Content-Type» .

Во-первых, мы прольем свет на основную причину исключения. Затем мы спустимся в кроличью нору, чтобы увидеть, как воспроизвести ее на практическом примере и, наконец, как ее решить.

2. Причина

Прежде чем углубляться в детали, давайте попробуем понять, что означает исключение.

Трассировка стека исключения говорит сама за себя: она сообщает нам, что Spring не может найти подходящий HttpMessageConverter, способный преобразовать объект Java в ответ HTTP .

По сути, 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.foreach.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.foreach.boot.noconverterfound.model.Student] with preset Content-Type"));
}

4. Решение

Исправить это исключение можно только одним способом — использовать медиа-тип, имеющий зарегистрированный преобразователь сообщений.

Spring Boot использует автоматическую настройку для регистрации встроенных преобразователей сообщений .

Например, он автоматически зарегистрирует MappingJackson2HttpMessageConverter , если в classpath присутствует зависимость Jackson 2 .

С учетом сказанного и зная, что Spring Boot включает Джексона в веб-стартер , давайте создадим новую конечную точку с типом носителя 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 выбрасывать «HttpMessageNotWritableException No convertor for [class…] с предустановленным Content-Type» .

Попутно мы продемонстрировали, как создать исключение и как исправить его на практике.

Как всегда, полный исходный код примеров доступен на GitHub .