Как автоматически подключить XmlMapper по умолчанию в приложении Spring Boot

У меня возникли проблемы с автоматическим подключением Jackson XmlMapper по умолчанию в одном из моих проектов Spring Boot. Я создал простой пример проекта, который это иллюстрирует.

То, что я делаю здесь, примерно основано на следующем:

http://docs.spring.io/spring-boot/docs/1.2.2.RELEASE/reference/htmlsingle/#howto-write-an-xml-rest-service.

http://docs.spring.io/spring-boot/docs/1.2.2.RELEASE/reference/htmlsingle/#howto-customize-the-jackson-objectmapper

Из pom.xml

<!-- ... -->

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.2.2.RELEASE</version>
</parent>

<!-- ... -->

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
    </dependency>
</dependencies>

<!-- ... -->

Основной класс:

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

Demo POJO, без указания @XmlRootElement, поэтому он не будет использовать JAXB:

@JsonInclude(Include.NON_NULL)
public class Demo {
    private String stringProperty;
    private int intProperty;
    public String getStringProperty() {
        return stringProperty;
    }
    public void setStringProperty(String stringProperty) {
        this.stringProperty = stringProperty;
    }
    public int getIntProperty() {
        return intProperty;
    }
    public void setIntProperty(int intProperty) {
        this.intProperty = intProperty;
    }
}

Демо-контроллер:

@RestController
public class DemoController {
    @Autowired 
    private ObjectMapper objectMapper;
    // @Autowired 
    // private XmlMapper xmlMapper;

    @RequestMapping(value = "/demo", method = RequestMethod.GET)
    public Demo getDemo() {
        Demo demo = new Demo();
        demo.setStringProperty("Hello world!");
        demo.setIntProperty(42);
        return demo;
    }
}

Все работает нормально и так, в зависимости от Accept заголовков будет возвращен либо JSON, либо XML.

Я легко могу автоматически подключить ObjectMapper по умолчанию, настроенный с помощью Spring Boot. Все идет нормально.

Если я прокомментирую автоподключение XmlMapper, я получаю:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.fasterxml.jackson.dataformat.xml.XmlMapper] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1301)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1047)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533)
... 18 more

Есть идеи, почему это так? Я бы предположил, что он работает так же, как ObjectMapper. Чтобы прояснить, я не хочу настраивать мапперы, мне просто нужна ссылка на те, которые по умолчанию созданы Spring Boot.


person ci_    schedule 02.03.2015    source источник
comment
Вероятно, Spring Boot не содержит XmlMapper в контейнере IoC, попробуйте сначала ввести bean-компонент.   -  person kris14an    schedule 02.03.2015
comment
Я знаю, что могу создать свой собственный XmlMapper, но мне нужен тот, который используется по умолчанию, тот, который фактически используется для сопоставления класса с Xml в теле ответа, который сейчас работает правильно, так что где-то должен быть XmlMapper? @ kris14an   -  person ci_    schedule 02.03.2015


Ответы (3)


Spring Boot не раскрывает XmlMapper как bean-компонент, поскольку он будет конфликтовать с bean-компонентом ObjectMapper, который используется для сопоставления JSON (XmlMapper Джексона является подклассом ObjectMapper Джексона).

person Andy Wilkinson    schedule 02.03.2015
comment
Это, конечно, ответ на вопрос. Подразумеваемым следующим вопросом было бы, могу ли я получить доступ к XmlMapper каким-либо другим способом ?, и я обнаружил, что вместо этого вы можете автоматически подключать MappingJackson2XmlHttpMessageConverter и получать XmlMapper через .getObjectMapper(). - person ci_; 02.03.2015
comment
Я пытаюсь сделать то же самое с последней версией Spring-Boot, но обнаружил, что не могу Autowire ObjectMapper или MappingJackson2XmlHttpMessageConverter. Оба результата приводят к ошибкам типа "Нет подходящего компонента типа". - person robross0606; 05.03.2015

Основываясь на ответе @Andy Wilkinson, я еще немного покопался и нашел способ получить то, что хотел. Spring Boot не раскрывает XmlMapper как компонент, но предоставляет преобразователь сообщений, в котором он используется, то есть MappingJackson2XmlHttpMessageConverter. Таким образом, я мог автоматически подключить этот bean-компонент и получить от него ObjectMapper (который теперь является XmlMapper).

Демо-контроллер из моего вопроса теперь выглядит так:

    @RestController
    public class DemoController {
        @Autowired 
        private ObjectMapper objectMapper;
        @Autowired
        private MappingJackson2XmlHttpMessageConverter xmlConverter;

        @RequestMapping(value = "/demo", method = RequestMethod.GET)
        public Demo getDemo() {
            Demo demo = new Demo();
            demo.setStringProperty("Hello world!");
            demo.setIntProperty(42);
            ObjectMapper xmlMapper = xmlConverter.getObjectMapper();
            return demo;
        }
    }
person ci_    schedule 05.03.2015
comment
Я пытаюсь сделать то же самое с последней версией Spring-Boot, но обнаружил, что не могу Autowire ObjectMapper или MappingJackson2XmlHttpMessageConverter. Оба результата приводят к ошибкам типа "Нет подходящего компонента типа". - person robross0606; 05.03.2015
comment
@ robross0606 Убедитесь, что у вас есть зависимости в pom.xml в вопросе. - person ci_; 05.03.2015
comment
Это уже проверено. Я использую Gradle для управления зависимостями, но подтвердил, что у меня есть все те же зависимости, что указаны выше в вашем Maven POM. - person robross0606; 05.03.2015
comment
@ robross0606 Не уверен, что я создал этот минимальный проект специально для этого вопроса, его больше нет. Я не знаком с Gradle. Может новый вопрос? - person ci_; 05.03.2015

Это можно сделать двумя способами:

@Bean
public XmlMapper xmlMapper(MappingJackson2XmlHttpMessageConverter converter) {
    return (XmlMapper) converter.getObjectMapper();
}

И

@Bean
@Primary
public ObjectMapper objectMapper(MappingJackson2XmlHttpMessageConverter converter) {
    return converter.getObjectMapper();
}

Если вам нужна поддержка JSON и XML, вы должны зарегистрировать эти bean-компоненты самостоятельно с помощью метода BeanFactoryPostProcessor, описанного в Как создать несколько bean-компонентов одного типа в соответствии с конфигурацией в Spring?

person Adam Ostrožlík    schedule 03.10.2019