一、SpringMVC介绍

概述

Spring MVC是Spring Framework的一部分,是基于Java实现MVC的轻量级Web框架

查看官方文档:https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet.html

SpringMVC 的优点

Spring MVC的特点:

  1. 轻量级,简单易学
  2. 高效 , 基于请求响应的MVC框架
  3. 与Spring兼容性好,无缝结合
  4. 约定优于配置
  5. 功能强大:RESTful、数据验证、格式化、本地化、主题等
  6. 简洁灵活

Spring的web框架围绕DispatcherServlet[ 调度Servlet ] 设计。

DispatcherServlet的作用是将请求分发到不同的处理器。从Spring 2.5开始,使用Java 5或者以上版本的用户可以采用基于注解形式进行开发,十分简洁;

正因为SpringMVC 简单 , 便捷 , 易学 , 天生和Spring无缝集成(使用SpringIoC和Aop) , 使用约定优于配置 . 能够进行简单的junit测试 . 支持Restful风格 .异常处理 , 本地化 , 国际化 , 数据验证 , 类型转换 , 拦截器 等等……所以我们要学习 .

二、🌈SpringMVC注解

@RequestMapping

@RequestMapping注解用于映射url到控制器类或一个特定的处理程序方法。

可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径

@RequestMapping作用在类和方法上的区别

  • 在方法上使用: 访问路径 http://localhost:8080/h1

    1
    2
    3
    4
    5
    6
    7
    @Controller
    public class TestController {
    @RequestMapping("/h1")
    public String test(){
    return "hello";
    }
    }
  • 注解在类和方法上, 访问路径: http://localhost:8080/admin/h1

    1
    2
    3
    4
    5
    6
    7
    8
    @Controller
    @RequestMapping("/admin")
    public class TestController {
    @RequestMapping("/h1")
    public String test(){
    return "hello";
    }
    }

一般用于区分各个模块中的请求

路径和通配符

通配符使用

  1. *匹配任意多个字符(0~N); 不能匹配多个路径

  2. **: 匹配任意多层路径

  3. ?: 匹配任意单个字符(1)

使用通配符后, 若能匹配到多个路径:

  1. 精确优先
  2. 精确程度: 完全匹配 > ? > * > **
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@ResponseBody
@RequestMapping("/hello")
public String handle() {
return "Hello,Spring MVC!"; //默认认为返回值是跳到一个页面
}
@ResponseBody
@RequestMapping("/he?ll")
public String handle01() {
return "handle01";
}

@RequestMapping("/he*ll")
public String handle02() {
return "handle02";
}

@ResponseBody
@RequestMapping("/he/**")
public String handle03() {
return "handle03";
}

请求限定

  • 请求方式 : GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE

    1
    2
    3
    4
    @RequestMapping(value = "/test01",method = {RequestMethod.DELETE,RequestMethod.GET})
    public String test01(){
    return "hello world";
    }
  • 请求参数

    params = {"age=18","username","gender!=1"} 表示

    1. 表示请求必须包含username参数

    2. age=18: 表示请求参数中必须包含age=18的参数

      1. gender!=1: 表示请求参数中不能包含gender=1的参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * 请求参数:params = {"username","age"}
    * 1)、username: 表示请求必须包含username参数
    * 2)、age=18: 表示请求参数中必须包含age=18的参数
    * 3)、gender!=1: 表示请求参数中不能包含gender=1的参数
    */
    @RequestMapping(value = "/test02",params = {"age=18","username","gender!=1"})
    public String test02(){
    return "test02";
    }
  • 请求头

    headers = {"haha"} 表示的含义:

    1. haha: 表示请求中必须包含名为haha的请求头
    2. hehe!=1: 表示请求头中 的 hehe 不能是1
    1
    2
    3
    4
    @RequestMapping(value = "/test03",headers = "haha")
    public String test03(){
    return "test03";
    }
  • 请求内容类型

    consumes = {"application/json"} 表示的含义

    1. consumes 请求内容类型:消费什么数据; Media Type:媒体类型
    2. application/json: 表示浏览器必须携带 json 格式的数据。
    1
    2
    3
    4
    @RequestMapping(value = "/test04",consumes = "application/json")
    public String test04(){
    return "test04";
    }
  • 响应类型限定

    produces = {"text/plain;charset=utf-8"} 生产什么数据;

    1
    2
    3
    4
    @RequestMapping(value = "/test05",produces = "text/html;charset=utf-8")
    public String test05(){
    return "<h1>你好,张三</h1>";
    }

