Contents

Spring中的@RequestMapping注解

1.概述

在本教程中,我们将关注Spring MVC 中的主要注解之一:@RequestMapping 简单来说,注解就是用来将 web 请求映射到 Spring Controller 方法上的。

2. @RequestMapping基础

让我们从一个简单的示例开始:使用一些基本标准将 HTTP 请求映射到方法。

2.1. @RequestMapping — 按路径

@RequestMapping(value = "/ex/foos", method = RequestMethod.GET)
@ResponseBody
public String getFoosBySimplePath() {
    return "Get some Foos";
}

要使用简单的curl命令测试此映射,请运行:

curl -i http://localhost:8080/spring-rest/ex/foos

2.2. @RequestMapping — HTTP 方法

HTTP方法参数**没有默认值。**因此,如果我们不指定值,它将映射到任何 HTTP 请求。

这是一个简单的示例,与上一个类似,但这次映射到 HTTP POST 请求:

@RequestMapping(value = "/ex/foos", method = POST)
@ResponseBody
public String postFoos() {
    return "Post some Foos";
}

通过curl命令测试 POST:

curl -i -X POST http://localhost:8080/spring-rest/ex/foos

3. RequestMapping和 HTTP 标头

3.1. @RequestMapping带有headers属性

通过为请求指定标头,可以进一步缩小映射范围:

@RequestMapping(value = "/ex/foos", headers = "key=val", method = GET)
@ResponseBody
public String getFoosWithHeader() {
    return "Get some Foos with Header";
}

为了测试操作,我们将使用curl标头支持:

curl -i -H "key:val" http://localhost:8080/spring-rest/ex/foos

甚至通过*@RequestMappingheaders*属性设置多个标头:

@RequestMapping(
  value = "/ex/foos", 
  headers = { "key1=val1", "key2=val2" }, method = GET)
@ResponseBody
public String getFoosWithHeaders() {
    return "Get some Foos with Header";
}

我们可以使用以下命令进行测试:

curl -i -H "key1:val1" -H "key2:val2" http://localhost:8080/spring-rest/ex/foos

请注意,对于curl语法,冒号分隔标头键和标头值,与 HTTP 规范中相同,而在 Spring 中,使用等号。

3.2. @RequestMapping消费和生产

由控制器方法生成的映射媒体类型值得特别注意。 我们可以通过上面介绍的*@RequestMapping* headers属性根据其Accept标头映射请求:

@RequestMapping(
  value = "/ex/foos", 
  method = GET, 
  headers = "Accept=application/json")
@ResponseBody
public String getFoosAsJsonFromBrowser() {
    return "Get some Foos with Header Old";
}

这种定义Accept标头的方式的匹配是灵活的——它使用 contains 而不是 equals,所以像下面这样的请求仍然可以正确映射:

curl -H "Accept:application/json,text/html" 
  http://localhost:8080/spring-rest/ex/foos

*从 Spring 3.1 开始,@RequestMapping注解现在具有*produces consumes 属性,专门用于此目的:

@RequestMapping(
  value = "/ex/foos", 
  method = RequestMethod.GET, 
  produces = "application/json"
)
@ResponseBody
public String getFoosAsJsonFromREST() {
    return "Get some Foos with Header New";
}

此外,从 Spring 3.1 开始,带有headers属性的旧映射类型将自动转换为新的produces机制,因此结果将是相同的。 这是通过curl以相同的方式使用的:

curl -H "Accept:application/json" 
  http://localhost:8080/spring-rest/ex/foos

此外,*produces *还支持多个值:

@RequestMapping(
  value = "/ex/foos", 
  method = GET,
  produces = { "application/json", "application/xml" }
)

请记住,这些(指定Accept标头的新旧方法)基本上是相同的映射,因此 Spring 不允许它们一起使用。 激活这两种方法将导致:

Caused by: java.lang.IllegalStateException: Ambiguous mapping found. 
Cannot map 'fooController' bean method 
java.lang.String 
com.blogdemo.spring.web.controller.FooController.getFoosAsJsonFromREST()
to 
{ [/ex/foos],
  methods=[GET],params=[],headers=[],
  consumes=[],produces=[application/json],custom=[]
}: 
There is already 'fooController' bean method
java.lang.String 
com.blogdemo.spring.web.controller
  .FooController.getFoosAsJsonFromBrowser() 
mapped.

关于新producesconsumes机制的最后一点说明,其行为与大多数其他注释不同:在类型级别指定时,方法级别注释不会补充而是覆盖类型级别信息。

4. 带路径变量的 RequestMapping

映射 URI 的一部分可以通过*@PathVariable*注解绑定到变量。

