一段典型的后端 Controller
层的代码
@RestController
public class HTTPController {
@GetMapping("get/params")
public HashMap<String, Integer> queryData(@RequestParam Integer pageNo, @RequestParam Integer pageSize) {
log.info("pageNo {}, pageSize {}", pageNo, pageSize);
HashMap<String,Integer> info = new HashMap<>();
info.put("pageNo",pageNo);
info.put("pageSize", pageSize);
return info;
}
}
这段代码中涉及到了三个注解的使用 @RestController
,@GetMapping
, @RequestParam
,也分别对应了注解的三个使用层面,类相关注解,方法注解,以及方法参数注解。
这篇文章目的是希望能详细了解三种使用层面上的不同注解,搞清楚不同注解的适用场景。
# 类相关的注解
# @Controller
@Controller
注解官方的解释 (opens new window)是,它表示被注解的这个类是一个控制器类(比如声明可以返回 HTTP 响应的 Web 控制器)。
但是如果将类只用 @Controller
注释的话,访问方法对应的路径还是会报 404 ,这是为什么?因为只使用 @Conroller
,返回值默认是一个视图名称,Spring 会通过这个视图名称查找相关连的视图来呈现,这个视图配置过程需要单独进行,如果找不到对应视图的话,则会导致 404。
视图配置流程可以参考这里 (opens new window),这里不再赘述了。
现代的业务开发里前后端都是分离的,一般在和前端对接的时候,后端只需要返回前端需要的数据即可,具体的页面逻辑是前端自己完成的,所以我们想要返回数据应该怎么办呢?
答案是可以增加一个 @ResponseBody
,这个注解表示方法返回值应绑定到 Web 响应正文。比如下面这个方法里,最终只返回一个字符串:
// 控制器部分
@Controller
@ResponseBody
public class HTTPController {
@GetMapping("get/pure")
public String queryData() {
return "query";
}
}
// 网络请求
curl "http://localhost:8083/get/pure"
// 响应结果
query
# @RestController
@RestController
注解官方的解释 (opens new window)是,它是 @Controller
和 @ResponseBody
的组合的一种简便写法。
像上面的例子中 将 @Contoller
和 @ResponseBody
替换为 @RestController
之后,返回的结果并不会变。
@RestController
还有一个额外的好处是,如果你返回的是对象、字典或者数组,则返回值会自动变为 JSON 的格式,比如如下方法
@GetMapping("get/person")
public PersonResponse queryPerson() {
PersonResponse personResponse = new PersonResponse();
personResponse.setName("fanthus");
personResponse.setId(1);
return personResponse;
}
public class PersonResponse {
private Integer id;
private String name;
}
请求后得到的响应为👇
{"id":1,"name":"fanthus"}
# @Controller VS @RestController
@Controller
返回一个视图页面,在前后端不分离的年代,经常用到。
@RestController
是 @Contoller
和 @ResponseBody
的组合,它返回普通数据,并将数据写入到 HTTP 响应的 body 中,同时它对于复杂数据会进行序列化。
现在开发里,基本上所有正常返回数据的控制器,都需要增加 @RestController
这个注解。
# 方法相关注解
# @RequestMapping
@RequestMapping (opens new window) 表示将 Web 请求映射到请求处理类中对应方法。
@RequestMapping
也可以用在类级别上(类似 @Contorlller),不过大部分时候还是用在方法层级上。
那如何让一个方法表示成是 GET 方法以及指定请求对应路径呢?@RequestMapping
提供了一系列参数来指定这些 HTTP 请求参数。
参数列表有 method
, name
, value
, path
, headers
, params
, produces
, consumes
.
method
标识对应的请求方法。最常见的请求方法就是RequestMethod.GET
,RequestMethod.POST
。value
和path
都表示 URL 请求路径,这俩可以互相替代。value
是默认属性,可以省略不写。所以我们常见的这种写法 @RequestMapping("/pets") 其实是省略了参数名的。而且根据 @RequestMapping (opens new window) 的官方文档,可以给这两个参数提供数组,即同一个方法可以匹配多个路径。
params
限制请求参数,比如这种写法@RequestMapping(method = RequestMethod.GET, path = {"/"} , params = {"id=1"})
就会限制请求带的参数 id 只能为 1,如果带别的参数,或者不带参数都会报 HTTP 400 请求错误。headers
限制请求头。consumes
显示请求内容类型,即请求头中的Content-Type
值。produces
限制响应内容类型。
上面这些参数组合起来能控制请求的处理,举个例子
@RequestMapping(method = RequestMethod.GET, path = {"requestMapping/get", "requestMapping/get2"} , params = {"id=1"}, headers = "Content-Type=application/json")
public String reqestMapping() {
return "request mapping";
}
// 上面这个方法只能通过 curl 命令来请求
- curl "http://localhost:8083/requestMapping/get?id=1" \
-H 'Content-Type: application/json'
- curl "http://localhost:8083/requestMapping/get2?id=1" \
-H 'Content-Type: application/json'
有一个name属性没有介绍,不过这个用的不多,经常用到的场景是用于文档生成。
# @GetMapping
@GetMapping (opens new window) 表示注释的方法对应的 HTTP 请求为 GET 请求。
本质上 @GetMapping
是 @RequestMapping(method = RequestMethod.GET)
的缩写形式。所以 @GetMapping
也能复用 @RequestMapping
的参数列表。
# @PostMapping
@PostMapping (opens new window) 表示注释的方法对应的 HTTP 请求为 POST 请求。
本质上 @PostMapping
是 @RequestMapping(method = RequestMethod.POST)
的缩写形式。具体使用方式可以参考 @GetMapping
。
# 参数相关注解
说完了方法上的注解,接下来聊聊方法参数内的注解,比较常用就是
# @RequestParam
@RequestParams (opens new window) 这个注解表示Web 请求参数绑定到方法参数。
在 Spring MVC 中,请求参数可能是 query parameters(查询参数)、form data(表单数据)和 multipart requests 中的一个,所以这个参数不光能用在 GET 请求,也能用在 POST 请求中。
这个注解也有自己的参数设置,如下
name
。要绑定到的请求参数的名称,即如果真实方法参数名是pageNo
,你想要映射到的 HTTP 请求参数里的参数名是pageStart
,则可以用name
属性。例子如下public String queryData(@RequestParam(name = "pageStart") Integer pageNo, @RequestParam Integer pageSize) 上面这个方法对应的请求命令如下 curl "http://localhost:8083/get/params?pageSize=200&pageStart=1"
required
。表示这个参数是否是必传的,这个注解参数经常会用到,比如用户登录的时候,用户名和密码这些都是必填的,我们可以通过这个参数来对 HTTP 请求参数进行验证。defaultValue
。当未提供请求参数或请求参数为空值时用作后备的默认值,如果提供了这个值,则会将required
参数设置为 false。
有的时候我们进行一些数据批量查询的时候,可能会传给服务器一个数组列表,比如 curl "http://localhost:8083/get/paramArray?params=200,300,number,400 (opens new window)" 这种命令,那应该怎么设计参数列表,才能接收这样的参数值呢?可以将参数设置为字符数组类型,如下
public String queryParamArray(@RequestParam List<String > params)
我觉得最好是设置为字符串数组,因为足够通用,如果确保传过来的就是数字,则
List<Integer>
类型也行,但如果不是的话,就可能报异常了。
有的时候请求参数非常多,我们希望能将这些请求参数封装成一个类,怎么做?直接封装即可。例子如下
// 参数封装成类
public class PageGetRequest {
private Integer pageNo;
private Integer pageSize;
}
// 直接将参数类型声明为对应类就 OK
@GetMapping("get/paramModel")
public String queryData(PageGetRequest requestGetModel) {
log.info("pageNo {}, pageSize {}", requestGetModel.getPageNo(), requestGetModel.getPageSize());
return "hello";
}
注意上面的方法参数前面并没有加 @RequestParam
注解,如果加上注解则会报错如下,目测是不支持的参数类型。
Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'requestGetModel' for method parameter type PageGetRequest is not present]
@RequestParam
有的时候并不是必须的,因为 Spring Boot 提供了自动参数解析的功能。Spring Boot 可以自动解析请求中的查询参数并将它们绑定到方法参数,如果HTTP 请求参数名和方法参数名保持一致的话,则无需显式使用 @RequestParam
注解。这个规则不仅适用于 GET 请求,也适用于 POST 的表单请求。不过如果你想要 @RequestParam
的别的参数,那还是要显式的去写注解。
# RequestBody
@RequestBody (opens new window) 表示方法参数应绑定到 HTTP 请求体 body。所以 @RequestBody 基本上不会用 GET 请求中。
@RequestBody (opens new window) 注解也有参数,不过比 @RequestParam
参数少,只有一个 required
参数,含义和 @RequestParam
中的 required
一样。
下面这个例子展示了使用 application/json
做请求类型的 POST 请求的执行情况
// curl 命令
curl -X "POST" "http://localhost:8083/post/json" \
-H 'Content-Type: application/json; charset=utf-8' \
-d $'{
"name": "fanthus"
}'
// POST 请求
@PostMapping("post/json")
public String postJSON(@RequestBody String jsonStr) {
log.info("content {}", jsonStr);
return "hello";
}
// 请求后方法打印内容
content {"name":"fanthus"}
表单请求也类似,POST 请求方法不需要改变,只需要将请求命令替换为 POST 表单请求即可,但是如果参数类型不是字符串,而是对象的话,表单请求的参数不能使用 @RequestBody 去接。 否则报错 Unsupported Media Type..
POST 请求体通常都会映射到一个对象,如果我们按照上面的写法,我们需要自己去进行字符串转对象的操作,但我们通常不会这么做,我们希望在方法里直接拿到 POST 请求后,直接拿到对象实例,这时候只需要将 POST 请求体封装成对象形式,然后直接接收就行,例子如下
// 将请求体映射为对象
public class PagePostRequest {
private String name;
}
// 参数类型替换
@PostMapping("post/jsonModel")
public String postJSONModel(@RequestBody **PagePostRequest** jsonRequest) {
log.info("request {}, name {}", jsonRequest, jsonRequest.getName());
return "hello";
}
// 请求内容不变,对应执行结果如下
request PagePostRequest(name=fanthus), name fanthus
# @RequestParam VS @RequestBody
这两个注解比较容易混淆,我刚接触的时候就有点晕,不过想要区分也很简单。
@RequestBody
基本上是用于application/json
这种类型的 POST 请求的,因为它是将请求体绑定了方法参数。而且通常@RequestBody
附带的参数是个对象,因为它类型直接映射请求体会比较复杂。@RequestParam
是用户 GET 请求,附加表单和 multipart 的 POST 请求,不支持application/json
类型的 POST 请求。
我们实现的时候如果按照 application/json
类型POST 请求用 @RequestBody
,其余请求用 @RequestParam
则一般就不会出错。
大家如果觉得这部分我说的有问题,可以在评论里指出来。
# @RequestHeader
@RequestHeader (opens new window) 是Spring MVC中的注解之一,用于从HTTP请求头中提取信息并将其绑定到方法参数。 通常用法如下
@GetMapping("get/requestHeader")
public String getRequestHeader(@RequestHeader HashMap<String, Object> header) {
log.info("getRequestHeader {}",header);
return "hello";
}
// 执行请求命令
curl "http://localhost:8083/get/requestHeader?pageSize=200&pageNo=1"
// 打印结果如下
getRequestHeader {host=localhost:8083, connection=close, user-agent=RapidAPI/4.2.0 (Macintosh; OS X/13.6.0) GCDHTTPRequest}
# @Valid
这个注解也经常用到,主要是校验对象参数,它需要单独引入一个库,如下
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
@Valid 主要是配合一些对象内部的属性修饰符使用,比如下面这个例子
// 请求内容
public class ValidRequest {
@NotNull
private String name;
@AssertFalse
private boolean hasVote;
@Min(1)
private Integer voteNumber;
@NotBlank(message="原因不能为空")
private String reason;
}
// 获取数据
@GetMapping("get/valid")
public String getValid(@Valid ValidRequest requst) {
log.info("request {}", requst);
return "hello";
}
// 符合条件的请求
curl "http://localhost:8083/get/valid?name=fanthus&hasVote=false&voteNumber=2&reason=hello"
// 执行结果
request ValidRequest(name=fanthus, hasVote=false, voteNumber=2, reason=hello)
注意的是 @Valid 并不限制 GET 或者 POST 请求。上面的例子是 GET 请求,如果是 POST 请求的话,则方法如下
@PostMapping("post/valid")
public String postValid(@RequestBody @Valid ValidRequest requst)
注意 @Valid 不要和 @Validated 搞混。
如果因为一些原因不能使用 @RequestParam
,则使用 @Valid+@NotNull
组合可以替代 @RequestParam
中的 required = true
的设置。
# End
以上就是我总结的一些 Spring 中 HTTP 相关注解的使用方法,只是从使用层面梳理了下,并没有深入到原理层,不过对于初学者应该够用了。
参考地址:
- @RestController vs @Controller (opens new window)
- What is difference between @RequestBody and @RequestParam? (opens new window)
- What does the @Valid annotation indicate in Spring? (opens new window)
- SpringBoot 中使用 @Valid 注解 + Exception 全局处理器优雅处理参数验证 (opens new window)
关注我的微信公众号,我在上面会分享我的日常所思所想。