Skip to content

SpringMVC

导航目录

第 1 章 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前端控制器。整个流程控制中心,负责接收请求并分发给各组件执行。
  2. HandlerMapping处理器映射器。根据请求的 URL 找到对应的 Handler(Controller 中的方法)。
  3. HandlerAdapter处理器适配器。负责调用 Handler 对应的方法,并处理参数绑定、类型转换等细节。
  4. Handler处理器(Controller)。由开发人员编写,负责处理具体的业务逻辑。
  5. ModelAndView模型和视图。封装了业务处理返回的数据(Model)和跳转的视图(View)。
  6. ViewResolver视图解析器。将逻辑视图名解析为真正的物理视图资源。
  7. View视图。负责数据的渲染并生成最终的响应界面(如 HTML、JSON)。

1.3.2 SpringMVC 执行流程(面试高频)

  1. 用户发送请求至 DispatcherServlet
  2. DispatcherServlet 调用 HandlerMapping 查找请求对应的 Handler
  3. DispatcherServlet 调用 HandlerAdapter 执行 Handler。
  4. Handler 执行完成后,返回 ModelAndView 对象给 HandlerAdapter。
  5. HandlerAdapter 将结果返回给 DispatcherServlet。
  6. DispatcherServlet 将逻辑视图名传给 ViewResolver 进行解析。
  7. ViewResolver 返回真正的 View 对象。
  8. DispatcherServlet 根据 Model 对 View 进行渲染
  9. 最终将响应结果返回给客户端。

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. 启动应用:运行 Spring Boot 主启动类。
  2. 测试接口:访问 http://localhost:8080/hello
  3. 预期结果:页面显示 hello springMVC!!!

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;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private Integer stuId;
    private String stuName;
    private Integer stuAge;
    private String[] hobbys;

    // 日期格式化处理(面试点)
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthDay;
}
  • 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";
    }
}

2.9 自定义类型转换器(Converter)

当默认转换器(如 String 转 Date)无法满足需求时,可以自定义转换器。

实现步骤

  1. 实现 Converter 接口
java
public class MyDateConverter implements Converter<String, Date> {
    @Override
    public Date convert(String source) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        try {
            return sdf.parse(source);
        } catch (ParseException e) {
            throw new IllegalArgumentException("日期格式有误: " + source);
        }
    }
}
  1. 注册转换器
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new MyDateConverter());
    }
}

第 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.1.3 ResponseEntity 自定义响应(推荐)

ResponseEntity 不仅可以返回数据,还可以自定义 HTTP 状态码响应头

java
@GetMapping("/testResponseEntity")
public ResponseEntity<Student> testResponseEntity() {
    Student student = new Student(101, "admin", 20, null, new Date());

    HttpHeaders headers = new HttpHeaders();
    headers.add("Custom-Header", "SpringMVC-Demo");

    return new ResponseEntity<>(student, headers, HttpStatus.CREATED); // 返回 201 状态码
}

3.2 响应静态资源

3.2.1 转发与重定向 (HTML 页面)

  • 转发 (Forward):浏览器地址栏不变,服务器内部跳转。
java
@RequestMapping("/forward")
public String doForward(){
    return "/html/index.html";           // 默认转发
}
  • 重定向 (Redirect):浏览器地址栏改变,两次请求。
java
@RequestMapping("/redirect")
public String doRedirect(){
    return "redirect:/html/index.html";  // 重定向
}

3.2.2 响应其他静态资源 (图片等)

java
@RequestMapping("/showImage")
public String showImage(){
    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;
}

3.4 统一结果封装

在企业级开发中,为了让所有接口返回格式完全一致,通常会设计一个统一的返回结果类 Result。它可以解决格式混乱、异常无标准等问题。

3.4.1 Result 类核心字段

字段名类型说明
codeInteger业务状态码(200=成功,400=参数错误,500=系统异常,支持自定义)
messageString提示信息(如:“操作成功”;“手机号已存在”等具体原因)
dataObject业务数据(列表=数组、详情=对象、无数据=null)

3.4.2 Result 类实现

java
/**
 * 统一接口返回结果类
 * 所有接口返回格式完全一致,解决格式混乱、异常无标准等问题
 * 核心字段:code(业务状态码)、message(提示信息)、data(业务数据)
 */
@Data
public class Result {
    /**
     * 业务状态码:200=成功,400=参数错误,500=系统异常(可自定义)
     */
    private Integer code;
    /**
     * 提示信息:成功返回"操作成功",失败返回具体原因(如"手机号已存在")
     */
    private String message;
    /**
     * 业务数据:列表返回数组、详情返回对象、无数据返回null
     */
    private Object data;

    // 私有构造方法,禁止外部直接new,保证返回格式规范
    private Result() {}

    // ========== 成功场景快捷方法 ==========
    /**
     * 成功返回(默认提示+业务数据)
     * @param data 业务数据(数组/对象/其他)
     * @return 统一返回结果
     */
    public static Result success(Object data) {
        return success("操作成功", data);
    }