@RequestMapping注解的变体:

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

@RequestMapping(name = "/add",method = RequestMethod.GET)方式等同与 @GetMapping("/add")

@RestController

注解说明

@RestController = @Controller + @ResponseBody

@RestController 是 Spring MVC 中的注解,是 @Controller 注解的一个变体。作用为标识类为 RESTful 风格的控制器组件,专门用于处理 HTTP 请求并返回数据(通常是 JSON 格式)。

@Controller 标识类为 Spring MVC 控制器组件,用于处理请求。

@ResponseBody 注解用于把返回值放到响应体中

三、⭐请求和响应处理

HTTP请求与响应

HTTP请求会带来各种数据

  1. 请求首行:(请求方式、请求路径、请求协议)
  2. 请求头:(k: v k: v)
  3. 请求体:(此次请求携带的其他数据)

URL 携带大量数据,特别是GET请求,会把参数放在URL上

image-20240903195323243

请求头 有很多重要信息,SpringMVC 可以快速获取到

请求体 携带大量数据,特别是POST请求,会把参数放在请求体中

image-20240903195422726

⭐JSON

什么是JSON?

  • JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式,目前使用特别广泛。
  • 采用完全独立于编程语言的文本格式来存储和表示数据。
  • 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。
  • 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

语法

在 JavaScript 语言中,一切都是对象。因此,任何JavaScript 支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。看看他的要求和语法格式:

  • 对象表示为键值对,数据由逗号分隔
  • 花括号保存对象
  • 方括号保存数组

JSON 的表示

JSON 键值对是用来保存 JavaScript 对象的一种方式,和 JavaScript 对象的写法也大同小异,键/值对组合中的键名写在前面并用双引号 “” 包裹,使用冒号 : 分隔,然后紧接着值:

1
2
3
{"name": "QinJiang"}
{"age": "3"}
{"sex": "男"}

备注:将字符串转换为原生对象称为反序列化(deserialization),

而将原生对象转换为可以通过网络传输的字符串称为序列化(serialization

⭐请求处理

URL 中请携带的参数

1
2
3
4
5
6
7
8
@RequestMapping("/handle01")
public String handle01(String username, String password, String cellphone, Boolean agreement) {
System.out.println(username);
System.out.println(password);
System.out.println(cellphone);
System.out.println(agreement);
return "OK";
}

@RequestParm 使用

@RequestParm明确指定获取哪个参数的值

无论请求参数是在url 中还是在请求体中, 都可以使用@RequestParm 或 同一个变量名获取

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("/handle02")
public String handle02(@RequestParam("username") String name,
@RequestParam("password") String pwd,
@RequestParam("cellphone") String phone,
@RequestParam("agreement") Boolean arg) {
System.out.println(name);
System.out.println(pwd);
System.out.println(phone);
System.out.println(arg);
return "OK";
}

将请求参数封装到实体类中

  1. 准备实体类, 属性名需要和参数名一致(所有的类型都尽量使用包装类)

    1
    2
    3
    4
    5
    6
    7
    @Data
    public class Person {
    private String username;
    private String password;
    private String cellphone;
    private Boolean agreement;
    }
  2. 将参数封装到实体类中

    1
    2
    3
    4
    5
    @RequestMapping("/handle03")
    public String handle03(Person person) {
    System.out.println(person);
    return "OK";
    }

@RequestHeader

@RequestHeader用于获取请求头数据

1
2
3
4
5
6
7
@RequestMapping("/handle04")
public String handle04(@RequestHeader("connection") String connection,
@RequestHeader("host") String host) {
System.out.println(connection);
System.out.println(host);
return "OK";
}

@CookieValue

@CookieValue用于获取Cookie数据

1
2
3
4
5
6
7
@RequestMapping("/handle05")
public String handle05(@CookieValue("Idea-cccb35d1") String ideaCookie,
@CookieValue("Pycharm-ebfcc76e") String pyCookie) {
System.out.println(ideaCookie);
System.out.println(pyCookie);
return "OK";
}

⭐使用实体类级联封装复杂属性

  1. 需要封装的参数

    1
    http://localhost:8080/handle06?username=xiaoyu&password=123456&cellphone=&address.province=陕西&address.city=西安&address.area=鄠邑&sex=男&hobby=篮球&hobby=足球&grade=一年级&agreement=on
  2. 根据参数编写实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Data
    public class Person {
    private String username;
    private String password;
    private String cellphone;
    private Boolean agreement;
    private Address address;
    private String sex;
    private String grade;
    private String[] hobby;
    }

    @Data
    class Address {
    private String province;
    private String city;
    private String area;
    }
  3. 使用对象来封装请求参数

    1
    2
    3
    4
    5
    @RequestMapping("/handle06")
    public String handle06(Person person) {
    System.out.println(person);
    return "OK";
    }

@RequestBody,封装JSON对象

@RequestBody用于将JSON参数封装为实体类对象

  1. 使用postman 发送 请求体中含有json字符串的请求

  2. @RequestBody可以获取请求体中的json字符串, 并将json字符串转为对象

    1
    2
    3
    4
    5
    6
    @RequestMapping("/handle07")
    public String handle07(@RequestBody Person person){
    System.out.println(person);
    //自己把字符串转为对象。
    return "ok";
    }

文件上传

MultipartFile 专门用于封装文件项

@RequestPart/@RequestParam用于封装文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

@RequestMapping("/handle08")
public String handle08(Person person,
@RequestParam("headerImg") MultipartFile headerImgFile,
@RequestPart("lifeImg") MultipartFile[] lifeImgFiles)
throws IOException {

//1、获取原始文件名
String originalFilename = headerImgFile.getOriginalFilename();
//2、文件大小
long size = headerImgFile.getSize();
//3、获取文件流
InputStream inputStream = headerImgFile.getInputStream();
System.out.println(originalFilename + " ==> " + size);
//4、文件保存
headerImgFile.transferTo(new File("D:\\img\\" + originalFilename));
System.out.println("===============以上处理了头像=================");
if (lifeImgFiles.length > 0) {
for (MultipartFile imgFile : lifeImgFiles) {
imgFile.transferTo(new File("D:\\img\\"+imgFile.getOriginalFilename()));
}
System.out.println("=======生活照保存结束==========");
}
System.out.println(person);
return "ok!!!";
}

注意:

  1. Spring MVC 默认限制上传文件大小为1M, 可以在application.yaml配置文件中修改

HttpEntity

HttpEntity 用于封装请求头和请求体(把整个请求拿过来)

泛型:<String> 请求体类型: 可以把请求体中的数据自动转化泛型类型

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("/handle09")
public String handle09(HttpEntity<Person> entity){

//1、拿到所有请求头
HttpHeaders headers = entity.getHeaders();
System.out.println("请求头:"+headers);
//2、拿到请求体
Person body = entity.getBody();
System.out.println("请求体:"+body);
return "Ok~~~";
}

获取servlet 原生API

可以获取Servlet 的原生API, 如HttpServletRequestHttpServletResponseHttpSession

1
2
3
4
5
6
7
8
9
@RequestMapping("/handle10")
public void handle10(HttpServletRequest request,
HttpServletResponse response,
HttpMethod method) throws IOException {
System.out.println("请求方式:"+method);
String username = request.getParameter("username");
System.out.println(username);
response.getWriter().write("ok!!!"+username);
}

响应处理

将数据以JSON格式写到响应体中

在类上标注@RestController注解, 即可以JSON 格式响应数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

@RestController
public class ResponseTestController {

@RequestMapping("/resp01")
public Person resp01() {
Person person = new Person();
person.setUsername("张三");
person.setPassword("1111");
person.setCellphone("22222");
person.setAgreement(false);
person.setSex("男");
person.setHobby(new String[]{"足球", "篮球"});
person.setGrade("三年级");
return person;
}
}

文件下载

  • HttpEntity:拿到整个请求数据
  • ResponseEntity:拿到整个响应数据(响应头、响应体、状态码)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RequestMapping("/download")
public ResponseEntity<InputStreamResource> download() throws IOException {

//以上代码永远别改
FileInputStream inputStream = new FileInputStream("H:\Media\壁纸\123.jpg");
//1、文件名中文会乱码:解决:
String encode = URLEncoder.encode("哈哈美女.jpg", "UTF-8");
//以下代码永远别改
//2、文件太大会oom(内存溢出)
InputStreamResource resource = new InputStreamResource(inputStream);
return ResponseEntity.ok()
//内容类型:流
.contentType(MediaType.APPLICATION_OCTET_STREAM)
//内容大小
.contentLength(inputStream.available())
// Content-Disposition :内容处理方式
.header("Content-Disposition", "attachment;filename="+encode)
.body(resource);
}

四、⭐RestFul 风格

基本介绍

概念

Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

功能

资源:互联网所有的事物都可以被抽象为资源

资源操作:使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作。

分别对应 添加、 删除、修改、查询。

传统方式操作资源 :通过不同的参数来实现不同的效果!方法单一,post 和 get

使用RESTful操作资源可以通过不同的请求方式来实现不同的效果

如下:请求地址一样,但是功能可以不同!

使用

RestFul接口案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@RestController()
@CrossOrigin(origins = {"http://localhost"})
public class EmployeeController {
@Resource
EmployeeService employeeService;

// 根据id查询员工
@GetMapping("/employee/{id}")
public BaseResponse<Employee> getEmployeeById(@PathVariable Long id) {
return ResultUtils.success(employeeService.getEmployeeById(id));
}

// 根据id 更新员工
@PutMapping("/employee")
public BaseResponse<Integer> updateEmployee(@RequestBody Employee employee) {
return ResultUtils.success(employeeService.updateEmployeeByID(employee));
}

// 新增员工
@PostMapping("/employee")
public BaseResponse<Integer> addEmployee(@RequestBody Employee employee) {
return ResultUtils.success(employeeService.addEmployee(employee));
}

// 根据id 删除员工
@DeleteMapping("/employee/{id}")
public BaseResponse<Integer> deleteEmployeeById(@PathVariable Long id) {
return ResultUtils.success(employeeService.deleteEmployeeByID(id));
}

// 查询所有员工
@GetMapping("/employees")
public BaseResponse<List<Employee>> listEmployee() {
return ResultUtils.success(employeeService.getListEmployee());
}
}

@PathVariable 路径变量

@PathVariable 注解,让方法参数的值对应绑定到一个URI模板变量上。

1
2
3
4
5
6
7
8
9
@GetMapping("/add/{a}/{b}")
//上面的方式等同与 @RequestMapping(name = "/add/{a}/{b}", method = RequestMethod.GET)
public String test(@PathVariable int a, @PathVariable int b){

System.out.println("a ->" + a);
System.out.println("b ->" + b);
int res = a + b;
return "结果为" + res;
}

使用路径变量的好处?

  • 使路径变得更加简洁;
  • 获得参数更加方便,Spring MVC会自动进行类型转换。
  • 通过路径变量的类型可以约束访问参数,如果类型不一样,则访问不到对应的请求方法,如这里访问是的路径是/add/1/a,则路径与方法不匹配,而不会是参数转换失败。

五、🌈拦截器

SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现特定的功能。

SpringMVC 拦截器,允许在请求被目标方法处理的前后进行拦截,执行一些额外操作;比如:权限验证、日志记录、数据共享等…

基本使用

使用步骤

  1. 实现 HandlerInterceptor 接口的组件即可成为拦截器
  2. 创建 WebMvcConfigurer 组件,并配置拦截器的拦截路径
  3. 查看执行顺序效果:preHandle => 目标方法 => postHandle => afterCompletion

案例

  1. 实现 HandlerInterceptor 接口的组件即可成为拦截器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    @Component
    public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
    HttpServletResponse response, Object handler) {
    System.out.println("preHandle......");
    return true; // 放行拦截的请求
    }

    @Override
    public void postHandle(HttpServletRequest request,
    HttpServletResponse response,
    Object handler, ModelAndView modelAndView) {
    System.out.println("postHandle......");
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
    HttpServletResponse response,
    Object handler, Exception ex) {
    System.out.println("afterCompletion......");
    }
    }
  2. 创建 WebMvcConfigurer 组件,并配置拦截器的拦截路径

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    MyInterceptor interceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    // 注册拦截器
    registry.addInterceptor(interceptor)
    .addPathPatterns("/**"); // 拦截所有请求
    }
    }
  3. 查看结果

