Spring Boot 控制器内容协商

Posted

技术标签:

【中文标题】Spring Boot 控制器内容协商【英文标题】:Spring boot controller content negotiation 【发布时间】:2016-01-05 17:29:50 【问题描述】:

我在 Spring-boot 应用程序中编写了一个简单的 REST 控制器,但我不确定如何实现内容协商以使其根据请求标头中的 Content-Type 参数返回 JSON 或 XML。有人可以向我解释一下,我做错了什么吗?

控制器方法:

@RequestMapping(value = "/message", method = RequestMethod.GET, produces =  MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE )
  public Message getMessageXML(@RequestParam("text") String text) throws Exception 
    Message message = new Message();
    message.setDate(new Date());
    message.setName("Test");
    message.setAge(99);
    message.setMessage(text);

    return message;
  

调用此方法时我总是得到 JSON(即使我将 Content-Type 指定为 application/xmltext/xml)。

当我实现两个方法时,每个方法都有不同的映射和不同的内容类型,我可以从 xml 中获取 XML,但是如果我在一个方法中指定两个 mediaTypes(如提供的示例),它将不起作用。

我想要的是调用\message 端点并接收

当 GET 请求的 Content-Type 设置为 application/xml 时的 XML Content-Type 为 application/json 时的 JSON

感谢任何帮助。

编辑: 我更新了我的控制器以接受所有媒体类型

@RequestMapping(value = "/message", method = RequestMethod.GET, produces =  MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE , consumes = MediaType.ALL_VALUE)
  public Message getMessageXML(@RequestParam("text") String text) throws Exception 
    Message message = new Message();
    message.setDate(new Date());
    message.setName("Vladimir");
    message.setAge(35);
    message.setMessage(text);

    return message;
  

【问题讨论】:

您需要提供Accept 标头,其值为application/xml 或任何受支持的媒体类型。 “Content-Type”标头定义了您发送的内容类型 - 而不是您想要接收的内容(这就是“Accept”标头的用途。所以使用“Content-Type” GET请求的标头没有意义,因为不能有任何(正文)内容。因此,在您的情况下,您应该为您的请求使用“Accept”标头,并在响应中使用“Content-Type”标头来命名实际发送的内容类型。 【参考方案1】:

您可以在博文@RequestMapping with Produces and Consumes 第 6 点找到一些提示。

注意关于 Content-Type 和 Accept headers 的部分:

@RequestMapping 与 Produces 和 Consumes:我们可以使用 header Content-Type 和 Accept 找出请求内容和什么是 它想要响应的 mime 消息。为清楚起见,@RequestMapping 提供生产和消费变量,我们可以在其中指定 请求内容类型将调用哪个方法和响应 内容类型。例如:

@RequestMapping(value="/method6", produces="application/json","application/xml", consumes="text/html")
@ResponseBody
public String method6()
    return "method6";

上述方法只能使用 Content-Type 为 text/html 的消息 并且能够生成 application/json 类型的消息和 应用程序/xml。

您也可以尝试this 不同的方法(使用 ResponseEntity 对象),它可以让您找出传入的消息类型并生成相应的消息(也利用 @ResponseBody 注释)

【讨论】:

【参考方案2】:

您可以使用ContentNegotiationConfigurer

首先,您应该在配置类中覆盖configureContentNegotiation 方法:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter 

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) 
    configurer.favorPathExtension(false).
            favorParameter(true).
            defaultContentType(MediaType.APPLICATION_JSON).
            mediaType("xml", MediaType.APPLICATION_XML);
    

favorParameter(true) - 启用优先路径表达式而不是参数或接受标头。

defaultContentType(MediaType.APPLICATION_JSON) - 设置默认内容类型。这意味着如果您不传递路径表达式,那么 Spring 将生成 JSON 作为响应。

mediaType("xml", MediaType.APPLICATION_XML) - 设置 XML 的路径表达式键。

现在如果你像这样声明你的控制器:

@Controller
class AccountController 

    @RequestMapping(value="/accounts", method=RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    public @ResponseBody List<Account> list(Model model, Principal principal) 
        return accountManager.getAccounts(principal) );
    

并将其命名为 localhost:8080/app/accounts.json,然后 Spring 将生成 JSON 作为响应。因此,如果您致电localhost:8080/app/accounts.xml,您将收到 XML 响应

您可以找到有关此here 的更多信息。

【讨论】:

只是对 defaultContentType 的一个小脚注:browsers tend to send accept headers that prefer XML。绕过(假设您没有使用它)Accept 标头可以在您的 contentNegotiation 覆盖中完成:configurer.ignoreAcceptHeader(true) 我认为这里有一个小错误 - 您声称控制器工作的方式,您应该将喜爱路径扩展设置为真,而不是喜爱参数。 另一个小脚注:在 Spring Boot 应用程序中,您的 @Configuration 类应该包含 @EnableWebMvc 注释 (source)。它可能会阻止其他东西工作,例如 springfox-swagger-ui html 页面。 使用 implements WebMvcConfigurer 而不是已弃用的 extends WebMvcConfigurerAdapter 用于当前的 Spring Boot 版本。

以上是关于Spring Boot 控制器内容协商的主要内容,如果未能解决你的问题,请参考以下文章

Spring boot 控制器调用不支持内容类型“application/json;charset=UTF-8”

SpringBoot/Kotlin 和通过内容协商进行版本控制:正确的方法?

使用 Spring Boot 到达静态内容时如何添加响应标头?

如何在spring boot中从restful控制器返回一个html页面?

Spring Boot MVC 控制器是多线程的吗?

在 Spring Boot with Slice 中使用 MockMVC 测试静态内容