乐趣区

关于spring: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 类的 beanNamestudentController

  • @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 类的 beanNamestudentController

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的属性有很多,然而罕用的只有 valuepathmethod。其中 valuepath等价,用来指定散发地址。method则用来指定对应的 HTTP 申请形式。

1、valuepath

对于 valuepath属性,它们的性能其实咱们之前就见到过了:指定绝对于服务器套接字的散发地址。要小心的是在类上是否标注了@RequestMapping

如果 @RequestMapping 不显式指定属性名,那么默认是 value 属性:

@RequestMapping("student")

当然咱们也能够显式指定属性名:

@RequestMapping(value = "student")
@RequestMapping(path = "student")

须要留神的是 valuepath属性的类型是String[],这示意它们能够同时指定多个散发地址,即一个办法能够同时解决多个申请。如果咱们指定了两个散发地址:

@RestController
public class StudentController {@RequestMapping(path = {"student", "/getStudent"})
    public Student getStudent() {
        // 简略模仿获取 student 流程
        return new Student("Xianhuii", 18);
    }
}

此时,无论浏览器发送 GET http://localhost:8080/studentGET 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
}

咱们对 valuepath属性做一个小结:

  • 在不显式申明属性名的时候,默认为 value 属性,如 @RequestMapping("/student") 等价于@RequestMapping(value = "/student")
  • 在申明多个 @RequestMapping 的属性时,必须显式指出 value 属性名,如@RequestMapping(value = "student", method = RequestMethod.GET)
  • valuepath 等价,如 @RequestMapping(value = "/student") 等价于@RequestMapping(path = "/student")
  • valuepath 属性的类型是 String[],个别至多为其指定一个值。在指定多个值的状况下,须要用{} 将值包裹,如@RequestMapping({"/student", "/getStudent"}),此时示意该办法能够解决的所有散发地址。
  • 须要留神类上是否标注了@RequestMapping,如果标注则为散发地址的根底,具体方法的理论散发地址须要与之进行拼接。
  • 此外,在某些状况下,@RequestMapping的作用不是指定散发地址,能够不指定该属性值。

2、method

method属性用来指定映射的 HTTP 申请办法,包含 GETPOSTHEADOPTIONSPUTPATCHDELETETRACE,别离对应 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() 办法能够反对 GETPOST两种 HTTP 申请办法。因而,发送 GET http://localhost:8080/studentPOST http://localhost:8080/student都能失去正确的响应。然而若发送其余 HTTP 申请办法,如PUT http://localhost:8080/student,则同样会返回上述 405 响应。

除了指定 method 属性值的个数,其标注地位也非常重要。如果在类上 @RequestMappingmethod属性中指定了某些 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/studentPOST 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 申请办法。
  • 类上 @RequestMappingmethod属性所指定的 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} 的门路变量。
  • 如果没有显式指定 valuename属性,则形参名必须与对应的门路变量名统一。
  • 门路变量中能够应用 {pathVariableName:regex} 形式指明匹配规定。

3 接管申请数据

咱们能够间接在 Controller 的办法的形参中应用特定的注解,来接管 HTTP 申请中特定的数据,包含申请参数、申请头、申请体和 cookie 等。

也能够间接申明特定的形参,从而能够获取框架中用于与客户端交互的非凡对象,包含 HttpServletRequestHttpServletResponse等。

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;
    }
}

上述过程没有显式指定 @RequestParamvaluename 属性,因而形参名必须与申请参数名一一对应。如果咱们显式指定了 valuename属性,那么形参名就能够任意了:

@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>作为形参,那么会将所有申请参数纳入该汇合中,并且此时对 valuename属性没有要求:

@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 申请参数值。
  • 如果形参为根本类型,能够获取对应的申请参数值。此时须要留神申请参数名是否须要与形参名统一(是否指定 valuename属性)。
  • 如果形参为 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-EncodingKeep-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
    }
}

实践上,咱们也能够不显式指定 @RequestHeadervaluename 属性值,而应用对应的形参名。然而因为 HTTP 申请头中个别含有 -,而 Java 不反对此种命名形式,因而举荐还是显式指定valuename属性值。

另外,咱们也能够应用 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 申请头,个别举荐应用 valuename显式指定申请头名。
  • 也能够应用 Map<String, String>MultiValueMap<String, String>一次性获取所有申请头,然而从该汇合中获取对应值时要留神其 key 值的大小写模式,如accept-encoding
  • 咱们也能够应用 requireddefaultValue对是否必须具备该申请头进行非凡解决。

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
    }
}

同样,咱们也能够不显式指定 valuename属性值,此时形参名应与须要获取的 cookie 键值对的 key 统一:

@RestController
public class StudentController {@GetMapping("/cookie")
    public void handle(@CookieValue String cookie1) {System.out.println(cookie1);    // value1
    }
}

须要留神的是,默认状况下不能同之前的 @RequestParam@RequestHeader那样应用 MapMultiValueMap来一次性获取所有 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 键绝对应(是否指定了 requireddefaultValue属性)。
  • 留神:不能应用 MapMultiValueMap一次性获取所有 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 属性。如果 requiredtrue,示意申请体中必须蕴含对应数据,否则会抛异样。如果 requiredfalse,示意申请体中能够没有对应数据,此时形参值为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 对象中值蕴含 headersbody两个成员变量,别离代表申请头和申请体,对应为 HttpHeaders 和泛型 T 类型。咱们能够调用 HttpEntitygetHeaders()getBody() 办法别离获取到它们的数据。

另外,HttpHeaders类中只有一个 Map 属性:final MultiValueMap<String, String> headers,为各种申请头的汇合。咱们能够对其进行汇合相干操作,获取到须要的申请头。

3.6 @RequestPartMultipartFile

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 形参,别离用来接管表单中定义的两个文件。

留神到此时形参名与表单中标签名统一,所以其实这里也能够不显式指出 @RequestPartvaluename 属性(然而不统一时必须显式指出):

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);
    }
}

最初,咱们 @RequestPartMultipartFile接口做一个总结:

  • @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 响应,示意接管到 studentjson数据:

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> 的子类,它除了领有父类中的 headersbody成员变量,本人还新增了一个 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>中蕴含 statusheadersbody三个成员变量,独特组成 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

上述执行流程如下:

  1. 接管 GET http://localhost:8080/student 申请,散发到 handle() 办法。
  2. handle()办法执行过程中抛出 NoSuchStudentException 异样。
  3. NoSuchStudentException被相应的 exception() 办法捕捉,而后依据 @ResponseStatus 和谬误音讯返回给客户端。

其实 @ExceptionHandler 所标注的办法非常灵便,比方:

  • 它的形参代表该办法所能捕捉的异样,作用与 @ExceptionHandlervalue属性雷同。
  • 它的返回值也非常灵便,既能够指定为上述的 @ResponseBodyResponseEntity<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 中对某些异样的解决办法。
  • 该办法的形参示意捕捉的异样,与 @ExceptionHandlervalue属性性能统一。
  • 该办法的返回值多种多样,在前后端拆散状况下,须要与 @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 扫描并治理。
  • 能够应用 basePackagesannotations 等属性来放大治理的 Controller 的范畴。

5 总结

在前后端拆散我的项目中,Spring MVC 治理着后端的 Controller 层,是前后端交互的接口。本文对 Spring MVC 中最罕用、最根底的注解的应用办法进行了零碎介绍,应用这些罕用注解,足以实现绝大部分的日常工作。

最初,咱们对 Spring MVC 的应用流程做一个总结:

  1. 引入依赖:
<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>
  1. 创立 Controller 类:@Controller@RestController 注解。
  2. 指定散发地址:@RequestMapping以及各种 @XxxMapping 注解。
  3. 接管申请参数:@PathVariable@RequestParam@RequestHeader@CookieValue@RequestBodyHttpEntity<T>以及 @RequestPartMultipartFile
  4. 发送响应数据:@ResponseBodyResponseEntity<T>以及 @ExceptionHandler@ControllerAdvice
退出移动版