【问题标题】:Spring-boot return json and xml from controllersSpring-boot 从控制器返回 json 和 xml
【发布时间】:2015-01-06 02:02:47
【问题描述】:

我有一个 spring-boot 1.1.7 应用程序,它在大部分 UI 中使用 Thymeleaf,所以我的控制器的响应并不是一个真正的问题。但是,现在我需要在用户通过 URL 提交请求时提供 XML 响应。

这是一个典型的请求:

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 客户端包含一个“text/xml”的接受标头(但老实说,我希望他们不必设置它。理想情况下,无论标头是否存在,对该控制器的任何调用都将始终获得 XML)。

有没有办法解决这个问题?我认为媒体转换器已包含在内,只是返回了控制器告诉他们的任何内容?

解决方案: 请参阅下面的我发布的答案。

【问题讨论】:

  • 请将您的解决方案作为答案单独发布并接受。不在问题中。它使读者感到困惑

标签: spring-mvc spring-boot


【解决方案1】:

我遇到了完全相同的问题,我在 Spring 文档网站上找到了解决方案:here

在综合中,我在我的项目的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 {...}

它奏效了。

【讨论】:

  • @XmlRootElement 对我来说是关键。它会解析传入的 XML,但如果没有该注释,它将返回 406 而不是生成 XML。
【解决方案2】:

解决方案:我使用了以下两个答案的组合(非常感谢!)。我在这里发帖以防其他人需要帮助。

我修改后的控制器:

@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;
    }
}

在我的客户端上,我设置了请求标头:

内容类型:应用程序/文本 接受:文本/xml 我认为最终的问题是我的客户端标头设置不正确,因此我可能不必进行其中一些更改。但我喜欢包含结果列表的 SearchResults 类的想法:

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

【讨论】:

  • 嗨,这个答案对我有用。但我想知道如何在其中使用 try catch。发生什么异常我应该返回什么。因为我无法为异常返回 SdnSerarchResults。
  • @KapilaRanasinghe:是的,你可以尝试捕获并抛出一个通用异常。该异常将由代码库中的单个异常处理程序处理。此异常处理程序使用 Spring AOP 在有异常时拦截响应并做出适当的响应。
【解决方案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);
  }
}

对代码更改的简要说明:

  1. @RequestBody 将自动将整个 HTTP 请求正文反序列化为 SdnSearch 实例。外部应用程序通常会将请求数据作为 HTTP 正文提交,因此@RequestBody 将确保自动反序列化为 Java 对象。
  2. @ResponseBody 将根据外部客户端的能力和类路径上可用的库自动序列化返回值。如果 Jackson 在类路径上可用并且客户端已指示他们可以接受 JSON,则返回值将自动作为 JSON 发送。如果 JRE 是 1.7 或更高版本(这意味着 JRE 中包含 JAXB)并且客户端已指示他们可以接受 XML,则返回值将自动以 XML 形式发送。
  3. List&lt;Sdn&gt; 需要更改为 SdnSearchResult 以确保应用程序可以使用单个控制器方法交换 JSON、XML、RSS 和 ATOM 格式,因为 XML(和基于 XML 的格式)需要在输出中使用根标签,这List&lt;Sdn&gt; 无法翻译成。

完成这些更改后,启动 REST 客户端(例如 Chrome 的 Postman 扩展程序)并向 /remote/search 提交请求,并提供以下信息:

  1. 请求标头Accepts设置为application/json
  2. 请求标头Content-Type 设置为application/json
  3. 请求正文设置为 JSON 字符串 { "sdnName" : "Victoria", "address" : "123 Maple Ave" }

这会给你一个 JSON 响应。

【讨论】:

  • 谢谢@Manish,我尝试了很多(我没有做 ModelAndView 控制器,因为我只想返回一个 XML 流),但我仍然得到同样的异常。尝试使用 RequestBody 而不是 ModelAttribute 实际上会导致 org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported
  • 看,有问题。无论您使用什么客户端,都将发送 HTTP 标头 Content-Type 设置为 application/octet-stream,默认情况下,任何反序列化程序都不支持。您需要确保您的客户端根据您在请求中发送的数据设置Content-Type 标头。我可以向您保证,如果您使用我共享的代码创建示例应用程序并使用像 Postman 扩展这样的客户端,您将获得所需的结果。我有几个使用这种设计运行的移动应用程序,并且都运行良好,所以我知道这种设置是有效的。
  • 我将 Content-Type 设置为“text/plain”并让我的控制器接受 consumes = MediaType.ALL_VALUE,但我仍然得到 org.springframework。 web.HttpMediaTypeNotAcceptableException:找不到可接受的表示异常。好像 Spring-MVC 不知道如何返回 XML。这可能是 spring-boot 配置问题吗?
  • 在您的控制器上,您只需要@RequestMapping(method = RequestMethod.POST, value = "/remote/search")。然后,控制器将根据客户端发送的标头自动检测和响应。 Spring Boot documentation 还建议使用 text/xml 的内容类型来返回 XML。