拦截器的执行顺序

拦截器执行顺序:顺序preHandle => 目标方法 => 倒序postHandle => 渲染 => 倒序afterCompletion

  1. 只有执行成功的 preHandle 会倒序执行 afterCompletion
  2. postHandle 、afterCompletion 从哪里炸,倒序链路从哪里结束
  3. postHandle 失败不会影响 afterCompletion 执行

image-20240905110749590

拦截器和过滤器的区别

拦截器 过滤器
接口 HandlerInterceptor Filter
定义 Spring 框架 Servlet 规范
放行 preHandle 返回 true 放行请求 chain.doFilter() 放行请求
整合性 可以直接整合Spring容器的所有组件 不受Spring容器管理,无法直接使用容器中组件 需要把它放在容器中,才可以继续使用
拦截范围 拦截 SpringMVC 能处理的请求 拦截Web应用所有请求
总结 SpringMVC的应用中,推荐使用拦截器

过滤器与拦截器的区别:拦截器是AOP思想的具体应用

过滤器

  • servlet规范中的一部分,任何java web工程都可以使用
  • url-pattern中配置了/*之后,可以对所有要访问的资源进行拦截

拦截器

  • 拦截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用
  • 拦截器只会拦截访问的控制器方法, 如果访问的是静态资源是不会进行拦截的

六、异常处理

声名式异常处理

声名式异常处理和编程式异常处理

  • 编程式异常处理:

    try - catch、throw

  • 声明式异常处理:

    1. SpringMVC 提供了 @ExceptionHandler@ControllerAdvice 等便捷的声明式注解来进行快速的异常处理
    2. @ExceptionHandler:可以处理指定类型异常
    3. @ControllerAdvice:可以集中处理所有Controller的异常
    4. @ExceptionHandler + @ControllerAdvice: 可以完成全局统一异常处理

自定义全局的异常处理

  1. 自定义全局异常类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    @Getter
    public class CustomException extends Exception{
    // 状态码
    private final int code;

    // 信息
    private final String message;

    // 描述
    private final String description;

    public CustomException(ErrorCode errorCode) {
    super(errorCode.getMessage());
    this.message = errorCode.getMessage();
    this.code = errorCode.getCode();
    this.description = errorCode.getDescription();
    }

    public CustomException(int code, String message, String description) {
    super(message);
    this.code = code;
    this.message = message;
    this.description = description;
    }
    }
  2. 定义业务错误状态码(枚举类)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Getter // 设置get方法
    public enum ErrorCode {
    SUCCESS(2000, "OK", ""),
    PARAMS_ERROR(40000, "请求参数错误", ""),
    NULL_ERROR(40001, "请求参数为空", ""),
    NOT_LOGIN(40100, "未登录", ""),
    NO_AUTH(40101, "无权限", ""),
    SYSTEM_ERROR(50000, "系统内部异常", "");

    final int code;

    final String message;

    final String description;

    ErrorCode(int code, String message, String description) {
    this.code = code;
    this.message = message;
    this.description = description;
    }
    }
  3. 编写全局异常处理器GlobalExceptionHandler, 处理所有异常,返回给前端约定的json数据与错误码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Slf4j
    @RestControllerAdvice // 全局统一异常处理(controller 层)
    public class GlobalExceptionHandler {

    @ExceptionHandler
    public BaseResponse<Object> customException(CustomException e) {
    log.error("CustomException -> " + e.getMessage() );
    return ResultUtils.error(e.getCode(), e.getMessage(), e.getDescription());
    }

    /**
    * 全局的运行时异常处理器
    */
    @ExceptionHandler(RuntimeException.class)
    public BaseResponse<Object> customExceptionHandler(RuntimeException e) {
    log.error("RuntimeException", e);
    return ResultUtils.error(ErrorCode.SYSTEM_ERROR);
    }
    }
  4. 编写业务代码的时候,只需要编写正确逻辑,如果出现预期的问题,需要以抛异常的方式中断逻辑并通知上层。

七、JSR 303 数据校验

JSR 303 是 Java 为 Bean 数据合法性校验 提供的标准框架,它已经包含在 JavaEE 6.0 标准中。JSR 303 通过在 Bean 属性上 标注 类似于 @NotNull@Max 等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。

使用

数据校验使用流程

  1. 引入校验依赖

    1
    2
    3
    4
    5
     <!--JSR303-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
  2. 定义封装数据的Bean

  3. 给Bean的字段标注校验注解,并指定校验错误消息提示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    @Data
    public class EmployeeUpdateVo implements Serializable {

    @NotNull(message = "ID不能为空")
    private Long id;

    // 员工名字
    @NotBlank(message = "姓名不能为空")
    private String name;

    // 年龄
    @Min(value = 0, message = "年龄不能小于0岁")
    @Max(value = 100, message = "年龄不能大于100岁")
    private Integer age;

    // 邮箱
    @Email(message = "格式不正确")
    private String email;

    // 性别
    private String gender;

    // 住址
    private String address;

    // 薪资
    private BigDecimal salary;
    }
  4. 使用@Valid、@Validated开启校验

    1
    2
    3
    4
    5
    6
    7
    8
    @Operation(summary = "根据id更新员工")
    @PutMapping("/employee")
    public BaseResponse<Integer> updateEmployee(@RequestBody @Validated
    EmployeeUpdateVo employeeUpdateVo) throws CustomException {
    Employee employee = new Employee();
    BeanUtils.copyProperties(employeeUpdateVo, employee);
    return ResultUtils.success(employeeService.updateEmployeeByID(employee));
    }
  5. 使用自定义校验注解 + 校验器(implements ConstraintValidator) 完成gender字段自定义校验规则

    // todo

  6. 结合全局异常处理,统一处理数据校验错误

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public BaseResponse
    methodArgumentNotValidException(MethodArgumentNotValidException ex) {
    //1、result 中封装了所有错误信息
    BindingResult result = ex.getBindingResult();

    List<FieldError> errors = result.getFieldErrors();
    Map<String, String> map = new HashMap<>();
    for (FieldError error : errors) {
    String field = error.getField();
    String message = error.getDefaultMessage();
    map.put(field, message);
    }
    return ResultUtils.error(ErrorCode.PARAMS_ERROR, map);
    }

常用的校验注解

校验注解 作用
@Min(value) 验证数字是否大于等于指定的最小值
@Max(value) 验证数字是否小于等于指定的最大值
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 与@Null相反**(a!=null)**
@NotEmpty 验证字符串是否非空**(a!=null && a!=“”)**
@NotBlank 验证字符串是否非空白字符**(a!=null && a.trim().length > 0)**
@Size(max=, min=) 验证字符串、集合、Map、数组的大小是否在指定范围内
@Pattern(regex=, flag=) 验证字符串是否符合指定的正则表达式

自定义校验器

  1. 自定义注解
  2. 自定义校验器

八、✅Knife4j 接口文档

Knife4j是对Swagger文档的增强解决方案,底层是对Springfox的(boot整合swagger)的封装,是对接口文档UI进行优化,注解与Swagger相同

核心功能

  • 文档说明:根据Swagger的规范说明,详细列出接口文档的说明,包括接口地址、类型、请求示例、请求参数、响应示例、响应参数、响应码等信息,对该接口的使用情况一目了然。

  • 在线调试:提供在线接口联调的强大功能,自动解析当前接口参数,同时包含表单验证,调用参数可返回接口响应内容、headers、响应时间、响应状态码等信息,帮助开发者在线调试。

Knife4j 配置使用

