多种情景下的 @RequestMapping 生成 JSON/XML 结合 Accept 或 ResponseEntity

41

我正在使用Spring 4.0.7。

关于Spring MVC,出于研究目的,我拥有以下内容:

@RequestMapping(value="/getjsonperson", 
                method=RequestMethod.GET, 
                produces=MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody Person getJSONPerson(){
    logger.info("getJSONPerson - getjsonperson");
    return PersonFactory.createPerson();
}

@RequestMapping(value="/getperson.json", method=RequestMethod.GET)
public @ResponseBody Person getPersonJSON(){
    logger.info("getPerson - getpersonJSON");
    return PersonFactory.createPerson();
}

两个都可以正常工作,针对JSON格式,一个带扩展名,一个不带:

  • /getjsonperson
  • /getperson.json

XML也是一样。

@RequestMapping(value="/getxmlperson",
                method=RequestMethod.GET,
                produces=MediaType.APPLICATION_XML_VALUE
                )
public @ResponseBody Person getXMLPerson(){
    logger.info("getXMLPerson - getxmlperson");
    return PersonFactory.createPerson();
}

@RequestMapping(value="/getperson.xml", method=RequestMethod.GET)
@ResponseBody
public Person getPersonXML(){
    logger.info("getPerson - getpersonXML");
    return PersonFactory.createPerson();
}

每个都可以正常工作,针对XML格式的消息观察以下两种方式,一种带扩展名,另一种不带:

  • /getxmlperson
  • /getperson.xml

现在来谈谈Restful,我有以下内容:

@RequestMapping(value="/person/{id}/", 
                method=RequestMethod.GET,
                produces={MediaType.APPLICATION_JSON_VALUE, 
                          MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<Person> getPersonCustomizedRestrict(@PathVariable Integer id){
    Person person = personMapRepository.findPerson(id);
    return new ResponseEntity<>(person, HttpStatus.FOUND);//302     
}

观察MediaType,它是混合的,适用于JSON和XML

通过RestTemplate我可以指定Accept

    if(type.equals("JSON")){
        logger.info("JSON");
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
    }
    else if(type.equals("XML")){
        logger.info("XML");
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_XML));
    }
    ….

    ResponseEntity<Person> response =
                restTemplate.exchange("http://localhost:8080/spring-utility/person/{id}/customizedrestrict",
                                      HttpMethod.GET,
                                      new HttpEntity<Person>(headers),  
                                      Person.class,
                                       id
                                     ); 

因此,我能够使用一个URL/URI来获取XML或JSON格式的某些数据。它运行良好。

我的问题在于Spring MVC...只需要考虑

@RequestMapping(value="/{id}/person", 
                method=RequestMethod.GET,
                produces={MediaType.APPLICATION_JSON_VALUE,  
                          MediaType.APPLICATION_XML_VALUE})
public @ResponseBody Person getPerson(@PathVariable Integer id){
    return personMapRepository.findPerson(id);
}

通过以下方式,我可以调用或激活处理程序方法(@RequestMapping):

  1. 使用Ajax的jQuery,我能够指定Accept值(例如JSON)
  2. Poster,通过Headers按钮,我可以设置Accept

问题一:

但对于一个常规的链接呢? 我如何设置Accept值?是否可能?

我想到了解决这个问题的其他方法。

  • http://localhost:8080/spring-utility/person/getpersonformat?format=json
  • http://localhost:8080/spring-utility/person/getpersonformat?format=xml

观察:

  • ?format

因此

@RequestMapping(value="/getpersonformat", 
                method=RequestMethod.GET,
                produces={MediaType.APPLICATION_JSON_VALUE,  
                          MediaType.APPLICATION_XML_VALUE})
public @ResponseBody Person getPerson(@RequestParam String format){
    return personMapRepository.findPerson(id);
}

问题二:

以上方法中,需要添加什么代码来自定义返回类型格式呢?比如说 JSON 或 XML,这是可能的吗?

我考虑以下内容:

@RequestMapping(value="/getpersonformataltern",
        method=RequestMethod.GET
        produces={MediaType.APPLICATION_JSON_VALUE, 
                  MediaType.APPLICATION_XML_VALUE}
        )
public ResponseEntity<Person> getPersonFormat(@RequestParam String format){
    logger.info("getPersonFormat - format: {}", format);
    HttpHeaders httpHeaders = new HttpHeaders();
    if(format.equals("json")){
        logger.info("Ok JSON");
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
    }
    else{
        logger.info("Ok XML");
        httpHeaders.setContentType(MediaType.APPLICATION_XML);
    }
    return new ResponseEntity<>(PersonFactory.createPerson(), httpHeaders, HttpStatus.OK);
}

但是:

如果我执行以下URL:

  • http://localhost:8080/spring-utility/person/getpersonformataltern?format=json

我会得到:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person>
    <id>1</id>
    <firstName>Manuel</firstName>
    <lastName>Jordan</lastName></person>

是的,在XML中!

注意:我可以确认控制台会打印Ok JSON

如果我执行以下URL:

  • http://localhost:8080/spring-utility/person/getpersonformataltern?format=xml

我会得到

This XML file does not appear to have any style information associated with it. 
The document tree is shown below.

<person>
    <id>1</id>
    <firstName>Manuel</firstName>
    <lastName>Jordan</lastName> 
    …
</person>

第三个问题

为了修复JSON输出,必须添加哪些方法代码? 我不知道哪里出了问题或遗漏了什么..

有三个问题。

谢谢

Alpha

@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    Map<String,MediaType> mediaTypes = new LinkedHashMap<>();
    mediaTypes.put("json", MediaType.APPLICATION_JSON);
    mediaTypes.put("xml", MediaType.APPLICATION_XML);
    configurer.mediaTypes(mediaTypes);
    configurer.defaultContentType(MediaType.TEXT_HTML);
}

