Skip to content

SpringMVC

1.1 SpringMVC 简介

Spring MVC 全称:Spring Web MVC,是 Spring 框架的一部分,专注于实现 Web 应用程序的模型-视图-控制器(Model-View-Controller, MVC)设计模式。它为构建灵活且松耦合的 Web 应用提供了强大的功能,同时保持了与 Spring 框架其他模块的良好集成。

1.2 SpringMVC 特点

  • Spring 家族原生产品:与 IOC 容器等基础设施无缝对接
  • 全方位覆盖:表述层各细分领域需要解决的问题提供全面解决方案
  • 开发效率高:代码清新简洁,大幅度提升开发效率
  • 组件化程度高:内部组件化程度高,可插拔式组件即插即用
  • 性能卓越:性能卓著,适合现代大型、超大型互联网项目要求
  • 异常处理机制
    • 使用 @ExceptionHandler 注解定义全局异常处理器
    • 支持自定义错误页面和 HTTP 状态码返回
  • RESTful 支持
    • 内置对 RESTful Web 服务的支持
    • 支持多种数据格式(JSON、XML)
  • AOP 集成
    • 与 Spring AOP 集成良好
    • 通过拦截器添加横切关注点

1.3 核心组件及调用流程

1.3.1 SpringMVC 核心组件

  1. DispatcherServlet

    • 功能:前端控制器,接收所有 HTTP 请求并分发
    • 作用:统一入口点,初始化 WebApplicationContext
  2. HandlerMapping

    • 功能:定义规则决定哪个处理器处理特定请求
    • 作用:将请求映射到具体的处理器
  3. HandlerAdapter

    • 功能:适配器接口,执行映射到的处理器
    • 作用:使不同类型处理器可以被一致调用
  4. ViewResolver

    • 功能:根据逻辑视图名找到实际视图资源
    • 作用:解耦视图名称和物理位置
  5. Controller

    • 功能:接收请求并调用业务逻辑
    • 作用:处理用户输入,准备数据,选择视图
  6. ModelAndView

    • 功能:包含模型数据和逻辑视图名
    • 作用:连接业务逻辑层与表示层
  7. View

    • 功能:将模型数据转换为最终用户界面
    • 作用:数据转换及数据渲染
  8. Interceptor

    • 功能:在请求到达控制器前后执行操作
    • 作用:实现日志记录、权限检查等功能
  9. ExceptionHandler

    • 功能:全局捕获和处理控制器抛出的异常
    • 作用:集中处理错误,简化代码结构

1.4 SpringMVC 之 Helloworld(入门体验)

1.4.1 环境准备

xml
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

1.4.2 定义请求处理器 Handler(Controller)

java
package com.at.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller("helloController")
public class HelloController {

    @RequestMapping("/hello")
    @ResponseBody
    public String hello(){
        return "hello springMVC!!!";
    }
}

1.4.3 启动及测试项目

1.5 @RequestMapping 详解

@RequestMapping 是 Spring MVC 中用于映射 Web 请求到处理方法的注解。它可以应用于类或方法级别,用来指定 URL 模式、HTTP 方法类型等条件。

1.5.1 @RequestMapping 位置

  • 类级别:为当前类映射 URL(不能单独使用)
java
@Controller
@RequestMapping("/users")
public class UserController {
    // ...
}
  • 方法级别:为当前方法映射 URL(可以单独使用)
java
@RequestMapping(value = "/getUser", method = RequestMethod.GET)
@ResponseBody
public String getUser() {
    return "success";
}

1.5.2 @RequestMapping 路径匹配

  • 精确匹配
java
@RequestMapping("/hello")
@RequestMapping("/helloController/hello")
  • 模糊匹配
java
@RequestMapping("/hello/?")        // ?: 任意单个字符
@RequestMapping("/helloController/*")  // *: 任意数量任意字符(单层)
@RequestMapping("/helloController/**") // **: 任意层目录的任意数量字符

1.5.3 @RequestMapping 常用属性

  • value:指定请求的 URL 模式
  • method:指定允许的 HTTP 方法(GET、POST、PUT、DELETE 等)
  • params:限定特定的请求参数存在时才匹配
  • headers:限定特定的请求头存在时才匹配
  • consumes:限定请求的内容类型匹配
  • produces:限定响应的内容类型匹配