注意: 所使用的 SpringBoot 版本为3.3.3

  1. 在pom.xml 引入所需依赖

    1
    2
    3
    4
    5
    6
    <!-- knife4j 接口文档 -->
    <dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
    <version>4.4.0</version>
    </dependency>
  2. 在项目的的config配置中创建SwaggerConfig

    此处要千万注意: 线上环境不要把接口暴露出去!!!

    1
    // todo  此项不配置也可使用
  3. 在application.yaml中添加配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # springdoc-openapi项目配置
    springdoc:
    swagger-ui:
    path: /swagger-ui.html
    tags-sorter: alpha
    operations-sorter: alpha
    api-docs:
    path: /v3/api-docs
    group-configs:
    - group: 'default'
    paths-to-match: '/**'
    packages-to-scan: com.xiaoyu.controller
    # knife4j的增强配置,不需要增强可以不配
    knife4j:
    enable: true
    setting:
    language: zh_cn
  4. 启动项目, swagger访问地址 http://localhost:8080/api/v1/doc.html

接口文档信息标注

常用注解 (SpringBoot3 -> OpenAPI3的规范注解)

注解 标注位置 作用
@Tag Controller 类 描述 controller 作用
@Parameter 参数 标识参数作用
@Parameters 参数 参数多重说明
@Schema model 层的 JavaBean 描述模型作用及每个属性
@Operation 方法 描述方法作用
@ApiResponse 方法 描述响应状态码等

注解使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Tag(name = "员工接口")
@RestController()
@CrossOrigin(origins = {"http://localhost"}) // 允许跨域
public class EmployeeController {
@Resource
EmployeeService employeeService;


@Parameters({@Parameter(name = "id", description = "员工id",
in = ParameterIn.PATH, required = true)})
@Operation(summary = "根据id查询员工")
@GetMapping("/employee/{id}")
public BaseResponse<Employee> getEmployeeById(@PathVariable Long id) {
return ResultUtils.success(employeeService.getEmployeeById(id));
}
}

请求参数是对象时, 可以标注在对象上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Data
@Schema(description = "新增员工")
public class EmployeeAddVo implements Serializable {

@Schema(description = "姓名")
private String name;

@Schema(description = "年龄")
private Integer age;

@Schema(description = "邮箱")
private String email;

@Schema(description = "性别")
private String gender;

@Schema(description = "住址")
private String address;

@Schema(description = "薪资")
private BigDecimal salary;
}

九、Spring MVC 原理

中心控制器

​ Spring的web框架围绕DispatcherServlet设计。DispatcherServlet的作用是将请求分发到不同的处理器

​ SpringMVC框架像许多其他MVC框架一样, 以请求为驱动 , 围绕一个中心Servlet分派请求及提供其他功能DispatcherServlet是一个实际的Servlet (它继承自HttpServlet 基类)

SpringMVC的原理如下图所示:

​ 当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。

⭐SpringMVC执行原理

执行流程

image-20240906083003054

图为SpringMVC的一个较完整的流程图,实线表示SpringMVC框架提供的技术,不需要开发者实现,虚线表示需要开发者实现。

简要分析执行流程

  1. DispatcherServlet表示前端控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求。

    我们假设请求的url为 : http://localhost:8080/SpringMVC/hello 将url拆分成三部分:

    通过分析,如上url表示为:请求位于服务器localhost:8080上的SpringMVC站点的hello控制器。

  2. HandlerMapping处理器映射。DispatcherServlet调用HandlerMapping, HandlerMapping根据请求url查找Handler。

  3. HandlerExecution表示具体的Handler,其主要作用是根据url查找控制器,如上url被查找控制器为: hello

  4. HandlerExecution将解析后的信息传递给DispatcherServlet,如解析控制器映射等。

  5. HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler。

  6. Handler让具体的Controller执行。

  7. Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView

  8. HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet。

  9. DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名

  10. 视图解析器将解析的逻辑视图名传给DispatcherServlet。

  11. DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图。

  12. 最终视图呈现给用户。