201902W9 Review, 一篇关于Spring RequestMapping的译文。
Spring RequestMapping
1. 概述
在这篇文章中,我们将关注于这个Spring MVC中常用的注释 - @RequestMapping。
简单地说,这个注释用于将网络请求映射到Spring Controller的方法。
2. @RequestMapping基础
让我们从一个简单的例子开始 - 按照一些基本的标准将一个HTTP请求映射到一个方法。
2.1. @RequestMapping - 按路径
1 |
|
要使用简单的curl命令测试此映射,请运行:1
curl -i http://localhost:8080/spring-rest/ex/foos
2.2. @RequestMapping - HTTP方法
HTTP方法参数没有默认值 - 因此,如果我们不指定值,它将映射到任何HTTP请求。
这是一个简单的示例,类似于前一个示例 - 但这次映射到一个HTTP POST请求:1
2
3
4
5
public String postFoos() {
return "Post some Foos";
}
要通过curl命令测试POST :1
curl -i -X POST http://localhost:8080/spring-rest/ex/foos
3. RequestMapping 和 HTTP Headers
3.1. 带有headers Attribute的@RequestMapping
给请求指定一个header,可以进一步限制映射范围。1
2
3
4
5
public String getFoosWithHeader() {
return "Get some Foos with Header";
}
为了测试运行,我们将使用curl的header参数1
curl -i -H "key:val" http://localhost:8080/spring-rest/ex/foos
更进一步,通过 @RequestMapping的 header属性实现多个header1
2
3
4
5
6
7
public String getFoosWithHeaders() {
return "Get some Foos with Header";
}
可以通过命令测试这个方法1
curl -i -H "key1:val1" -H "key2:val2" http://localhost:8080/spring-rest/ex/foos
注意,对于curl语法,用冒号分离header的键和值,与HTTP中的规范相同,而在Spring中是用等号。
3.2. @RequestMapping 消费和生产
映射由Controller产生的媒体类型的方法特别值得注意 - 我们可以通过上面介绍的 @RequestMapping 的headers属性,基于Accept header映射请求。1
2
3
4
5
6
7
8
public String getFoosAsJsonFromBrowser() {
return "Get some Foos with Header Old";
}
匹配这种定义Accept header的方法是灵活的 - 是用包含而不是等于,所以一个下面这样的请求也能正确地匹配。1
curl -H "Accept:application/json,text/html" http://localhost:8080/spring-rest/ex/foos
从Spring 3.1开始, @RequestMapping注释具有produces和consumes属性,特别是为了这种:1
2
3
4
5
6
7
8
9
public String getFoosAsJsonFromREST() {
return "Get some Foos with Header New";
}
此外,带有headers属性的旧类型的映射将自动转换成Spring 3.1开始的新produces机制,所以结果是相同的。
同样的方式,通过curl进行consume:1
curl -H "Accept:application/json" http://localhost:8080/spring-rest/ex/foos
另外,produces也支持多值:1
2
3
4
5
记住这些 - 指定accept header的旧的方法和新的方法 - 基本上是相同的映射,所以Spring不允许它们一起使用 - 一起用将会导致:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Caused by: java.lang.IllegalStateException: Ambiguous mapping found.
Cannot map 'fooController' bean method
java.lang.String
org.baeldung.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
org.baeldung.spring.web.controller
.FooController.getFoosAsJsonFromBrowser()
mapped.
关于新的produces和consumes机制的最后一点 - 与其他注释表现不同的是 - 当指定类型级别时,方法级别的注释不会补充而会覆盖类型级别的信息。
这句没理解,原文
when specified at the type level, the method level annotations do not complement but override the type level information.
当然,如果你想深入了解用Spring构建REST API - 请看the new REST with Spring course。
4. RequestMapping使用路径变量
映射URI的一部分可以通过 @PathVariable注解绑定到变量。
4.1 单个 @PathVariable
一个简单的单路径变量例子:1
2
3
4
5
6
public String getFoosBySimplePathWithPathVariable(
long id) {
return "Get a specific Foo with id=" + id;
}
可以用curl测试:1
curl http://localhost:8080/spring-rest/ex/foos/1
如果方法的参数名和路径名相同,可以只用 @PathVariable 而不附加值:1
2
3
4
5
6
public String getFoosBySimplePathWithPathVariable(
{ String id)
return "Get a specific Foo with id=" + id;
}
注意 @PathVariable 受利于自动类型转换,所以我们也可以修饰id为:1
long id
4.2 多个 @PathVariable
更复杂的URI可能需要映射URI的多个部分到多个值:1
2
3
4
5
6
7
public String getFoosBySimplePathWithPathVariables
(long fooid, long barid) {
return "Get a specific Bar with id=" + barid +
" from a Foo with id=" + fooid;
}
同样这可以用curl容易的测试:1
curl http://localhost:8080/spring-rest/ex/foos/1/bar/2
4.3 带正则表达式的 @PathVariable
正则表达式也能用来映射 @PathVariable;举个例子,我们可以限制映射id只接受数字类型的值:1
2
3
4
5
6
public String getBarsBySimplePathWithPathVariable(
long numericId) {
return "Get a specific Bar with id=" + numericId;
}
这将意味着下面的URI可以适配:1
http://localhost:8080/spring-rest/ex/bars/1
但这个不能:1
http://localhost:8080/spring-rest/ex/bars/abc
5. RequestMapping使用Request Parameters
@RequestMapping允许方便的使用 @RequestParam注解映射URL参数。
我们现在映射一个这样的URI请求:1
http://localhost:8080/spring-rest/ex/bars?id=100
1 |
|
我们接着在controller方法中使用 @RequestParam(“id”) 注解取出id参数的值。
要发送带id参数的请求,我们在curl中使用参数支持:1
curl -i -d id=100 http://localhost:8080/spring-rest/ex/bars
在这个例子中,参数直接绑定而不先声明。
对于更进一步的场景, @RequestMapping有可选的参数定义 - 作为又一个限制请求映射的方法:1
2
3
4
5
6
public String getBarBySimplePathWithExplicitRequestParam(
long id) {
return "Get a specific Bar with id=" + id;
}
甚至可以更灵活的映射 - 可以设置多个params值,并且不是所有都使用:1
2
3
4
5
6
7
8
9
public String getBarBySimplePathWithExplicitRequestParams(
long id) {
return "Narrow Get a specific Bar with id=" + id;
}
当然,一个像这样的请求URI:1
http://localhost:8080/spring-rest/ex/bars?id=100&second=something
将总会被映射到最好的适配 - 更进一步的适配,同时定义id和second参数。
6. RequestMapping Corner Cases
6.1. @RequestMapping – 多个路径映射到同一Controller方法
虽然一个单个 @RequestMapping路径值通常用于单个controller方法, 但这只是一种好的用法,而不是强制规则 - 有一些情况下,多个请求到同一方法的可能是必要的。在那种情况下, @RequestMapping的value属性接收多个映射,而不是一个映射:1
2
3
4
5
6
7
public String getFoosOrBarsByPath() {
return "Advanced - Get some Foos or Bars";
}
现在,这两个curl命令应该使用相同的方法:1
2curl -i http://localhost:8080/spring-rest/ex/advanced/foos
curl -i http://localhost:8080/spring-rest/ex/advanced/bars
6.2. @RequestMapping – 多个HTTP请求方法到同一个controller方法
用不同HTTP请求方式的多个请求可以被映射到同一个Controller方法:1
2
3
4
5
6
7
8
public String putAndPostFoos() {
return "Advanced - PUT and POST within single method";
}
用curl,这些请求都被打到同一个方法:1
2curl -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方法:1
2
3
4
5
public String getFallback() {
return "Fallback for GET Requests";
}
或者对所有请求:1
2
3
4
5
6
7
public String allFallback() {
return "Fallback for All Requests";
}
6.4 模糊映射错误
模糊映射错误发生与:当Spring评估两个或多个请求映射对于不同的Controller方法是相同的。当两个请求映射有同样的HTTP方法、URL、参数、头部和媒体类型时,它们就是相同的。例如,这是一个模糊映射:1
2
3
4
5
6
7
8
9
public String duplicate() {
return "Duplicate";
}
public String duplicateEx() {
return "Duplicate";
}
抛出的异常通常在这些行有异常信息:1
2
3
4
5
6Caused by: java.lang.IllegalStateException: Ambiguous mapping.
Cannot map 'fooMappingExamplesController' method
public java.lang.String org.baeldung.web.controller.FooMappingExamplesController.duplicateEx()
to {[/ex/foos/duplicate],methods=[GET]}:
There is already 'fooMappingExamplesController' bean method
public java.lang.String org.baeldung.web.controller.FooMappingExamplesController.duplicate() mapped.
仔细阅读错误信息,指出Spring不能映射方法org.baeldung.web.controller.FooMappingExamplesController.duplicateEx() ,因为它与一个已经映射的org.baeldung.web.controller.FooMappingExamplesController.duplicate() 有冲突。
下面这个代码段将不会产生模糊映射错误,因为两个方法返回不同的内容类型:1
2
3
4
5
6
7
8
9
public String duplicate() {
return "Duplicate";
}
public String duplicateEx() {
return "Duplicate";
}
7. 新的请求映射捷径
Spring Framework 4.3引入了一些新的HTTP映射注释,所有这些都基于 @RequestMapping :
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
- @PatchMapping
这些新的注解可以提高可读性,减少代码冗长。让我们看看这些新的注解在创建一个支持CRUD操作RESTFUL API的实例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public ResponseEntity<?> getBazz( String id){
return new ResponseEntity<>(new Bazz(id, "Bazz"+id), HttpStatus.OK);
}
public ResponseEntity<?> newBazz( String name){
return new ResponseEntity<>(new Bazz("5", name), HttpStatus.OK);
}
public ResponseEntity<?> updateBazz(
String id,
String name) {
return new ResponseEntity<>(new Bazz(id, name), HttpStatus.OK);
}
public ResponseEntity<?> deleteBazz( String id){
return new ResponseEntity<>(new Bazz(id), HttpStatus.OK);
}
深入了解这些可以在这里找到。
8. Spring配置
Spring MVC的配置足够简单 - 考虑到我们的FooController被定义在接下来的包里:1
2
3
4package org.baeldung.spring.web.controller;
public class FooController { ... }
我们只用一个 @Configuration 去启用完整的MVC支持并为控制器配置类路径扫描:1
2
3
4
5
6
public class MvcConfig {
//
}
9. 结论
这篇文章专注于Spring的 @RequestMapping 注解 - 讨论一个简单的用例, HTTP头的映射, @PathVariable 绑定URI部分, 使用URL参数工作, 和使用 @RequestParam 注解.
如果你想学习如何使用另一个Spring MVC的核心注解, 你可以在这浏览 @ModelAttribu 注解.
这篇文章的全部代码在Github可见. 这是一个Maven项目, 所以它可以很容易被导入和运行.