    /**
     * 成功返回(自定义提示+业务数据)
     * @param message 自定义成功提示
     * @param data 业务数据
     * @return 统一返回结果
     */
    public static Result success(String message, Object data) {
        Result result = new Result();
        result.setCode(200);  // 成功固定状态码200
        result.setMessage(message);
        result.setData(data);
        return result;
    }

    // ========== 失败场景快捷方法 ==========
    /**
     * 失败返回(自定义状态码+提示,无数据)
     * @param code 业务状态码(如400/500)
     * @param message 失败具体原因
     * @return 统一返回结果
     */
    public static Result fail(Integer code, String message) {
        Result result = new Result();
        result.setCode(code);
        result.setMessage(message);
        result.setData(null);  // 失败场景默认无数据
        return result;
    }

    /**
     * 失败返回(默认系统异常码500+自定义提示)
     * @param message 失败具体原因
     * @return 统一返回结果
     */
    public static Result fail(String message) {
        return fail(500, message);
    }
}

3.4.3 Controller 中使用 Result

java
@GetMapping("/user/{id}")
public Result getUser(@PathVariable Integer id) {
    User user = userService.getById(id);
    if (user != null) {
        return Result.success(user);
    } else {
        return Result.fail(404, "用户不存在");
    }
}

3.5 消息转换器(HttpMessageConverter)

HttpMessageConverter 是 SpringMVC 处理 @RequestBody@ResponseBody 的核心机制。它负责将 HTTP 请求体转换为 Java 对象,或将 Java 对象转换为 HTTP 响应体。

3.5.1 工作原理

  • 读操作:当方法参数标记了 @RequestBody 时,Spring 会遍历已注册的转换器,根据请求头的 Content-Type 找到合适的转换器进行转换。
  • 写操作:当方法标记了 @ResponseBody 时,Spring 会根据请求头的 Accept 信息和返回值类型,找到合适的转换器将对象转换为指定格式(如 JSON)。

3.5.2 常用实现类

实现类说明
StringHttpMessageConverter处理字符串类型
MappingJackson2HttpMessageConverter处理 JSON 格式(基于 Jackson 库,默认加载)
ByteArrayHttpMessageConverter处理字节数组(如文件上传/下载)
Jaxb2RootElementHttpMessageConverter处理 XML 格式

3.6 内容协商(Content Negotiation)

内容协商是指服务端根据客户端的需求(通过 Accept 请求头或 URL 后缀),返回不同格式的同一资源。

配置示例

java
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.favorParameter(true) // 支持通过请求参数指定格式(如 ?format=json)
                  .parameterName("format")
                  .ignoreAcceptHeader(false) // 不忽略 Accept 请求头
                  .defaultContentType(MediaType.APPLICATION_JSON); // 默认返回 JSON
    }
}

3.7 异步请求处理(面试点)

SpringMVC 支持非阻塞的异步请求处理,可以提高服务器的吞吐量。

3.7.1 Callable 方式

java
@GetMapping("/async-callable")
public Callable<String> asyncCallable() {
    return () -> {
        Thread.sleep(2000); // 模拟耗时操作
        return "Async Result";
    };
}

3.7.2 DeferredResult 方式(推荐)

DeferredResult 更加灵活,可以在任意线程中设置结果。

java
@GetMapping("/async-deferred")
public DeferredResult<Result> asyncDeferred() {
    DeferredResult<Result> output = new DeferredResult<>(5000L); // 5秒超时

    // 模拟异步任务(如消息队列监听)
    CompletableFuture.runAsync(() -> {
        try {
            Thread.sleep(2000);
            output.setResult(Result.success("异步处理成功", null));
        } catch (InterruptedException e) {
            output.setErrorResult(Result.fail("处理失败"));
        }
    });

    return output;
}

3.8 重定向传参(FlashAttributes)

重定向时,数据会丢失。SpringMVC 提供 RedirectAttributes 来解决这个问题。

  • addFlashAttribute:数据保存在 Session 中,但在下次请求完成后会自动销毁。常用于传递操作结果(如“保存成功”)。
  • addAttribute:将数据作为 URL 查询参数(如 ?id=101)。

示例代码

java
@PostMapping("/saveUser")
public String saveUser(User user, RedirectAttributes redirectAttributes) {
    userService.save(user);
    // 使用 FlashAttribute 传递消息
    redirectAttributes.addFlashAttribute("msg", "保存成功!");
    return "redirect:/userList";
}

@GetMapping("/userList")
public String userList(@ModelAttribute("msg") String msg) {
    System.out.println("msg = " + msg); // 获取重定向传递的消息
    return "userList";
}

第 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 Result addEmployee(@RequestBody Employee employee) {
        employeeService.saveEmployee(employee);
        return Result.success("添加员工成功", null);
    }

    @DeleteMapping("/employee/{eid}")
    public Result deleteEmployeeById(@PathVariable("eid") Integer id) {
        employeeService.deleteEmployeeById(id);
        return Result.success("删除员工成功", null);
    }

    @PutMapping("/employee/all")
    public Result updateEmployee(@RequestBody Employee employee) {
        employeeService.updateEmployee(employee);
        return Result.success("修改员工成功", null);
    }

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

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

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

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", methods = {RequestMethod.GET, RequestMethod.POST})
@RestController
@RequestMapping("/api")
public class MyController {