4.1. 单个PathVariable 变量

带有单个路径变量的简单示例:

@RequestMapping(value = "/ex/foos/{id}", method = GET)
@ResponseBody
public String getFoosBySimplePathWithPathVariable(
  @PathVariable("id") long id) {
    return "Get a specific Foo with id=" + id;
}

这可以用curl测试:

curl http://localhost:8080/spring-rest/ex/foos/1

如果方法参数的名称与路径变量的名称完全匹配,则可以使用不带值的*@PathVariable*来简化

@RequestMapping(value = "/ex/foos/{id}", method = GET)
@ResponseBody
public String getFoosBySimplePathWithPathVariable(
  @PathVariable String id) {
    return "Get a specific Foo with id=" + id;
}

请注意,@PathVariable受益于自动类型转换,因此我们也可以将id声明为:

@PathVariable long id

4.2. 多个*@PathVariable变量*

更复杂的 URI 可能需要将 URI 的多个部分映射到多个值

@RequestMapping(value = "/ex/foos/{fooid}/bar/{barid}", method = GET)
@ResponseBody
public String getFoosBySimplePathWithPathVariables
  (@PathVariable long fooid, @PathVariable long barid) {
    return "Get a specific Bar with id=" + barid + 
      " from a Foo with id=" + fooid;
}

这很容易用curl以相同的方式进行测试:

curl http://localhost:8080/spring-rest/ex/foos/1/bar/2

4.3. 带有正则表达式*@PathVariable*

映射*@PathVariable*时也可以使用正则表达式。

例如,我们将映射限制为只接受id的数值:

@RequestMapping(value = "/ex/bars/{numericId:[\\d]+}", method = GET)
@ResponseBody
public String getBarsBySimplePathWithPathVariable(
  @PathVariable long numericId) {
    return "Get a specific Bar with id=" + numericId;
}

这意味着以下 URI 将匹配:

http://localhost:8080/spring-rest/ex/bars/1

但这不会:

http://localhost:8080/spring-rest/ex/bars/abc

5. 请求参数*@RequestMapping*

@RequestMapping 允许使用*@RequestParam*注解轻松映射 URL 参数

我们现在将请求映射到 URI:

http://localhost:8080/spring-rest/ex/bars?id=100
@RequestMapping(value = "/ex/bars", method = GET)
@ResponseBody
public String getBarBySimplePathWithRequestParam(
  @RequestParam("id") long id) {
    return "Get a specific Bar with id=" + id;
}

然后,我们使用控制器方法签名中的*@RequestParam(“id”)注释来提取id参数的值。 要发送带有id参数的请求,我们将使用curl*中的参数支持:

curl -i -d id=100 http://localhost:8080/spring-rest/ex/bars

在这个例子中,参数是直接绑定的,没有先声明。

对于更高级的场景,** @RequestMapping可以选择将参数定义**为缩小请求映射的另一种方式:

@RequestMapping(value = "/ex/bars", params = "id", method = GET)
@ResponseBody
public String getBarBySimplePathWithExplicitRequestParam(
  @RequestParam("id") long id) {
    return "Get a specific Bar with id=" + id;
}

允许更灵活的映射。可以设置多个*params *,而不是必须使用所有参数值:

@RequestMapping(
  value = "/ex/bars", 
  params = { "id", "second" }, 
  method = GET)
@ResponseBody
public String getBarBySimplePathWithExplicitRequestParams(
  @RequestParam("id") long id) {
    return "Narrow Get a specific Bar with id=" + id;
}

当然,还有对 URI 的请求,例如:

http://localhost:8080/spring-rest/ex/bars?id=100&second=something

将始终映射到最佳匹配——这是更窄的匹配,它定义了id和*second *参数。

6. RequestMapping案例

6.1. @RequestMapping — 映射到同一个控制器方法的多个路径

尽管单个*@RequestMapping*路径值通常用于单个控制器方法(只是一种好的做法,而不是硬性规定),但在某些情况下,可能需要将多个请求映射到同一个方法。

在这种情况下,** @RequestMappingvalue属性确实接受多个映射**,而不仅仅是一个:

@RequestMapping(
  value = { "/ex/advanced/bars", "/ex/advanced/foos" }, 
  method = GET)
@ResponseBody
public String getFoosOrBarsByPath() {
    return "Advanced - Get some Foos or Bars";
}

现在这两个curl命令都应该使用相同的方法:

curl -i http://localhost:8080/spring-rest/ex/advanced/foos
curl -i http://localhost:8080/spring-rest/ex/advanced/bars