研究内容协商。 - Sotirios Delimanolis
请查看“Alpha”部分。它有效,记住我也在URL中使用.json和.xml。 - Manuel Jordan
如果您想使用URL,您不能设置内容类型,除非您控制调用(如JavaScript中)。您可以做的一件事是将服务器上的默认内容类型设置为JSON(而不是现在的HTML)。无法控制内容类型有点合乎逻辑,因为从HTML链接应该产生HTML。 - M. Deinum
@M.Deninum 我明白了,可惜不可能。谢谢你的回复。 - Manuel Jordan
1
你可以随时使用curl测试任何你想要的格式化响应。对于JSON,请使用curl -H "Accept: application/json" <URL>,对于XML,请使用curl -H "Accept: application/xml" <URL>。 - Micho Rizo
明白了,谢谢!顺便提一下,在 Firefox 中我使用一个名为“Poster”的插件。 - Manuel Jordan
3个回答

47
使用 Accept 标头非常容易从 REST 服务中获取 JSON 或 XML 格式。这是我的控制器,请查看 produces 部分。
@RequestMapping(value = "properties", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}, method = RequestMethod.GET)
    public UIProperty getProperties() {
        return uiProperty;
    }
为了使用 REST 服务,我们可以使用下面的代码,其中 header 可以是 MediaType.APPLICATION_JSON_VALUE 或 MediaType.APPLICATION_XML_VALUE。
HttpHeaders headers = new HttpHeaders();
headers.add("Accept", header);

HttpEntity entity = new HttpEntity(headers);

RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange("http://localhost:8080/properties", HttpMethod.GET, entity,String.class);
return response.getBody();

编辑01:

为了使用application/xml,请添加此依赖项。

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

当时机合适的时候,让我测试一下你的解决方案。现在我又回到了服务器端。谢谢。 - Manuel Jordan
3
节省我的时间!忘记在POM中添加jackson-dataformat-xml - bob
1
第一个产生参数定义默认格式(对于没有accept头的请求),例如使用 produces = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE},如果客户端没有指定格式,则端点将发送XML。 - foal
我在Postman中进行测试,同时提供了XML和JSON格式的数据,但是在没有发送Accept头部的情况下,我只收到了JSON格式的响应,而没有收到XML格式的响应。 - undefined

2
你的问题在于混淆了内容类型协商和参数传递。它们是不同层面的事情。更具体地说,对于你的第二个问题,你构造了响应头,其中包含你想要返回的媒体类型。实际的内容协商是基于请求头中的接受媒体类型,而不是响应头。当执行到getPersonFormat方法的实现时,我不确定内容协商是否已经完成。这取决于实现方式。如果没有完成并且你想让事情正常工作,你可以用你想要返回的内容覆盖请求头中的接受类型。

return new ResponseEntity<>(PersonFactory.createPerson(), httpHeaders, HttpStatus.OK);

0

我更喜欢使用params过滤器来处理参数中心的内容类型。我相信这应该与produces属性一起使用。

@GetMapping(value="/person/{id}/", 
            params="format=json",
            produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Person> getPerson(@PathVariable Integer id){
    Person person = personMapRepository.findPerson(id);
    return ResponseEntity.ok(person);
} 
@GetMapping(value="/person/{id}/", 
            params="format=xml",
            produces=MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<Person> getPersonXML(@PathVariable Integer id){
    return GetPerson(id); // delegate
}

5
如果在所有REST控制器的URI中都使用了format参数,那么其中的代码将会增加很多倍。 - Rui

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接