用于生成和使用 JSON 的控制器的 Spring RequestMapping

Posted

技术标签:

【中文标题】用于生成和使用 JSON 的控制器的 Spring RequestMapping【英文标题】:Spring RequestMapping for controllers that produce and consume JSON 【发布时间】:2016-05-09 12:12:46 【问题描述】:

由于多个 Spring 控制器使用和生成 application/json,我的代码中到处都是长注释,例如:

    @RequestMapping(value = "/foo", method = RequestMethod.POST,
            consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)

有没有办法为consumesproduces 生成具有默认 值的“复合/继承/聚合”注释,这样我就可以改写如下内容:

    @JSONRequestMapping(value = "/foo", method = RequestMethod.POST)

我们如何定义像上面的@JSONRequestMapping 这样的东西?注意valuemethod 就像在@RequestMapping 中一样传入,如果默认不合适,也可以传入consumesproduces

我需要控制要返回的内容。我想要 produces/consumes 注释方法,以便获得适当的 Content-Type 标头。

【问题讨论】:

【参考方案1】:

您的问题的简单答案是没有Annotation-Inheritance in Java。但是,有一种方法可以以我认为有助于解决您的问题的方式使用 Spring 注释。

@RequestMapping 在类型级别和方法级别均受支持。

当您将@RequestMapping 放在类型级别时,该类中的每个方法的大多数属性都是“继承的”。这是 Spring 参考文档中的 mentioned。查看api docs 以了解在将@RequestMapping 添加到类型时如何处理每个属性的详细信息。我为下面的每个属性总结了这一点:

name:类型级别的值与方法级别的值连接,使用“#”作为分隔符。 value:类型级别的值是由方法继承的。 path:类型级别的值是由方法继承的。 method:类型级别的值是由方法继承的。 params:类型级别的值是由方法继承的。 headers:类型级别的值是由方法继承的。 consumes:类型级别的值被方法覆盖。 produces:类型级别的值被方法覆盖。

这是一个简短的控制器示例,展示了如何使用它:

package com.example;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(path = "/", 
        consumes = MediaType.APPLICATION_JSON_VALUE, 
        produces = MediaType.APPLICATION_JSON_VALUE, 
        method = RequestMethod.GET, RequestMethod.POST)
public class JsonProducingEndpoint 

    private FooService fooService;

    @RequestMapping(path = "/foo", method = RequestMethod.POST)
    public String postAFoo(@RequestBody ThisIsAFoo theFoo) 
        fooService.saveTheFoo(theFoo);
        return "http://myservice.com/foo/1";
    

    @RequestMapping(path = "/foo/id", method = RequestMethod.GET)
    public ThisIsAFoo getAFoo(@PathVariable String id) 
        ThisIsAFoo foo = fooService.getAFoo(id);
        return foo;
    

    @RequestMapping(path = "/foo/id", produces = MediaType.APPLICATION_XML_VALUE, method = RequestMethod.GET)
    public ThisIsAFooXML getAFooXml(@PathVariable String id) 
        ThisIsAFooXML foo = fooService.getAFoo(id);
        return foo;
    

【讨论】:

从技术上讲,Ali Dehghani 的回答最好地回答了我提出的问题,因此我将其标记为已接受。但我最终使用了你的建议,在某种意义上看起来更干净,所以我也给你一个赏金,(+100,因为我不能做两次 +50)。谢谢。 问题:我的场景和你一模一样,但是不能接受返回字符串的方法(415),因为它不是 JSON。你没有这个问题吗?对象被正确反序列化,因为我们在模型的字段上有 Jackson 和 @Jsonproperty @WesternGun 不确定我是否关注。 java 方法应该返回一个可序列化的对象,而不是一个字符串。在我的示例中,只有 POST 方法返回一个字符串,因为它返回创建资源的位置。可能值得打开您自己的问题。【参考方案2】:

从 Spring 4.2.x 开始,您可以创建自定义映射注释,使用 @RequestMapping 作为元注释。所以:

有没有办法产生“复合/继承/聚合” 带有消耗和生产的默认值的注释,这样我 可以改为这样写:

@JSONRequestMapping(value = "/foo", method = RequestMethod.POST)

是的,有这样的方法。您可以创建如下元注释:

@Target(ElementType.METHOD, ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(consumes = "application/json", produces = "application/json")
public @interface JsonRequestMapping 
    @AliasFor(annotation = RequestMapping.class, attribute = "value")
    String[] value() default ;

    @AliasFor(annotation = RequestMapping.class, attribute = "method")
    RequestMethod[] method() default ;

    @AliasFor(annotation = RequestMapping.class, attribute = "params")
    String[] params() default ;

    @AliasFor(annotation = RequestMapping.class, attribute = "headers")
    String[] headers() default ;

    @AliasFor(annotation = RequestMapping.class, attribute = "consumes")
    String[] consumes() default ;

    @AliasFor(annotation = RequestMapping.class, attribute = "produces")
    String[] produces() default ;

然后您可以使用默认设置,甚至可以根据需要覆盖它们:

@JsonRequestMapping(method = POST)
public String defaultSettings() 
    return "Default settings";


@JsonRequestMapping(value = "/override", method = PUT, produces = "text/plain")
public String overrideSome(@RequestBody String json) 
    return json;

您可以在 spring 的 javadoc 和 github wiki 中阅读有关 AliasFor 的更多信息。

【讨论】:

这真的很有趣;不知道这个新功能。 JavaDoc for @AliasFor 真的很好。还有一个wiki page更详细地描述了Spring的注解模型。 谢谢!这看起来像我正在寻找的信息。在public @interface JsonRequestMapping 中,是否需要重新声明namevaluepathmethodparamsheaders?我只对consumesproduces 的默认值感兴趣。 是的,如果你想覆盖它们在RequestMapping中的对应值,你应该重新声明它们。【参考方案3】:

您根本不需要配置消费或生产属性。 Spring 将根据以下因素自动提供 JSON。

请求的接受头是application/json @ResponseBody 带注释的方法 类路径上的杰克逊库

您还应该遵循 Wim 的建议并使用 @RestController 注释定义您的控制器。这将使您免于使用 @ResponseBody

注释每个请求方法

这种方法的另一个好处是,如果客户想要 XML 而不是 JSON,他们会得到它。他们只需要在接受标头中指定 xml。

【讨论】:

对我来说完美无缺。我什至删除了消费和生产【参考方案4】:

您可以使用@RestController 代替@Controller 注释。

【讨论】:

其实是@Controller@ResponseBody的组合,所以和Content Type没有任何共同之处【参考方案5】:

Spring 中有 2 个注解:@RequestBody@ResponseBody。这些注释分别消费和生成 JSON。更多信息here。

【讨论】:

这是有趣的信息,但不是我想要的。我需要控制我要返回的内容。我想要 produces/consumes 注释方法,以便获得适当的 Content-Type 标头。

以上是关于用于生成和使用 JSON 的控制器的 Spring RequestMapping的主要内容,如果未能解决你的问题,请参考以下文章

用于发布 JSON 的标准 spring mvc 控制器的 Katharsis 返回 400

Spring 4 mvc REST XML 和 JSON 响应

我如何在 Spring Boot 中更改 Repository.findAll() 生成的 json 数组的格式

Spring 4 vs Jersey 用于 REST Web 服务

Spring REST API 中的 Json 模式验证

Spring @ExceptionHandler 不适用于 @ResponseBody