Как я дважды боролся с XML в Spring Boot

Как я дважды боролся с XML в Spring Boot

История одной зависимости

Было у меня 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 строкам кода. Три дня. Но теперь я точно запомню это навсегда.

(Просмотрено 1 раз, 1 раз за сегодня)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *