共计 35662 个字符,预计需要花费 90 分钟才能阅读完成。
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
。