Spring-boot从控制器返回json和xml

时间:2015-01-06 02:02:47

标签: spring-mvc spring-boot

我有一个spring-boot 1.1.7应用程序,它将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&amp;我的类路径上的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客户端包含一个&#34; text / xml&#34;的接受标头。 (但是说实话,我宁愿他们不必设置它。理想情况下,无论头部是否存在,对此Controller的任何调用都将始终获得XML。

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

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

8 个答案:

答案 0 :(得分:10)

我遇到了完全相同的问题,我在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 {...}

它有效。

答案 1 :(得分:9)

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

我修改过的控制器:

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

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

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

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

答案 2 :(得分:7)

创建新类可能更好:

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或更高(这意味着JAX包含在JRE中)并且客户端已表明它们可以接受XML,则返回值将自动以XML格式发送。
  3. List<Sdn>需要更改为SdnSearchResult以确保应用程序可以使用单个控制器方法交换JSON,XML,RSS和ATOM格式,因为XML(和基于XML的格式)需要root输出中的-tag,List<Sdn>无法转换为。
  4. 完成这些更改后,启动REST客户端,例如Chrome的Postman扩展程序,并向/remote/search提交请求,并提供以下信息:

    1. 请求标头Accepts设置为application/json
    2. 请求标头Content-Type设置为application/json
    3. 请求正文设置为JSON字符串{ "sdnName" : "Victoria", "address" : "123 Maple Ave" }
    4. 这将为您提供JSON响应。

答案 3 :(得分:7)

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

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

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

答案 4 :(得分:1)

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

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

出于某种原因,杰克逊数据格式-xml本身并没有帮助。 我还确保在get调用中返回ResponseEntity,并从RequestMapping注释中删除produce = MediaType。

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

答案 5 :(得分:0)

就我而言,我想返回一个格式化的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 {

}

祝你好运。

答案 6 :(得分:0)

我不确定您的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:Spring Boot中已经包含了用于json的Jackson,因此不会明确包含在json输出中

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

因此,我认为,如果仅将XmlRootElement添加到基类并升级Spring Boot版本,则服务器端已设置好。客户负责设置正确的 Accept 标头。

答案 7 :(得分:0)

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

// or router.use((req, res, next) => ...)
router.post('/', (req, res, next) => {
   // This should be BEFORE `fileUpload`
   req.setTimeout(0);
   next();
});

router.use(fileUpload());

//@route   POST api/students
//@desc    Fill the database with the json information
//@access  Public
router.post('/', async (req, res, next) => {
   // ...rest of your code
})

依赖关系,并根据您的需要添加标头content-type或accept,Spring Boot将在json / xml中处理请求或响应。有关全文,请访问:How to handle xml/json in spring boot