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
甚至通过@RequestMapping的headers属性使用多个标头:
@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
cn.tuyucheng.taketoday.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
cn.tuyucheng.taketoday.spring.web.controller
.FooController.getFoosAsJsonFromBrowser()
mapped.
关于新的生产和消费机制的最后一点说明,它们的行为与大多数其他注解不同:在类型级别指定时,方法级别的注解不会补充而是覆盖类型级别的信息。
当然,如果你想更深入地了解如何使用Spring构建REST API,请查看新的REST与Spring课程。
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和第二个参数。
6. RequestMapping边角案例
6.1 @RequestMapping-映射到同一控制器方法的多个路径
尽管单个@RequestMapping路径值通常用于单个控制器方法(只是良好实践,不是硬性规定),但在某些情况下可能需要将多个请求映射到同一方法。
在这种情况下,@RequestMapping的值属性确实接受多个映射,而不仅仅是一个:
@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 cn.tuyucheng.taketoday.web.controller.FooMappingExamplesController.duplicateEx()
to {[/ex/foos/duplicate],methods=[GET]}:
There is already 'fooMappingExamplesController' bean method
public java.lang.String cn.tuyucheng.taketoday.web.controller.FooMappingExamplesController.duplicate() mapped.
仔细阅读错误消息指出Spring无法映射方法cn.tuyucheng.taketoday.web.controller.FooMappingExamplesController.duplicateEx(),因为它与已映射的cn.tuyucheng.taketoday.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 cn.tuyucheng.taketoday.spring.web.controller;
@Controller
public class FooController { ... }
我们只需要一个@Configuration类来启用完整的 MVC 支持并为控制器配置类路径扫描:
@Configuration
@EnableWebMvc
@ComponentScan({"cn.tuyucheng.taketoday.spring.web.controller"})
public class MvcConfig {
//
}
9. 总结
本文重点介绍了Spring中的@RequestMapping注解,讨论了一个简单的用例、HTTP标头的映射、使用@PathVariable绑定部分URI,以及使用URI参数和@RequestParam注解。
如果你想了解如何在Spring MVC中使用另一个核心注解,可以在此处探索@ModelAttribute注解。
与往常一样,本教程的完整源代码可在GitHub上获得。