Spring -boot возвращает json и xml из контроллеров

У меня есть приложение spring -boot 1.1.7, которое использует Thymeleaf для большей части пользовательского интерфейса, поэтому ответ от моих контроллеров действительно не вызывает беспокойства. Однако теперь мне нужно предоставить ответ XML, когда пользователь отправляет запрос через URL.

Вот типичный запрос:

http://localhost:9001/remote/search?sdnName=Victoria&address=123 Maple Ave

Вот моя конфигурация gradle:

project.ext {
    springBootVersion = '1.1.7.RELEASE'
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
    compile("org.springframework.boot:spring-boot-starter-thymeleaf")
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion")
    compile("org.springframework.security:spring-security-web:4.0.0.M1")
    compile("org.springframework.security:spring-security-config:4.0.0.M1")
    compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity3:2.1.1.RELEASE')
    compile("org.springframework.boot:spring-boot-starter-actuator")
    compile('com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.5.0')
}

И вот мой контроллер:

@Controller
public class RemoteSearchController {

    @Autowired
    private SdnSearchService sdnSearchService;

    @RequestMapping(value = "/remote/search", method = RequestMethod.GET, produces = MediaType.APPLICATION_XML_VALUE)
    public List<Sdn> search(@ModelAttribute SdnSearch sdnSearch) {
        List<Sdn> foundSdns = sdnSearchService.find( sdnSearch );
        return foundSdns;
}

Вот мой объект, который нужно вернуть:

@Entity
public class Sdn {

    @Id
    private long entNum;
    private String sdnName;
...
//getters & setters here
}

Я могу получить запрос через клиента REST (например, CocoaREST) ​​и обработать его. Но когда я возвращаю список SDN, я получаю следующее исключение, даже если у меня есть Jackson и jackson-dataformat-xml в моем пути к классам:

org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
    at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:229)
    at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:301)
    at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:248)
    at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:57)
    at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:299)

Мой клиент REST включает в себя Accept Header "text/xml" (но, честно говоря, я бы предпочел, чтобы им не пришлось устанавливать это. В идеале любой вызов этого контроллера всегда будет получать XML, независимо от того, какой заголовок присутствует).

Есть ли способ справиться с этим? Я думал, что Media Converters были включены и просто вернули все, что им сказал контроллер.

РЕШЕНИЕ: Ниже приведен ответ, который я опубликовал.

Ответ 1

РЕШЕНИЕ: Я использовал комбинацию обоих ответов ниже (большое спасибо!). Я размещаю здесь, если кому-то еще нужна помощь.

Мой измененный контроллер:

@Controller
public class RemoteSearchController {

    @Autowired
    private SdnSearchService sdnSearchService;

    @RequestMapping(value = "/remote/search", method = RequestMethod.GET, produces = { "application/xml", "text/xml" }, consumes = MediaType.ALL_VALUE )
    @ResponseBody
    public SdnSearchResults search(@ModelAttribute SdnSearch sdnSearch) {
        List<Sdn> foundSdns = sdnSearchService.find( sdnSearch );
        SdnSearchResults results = new SdnSearchResults();
        results.setSdns( foundSdns );
        return results;
    }
}

И на моем клиенте я устанавливаю заголовки запроса:

Тип контента: приложение/текст Accept: text/xml Я думаю, что в конечном итоге проблема заключалась в том, что мои клиентские заголовки не были правильно настроены, поэтому мне, возможно, не пришлось делать некоторые из этих изменений. Но мне понравилась идея класса SearchResults, содержащего список результатов:

@XmlRootElement
public class SdnSearchResults {
    private List<Sdn> sdns;
...
}

Ответ 2

У меня была такая же проблема, и я нашел решение на веб-сайте документации Spring: здесь

В синтезе я добавил следующую зависимость к pom.xml моего проекта:

<dependency>
     <groupId>com.fasterxml.jackson.dataformat</groupId>
     <artifactId>jackson-dataformat-xml</artifactId>
 </dependency>

Затем я добавил следующий класс кода в класс, который служба должна была вернуть:

 import javax.xml.bind.annotation.XmlRootElement;

 @XmlRootElement
 public class Greeting {...}

И это сработало.

Ответ 3

Возможно, лучше создать новый класс:

public class SdnSearchResult {
  private List<Sdn> sdns;
  ...
}

Затем для существующих классов потребуется небольшое изменение:

public interface SdnSearchService {
  SdnSearchResult find(SdnSearch sdnSearch);
}

@Controller
public class UISearchController {
  @Autowired
  private SdnSearchService sdnSearchService;

  @RequestMapping("/search")
  public ModelAndView search(@ModelAttribute SdnSearch sdnSearch) {
    return new ModelAndView("pages/search/results", "sdns", sdnSearchService.find(sdnSearch).getSdns());
  }
}

Как только это будет сделано, другой контроллер должен быть закодирован как:

@Controller
public class RemoteSearchController {
  @Autowired
  private SdnSearchService sdnSearchService;

