История одной зависимости
Было у меня REST API приложение на Spring Boot 3. Всё работало идеально, все ответы от контроллеры отдавали в JSON. Но однажды понадобилось обработать пару XML-файлов. Я добавил зависимость:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
И тут начались страдания…
Первый раз: Spring Boot 3
После добавления зависимости все мои контроллеры, которые раньше возвращали JSON, начали отдавать XML! Без всякой причины, без изменения кода.
Спринг почему-то решил, что основной сериализатор отныне XmlMapper и везде использовал его, кроме тех случаев, когда при запросе на контроллер явно не указывался хэдер Accept: application/json.
Много вариантов я перепробовал, перечитал кучу информации и изучил множество советов. Ничего не помогало. В итоге после дебага и поиска различных мест куда можно заинжектить корректирующий код, я наконец нашёл кажется идеальное решение в конфигурации WebMvcConfigurer:
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(httpMessageConverter ->
httpMessageConverter.getClass().getSimpleName().contains("Xml")
);
WebMvcConfigurer.super.configureMessageConverters(converters);
}
Работало! XML-конвертеры удалялись, JSON возвращался. А мой XmlMapper я использовал отдельно для редких XML-задач.
Второй раз: Spring Boot 4
Прошёл год, я обновился до Spring Boot 4. И всё сломалось снова!
Мой код перестал работать. Метод configureMessageConverters стал устаревшим, и в List> converters больше ничего не передавалось. Spring полностью переписал механизм настройки конвертеров.
Три дня я бился с новой архитектурой. Интернет предлагал кучу решений:
spring:
http:
converters:
preferred-json-mapper: jackson
Не работает.
@Configuration
@ConditionalOnMissingBean(XmlMapper.class)
public class DisableXmlMapperConfig {
@Bean
public XmlMapper xmlMapper() {
throw new IllegalStateException("XmlMapper is disabled");
}
}
Тоже не работает.
@SpringBootApplication(excludeName = "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$XmlConfiguration")
И это не работает.
Я уже отчаялся. Перерыл весь код Spring, посмотрел, как устроен HttpMessageConverters.ServerBuilder, и наконец нашёл рабочий вариант.
Решение: пришлось снова изобретать велосипед
В WebMvcConfigurer я переопределил XML-конвертер своим собственным, который просто ничего не умеет:
@Override
public void configureMessageConverters(HttpMessageConverters.ServerBuilder builder) {
WebMvcConfigurer.super.configureMessageConverters(builder);
builder.withXmlConverter(new HttpMessageConverter<Object>() {
@Override
public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
return false; // Ничего не читаем
}
@Override
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
return false; // Ничего не пишем
}
@Override
public List<MediaType> getSupportedMediaTypes() {
return List.of(MediaType.TEXT_XML);
}
@Override
public Object read(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
throw new UnsupportedOperationException("XML message converter is disabled");
}
@Override
public void write(Object o, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
throw new UnsupportedOperationException("XML message converter is disabled");
}
});
}
И ЭТО СРАБОТАЛО!
Я подменил стандартный XML-конвертер на свой, который всегда возвращает false в canRead и canWrite. Spring пробует его, видит, что он не подходит, и идёт дальше к JSON-конвертеру.
Суть проблемы
Spring Boot 3: создавал один ObjectMapper, который мог работать и с JSON, и с XML. XML-конвертер просто перехватывал формат ответа.
Spring Boot 4: создаёт два отдельных бина — JsonMapper и XmlMapper. И если у вас есть зависимость jackson-dataformat-xml, Spring решает, что XML — ваш основной формат.
Итог
Просто добавив библиотеку для работы с XML, вы нечаянно меняете формат ответа всего REST API. Интернет предлагал кучу простых решений, но ни одно не работало. Пришлось лезть в дебри Spring и делать по-своему.
Теперь я знаю: если в проекте есть jackson-dataformat-xml и нужен JSON по умолчанию, не верьте советам из интернета. Делайте, как я, и живите спокойно.
P.S. Три дня мучений, чтобы прийти к 20 строкам кода. Три дня. Но теперь я точно запомню это навсегда.