SpringMVC常用注解(前后端分离)
1 Spring MVC的职责
说明:本文中框架直接使用Spring Boot,因此除了特别说明,都使用默认配置。并且只讲解相关操作,不涉及深入的原理。
我们可以将前后端开发中的各个组成部分做一个抽象,它们之间的关系如下图所示:
在浏览器-服务器的交互过程中,Spring MVC起着“邮局”的作用。它一方面会从浏览器接收各种各样的“来信”(HTTP请求),并把不同的请求分发给对应的服务层进行业务处理;另一方面会发送“回信”(HTTP响应),将服务器处理后的结果回应给浏览器。
因此,开发人员就像是“邮递员”,主要需要完成三方面工作:
- 指定分发地址:使用
@RequestMapping
等注解指定不同业务逻辑对应的URL。 - 接收请求数据:使用
@RequestParam
等注解接收不同类型的请求数据。 - 发送响应数据:使用
@ResponseBody
等注解发送不同类型的响应数据。
本文涉及到的相关依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
在介绍Spring MVC这三方面的工作内容之前,我们先来看一下如何使用@Controller
或@RestController
标注XxxController
类。
@Controller
:
package com.xianhuii.controller;
import org.springframework.stereotype.Controller;
@Controller
public class StudentController {
}
最基础的做法是使用@Controller
注解将我们的XxxController
类声明为Spring容器管理的Controller,其源码如下。
package org.springframework.stereotype;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(annotation = Component.class)
String value() default "";
}
@Controller
的元注解是@Component
,它们的功能相同,只不过@Controller
显得更加有语义,便于开发人员理解。
此外,需要注意的是@Controller
头上@Target
的值是ElementType.Type
,说明它只能标注在类上。
@Controller
有且仅有一个value
属性,该属性指向@Component
注解,用来指示对应的beanName
。如果没有显式指定该属性,Spring的自动检测组件会将首字母小写的类名设置为beanName
。即上面实例代码StudentController
类的beanName
为studentController
。
@RestController
:
package com.xianhuii.controller;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class StudentController {
}
在前后端分离的开发环境下,@RestController
是开发人员更好的选择。它除了具有上述@Controller
声明Controller的功能外,还可以自动将类中所有方法的返回值绑定到HTTP响应体中(而不再是视图相关信息),其源码如下。
package org.springframework.web.bind.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Controller;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(
annotation = Controller.class
)
String value() default "";
}
@RestController
的元注解包括@Controller
和@ResponseBody
,分别起着声明Controller和绑定方法返回值的作用。
此外,需要注意的是@RestController
头上@Target
的值也是ElementType.Type
,说明它只能标注在类上。
@Controller
有且仅有一个value
属性,该属性指向@Controller
注解(最终指向@Component
),用来指示对应的beanName
。如果没有显式指定该属性,Spring的自动检测组件会将首字母小写的类名设置为beanName
。即上面实例代码StudentController
类的beanName
为studentController
。
2 指定分发地址
映射请求分发地址的注解以@Mapping
为基础,并有丰富的实现:
2.1 @RequestMapping
2.1.1 标注位置
@RequestMapping
是最基础的指定分发地址的注解,它既可以标注在XxxController
类上,也可以标注在其中的方法上。理论上有三种组合方式:类、方法和类+方法。但是,实际上只有后面两种方式能起作用。
- 仅标注在方法上:
@RestController
public class StudentController {
@RequestMapping("/getStudent")
public Student getStudent() {
// 简单模拟获取student流程
return new Student("Xianhuii", 18);
}
}
此时,@RequestMapping
的/getStudent
属性值表示相对于服务端套接字的请求地址。
从浏览器发送GET http://localhost:8080/getStudent
请求,会得到如下响应,响应体是Student
对象的JSON字符串:
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 02 May 2021 13:23:02 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{
"name": "Xianhuii",
"age": 18
}
- 类+方法:
@RequestMapping("/student")
@RestController
public class StudentController {
@RequestMapping("/getStudent")
public Student getStudent() {
// 简单模拟获取student流程
return new Student("Xianhuii", 18);
}
}
此时,标注在类上的@RequestMapping
是内部所有方法分发地址的基础。因此,getStudent()
方法的完整分发地址应该是/student/getStudent
。
从浏览器发送GET http://localhost:8080/student/getStudent
请求,会得到如下响应,响应体是Student
对象的JSON字符串:
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 02 May 2021 13:26:57 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{
"name": "Xianhuii",
"age": 18
}
- 仅标注在类上(注意:此方式不起作用):
@RequestMapping("/student")
@RestController
public class StudentController {
public Student getStudent() {
// 简单模拟获取student流程
return new Student("Xianhuii", 18);
}
}
我们仅将@RequestMapping
标注在StudentController
类上。需要注意的是,这种标注方式是错误的,服务器不能确定具体的分发方法到底是哪个(尽管我们仅定义了一个方法)。
如果从浏览器发送GET http://localhost:8080/student
请求,会得到如下404的响应:
HTTP/1.1 404
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 02 May 2021 13:36:56 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{
"timestamp": "2021-05-02T13:36:56.056+00:00",
"status": 404,
"error": "Not Found",
"message": "",
"path": "/student"
}
以上介绍了@RequestMapping
的标注位置,在此做一个小结:
@RequestMapping
的标注方式有两种:方法或类+方法。- 如果将
@RequestMapping
标注在类上,那么该value
属性值是基础,实际的分发地址是类和方法上@RequestMapping
注解value
属性值的拼接。如果类和方法上@RequestMapping
注解value
属性值分别为/classValue
和/methodValue
,实际分发地址为/classValue/methodValue
。 - 分发地址相对于服务器套接字。如果服务器套接字为
http://localhost:8080
,分发地址为/student
,那么对应的HTTP请求地址应该是http://localhost:8080/student
。
2.1.2 常用属性
@RequestMapping
的属性有很多,但是常用的只有value
、path
和method
。其中value
和path
等价,用来指定分发地址。method
则用来指定对应的HTTP请求方式。
1、value
和path
对于value
和path
属性,它们的功能其实我们之前就见到过了:指定相对于服务器套接字的分发地址。要小心的是在类上是否标注了@RequestMapping
。
如果@RequestMapping
不显式指定属性名,那么默认是value
属性:
@RequestMapping("student")
当然我们也可以显式指定属性名:
@RequestMapping(value = "student")
@RequestMapping(path = "student")
需要注意的是value
和path
属性的类型是String[]
,这表示它们可以同时指定多个分发地址,即一个方法可以同时处理多个请求。如果我们指定了两个分发地址:
@RestController
public class StudentController {
@RequestMapping(path = {"student", "/getStudent"})
public Student getStudent() {
// 简单模拟获取student流程
return new Student("Xianhuii", 18);
}
}
此时,无论浏览器发送GET http://localhost:8080/student
或GET http://localhost:8080/getStudent
哪种请求,服务器斗殴能正确调用getStudent()
方法进行处理。最终都会得到如下响应:
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 02 May 2021 14:06:47 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{
"name": "Xianhuii",
"age": 18
}
我们对value
和path
属性做一个小结:
- 在不显式声明属性名的时候,默认为
value
属性,如@RequestMapping("/student")
等价于@RequestMapping(value = "/student")
。 - 在声明多个
@RequestMapping
的属性时,必须显式指出value
属性名,如@RequestMapping(value = "student", method = RequestMethod.GET)
。 value
和path
等价,如@RequestMapping(value = "/student")
等价于@RequestMapping(path = "/student")
。value
和path
属性的类型是String[]
,一般至少为其指定一个值。在指定多个值的情况下,需要用{}
将值包裹,如@RequestMapping({"/student", "/getStudent"})
,此时表示该方法可以处理的所有分发地址。- 需要注意类上是否标注了
@RequestMapping
,如果标注则为分发地址的基础,具体方法的实际分发地址需要与之进行拼接。 - 此外,在某些情况下,
@RequestMapping
的作用不是指定分发地址,可以不指定该属性值。
2、method
method
属性用来指定映射的HTTP请求方法,包括GET
、POST
、HEAD
、OPTIONS
、PUT
、PATCH
、DELETE
和TRACE
,分别对应RequestMethod
枚举类中的不同值:
package org.springframework.web.bind.annotation;
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
method
属性的类型是RequestMethod[]
,表明其可以声明零个、一个或多个RequestMethod
枚举对象。
- 零个
RequestMethod
枚举对象:
@RestController
public class StudentController {
@RequestMapping("student")
public Student getStudent() {
// 简单模拟获取student流程
return new Student("Xianhuii", 18);
}
}
当没有为method
属性指定明确的RequestMethod
枚举对象时(即默认情况),表明该方法可以映射所有HTTP请求方法。此时,无论是GET http://localhost:8080/student
还是POST http://localhost:8080/student
请求,都可以被getStudent()
方法处理:
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 02 May 2021 15:12:44 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{
"name": "Xianhuii",
"age": 18
}
- 一个
RequestMethod
枚举对象:
@RestController
public class StudentController {
@RequestMapping(value = "student", method = RequestMethod.GET)
public Student getStudent() {
// 简单模拟获取student流程
return new Student("Xianhuii", 18);
}
}
当显式为method
属性指定某个RequestMethod
枚举类时(这个例子中是RequestMethod.GET
),表明该方法只可以处理对应的HTTP请求方法。此时,GET http://localhost:8080/student
请求可以获得与前面例子中相同的正确响应。而POST http://localhost:8080/student
请求却会返回405响应,并指明服务器支持的是GET方法:
HTTP/1.1 405
Allow: GET
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 02 May 2021 15:17:05 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{
"timestamp": "2021-05-02T15:17:05.515+00:00",
"status": 405,
"error": "Method Not Allowed",
"message": "",
"path": "/student"
}
- 多个
RequestMethod
枚举对象:
@RestController
public class StudentController {
@RequestMapping(value = "student", method = {RequestMethod.GET, RequestMethod.POST})
public Student getStudent() {
// 简单模拟获取student流程
return new Student("Xianhuii", 18);
}
}
当显式为method
属性指定多个RequestMethod
枚举对象时,需要使用{}
包裹起来,表明该方法支持所指定的所有方法,但是没有指定的方法则不会支持。此时,我们指定了method = {RequestMethod.GET, RequestMethod.POST}
,说明getStudent()
方法可以支持GET
和POST
两种HTTP请求方法。因此,发送GET http://localhost:8080/student
或POST http://localhost:8080/student
都能得到正确的响应。但是若发送其他HTTP请求方法,如PUT http://localhost:8080/student
,则同样会返回上述405响应。
除了指定method
属性值的个数,其标注位置也十分重要。如果在类上@RequestMapping
的method
属性中指定了某些RequestMethod
枚举对象,这些对象会被实际方法继承:
@RequestMapping(method = RequestMethod.GET)
@RestController
public class StudentController {
@RequestMapping(value = "student", method = RequestMethod.POST)
public Student getStudent() {
// 简单模拟获取student流程
return new Student("Xianhuii", 18);
}
}
此时在StudentController
类上指定了method = RequestMethod.GET
,而getStudent()
方法上指定了method = RequestMethod.POST
。此时,getStudent()
方法会从StudentController
类上继承该属性,从而实际上为method = {RequestMethod.GET, RequestMethod.POST}
。因此,该方法可以接收GET http://localhost:8080/student
或POST http://localhost:8080/student
请求。当然,其他请求会响应405。
另外比较有趣的是,此时可以不必为StudentController
类上的@RequestMapping
指定value
属性值。因为此时它的作用是类中的所有方法指定共同支持的HTTP请求方法。
3、源码
package org.springframework.web.bind.annotation;
/**
* Annotation for mapping web requests onto methods in request-handling classes
* with flexible method signatures.
* —— 将web请求映射到方法的注解
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping // ——web映射的元注解,其中没有任何属性,相当于标记
public @interface RequestMapping {
/**
* Assign a name to this mapping. ——映射名
*/
String name() default "";
/**
* The primary mapping expressed by this annotation. ——映射路径
*/
@AliasFor("path")
String[] value() default {};
/**
* The path mapping URIs (e.g. {@code "/profile"}). ——映射路径
*/
@AliasFor("value")
String[] path() default {};
/**
* The HTTP request methods to map to, narrowing the primary mapping:
* GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
* <p><b>Supported at the type level as well as at the method level!</b>
* When used at the type level, all method-level mappings inherit this
* HTTP method restriction.
* ——映射HTTP请求方法。
* ——当标记在类上时,会被所有方法级别的映射继承。
*/
RequestMethod[] method() default {};
/**
* The parameters of the mapped request, narrowing the primary mapping.
* <p><b>Supported at the type level as well as at the method level!</b>
* When used at the type level, all method-level mappings inherit this
* parameter restriction.
* ——映射请求参数,如params = "myParam=myValue"或params = "myParam!=myValue"。
* ——当标记在类上时,会被所有方法级别的映射继承。
*/
String[] params() default {};
/**
* The headers of the mapped request, narrowing the primary mapping.
* <p><b>Supported at the type level as well as at the method level!</b>
* When used at the type level, all method-level mappings inherit this
* header restriction.
* ——映射请求头,如headers = "My-Headre=myValue"或headers = "My-Header!=myValue"。
* ——当标记在类上时,会被所有方法级别的映射继承。
*/
String[] headers() default {};
/**
* Narrows the primary mapping by media types that can be consumed by the
* mapped handler. Consists of one or more media types one of which must
* match to the request {@code Content-Type} header.
* <p><b>Supported at the type level as well as at the method level!</b>
* If specified at both levels, the method level consumes condition overrides
* the type level condition.
* ——映射请求媒体类型(media types),即服务端能够处理的媒体类型,如:
* consumes = "!text/plain"
* consumes = {"text/plain", "application/*"}
* consumes = MediaType.TEXT_PLAIN_VALUE
* ——当标记在类上时,会被所有方法级别的映射继承。
*/
String[] consumes() default {};
/**
* Narrows the primary mapping by media types that can be produced by the
* mapped handler. Consists of one or more media types one of which must
* be chosen via content negotiation against the "acceptable" media types
* of the request.
* <p><b>Supported at the type level as well as at the method level!</b>
* If specified at both levels, the method level produces condition overrides
* the type level condition.
* ——映射响应媒体类型(media types),即客户端能够处理的媒体类型,如:
* produces = "text/plain"
* produces = {"text/plain", "application/*"}
* produces = MediaType.TEXT_PLAIN_VALUE
* produces = "text/plain;charset=UTF-8"
* ——当标记在类上时,会被所有方法级别的映射继承。
*/
String[] produces() default {};
}
我们对method
属性做一个小结:
method
属性用来指定方法所支持的HTTP请求方法,对应为RequestMethod
枚举对象。method
属性的类型是RequestMethod[]
,可以指定零个至多个RequestMethod
枚举对象。零个时(默认情况)表明支持所有HTTP请求方法,多个时则仅支持指定的HTTP请求方法。- 类上
@RequestMapping
的method
属性所指定的RequestMethod
枚举对象,会被具体的方法继承。可以使用该方式为所有方法指定同一支持的HTTP请求方法。
2.2 @XxxMapping
在@RequestMapping
的基础上,Spring根据不同的HTTP请求方法,实现了具体化的@XxxMapping
注解。如@GetMapping
、@PostMapping
、@PutMapping
、@DeleteMapping
和@PatchMapping
。
它们并没有很神秘,只是以@RequestMapping
为元注解,因此具有之前介绍的所有属性,用法也完全一样。唯一特殊的是在@RequestMapping
的基础上指定了对应的method
属性值,例如@GetMapping
显式指定了method = RequestMethod.GET
。
需要注意的是,@XxxMapping
只能用作方法级别,此时可以结合类级别的@RequestMapping
定制分发地址:
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
相对于@RequestMapping
,增强版@XxxMapping
显得更加有语义,便于开发人员阅读。我们以@GetMapping
为例,简单看一下其源码:
package org.springframework.web.bind.annotation;
@Target(ElementType.METHOD) // 只能用作方法级别
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET) // 以@RequestMapping为元注解,并指定了对应的method属性
public @interface GetMapping {
@AliasFor(annotation = RequestMapping.class)
String name() default ""; // 映射名
@AliasFor(annotation = RequestMapping.class)
String[] value() default {}; // 映射路径
@AliasFor(annotation = RequestMapping.class)
String[] path() default {}; // 映射路径
@AliasFor(annotation = RequestMapping.class)
String[] params() default {}; // 映射参数
@AliasFor(annotation = RequestMapping.class)
String[] headers() default {}; // 映射请求头
@AliasFor(annotation = RequestMapping.class)
String[] consumes() default {}; // 映射服务器能接收媒体类型
@AliasFor(annotation = RequestMapping.class)
String[] produces() default {}; // 映射客户端能接收媒体类型
}
2.3 @PathVariable
@PathVariable
是一种十分特别的注解,从功能上来看它并不是用来指定分发地址的,而是用来接收请求数据的。但是由于它与@XxxMapping
系列注解的关系十分密切,因此放到此部分来讲解。
@PathVariable
的功能是:获取分发地址上的路径变量。
@XxxMapping
中的路径变量声明形式为{}
,内部为变量名,如@RequestMapping("/student/{studentId}")
。后续我们在对应方法参数前使用@PathVariable
获取该路径变量的值,如pubic Student student(@PathVariable int studentId)
。该变量的类型会自动转换,如果转化失败会抛出TypeMismatchException
异常。
我们也可以同时声明和使用多个路径变量:
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
或:
@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {
@GetMapping("/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
}
我们甚至可以使用{valueName:regex}
的方式指定该路径变量的匹配规则:
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) {
// ...
}
上述情况中,我们都没有为@PathVariable
指定value
属性,因此路径变量名必须与方法形参名一致。我们也可以显式指定value
属性与路径变量名一致,此时方法形参名就可以随意:
@RestController
public class StudentController {
@PostMapping("/student/{studentId}")
public int getStudent(@PathVariable("studentId") int id) {
return id;
}
}
我们来看一下@PathVairable
的源码:
package org.springframework.web.bind.annotation;
@Target(ElementType.PARAMETER) // 只能标注在形参上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
/**
* Alias for {@link #name}. 同name属性,即形参绑定的路径变量名。
*/
@AliasFor("name")
String value() default "";
/**
* The name of the path variable to bind to. 形参绑定的路径变量名
*/
@AliasFor("value")
String name() default "";
/**
* Whether the path variable is required. 路径变量是否是必须的。
*/
boolean required() default true;
}
最后,我们来总结一下@PathVariable
的用法:
@PathVariable
只能标注在方法形参上,用来匹配@XxxMapping()
中形如{pathVariableName}
的路径变量。- 如果没有显式指定
value
或name
属性,则形参名必须与对应的路径变量名一致。 - 路径变量中可以使用
{pathVariableName:regex}
方式指明匹配规则。
3 接收请求数据
我们可以直接在Controller的方法的形参中使用特定的注解,来接收HTTP请求中特定的数据,包括请求参数、请求头、请求体和cookie等。
也可以直接声明特定的形参,从而可以获取框架中用于与客户端交互的特殊对象,包括HttpServletRequest
和HttpServletResponse
等。
3.1 @RequestParam
@RequestParam
用来接收HTTP请求参数,即在分发地址之后以?
开头的部分。
请求参数本质上是键值对集合,我们使用@RequestParam
来获取某个指定的参数值,并且在这个过程中会进行自动类型转换。
例如,对于GET http://localhost:8080/student?name=Xianhuii&age=18
请求,我们可以使用如下方式来接收其请求参数name=Xianhuii&age=18
:
@RestController
public class StudentController {
@GetMapping("/student")
public Student getStudent(@RequestParam String name, @RequestParam int age) {
// 简单模拟获取student流程
Student student = new Student(name, age);
return student;
}
}
上述过程没有显式指定@RequestParam
的value
或name
属性,因此形参名必须与请求参数名一一对应。如果我们显式指定了value
或name
属性,那么形参名就可以任意了:
@RestController
public class StudentController {
@GetMapping("/student")
public Student getStudent(@RequestParam("name") String str, @RequestParam("age") int num) {
// 简单模拟获取student流程
Student student = new Student(str, num);
return student;
}
}
如果我们使用Map<String, String>
或MultiValueMap<String, String>
作为形参,那么会将所有请求参数纳入该集合中,并且此时对value
或name
属性没有要求:
@RestController
public class StudentController {
@GetMapping("/student")
public Student getStudent(@RequestParam Map<String, String> params) {
params.forEach((key, val)-> System.out.println(key + ": " + val));
// 简单模拟获取student流程
Student student = new Student(params.get("name"), Integer.parseInt(params.get("age")));
return student;
}
}
我们来看一下@RequestParam
源码:
package org.springframework.web.bind.annotation;
@Target(ElementType.PARAMETER) // 只能标注在形参上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
/**
* Alias for {@link #name}. 同name属性,即绑定的请求参数名。
*/
@AliasFor("name")
String value() default "";
/**
* The name of the request parameter to bind to. 绑定的请求参数名。
*/
@AliasFor("value")
String name() default "";
/**
* Whether the parameter is required.
* <p>Defaults to {@code true}, leading to an exception being thrown
* if the parameter is missing in the request. Switch this to
* {@code false} if you prefer a {@code null} value if the parameter is
* not present in the request.
* <p>Alternatively, provide a {@link #defaultValue}, which implicitly
* sets this flag to {@code false}.
*/
boolean required() default true;
/**
* The default value to use as a fallback when the request parameter is
* not provided or has an empty value. 默认值,如果没有提供该请求参数,则会使用该值。
*/
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
最后,我们来总结一下@RequestParam
的用法:
@RequestParam
标注在方法形参上,用来获取HTTP请求参数值。- 如果形参为基本类型,可以获取对应的请求参数值。此时需要注意请求参数名是否需要与形参名一致(是否指定
value
或name
属性)。 - 如果形参为
Map<String, String>
或MultiValueMap<String, String>
,则可以一次性获取全部请求参数。此时请求参数名与形参名无关。 required
属性默认为true
,此时必须保证HTTP请求中包含与形参一致的请求参数,否则会报错。- 我们可以使用
defaultValue
属性指定默认值,此时required
自动指定成false
,表示如果没有提供该请求参数,则会使用该值。
3.2 @RequestHeader
@RequestHeader
用来获取HTTP请求头。
请求头本质上也是键值对集合,只相对于请求参数,它们的键都具有固定的名字:
Accept-Encoding: UTF-8
Keep-Alive: 1000
例如,我们可以使用下面方式来获取请求头中的Accept-Encoding
和Keep-Alive
值:
@RestController
public class StudentController {
@GetMapping("/header")
public void handle(
@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
System.out.println("Accept-Encoding: " + encoding); // Accept-Encoding: UTF-8
System.out.println("Keep-Alive: " + keepAlive); // Keep-Alive: 1000
}
}
理论上,我们也可以不显式指定@RequestHeader
的value
或name
属性值,而使用对应的形参名。但是由于HTTP请求头中一般含有-
,而Java不支持此种命名方式,因此推荐还是显式指定value
或name
属性值。
另外,我们也可以使用Map<String, String>
或MultiValueMap<String, String>
一次性获取所有请求头,此时形参名与请求头参数名没有关系:
@RestController
public class StudentController {
@GetMapping("/header")
public void handle(@RequestHeader Map<String, String> headers) {
// headers.keySet().forEach(key->System.out.println(key));
System.out.println("Accept-Encoding: " + headers.get("accept-encoding"));
System.out.println("Keep-Alive: " + headers.get("keep-alive"));
}
}
此时我们需要注意请求头的名为小写形式,如accept-encoding
。我们可以遍历headers.keySet()
进行查看。
我们来看看@RequestHeader
的源码,可以发现与@RequestParam
十分相似:
package org.springframework.web.bind.annotation;
@Target(ElementType.PARAMETER) // 只可以标注在形参上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestHeader {
/**
* Alias for {@link #name}. 同name属性,即绑定的请求头名。
*/
@AliasFor("name")
String value() default "";
/**
* The name of the request header to bind to. 绑定的请求头名
*/
@AliasFor("value")
String name() default "";
/**
* Whether the header is required.
* <p>Defaults to {@code true}, leading to an exception being thrown
* if the header is missing in the request. Switch this to
* {@code false} if you prefer a {@code null} value if the header is
* not present in the request.
* <p>Alternatively, provide a {@link #defaultValue}, which implicitly
* sets this flag to {@code false}.
*/
boolean required() default true;
/**
* The default value to use as a fallback.
* <p>Supplying a default value implicitly sets {@link #required} to
* {@code false}.
*/
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
最后,我们来总结一下@RequestHeader
的用法:
@RequestHeader
标注在方法形参上,用来获取HTTP请求头,一般推荐使用value
或name
显式指定请求头名。- 也可以使用
Map<String, String>
或MultiValueMap<String, String>
一次性获取所有请求头,但是从该集合中获取对应值时要注意其key
值的大小写形式,如accept-encoding
。 - 我们也可以使用
required
或defaultValue
对是否必须具备该请求头进行特殊处理。
3.3 @CookieValue
我们可以将Cookie当做特殊的请求头,它的值是键值对集合,形如Cookie: cookie1=value1; cookie2 = value2
。
因此也可以使用之前的@RequestHeader
进行获取:
@RestController
public class StudentController {
@GetMapping("/header")
public void handle(@RequestHeader("cookie") String cookie) {
System.out.println(cookie); // cookie1=value1; cookie2 = value2
}
}
但是,一般来说我们会使用@CookieValue
显式获取Cookie键值对集合中的指定值:
@RestController
public class StudentController {
@GetMapping("/cookie")
public void handle(@CookieValue("cookie1") String cookie) {
System.out.println(cookie); // value1
}
}
同样,我们也可以不显式指定value
或name
属性值,此时形参名应与需要获取的cookie键值对的key一致:
@RestController
public class StudentController {
@GetMapping("/cookie")
public void handle(@CookieValue String cookie1) {
System.out.println(cookie1); // value1
}
}
需要注意的是,默认情况下不能同之前的@RequestParam
或@RequestHeader
那样使用Map
或MultiValueMap
来一次性获取所有cookies。
我们来看一下@CookieValue
的源码,其基本定义与@RequestParan
或@RequestHeader
完全一致,因此用法也类似:
package org.springframework.web.bind.annotation;
@Target(ElementType.PARAMETER) // 只能标注在形参上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CookieValue {
/**
* Alias for {@link #name}.
*/
@AliasFor("name")
String value() default "";
/**
* The name of the cookie to bind to.
* @since 4.2
*/
@AliasFor("value")
String name() default "";
/**
* Whether the cookie is required.
* <p>Defaults to {@code true}, leading to an exception being thrown
* if the cookie is missing in the request. Switch this to
* {@code false} if you prefer a {@code null} value if the cookie is
* not present in the request.
* <p>Alternatively, provide a {@link #defaultValue}, which implicitly
* sets this flag to {@code false}.
*/
boolean required() default true;
/**
* The default value to use as a fallback.
* <p>Supplying a default value implicitly sets {@link #required} to
* {@code false}.
*/
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
最后,总结一下@CookieValue
的用法:
@CookieValue
标注在方法形参上,用来获取HTTP请求中对应的cookie值。- 需要注意方法形参名是否需要与cookie键相对应(是否指定了
required
或defaultValue
属性)。 - 注意:不能使用
Map
或MultiValueMap
一次性获取所有cookies键值对。
3.4 @RequestBody
@RequestBody
可以接收HTTP请求体中的数据,但是必须要指定Content-Type
请求体的媒体类型为application/json
,表示接收json
类型的数据。
Spring会使用HttpMessageConverter
对象自动将对应的数据解析成指定的Java对象。例如,我们发送如下HTTP请求:
POST http://localhost:8080/student
Content-Type: application/json
{
"name": "Xianhuii",
"age": 18
}
我们可以在Controller中编写如下代码,接收请求体中的json
数据并转换成Student
对象:
@RestController
public class StudentController {
@PostMapping("/student")
public void handle(@RequestBody Student student) {
System.out.println(student); // Student{name='Xianhuii', age=18}
}
}
一般来说在Controller方法中仅可声明一个@RequestBody
注解的参数,将请求体中的所有数据转换成对应的POJO对象。
我们来看一下@RequestBody
的源码:
package org.springframework.web.bind.annotation;
@Target(ElementType.PARAMETER) // 只可以标注到方法形参上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
/**
* Whether body content is required.
*/
boolean required() default true;
}
可见@RequestBody
的定义十分简单,它只有一个required
属性。如果required
为true
,表示请求体中必须包含对应数据,否则会抛异常。如果required
为false
,表示请求体中可以没有对应数据,此时形参值为null
。
最后,总结一下@RequestBody
用法:
@RequestBody
标注在方法形参上,用来接收HTTP请求体中的json
数据。
3.5 HttpEntity<T>
上面介绍的注解都只是获取HTTP请求中的某个部分,比如@RequestParam
获取请求参数、@RequestHeader
获取请求头、@CookieValue
获取cookies、@RequestBody
获取请求体。
Spring提供了一个强大的HttpEntity<T>
类,它可以同时获取HTTP请求的请求头和请求体。
例如,对于如下HTTP请求:
POST http://localhost:8080/student
Content-Type: application/json
Cookie: cookie1=value1; cookie2 = value2
{
"name": "Xianhuii",
"age": 18
}
我们也可以编写如下接收方法,接收所有数据:
@RestController
public class StudentController {
@PostMapping("/student")
public void handle(HttpEntity<Student> httpEntity) {
Student student = httpEntity.getBody();
HttpHeaders headers = httpEntity.getHeaders();
System.out.println(student); // Student{name='Xianhuii', age=18}
/** [
* content-length:"37",
* host:"localhost:8080",
* connection:"Keep-Alive",
* user-agent:"Apache-HttpClient/4.5.12 (Java/11.0.8)",
* cookie:"cookie1=value1; cookie2 = value2",
* accept-encoding:"gzip,deflate",
* Content-Type:"application/json;charset=UTF-8"
* ]
*/
System.out.println(headers);
}
}
HttpEntity<T>
类中只包含三个属性:
其中,静态变量EMPTY
是一个空的HttpEntity
缓存(new HttpEntity<>()
),用来表示统一的没有请求头和请求体的HttpEntity
对象。
因此,可以认为一般HttpEntity
对象中值包含headers
和body
两个成员变量,分别代表请求头和请求体,对应为HttpHeaders
和泛型T
类型。我们可以调用HttpEntity
的getHeaders()
或getBody()
方法分别获取到它们的数据。
另外,HttpHeaders
类中只有一个Map
属性:final MultiValueMap<String, String> headers
,为各种请求头的集合。我们可以对其进行集合相关操作,获取到需要的请求头。
3.6 @RequestPart
和MultipartFile
Spring提供了@RequestPart
注解和MultipartFile
接口,专门用来接收文件。
我们先来编写一个极简版前端的文件上传表单:
<form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
<input name="image" type="file">
<input name="text" type="file">
<button type="submit">上传</button>
</form>
其中action
指定提交路径,对应为处理方法的分发地址。method
指定为post
方式。enctype
指定为multipart/form-data
格式。这里我们在内部定义了两个file
类型的<input>
标签,表示同时上传两个文件,用来说明多文件上传的情况(单文件上传的方式也相同)。
后端处理器:
@RestController
public class FileController {
@PostMapping("/upload")
public void upload(@RequestPart("image") MultipartFile image, @RequestPart("text") MultipartFile text) {
System.out.println(image);
System.out.println(text);
}
}
在Controller的对应方法中只需要声明MultipartFile
形参,并标注@RequestPart
注解,即可接收到对应的文件。这里我们声明了两个MultipartFile
形参,分别用来接收表单中定义的两个文件。
注意到此时形参名与表单中标签名一致,所以其实这里也可以不显式指出@RequestPart
的value
或name
属性(但是不一致时必须显式指出):
public void upload(@RequestPart MultipartFile image, @RequestPart MultipartFile text)
先来看一下@RequestPart
的源码,我保留了比较重要的文档:
package org.springframework.web.bind.annotation;
/**
* Annotation that can be used to associate the part of a "multipart/form-data" request
* with a method argument. 此注解用来将方法形参与"multipart/form-data"请求中的某个部分相关联。
*
* <p>Supported method argument types include {@link MultipartFile} in conjunction with
* Spring's {@link MultipartResolver} abstraction, {@code javax.servlet.http.Part} in
* conjunction with Servlet 3.0 multipart requests, or otherwise for any other method
* argument, the content of the part is passed through an {@link HttpMessageConverter}
* taking into consideration the 'Content-Type' header of the request part. This is
* analogous to what @{@link RequestBody} does to resolve an argument based on the
* content of a non-multipart regular request.
* 需要与MultipartFile结合使用。与@RequestBody类似(都解析请求体中的数据),但是它是不分段的,而RequestPart是分段的。
*
* <p>Note that @{@link RequestParam} annotation can also be used to associate the part
* of a "multipart/form-data" request with a method argument supporting the same method
* argument types. The main difference is that when the method argument is not a String
* or raw {@code MultipartFile} / {@code Part}, {@code @RequestParam} relies on type
* conversion via a registered {@link Converter} or {@link PropertyEditor} while
* {@link RequestPart} relies on {@link HttpMessageConverter HttpMessageConverters}
* taking into consideration the 'Content-Type' header of the request part.
* {@link RequestParam} is likely to be used with name-value form fields while
* {@link RequestPart} is likely to be used with parts containing more complex content
* e.g. JSON, XML).
* 在"multipart/form-data"请求情况下,@RequestParam也能以键值对的方式解析。而@RequestPart能解析更加复杂的内容:JSON等
*/
@Target(ElementType.PARAMETER) // 只能标注在方法形参上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestPart {
/**
* Alias for {@link #name}. 同name。
*/
@AliasFor("name")
String value() default "";
/**
* The name of the part in the {@code "multipart/form-data"} request to bind to.
* 对应"multipart/form-data"请求中某个部分的名字
*/
@AliasFor("value")
String name() default "";
/**
* Whether the part is required. 是否必须。
*/
boolean required() default true;
}
通过上述方式得到客户端发送过来的文件后,我们就可以使用MultipartFile
中的各种方法对该文件进行操作:
我们在这里举一个最简单的例子,将上传的两个文件保存在桌面下的test
文件夹中:
@RestController
public class FileController {
@PostMapping("/upload")
public void upload(@RequestPart MultipartFile image, @RequestPart MultipartFile text) throws IOException {
String path = "C:/Users/Administrator/Desktop/test";
String originImageName = image.getOriginalFilename();
String originTextName = text.getOriginalFilename();
File img = new File(path, UUID.randomUUID() + "." + originImageName.substring(originImageName.indexOf(".")));
File txt = new File(path, UUID.randomUUID() + "." + originTextName.substring(originTextName.indexOf(".")));
image.transferTo(img);
text.transferTo(txt);
}
}
最后,我们@RequestPart
和MultipartFile
接口做一个总结:
@RequestPart
专门用来处理multipart/form-data
类型的表单文件,可以将方法形参与表单中各个文件单独关联。@RequestPart
需要与MultipartFile
结合使用。@RequestParam
也能进行解析multipart/form-data
类型的表单文件,但是它们原理不同。MultipartFile
表示接收到的文件对象,通过使用其各种方法,可以对文件进行操作和保存。
4 发送响应数据
对请求数据处理完成之后,最后一步是需要向客户端返回一个结果,即发送响应数据。
4.1 @ResponseBody
@ResponseBody
可以标注在类或方法上,它的作用是将方法返回值作为HTTP响应体发回给客户端,与@ResquestBody
刚好相反。
我们可以将它标注到方法上,表示仅有handle()
方法的返回值会被直接绑定到响应体中,注意到此时类标注成@Controller
:
@Controller
public class StudentController {
@ResponseBody
@GetMapping("/student")
public Student handle() {
return new Student("Xianhuii", 18);
}
}
我们也可以将它标注到类上,表示类中所有方法的返回值都会被直接绑定到响应体中:
@ResponseBody
@Controller
public class StudentController {
@GetMapping("/student")
public Student handle() {
return new Student("Xianhuii", 18);
}
}
此时,@ResponseBody
和@Controller
相结合,就变成了@RestController
注解,也是前后端分离中最常用的注解:
@RestController
public class StudentController {
@GetMapping("/student")
public Student handle() {
return new Student("Xianhuii", 18);
}
}
如果客户端发送如下HTTP请求:GET http://localhost:8080/student
。此时上述代码都会有相同的HTTP响应,表示接收到student
的json
数据:
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 04 May 2021 13:04:15 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{
"name": "Xianhuii",
"age": 18
}
我们来看看@ResponseBody
的源码:
package org.springframework.web.bind.annotation;
/**
* Annotation that indicates a method return value should be bound to the web
* response body. Supported for annotated handler methods.
*
* <p>As of version 4.0 this annotation can also be added on the type level in
* which case it is inherited and does not need to be added on the method level.
*/
@Target({ElementType.TYPE, ElementType.METHOD}) // 可以标注到类或方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
最后,我们总结一下@ResponseBody
的用法:
@ResponseBody
表示将方法返回值直接绑定到web响应体中。@ResponseBody
可以标注到类或方法上。类上表示内部所有方法的返回值都直接绑定到响应体中,方法上表示仅有该方法的返回值直接绑定到响应体中。@ResponseBody
标注到类上时,与@Controller
相结合可以简写成@RestController
,这也是通常使用的注解。- 我们可以灵活地构造合适的返回对象,结合
@ResponseBody
,用作与实际项目最匹配的响应体返回。
4.2 ResponseEntity<T>
ResponseEntity<T>
是HttpEntity<T>
的子类,它除了拥有父类中的headers
和body
成员变量,自己还新增了一个status
成员变量。因此,ResponseEntity<T>
集合了响应体的三个最基本要素:响应头、状态码和响应数据。它的层次结构如下:
status
成员变量一般使用HttpStatus
枚举类表示,其中涵盖了几乎所有常用状态码,使用时可以直接翻看源码。
ResponseEntity<T>
的基本使用流程如下,注意我们此时没有使用@ResponseBody
(但是推荐直接使用@RestController
):
@Controller
public class StudentController {
@GetMapping("/student")
public ResponseEntity<Student> handle() {
// 创建返回实体:设置状态码、响应头和响应数据
return ResponseEntity.ok().header("hName", "hValue").body(new Student("Xianhuii", 18));
}
}
当客户端发送GET http://localhost:8080/student
请求时,上述代码会返回如下结果:
HTTP/1.1 200
hName: hValue
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 04 May 2021 13:38:00 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{
"name": "Xianhuii",
"age": 18
}
最后,总结一下ResponseEntity<T>
的用法:
ResponseEntity<T>
直接用作方法返回值,表示将其作为HTTP响应:包括状态码、响应头和响应体。ResponseEntity<T>
中包含status
、headers
和body
三个成员变量,共同组成HTTP响应。ResponseEntity
具有链式的静态方法,可以很方便地构造实例对象。
4.3 @ExceptionHandler
上面介绍的都是正常返回的情况,在某些特殊情况下程序可能会抛出异常,因此不能正常返回。此时,就可以用@ExceptionHandler
来捕获对应的异常,并且统一返回。
首先,我们自定义一个异常:
public class NoSuchStudentException extends RuntimeException {
public NoSuchStudentException(String message) {
super(message);
}
}
然后我们编写相关Controller方法:
@RestController
public class StudentController {
@GetMapping("/student")
public ResponseEntity<Student> handle() {
throw new NoSuchStudentException("没有找到该student");
}
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler
public String exception(NoSuchStudentException exception) {
return exception.getMessage();
}
}
此时发送GET http://localhost:8080/student
请求,会返回如下响应:
HTTP/1.1 404
Content-Type: text/plain;charset=UTF-8
Content-Length: 22
Date: Tue, 04 May 2021 14:09:51 GMT
Keep-Alive: timeout=60
Connection: keep-alive
没有找到该student
上述执行流程如下:
- 接收
GET http://localhost:8080/student
请求,分发到handle()
方法。 handle()
方法执行过程中抛出NoSuchStudentException
异常。NoSuchStudentException
被相应的exception()
方法捕获,然后根据@ResponseStatus
和错误消息返回给客户端。
其实@ExceptionHandler
所标注的方法十分灵活,比如:
- 它的形参代表该方法所能捕获的异常,作用与
@ExceptionHandler
的value
属性相同。 - 它的返回值也十分灵活,既可以指定为上述的
@ResponseBody
或ResponseEntity<T>
等绑定到响应体中的值,也可以指定为Model
等视图相关值。 - 由于当前考虑的是前后端分离场景,因此我们需要指定
@ResponseBody
,上面代码已经声明了@RestController
。 @ResponseStatus
不是必须的,我们可以自己构造出合适的响应对象。@ExceptionHandler
只能处理本类中的异常。
上面代码中我们只针对NoSuchStudentException
进行处理,如果此类中还有其他异常,则需要另外编写对应的异常处理方法。我们还有一种最佳实践方式,即定义一个统一处理异常,然后在方法中进行细化处理:
@RestController
public class StudentController {
@GetMapping("/student")
public ResponseEntity<Student> handle() {
throw new NoSuchStudentException();
}
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler
public String exception(Exception exception) {
String message = "";
if (exception instanceof NoSuchStudentException) {
message = "没有找到该student";
} else {
}
return message;
}
}
我们来看一下@ExceptionHandler
的源码:
package org.springframework.web.bind.annotation;
@Target(ElementType.METHOD) // 只能标注在方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
/**
* Exceptions handled by the annotated method. If empty, will default to any
* exceptions listed in the method argument list.
*/
Class<? extends Throwable>[] value() default {};
}
我们来看一下@ResponseStatus
的源码:
package org.springframework.web.bind.annotation;
@Target({ElementType.TYPE, ElementType.METHOD}) // 可以标记在类(会被继承)或方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseStatus {
/**
* Alias for {@link #code}. 状态码
*/
@AliasFor("code")
HttpStatus value() default HttpStatus.INTERNAL_SERVER_ERROR;
/**
* The status <em>code</em> to use for the response. 状态码
*/
@AliasFor("value")
HttpStatus code() default HttpStatus.INTERNAL_SERVER_ERROR;
/**
* The <em>reason</em> to be used for the response. 原因短语
*/
String reason() default "";
}
最后,总结一下@ExceptionHandler
的用法:
@ExceptionHandler
标记某方法为本Controller中对某些异常的处理方法。- 该方法的形参表示捕获的异常,与
@ExceptionHandler
的value
属性功能一致。 - 该方法的返回值多种多样,在前后端分离情况下,需要与
@ResponseBody
结合使用。 - 结合
@ResponseStatus
方便地返回状态码和对应的原因短语。
4.4 @ControllerAdvice
上面介绍的@ExceptionHandler
有一个很明显的局限性:它只能处理本类中的异常。
接下来我们来介绍一个十分强大的@ControllerAdvice
注解,使用它与@ExceptionHandler
相结合,能够管理整个应用中的所有异常。
我们定义一个统一处理全局异常的类,使用@ControllerAdvice
标注。并将之前的异常处理方法移到此处(注意此时需要添加@ResponseBody
):
@ControllerAdvice
@ResponseBody
public class AppExceptionHandler {
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler
public String exception(Exception exception) {
return exception.getMessage();
}
}
将之前的Controller修改成如下:
@RestController
public class StudentController {
@GetMapping("/student")
public ResponseEntity<Student> handle() {
throw new NoSuchStudentException("没有找到该student");
}
}
发送GET http://localhost:8080/student
请求,此时会由AppExceptionHanler
类中的exception()
方法进行捕获:
HTTP/1.1 404
Content-Type: text/plain;charset=UTF-8
Content-Length: 22
Date: Tue, 04 May 2021 14:39:26 GMT
Keep-Alive: timeout=60
Connection: keep-alive
没有找到该student
我们来看看@ControllerAdvice
的源码:
package org.springframework.web.bind.annotation;
/**
* Specialization of {@link Component @Component} for classes that declare
* {@link ExceptionHandler @ExceptionHandler}, {@link InitBinder @InitBinder}, or
* {@link ModelAttribute @ModelAttribute} methods to be shared across
* multiple {@code @Controller} classes.
* 可以统一管理全局Controller类中的@ExceptionHandler、@InitBinder和@ModelAttribute方法。
*
* <p>By default, the methods in an {@code @ControllerAdvice} apply globally to
* all controllers. 默认情况下会管理应用中所有的controllers。
*
* Use selectors such as {@link #annotations},
* {@link #basePackageClasses}, and {@link #basePackages} (or its alias
* {@link #value}) to define a more narrow subset of targeted controllers.
* 使用annotations、basePackageClasses、basePackages和value属性可以缩小管理范围。
*
* If multiple selectors are declared, boolean {@code OR} logic is applied, meaning
* selected controllers should match at least one selector. Note that selector checks
* are performed at runtime, so adding many selectors may negatively impact
* performance and add complexity.
* 如果同时声明上述多个属性,那么会使用它们的并集。由于在运行期间检查,所有声明多个属性可能会影响性能。
*/
@Target(ElementType.TYPE) // 只能标记到类上
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 含有@Component元注解,因此可以被Spring扫描并管理
public @interface ControllerAdvice {
/**
* Alias for the {@link #basePackages} attribute. 同basePackages,管理controllers的扫描基础包数组。
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* Array of base packages. 管理controllers的扫描基础包数组。
*/
@AliasFor("value")
String[] basePackages() default {};
/**
* 管理的Controllers所在的基础包中必须包含其中一个类。
*/
Class<?>[] basePackageClasses() default {};
/**
* Array of classes. 管理的Controllers必须至少继承其中一个类。
*/
Class<?>[] assignableTypes() default {};
/**
* Array of annotation types. 管理的Controllers必须至少标注有其中一个注解(如@RestController)
*/
Class<? extends Annotation>[] annotations() default {};
}
最后,我们总结@ControllerAdvice
的用法:
@ControllerAdvice
用来标注在类上,表示其中的@ExceptionHandler
等方法能进行全局管理。@ControllerAdvice
包含@Component
元注解,因此可以被Spring扫描并管理。- 可以使用
basePackages
、annotations
等属性来缩小管理的Controller的范围。
5 总结
在前后端分离项目中,Spring MVC管理着后端的Controller层,是前后端交互的接口。本文对Spring MVC中最常用、最基础的注解的使用方法进行了系统介绍,使用这些常用注解,足以完成绝大部分的日常工作。
最后,我们对Spring MVC的使用流程做一个总结:
- 引入依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 创建Controller类:
@Controller
或@RestController
注解。 - 指定分发地址:
@RequestMapping
以及各种@XxxMapping
注解。 - 接收请求参数:
@PathVariable
、@RequestParam
、@RequestHeader
、@CookieValue
、@RequestBody
、HttpEntity<T>
以及@RequestPart
和MultipartFile
。 - 发送响应数据:
@ResponseBody
、ResponseEntity<T>
以及@ExceptionHandler
和@ControllerAdvice
。