    @GetMapping("/data")
    public Result getData() {
        return Result.success("跨域请求成功", "Some data");
    }
}

第 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 Result doNullPointerException(Exception ex) {
        System.out.println("ex = " + ex);
        return Result.fail(400, "空指针异常,请检查请求数据"); // 使用 Result 统一返回格式
    }

    @ResponseBody
    @ExceptionHandler(ArithmeticException.class)
    public Result doArithmeticException(Exception ex) {
        System.out.println("ex = " + ex);
        return Result.fail(500, "算术异常(如除以0)");
    }
}

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)简介

拦截器用于在请求处理的不同阶段插入自定义逻辑,类似于 Servlet 中的 Filter,但它属于 SpringMVC 框架,能够访问 Spring 容器中的 Bean。

6.1.1 拦截器执行顺序(重点)

  1. preHandle:在控制器方法执行前执行。返回值 true 表示放行,false 表示中断。
  2. postHandle:在控制器方法执行后、视图渲染前执行(如果出现异常则不执行)。
  3. afterCompletion:在视图渲染完成后执行,常用于资源清理(无论是否异常都会执行)。

多个拦截器执行顺序

  • preHandle:按配置顺序正序执行(1 -> 2 -> 3)。
  • postHandle:按配置顺序逆序执行(3 -> 2 -> 1)。
  • afterCompletion:按配置顺序逆序执行(3 -> 2 -> 1)。

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 Result 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());
            }
            // 使用 Result.fail 返回统一格式
            return Result.fail(400, "参数校验失败:" + errorMap.toString());
        }

        Student student = new Student();
        BeanUtils.copyProperties(studentDTO, student);
        System.out.println("student = " + student);
        return Result.success("校验通过", student);
    }
}

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 Result getEmployees() {
        List<Employee> employees = employeeService.findAllEmployee();
        return Result.success(employees);
    }

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

第 9 章 SpringMVC 工作原理

9.1 SpringMVC 九大组件

  1. MultipartResolver:文件上传解析器。
  2. LocaleResolver:本地化(国际化)解析器。
  3. ThemeResolver:主题解析器。
  4. HandlerMapping:处理器映射器。
  5. HandlerAdapter:处理器适配器。
  6. HandlerExceptionResolver:异常解析器。
  7. RequestToViewNameTranslator:视图名翻译器(如果 Handler 没返回视图名,它会根据请求 URL 生成一个)。
  8. ViewResolver:视图解析器。
  9. FlashMapManager:重定向参数管理器。

第 10 章 SpringMVC 原理剖析(进阶)

10.1 初始化过程

DispatcherServlet 初始化时会通过 onRefresh 方法加载这九大组件,默认配置位于 org/springframework/web/servlet/DispatcherServlet.properties

10.2 HandlerAdapter 的作用

为什么需要适配器? 因为 Controller 有多种形式(实现接口的、注解标记的等),适配器统一了调用方式,并在调用前完成:

  • 参数绑定:将 HTTP 请求参数绑定到方法参数。
  • 数据转换:如 String 转 Integer。
  • 数据格式化:如 @DateTimeFormat
  • 数据校验:如 @Valid

10.3 WebMvcConfigurer 自定义配置

除了跨域,WebMvcConfigurer 还可以自定义以下内容:

java
@Configuration
public class MyWebConfig implements WebMvcConfigurer {

    // 1. 添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
    }

    // 2. 静态资源映射
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/my-static/**").addResourceLocations("classpath:/my-static/");
    }

    // 3. 自定义参数解析器
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new CurrentUserArgumentResolver());
    }

    // 4. 自定义消息转换器
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 在现有的转换器列表末尾添加一个
        converters.add(new MyCustomMessageConverter());
    }
}

第 11 章 SpringMVC 常用注解总结(面试必备)

注解说明
@Controller标识为控制器组件,参与 IOC 扫描
@RestController组合注解,等于 @Controller + @ResponseBody
@RequestMapping映射请求路径,支持类和方法级别
@RequestParam绑定查询参数或 Form 表单数据
@PathVariable绑定 URL 占位符路径参数
@RequestBody接收 JSON 格式的请求体,需配合 HttpMessageConverter
@ResponseBody响应 JSON/XML 格式数据,跳过视图解析
@RequestHeader获取指定请求头信息
@CookieValue获取指定 Cookie 信息
@ModelAttribute在方法执行前向 Model 中添加属性
@InitBinder自定义 WebDataBinder,用于格式化日期、字符串等
@ExceptionHandler定义局部异常处理逻辑
@RestControllerAdvice全局异常处理和增强(跨 Controller)
@CrossOrigin解决局部跨域问题