  @RequestMapping("/remote/search")
  @ResponseBody
  public SdnSearchResult search(@RequestBody SdnSearch sdnSearch) {
    return sdnSearchService.find(sdnSearch);
  }
}

Быстрое объяснение изменений вашего кода:

  • @RequestBody автоматически десериализует весь тело запроса HTTP в экземпляр SdnSearch. Внешние приложения обычно подают данные запроса как тело HTTP, поэтому @RequestBody гарантирует, что десериализация на объект Java произойдет автоматически.
  • @ResponseBody будет автоматически сериализовать возвращаемое значение в соответствии с возможностями внешнего клиента и библиотеками, доступными в пути к классам. Если Джексон доступен на пути к классам, и клиент указал, что они могут принимать JSON, возвращаемое значение будет автоматически отправлено как JSON. Если JRE равен 1.7 или выше (это означает, что JAXB включен в JRE), и клиент указал, что они могут принимать XML, возвращаемое значение будет автоматически отправлено как XML.
  • List<Sdn> необходимо изменить на SdnSearchResult, чтобы гарантировать, что приложение может обмениваться форматами JSON, XML, RSS и ATOM с помощью одного метода контроллера, поскольку XML (и форматы на основе XML) требуют корневого тега на вывода, который не может быть преобразован a List<Sdn>.

Как только эти изменения будут выполнены, запустите клиент REST, такой как расширение Postman для Chrome, и отправьте запрос на /remote/search со следующей информацией:

  • Заголовок запроса Accepts установлен на application/json.
  • Заголовок запроса Content-Type установлен на application/json.
  • Тело запроса, заданное в строку JSON { "sdnName" : "Victoria", "address" : "123 Maple Ave" }.

Это даст вам ответ JSON.

Ответ 4

Вы отметили метод контроллера как производящий ответы application/xml (produces = MediaType.APPLICATION_XML_VALUE). Заголовок принятия запроса (Accept: text/xml) не соответствует, поэтому Spring определяет, что ваш метод search не может обрабатывать запрос.

Есть несколько способов исправить это на сервере, в зависимости от ваших конкретных требований:

  • Вы можете полностью удалить атрибут produces
  • Вы можете указать несколько типов медиа: produces = { "application/xml", "text/xml" }

Ответ 5

В дополнение к тому, что Майкл сказал в ответе , я добавил следующие зависимости также в pom.xml

<dependency>
    <groupId>org.codehaus.woodstox</groupId>
    <artifactId>woodstox-core-asl</artifactId>
    <version>4.4.1</version>
</dependency>

По какой-то причине один только jackson-dataformat-xml не помогал. Я также удостоверился, что ResponseEntity возвращается в вызове get и удалил result = MediaType из аннотации RequestMapping.

С этими изменениями я смог получить правильные данные, но мне нужно было указать расширение типа mime на URL REST во время вызова. то есть указать явно: http://localhost:8080/hello.xml или http://localhost:8080/hello.json в браузере

Ответ 6

В моем случае я хотел вернуть отформатированную строку XML, и все это было объединено в одну строку.

Для добавления запроса = {"application/xml", "text/xml"} к отображению запроса было достаточно вернуть строку в формате XML (с отступом).

пример:

@RequestMapping(method= RequestMethod.GET, value="/generate/{blabla}", produces = { "application/xml", "text/xml" })
public String getBlaBla(@PathVariable("param") String param) throws IOException {

}

Удачи.

Ответ 7

Я не уверен в вашей версии Spring Boot (1.1.7.RELEASE), но я нахожусь на версии 1.5.2.RELEASE, и это преобразование/сериализация в xml происходит автоматически без использования каких-либо зависимостей Джексона, как упомянуто в нескольких ответах.

Я предполагаю, что это происходит потому, что org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter автоматически настраивается начиная с Spring Boot версии 1.5.1.RELEASE, и этот конвертер использует реализацию JRE по умолчанию JAXB (поэтому не требуется явной зависимости преобразования xml).

Во-вторых, заголовок Accept установленный клиентами в запросе, решает, в каком формате ожидается вывод, поэтому отображение запроса, как показано ниже (т.е. Одна конечная точка),

@RequestMapping(method = RequestMethod.GET, value = "/remote/search", produces = {
        MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE })

может использоваться для создания XML, а также ответа JSON (если заголовок Accept установлен как text/xml или application/xml & application/json соответственно.

Примечание 1: javax.xml.bind.annotation.XmlRootElement необходимо указать в корневом классе, если ожидается ответ xml для класса Java. Это обязательно.

Примечание 2: Jackson для json уже включен в Spring Boot, поэтому его не следует явно включать в выходные данные json

Примечание 3: заголовок Accept - совпадение выходных данных происходит автоматически фреймворком, и разработчику не нужно ничего специально кодировать для этого.

Так что, по моему мнению, если вы только добавите XmlRootElement в свой базовый класс и обновите версию Spring Boot, ваша серверная сторона готова. Ответственность за установку правильного заголовка Accept лежит на клиентах.