6.2. @RequestMapping — 对同一个控制器方法的多个 HTTP 请求方法

使用不同 HTTP 动词的多个请求可以映射到同一个控制器方法:

@RequestMapping(
  value = "/ex/foos/multiple", 
  method = { RequestMethod.PUT, RequestMethod.POST }
)
@ResponseBody
public String putAndPostFoos() {
    return "Advanced - PUT and POST within single method";
}

使用curl,这两个现在都将使用相同的方法:

curl -i -X POST http://localhost:8080/spring-rest/ex/foos/multiple
curl -i -X PUT http://localhost:8080/spring-rest/ex/foos/multiple

6.3. @RequestMapping — 所有请求的后备

要使用特定 HTTP 方法为所有请求实现简单的回退,例如,对于 GET:

@RequestMapping(value = "*", method = RequestMethod.GET)
@ResponseBody
public String getFallback() {
    return "Fallback for GET Requests";
}

甚至对于所有请求:

@RequestMapping(
  value = "*", 
  method = { RequestMethod.GET, RequestMethod.POST ... })
@ResponseBody
public String allFallback() {
    return "Fallback for All Requests";
}

6.4. 模棱两可的映射错误

当 Spring 评估两个或多个请求映射对于不同的控制器方法相同时,会发生不明确的映射错误。当请求映射具有相同的 HTTP 方法、URL、参数、标头和媒体类型时,它是相同的。

例如,这是一个模棱两可的映射:

@GetMapping(value = "foos/duplicate" )
public String duplicate() {
    return "Duplicate";
}
@GetMapping(value = "foos/duplicate" )
public String duplicateEx() {
    return "Duplicate";
}

抛出的异常通常确实包含以下错误消息:

Caused by: java.lang.IllegalStateException: Ambiguous mapping.
  Cannot map 'fooMappingExamplesController' method 
  public java.lang.String com.blogdemo.web.controller.FooMappingExamplesController.duplicateEx()
  to {[/ex/foos/duplicate],methods=[GET]}:
  There is already 'fooMappingExamplesController' bean method
  public java.lang.String com.blogdemo.web.controller.FooMappingExamplesController.duplicate() mapped.

仔细阅读错误消息会发现 Spring 无法映射方法 com.blogdemo.web.controller.FooMappingExamplesController.duplicateEx(),因为它与已映射的 com.blogdemo.web.controller的映射存在冲突.FooMappingExamplesController.duplicate()

下面的代码片段不会导致不明确的映射错误,因为两种方法都返回不同的内容类型

@GetMapping(value = "foos/duplicate", produces = MediaType.APPLICATION_XML_VALUE)
public String duplicateXml() {
    return "<message>Duplicate</message>";
}

@GetMapping(value = "foos/duplicate", produces = MediaType.APPLICATION_JSON_VALUE)
public String duplicateJson() {
    return "{\"message\":\"Duplicate\"}";
}

这种区分允许我们的控制器根据请求中提供的Accepts标头返回正确的数据表示 。

解决此问题的另一种方法是更新分配给所涉及的两种方法中的任何一种的 URL。

7. 新的请求映射快捷方式

Spring Framework 4.3 引入了一些新的 HTTP 映射注解,全部基于*@RequestMapping*:

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

这些新的注释可以提高可读性并减少代码的冗长。

让我们通过创建一个支持 CRUD 操作的 RESTful API 来看看这些新注释的实际应用:

@GetMapping("/{id}")
public ResponseEntity<?> getBazz(@PathVariable String id){
    return new ResponseEntity<>(new Bazz(id, "Bazz"+id), HttpStatus.OK);
}
@PostMapping
public ResponseEntity<?> newBazz(@RequestParam("name") String name){
    return new ResponseEntity<>(new Bazz("5", name), HttpStatus.OK);
}
@PutMapping("/{id}")
public ResponseEntity<?> updateBazz(
  @PathVariable String id,
  @RequestParam("name") String name) {
    return new ResponseEntity<>(new Bazz(id, name), HttpStatus.OK);
}
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteBazz(@PathVariable String id){
    return new ResponseEntity<>(new Bazz(id), HttpStatus.OK);
}

可以在这里 找到对这些的深入了解。

8. Spring配置

考虑到我们的FooController定义在以下包中,Spring MVC 配置非常简单:

package com.blogdemo.spring.web.controller;
@Controller
public class FooController { ... }

我们只需要一个*@Configuration*类来启用完整的 MVC 支持并为控制器配置类路径扫描:

@Configuration
@EnableWebMvc
@ComponentScan({ "com.blogdemo.spring.web.controller" })
public class MvcConfig {
    //
}