1.5.4 @RequestMapping 四大组合注解

Spring 提供了一些更具体的注解,它们是 @RequestMapping 的简化版本:

  • @GetMapping:组合了 @RequestMapping(method = RequestMethod.GET)
  • @PostMapping:组合了 @RequestMapping(method = RequestMethod.POST)
  • @PutMapping:组合了 @RequestMapping(method = RequestMethod.PUT)
  • @DeleteMapping:组合了 @RequestMapping(method = RequestMethod.DELETE)
  • @PatchMapping:组合了 @RequestMapping(method = RequestMethod.PATCH)

第 2 章 SpringMVC 处理请求数据

2.1 接收查询&请求体参数

2.1.1 默认情况:参数名与形参名一致

SpringMVC 中形参列表可直接接收请求参数

  • 参数名与形参列表中参数名必须一致
  • 如不一致:默认值 null
  • Controller 接收数据
java
@ResponseBody
@GetMapping("/doRequestParameter")
public String doRequestParameter(String stuName, Integer stuAge){
    System.out.println("stuName = " + stuName);
    System.out.println("stuAge = " + stuAge);
    return "success";
}

2.1.2 @RequestParam 注解

参数名与形参名不一致时,使用@RequestParam 注解获取请求参数

  • 常用属性

    • value:请求参数的名称
    • required:参数是否必需(默认 true)
    • defaultValue:未提供参数时的默认值
    • name:value 的别名
  • Controller 接收数据

java
@ResponseBody
@GetMapping("/doRequestParameter")
public String doRequestParameter(String stuName,
                                @RequestParam(value = "stuAge2", required = false) Integer stuAge){
    System.out.println("stuName = " + stuName);
    System.out.println("stuAge = " + stuAge);
    return "success";
}

2.1.3 POJO 入参

SpringMVC 支持 POJO 入参,将 POJO 对象设置为形参即可

  • 参数名与 POJO 中的属性名必须一致
  • 如不一致会注入 null 值
  • POJO 实体类
java
package com.at.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private Integer stuId;
    private String stuName;
    private Integer stuAge;
    private String[] hobbys;
}
  • Controller 接收数据
java
@ResponseBody
@PostMapping("/testPojoParam")
public String testPojoParam(Student student){
    System.out.println("student = " + student);
    return "success";
}

2.2 接收路径参数(@PathVariable)

SpringMVC 支持在 URL 中使用占位符入参,常用于 RESTFul 风格

  • 占位符语法:
  • @PathVariable("参数名")
  • Controller 接收数据
java
@ResponseBody
@GetMapping("/testURLParam/{stuId}")
public String testURLParam(@PathVariable("stuId") Integer stuId){
    System.out.println("stuId = " + stuId);
    return "success";
}

2.3 接收 JSON 参数

SpringMVC 使用@RequestBody 注解接收 Json 数据

  • 使用 String 类型接收:获取 Json 字符串
  • 使用 POJO 类型接收:获取 POJO 对象
  • Controller 接收数据
java
@ResponseBody
@PostMapping("/testJSONParam")
public String testJSONParam(@RequestBody Student student /*String jsonStr*/){
    // System.out.println("jsonStr = " + jsonStr);    // json字符串
    System.out.println("student = " + student);
    return "success";
}

SpringMVC 使用@CookieValue 注解获取 Cookie 数据

  • Controller 接收数据
java
@ResponseBody
@GetMapping("/testCookieInfo")
public String testCookieInfo(@CookieValue("JSESSIONID") String jsessionId){
    System.out.println("jsessionId = " + jsessionId);
    return "success";
}

2.5 接收请求头数据

SpringMVC 使用@RequestHeader 注解接收请求头信息

  • Controller 接收数据
java
@ResponseBody
@GetMapping("/testRequestHeaderInfo")
public String testRequestHeaderInfo(@RequestHeader("User-Agent") String userAgent){
    System.out.println("User-Agent = " + userAgent);
    return "success";
}

2.6 接收文件数据(文件上传)

SpringBoot 环境中自带文件上传处理器,可在 application.properties 中配置

properties
spring.application.name=spring09-mvc
# 总文件大小限定
spring.servlet.multipart.max-request-size=200MB
# 单文件大小限定
spring.servlet.multipart.max-file-size=20MB
# 文件数量限定
spring.webflux.multipart.max-parts=10
  • HTML 代码
