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。

京公网安备 11010502036488号