【解决方案4】:

您已将控制器方法标记为产生 application/xml 响应 (produces = MediaType.APPLICATION_XML_VALUE)。请求的接受标头 (Accept: text/xml) 不匹配,因此 Spring 确定您的 search 方法无法处理该请求。

根据您的具体要求,有几种不同的方法可以在服务器上解决此问题:

  • 您可以完全删除 produces 属性
  • 您可以指定多种媒体类型:produces = { "application/xml", "text/xml" }

【讨论】:

  • 我尝试了所有这三种方法(除了编译器不允许我使用通配符),我仍然得到相同的结果。我是否需要定义特定的 MediaType 表示?我不这么认为,因为一切似乎都在类路径上。
  • produces 属性采用字符串或字符串数​​组,因此您不应该遇到编译错误。错误是什么?
  • 我不应该说编译器,我很抱歉。当我尝试启动我的应用程序时,我收到错误 Caused by: org.springframework.util.InvalidMimeTypeException: Invalid mime type "/xml": wildcard type is legal only in '*/' (所有 mime 类型)
  • 糟糕,抱歉。我已经修改了答案。
  • 我将接受更改为 @RequestMapping(value = "/remote/search", method = RequestMethod.GET,produces = { "application/xml", "text/xml" }) 我仍然得到同样的错误。
【解决方案5】:

非常简单,只需在 pom.xml 文件中添加 jackson-dataformat-xml 依赖项,然后您的项目就可以处理 xml/json 请求或响应。添加

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

依赖并添加标题内容类型或根据您的需要接受spring boot将处理json/xml中的请求或响应。全文访问:How to handle xml/json in spring boot

【讨论】:

    【解决方案6】:

    我不确定您的 Spring Boot (1.1.7.RELEASE) 版本,但我使用的是 1.5.2.RELEASE 版本,并且此 xml 转换/序列化自动发生,无需使用任何 jackson 依赖项,如少数答案。

    我猜这是因为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/xmlapplication/xmlapplication/json

    注意 1:如果 Java 类需要 xml 响应,则需要在根类上指定 javax.xml.bind.annotation.XmlRootElement。这是强制性的。

    注意 2:Jackson for json 已经包含在 Spring Boot 中,因此不会明确包含在 json 输出中

    注 3:接受标头 - 输出 匹配由框架自动发生,开发人员无需为此编写任何特定代码。

    因此,在我看来,如果您只将XmlRootElement 添加到您的基类并升级您的 Spring Boot 版本,那么您的服务器端已经准备就绪。设置正确的 Accept 标头的责任在于客户。

    【讨论】:

      【解决方案7】:

      除了 Michael 在他的 answer 中所说的之外,我还在 pom.xml 中添加了以下依赖项

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

      由于某种原因,单独的 jackson-dataformat-xml 没有帮助。 我还确保在 get 调用中返回了 ResponseEntity,并从 RequestMapping 注释中删除了produces=MediaType。

      通过这些更改,我能够获得正确的数据,但我必须在 get 调用期间将 mime 类型的扩展名提供给 REST URL。即,在浏览器中明确指定为:http://localhost:8080/hello.xmlhttp://localhost:8080/hello.json

      【讨论】:

        【解决方案8】:

        在我的例子中,我想返回一个格式化的 XML 字符串,并将其全部合并到一行中。

        produces = { "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 {
        }
        

        祝你好运。

        【讨论】:

          猜你喜欢
          • 2020-01-12
          • 2018-07-14
          • 1970-01-01
          • 2016-12-31
          • 2019-01-31
          • 1970-01-01
          • 2015-10-12
          • 2015-06-10
          相关资源
          最近更新 更多