html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>文件上传</title>
  </head>
  <body>
    <!--
文件上传表单要求:
1. method必须是Post
2. enctype必须是multipart/form-data
3. SpringMVC默认限定:单个文件1M以内,总文件10M以内
-->
    <form method="post" enctype="multipart/form-data" action="file/upload">
      用户名 <input type="text" name="username" /> <br />
      头像 <input type="file" name="headImg" /> <br />
      生活照 <input type="file" name="lifeImg" multiple="multiple" /> <br />
      <input type="submit" value="上传" />
    </form>
  </body>
</html>
  • Controller 处理文件上传
java
package com.at.spring09mvc.controller;

import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.util.UUID;

@RequestMapping("/file")
@RestController
public class FileController {

    @RequestMapping("/upload")
    public String upload(
            @RequestParam("username") String username,
            @RequestParam("headImg") MultipartFile headImg,
            @RequestParam("lifeImg") MultipartFile[] lifeImg) throws Exception {

        // 文件名处理
        String originalFilename = headImg.getOriginalFilename();
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
        String newFileName = UUID.randomUUID().toString() + suffix;

        // 保存到指定目录
        File classPath = ResourceUtils.getFile("classpath:static/");
        File dir = new File(classPath, "upload");
        if (!dir.exists()) {
            dir.mkdirs();
        }
        headImg.transferTo(new File(dir, newFileName));
        return "success";
    }
}

2.7 原生 ServletAPI 入参

SpringMVC 支持原生 ServletAPI,直接作为形参入参即可

java
@ResponseBody
@PostMapping("/testServletAPIInfo")
public String testServletAPIInfo(HttpServletRequest request, HttpServletResponse response){
    HttpSession session = request.getSession();
    String stuId = request.getParameter("stuId");
    System.out.println("stuId = " + stuId);
    String stuName = request.getParameter("stuName");
    System.out.println("stuName = " + stuName);
    return "success";
}

2.8 接收 HttpEntity 获取所有请求信息(了解)

SpringMVC 可以使用 HttpEntity 获取请求信息,包括请求头、请求体等

java
package com.at.spring09mvc.controller;

import com.at.spring09mvc.pojo.Product;
import org.springframework.http.HttpEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/httpEntity")
@RestController
public class HttpEntityController {

    @RequestMapping("/test1")
    public String test1(HttpEntity<Product> entity) {
        System.out.println(entity.getHeaders());
        System.out.println(entity.getBody());
        return "test1";
    }
}

第 3 章 SpringMVC 处理响应数据

3.1 响应数据

SpringMVC 使用@ResponseBody 注解响应数据,包括普通文本或 Json 数据

@ResponseBody 书写位置

  • 方法上:表示当前方法响应数据
  • 类上:表示当前类中所有方法响应数据
  • @RestController = @Controller + @ResponseBody

3.1.1 普通文本

java
@ResponseBody
@RequestMapping("/testJsonData")
public String doJsonDataString(){
    return "success";
}

3.1.2 Json 数据

java
@ResponseBody
@RequestMapping("/testJsonData")
public Student doJsonDataString(){
    Student student = new Student();
    student.setStuId(1005);
    student.setStuName("wangwu");
    student.setStuAge(18);
    student.setHobbys(new String[]{"打游戏","看电影"});
    return student;
}

3.2 响应静态资源

3.2.1 转发与重定向(html)

  • 转发
java
@RequestMapping("/testStaticResource")
public String doStaticResource(){
    System.out.println("转发=======");
    return "/html/index.html";           // 默认转发
    // return "forward:/html/index.html"; // 显式转发
}
  • 重定向
java
@RequestMapping("/testStaticResource")
public String doStaticResource(){
    System.out.println("重定向=======");
    return "redirect:/html/index.html";  // 重定向
}

3.2.2 其他资源(了解)

java
@RequestMapping("/testStaticResource")
public String doStaticResource(){
    System.out.println("其他静态资源=======");
    return "/imgs/a.jpg";
}

3.3 响应文件(文件下载)

  • HTML 代码
html
<a href="fileController/filedownload?fileName=3.jpg">3.jpg下载</a>
  • Controller 下载
java
@RequestMapping("/filedownload")
public ResponseEntity<byte[]> filedownload(String fileName) throws Exception {
    File staticDir = ResourceUtils.getFile("classpath:static/path/"+fileName);

    InputStream inputStream = new FileInputStream(staticDir);
    byte[] bytes = new byte[inputStream.available()];
    inputStream.read(bytes);

    // 响应头设置
    MultiValueMap responseHeaderMap = new HttpHeaders();
    responseHeaderMap.add("Content-Disposition", "attachment; filename="+fileName);

    ResponseEntity responseEntity = new ResponseEntity(bytes, responseHeaderMap, HttpStatus.OK);
    return responseEntity;
}

第 4 章 RESTFul 风格设计及练习

4.1 RESTFul 风格概述

4.1.1 RESTFul 简介

RESTful(Representational State Transfer)是一种软件架构风格,用于设计网络应用程序和服务之间的通信。它是一种基于标准 HTTP 方法的简单和轻量级的通信协议。

4.1.2 RESTFul 特点

  1. 每一个 URI 代表 1 种资源
  2. 客户端使用 GET、POST、PUT、DELETE 对服务端资源进行操作
  3. 资源的表现形式是 XML 或者 JSON
  4. 客户端与服务端之间的交互在请求之间是无状态的

4.2 RESTFul 设计规范

4.2.1 HTTP 协议请求方式规范

操作请求方式
查询操作GET
保存操作POST
删除操作DELETE
更新操作PUT

4.2.2 RESTFul 与传统风格对比

传统风格 URL传统方式请求方式RESTFul 风格 URLRESTFul 请求方式
增加/saveEmpPOST/empPOST
删除/deleteEmp?id=1001GET/emp/101DELETE
修改/updateEmpPOST/empPUT
查询/getEmpById?id=1001GET/emp/101GET

4.3 RESTFul 实战案例

4.3.1 准备环境

  • 数据库环境
sql
CREATE TABLE t_emp(
  eid INT PRIMARY KEY AUTO_INCREMENT,
  ename VARCHAR(20) NOT NULL,
  salary DOUBLE(10,2) NOT NULL,
  address VARCHAR(20) NOT NULL,
  did INT NOT NULL
);

INSERT INTO t_emp(ename,salary,address,did) VALUES('zhangsan',15000,'北京昌平',1);
INSERT INTO t_emp(ename,salary,address,did) VALUES('lisi',15000,'北京朝阳',1);
INSERT INTO t_emp(ename,salary,address,did) VALUES('王五',16000,'北京昌平',2);
INSERT INTO t_emp(ename,salary,address,did) VALUES('zhaoliu',20000,'北京昌平',1);
INSERT INTO t_emp(ename,salary,address,did) VALUES('qianqi',25000,'北京昌平',2);

4.3.2 参考案例代码

  • pom.xml
xml
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
  • application.properties
properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/demo
spring.datasource.username=root
spring.datasource.password=root
  • POJO 实体层
java
package com.at.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
    private Integer eid;
    private String ename;
    private Double salary;
    private String address;
    private Integer did;
    private Dept dept;
}
  • DAO 持久化层
java
package com.at.dao;

import com.at.pojo.Employee;
import java.util.List;

public interface EmployeeDao {
    void insertEmployee(Employee employee);
    void deleteEmployeeById(Integer id);
    void updateEmployee(Employee employee);
    Employee getEmployeeById(Integer id);
    List<Employee> getAllEmps();
    List<Employee> getAllEmpAndDept();
}
java
package com.at.dao.impl;

import com.at.dao.EmployeeDao;
import com.at.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository("employeeDao")
public class EmployeeDaoImpl implements EmployeeDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void insertEmployee(Employee employee) {
        String sql = "INSERT INTO t_emp(ename,salary,address,did) VALUES(?,?,?,?)";
        jdbcTemplate.update(sql, employee.getEname(), employee.getSalary(),
                          employee.getAddress(), employee.getDid());
    }

    // 其他方法实现...
}
  • Service 业务逻辑层
java
package com.at.service;

import com.at.pojo.Employee;
import java.util.List;

public interface EmployeeService {
    void saveEmployee(Employee employee);
    void deleteEmployeeById(Integer id);
    void updateEmployee(Employee employee);
    Employee findEmployeeById(Integer id);
    List<Employee> findAllEmps();
    List<Employee> getAllEmpAndDept();
}
java
package com.at.service.impl;

import com.at.dao.EmployeeDao;
import com.at.pojo.Employee;
import com.at.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service("employeeService")
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeDao employeeDao;

    @Override
    public void saveEmployee(Employee employee) {
        employeeDao.insertEmployee(employee);
    }

    // 其他方法实现...
}
  • Controller 控制层
java
package com.at.controller;

import com.at.pojo.Employee;
import com.at.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    @PostMapping("/employees")
    public String addEmployee(@RequestBody Employee employee) {
        employeeService.saveEmployee(employee);
        return "{'status':'ok'}";
    }

    @DeleteMapping("/employee/{eid}")
    public String deleteEmployeeById(@PathVariable("eid") Integer id) {
        employeeService.deleteEmployeeById(id);
        return "{'status':'ok'}";
    }

    @PutMapping("/employee/all")
    public String updateEmployee(@RequestBody Employee employee) {
        employeeService.updateEmployee(employee);
        return "{'status':'ok'}";
    }

    @GetMapping("/employee/{eid}")
    public Employee getEmpById(@PathVariable("eid") Integer eid){
        return employeeService.findEmployeeById(eid);
    }

    @GetMapping("/employee")
    public List<Employee> getAllEmp(){
        return employeeService.findAllEmps();
    }

    @GetMapping("/empAndDept")
    public List<Employee> getAllEmpAndDept(){
        return employeeService.getAllEmpAndDept();
    }
}

4.4 跨域问题

4.4.1 跨域问题概述

  • 什么是跨域:客户端脚本尝试访问与当前页面不在同一个域下的资源
  • 跨域的原因:浏览器同源策略防止 CSRF 攻击

4.4.2 跨域问题解决方案

主要使用 CORS 解决方案

  • 配置类方式(了解)
java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("https://example.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true);
    }
}
  • 注解方式:@CrossOrigin
java
@CrossOrigin(origins = "https://example.com")
@RestController
@RequestMapping("/api")
public class MyController {
    // 所有方法允许来自 https://example.com 的请求
}
java
@RestController
@RequestMapping("/api")
public class MyController {

    @CrossOrigin(origins = "https://example.com", methods = {RequestMethod.GET, RequestMethod.POST})
    @GetMapping("/data")
    public ResponseEntity<String> getData() {
        return new ResponseEntity<>("Some data", HttpStatus.OK);
    }
}

第 5 章 SpringMVC 异常处理器

5.1 异常处理基本概念

为什么需要处理异常?

  • 提高用户体验,友好错误提示
  • 增强系统的可靠性,防止未处理异常传播
  • 简化调试与维护,减少重复代码

如何处理异常?

  • 编程式异常处理:耦合度高(不推荐)
  • 声明式异常处理:使用切面(推荐)

5.2 声明式异常处理

使用注解实现异常处理器:

  • @ControllerAdvice:定义全局异常处理器
  • @ExceptionHandler:处理特定类型异常

5.2.1 全局异常处理

java
package com.at.myexceptionhandler;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
public class MyExceptionHandler {

    @ResponseBody
    @ExceptionHandler(NullPointerException.class)
    public String doNullPointerException(Exception ex) {
        System.out.println("ex = " + ex);
        return "{'status:'501'}"; // 响应文本提示
    }

    @ExceptionHandler(ArithmeticException.class)
    public String doArithmeticException(Exception ex) {
        System.out.println("ex = " + ex);
        return "/error/error_502.html"; // 响应页面提示
    }
}

5.2.2 局部异常处理(了解)

java
@Controller
public class MyController {

    @ExceptionHandler(ResourceNotFoundException.class)
    public String handleResourceNotFoundException(ResourceNotFoundException ex, Model model) {
        model.addAttribute("errorMessage", ex.getMessage());
        return "error/resource-not-found";
    }
}

第 6 章 SpringMVC 拦截器

6.1 拦截器(Interceptor)简介

拦截器用于在请求处理的不同阶段插入自定义逻辑:

  1. 预处理请求:在请求传递给控制器之前执行
  2. 后处理响应:在控制器方法执行完毕但视图渲染之前执行
  3. 完成处理:在整个请求处理完成后执行

6.2 拦截器基本实现

6.2.1 定义拦截器

java
package com.at.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Component("myInterceptor1")
public class MyInterceptor1 implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("==>1.Myinterceprot111->preHandle()!!!");
        return true; // true:放行 false:不放行
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("==>3.Myinterceprot111->postHandle()!!!");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("==>4.Myinterceprot111->afterCompletion()!!!");
    }
}

6.2.2 注册拦截器

java
package com.at.config;

import com.at.interceptor.MyInterceptor1;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class SpringMVCConfig implements WebMvcConfigurer {

    @Autowired
    private MyInterceptor1 myInterceptor1;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor1)
                .addPathPatterns("/interceptorController/testInterceptor");
    }
}

6.3 拦截器工作原理

6.3.1 单个拦截器工作原理

  1. 客户端发送请求
  2. 执行 preHandle(),放行请求(return true)
  3. 执行 Controller 方法
  4. 执行 postHandle()
  5. 执行 afterCompletion()

6.3.2 多个拦截器工作原理

假设三个拦截器按 A -> B -> C 顺序注册:

java
A - Pre Handle
B - Pre Handle
C - Pre Handle
// Controller 执行...
C - Post Handle
B - Post Handle
A - Post Handle
// 视图渲染完成...
C - After Completion
B - After Completion
A - After Completion

6.4 拦截器与过滤器异同

特性拦截器(Interceptor)过滤器(Filter)
作用范围仅限于 Spring MVC 控制器请求整个 Web 应用程序中的所有请求/响应
生命周期依赖于 Spring 容器随应用启动而初始化
配置方式Java 配置类或 XMLweb.xml 或注解
执行顺序preHandle()按注册顺序,postHandle()和 afterCompletion()逆序按配置顺序依次执行
功能特性细粒度控制,认证授权、性能监控等字符编码设置、日志记录、权限检查等
性能考虑只针对 Spring MVC 请求,性能更好拦截所有请求,可能引入额外开销
适用场景处理 Spring MVC 控制器管理的请求需要对所有 HTTP 请求进行统一处理

第 7 章 SpringMVC 数据校验

7.1 SpringMVC 数据校验概念

使用 Bean Validation API 确保接收到的数据符合预期格式和规则

常用校验规则

校验注解作用
@AssertFalse验证 Boolean 类型字段是否为 false
@AssertTrue验证 Boolean 类型字段是否为 true
@DecimalMax验证数字是否小于等于指定最大值
@DecimalMin验证数字是否大于等于指定最小值
@Digits(integer, fraction)验证数值格式
@Email验证字符串是否为邮箱地址格式
@Future验证日期是否在当前时间之后
@Past验证日期是否在当前时间之前
@Min(value)验证数字是否大于等于指定最小值
@Max(value)验证数字是否小于等于指定最大值
@Null验证对象是否为 null
@NotNull验证对象是否不为 null
@NotEmpty验证字符串是否非空
@NotBlank验证字符串是否非空白字符
@Size(max=, min=)验证字符串、集合、Map、数组的大小
@Pattern(regexp=, flag=)验证字符串是否符合指定正则表达式

7.2 SpringMVC 数据校验基本实现

7.2.1 导入依赖

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

7.2.2 定义校验规则

java
package com.at.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.*;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class StudentDTO {

    @NotNull(message = "id不能为空!!!")
    private Integer stuId;

    @Length(min = 3, max = 6, message = "长度在3-6之间!!!")
    private String stuName;

    @Min(value = 18, message = "年龄最小18岁!!!")
    @Max(value = 120, message = "年龄最大120岁!!!")
    private Integer stuAge;

    @Email(message = "邮箱格式不正确!!!")
    private String stuEmail;
}

7.2.3 控制器中验证

java
package com.at.controller;

import com.at.pojo.Student;
import com.at.pojo.StudentDTO;
import org.springframework.beans.BeanUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
public class TestValidateController {

    @GetMapping("/testValidate")
    public String doValidate(@RequestBody @Valid StudentDTO studentDTO,
                             BindingResult br) {

        if(br.hasErrors()){
            Map<String,Object> errorMap = new HashMap<>();
            List<FieldError> fieldErrors = br.getFieldErrors();
            for (FieldError fieldError : fieldErrors) {
                errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());
            }
            return errorMap.toString();
        }

        Student student = new Student();
        BeanUtils.copyProperties(studentDTO, student);
        System.out.println("student = " + student);
        return "doValidate";
    }
}

7.3 SpringMVC 自定义数据校验器(了解)

7.3.1 定义校验注解

java
package com.at.annotation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Documented
@Constraint(validatedBy = {GenderValidate.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,
         ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Gender {
    String message() default "性别只能是男或女!";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

7.3.2 定义校验注解规则

java
package com.at.annotation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class GenderValidate implements ConstraintValidator<Gender,String> {
    @Override
    public void initialize(Gender constraintAnnotation) {
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value.equals("男") || value.equals("女");
    }
}
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StudentDTO {
    // 其他属性...

    @Gender(message = "请输入正确的性别!")
    private String stuGender;
}

7.4 VO 与 DTO

7.4.1 各种 O 的概念

  • Value Object (VO):强调不可变性和基于内容的相等性
  • Data Transfer Object (DTO):简化不同层次间的数据传输
  • Entity:表示持久化的业务实体
  • Form Object:专为表单提交设计
  • Command Object:封装执行某项操作所需的所有信息
  • Transfer Object (TO):专注于远程调用中的高效数据传输

7.4.2 DTO 介绍

DTO 全称:Data Transfer Object

  • 定义:在不同应用程序层之间传递数据的简单 POJO
  • 使用场景:
    • 简化复杂业务逻辑与视图之间的数据交换
    • 避免直接暴露实体类给前端
    • 支持多样的展示需求
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StudentDTO {
    @NotNull(message = "id不能为空!!!")
    private Integer stuId;

    @Length(min = 3, max = 6, message = "长度在3-6之间!!!")
    private String stuName;

    // 其他属性和校验规则...
}

7.4.3 VO 介绍

VO 全称:Value Object

  • 定义:表示值的对象,状态不可改变
  • 使用场景:
    • 需要确保对象状态不会发生变化时
    • 领域驱动设计(DDD)中封装特定含义的值
java
public final class Money {
    private final BigDecimal amount;
    private final Currency currency;

    public Money(BigDecimal amount, Currency currency) {
        this.amount = amount;
        this.currency = currency;
    }
    // Getters but no setters...
}

第 8 章 Swagger 接口文档

8.1 Swagger 介绍

Swagger 是一个用于设计、构建、记录和使用 RESTful Web 服务的开源框架,遵循 OpenAPI 规范。Knife4j 是基于 Swagger 的增强套件。

8.2 Swagger 基本应用

8.2.1 常用 API

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

8.2.2 实现步骤

  • 导入依赖
xml
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
    <version>4.4.0</version>
</dependency>
  • 编写配置文件
yaml
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.at.controller

knife4j:
  enable: true
  setting:
    language: zh_cn
  • 使用注解
java
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Employee implements Serializable {
    @Schema(description = "修改员工信息时,该属性必须有值.增加员工信息时则不需要赋值")
    private Integer id;

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

    // 其他属性...
}
java
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;

@RestController
@RequestMapping("/api/v1")
@Tag(name = "员工管理", description = "员工信息管理的控制器")
public class UserController {

    @Operation(summary = "查询全部员工信息的处理器")
    @GetMapping("/employees")
    public String getEmployees() {
        List<Employee> employees = employeeService.findAllEmployee();
        return employees.toString();
    }

    // 其他方法...
}
  • 访问 Swagger UI:http://ip:port/doc.html

第 9 章 SpringMVC 工作原理

9.1 SpringMVC 九大组件

9.1.1 DispatcherServlet

  • 作用:前端控制器,控制中心
  • 核心调度方法:doDispatch()

9.1.2 HandlerMapping

  • 作用:处理器映射器,根据请求找到 Handler
  • 通过 HandlerMapping 获取 HandlerAdapter 对象

9.1.3 HandlerAdapter

  • 作用:处理器适配器,按照规则执行 Handler
  • 通过 HandlerAdapter 调用 Controller 方法

9.1.4 Handler(Controller)

  • 作用:处理器,负责处理业务逻辑
  • 注解:@Controller

9.1.5 ModelAndView

  • 作用:封装 Model 和 View 信息

9.1.6 ViewResolver

  • 作用:视图解析器,解析逻辑视图名

9.1.7 View

  • 作用:视图,负责将结果显示给用户

9.1.8 ExceptionHandler

  • 作用:异常处理器
  • 注解:@ExceptionHandler

9.1.9 Interceptor

  • 作用:拦截器,预处理和后处理操作