目录
[Spring MVC 深度解析:从核心原理到企业级实战](#Spring MVC 深度解析:从核心原理到企业级实战)
[一、Spring MVC 的核心定位:为什么选择它?](#一、Spring MVC 的核心定位:为什么选择它?)
[1. 传统 Servlet 开发的痛点](#1. 传统 Servlet 开发的痛点)
[2. Spring MVC 的核心优势](#2. Spring MVC 的核心优势)
[3. Spring MVC 与 Spring Boot 的关系](#3. Spring MVC 与 Spring Boot 的关系)
[二、Spring MVC 核心原理:请求处理全流程](#二、Spring MVC 核心原理:请求处理全流程)
[1. 核心组件](#1. 核心组件)
[2. 请求处理完整流程](#2. 请求处理完整流程)
[3. 核心配置(非 Spring Boot 环境)](#3. 核心配置(非 Spring Boot 环境))
[三、Spring MVC 实战:从 0 到 1 开发 RESTful 接口](#三、Spring MVC 实战:从 0 到 1 开发 RESTful 接口)
[1. 环境搭建](#1. 环境搭建)
[(1)创建 Spring Boot 项目](#(1)创建 Spring Boot 项目)
[(2)Maven 依赖(pom.xml)](#(2)Maven 依赖(pom.xml))
[2. 核心开发](#2. 核心开发)
[(2)定义 Service 层(模拟业务逻辑)](#(2)定义 Service 层(模拟业务逻辑))
[(3)定义 Controller 层(核心请求处理)](#(3)定义 Controller 层(核心请求处理))
[3. 测试接口](#3. 测试接口)
[四、Spring MVC 高级特性](#四、Spring MVC 高级特性)
[1. 请求参数绑定的多种方式](#1. 请求参数绑定的多种方式)
[2. 拦截器(HandlerInterceptor)](#2. 拦截器(HandlerInterceptor))
[(2)注册拦截器(Spring Boot 环境)](#(2)注册拦截器(Spring Boot 环境))
[3. 文件上传](#3. 文件上传)
[4. 跨域处理(CORS)](#4. 跨域处理(CORS))
[1. 常见问题解决方案](#1. 常见问题解决方案)
[(1)404 错误(请求路径找不到)](#(1)404 错误(请求路径找不到))
[(2)参数绑定失败(参数为 null)](#(2)参数绑定失败(参数为 null))
[2. 性能优化技巧](#2. 性能优化技巧)
[(2)优化 DispatcherServlet 线程池](#(2)优化 DispatcherServlet 线程池)
Spring MVC 深度解析:从核心原理到企业级实战
Spring MVC 作为 Spring 框架的核心 Web 模块,是基于 MVC(Model-View-Controller)设计模式的轻量级 Web 开发框架,自诞生以来便成为 Java EE 领域主流的 Web 开发方案。它通过松耦合的组件设计、灵活的请求映射、强大的视图处理能力,完美适配从简单 REST 接口到复杂企业级 Web 应用的开发需求。本文将从核心定位、底层原理、核心组件、实战开发到高级特性,全面拆解这一框架的设计与使用,帮助开发者从 "会用" 到 "精通"。
一、Spring MVC 的核心定位:为什么选择它?
在深入原理前,首先明确 Spring MVC 的核心价值 ------ 它解决了传统 Servlet 开发的痛点,同时与 Spring 生态深度融合,成为 Java Web 开发的事实标准。
1. 传统 Servlet 开发的痛点
- 配置繁琐:每个 Servlet 需在
web.xml中注册,URL 映射、初始化参数等配置分散,维护成本高; - 代码耦合:Servlet 既处理请求参数解析,又处理业务逻辑,还需手动管理响应输出,职责混乱;
- 功能单一:无内置参数绑定、数据校验、视图解析等能力,需手动封装工具类;
- 扩展困难:难以集成 Spring 容器(如依赖注入、事务管理),无法复用 Spring 生态的核心能力。
2. Spring MVC 的核心优势
- 松耦合设计:基于 MVC 分层思想,将请求处理、业务逻辑、视图渲染解耦,符合 "单一职责原则";
- Spring 生态无缝集成:天然融入 Spring 容器,可直接使用 Spring 的依赖注入、AOP、事务管理等特性;
- 强大的请求处理能力:支持灵活的 URL 映射、参数自动绑定、数据校验、文件上传等;
- 多样化视图支持:不仅支持 JSP、Thymeleaf 等服务器端视图,也适配 RESTful 接口的 JSON/XML 响应;
- 可扩展的拦截器机制:通过拦截器实现登录校验、日志记录、性能监控等通用功能;
- 轻量级:核心依赖少,启动快,无侵入式设计,可与其他框架(如 MyBatis、Redis)灵活整合。
3. Spring MVC 与 Spring Boot 的关系
很多开发者易混淆两者的定位:
- Spring MVC:是 "Web 开发框架",专注于请求处理、视图解析等 Web 层能力;
- Spring Boot:是 "Spring 应用的快速开发脚手架",通过自动配置简化 Spring MVC 的配置(如无需手动配置
DispatcherServlet、视图解析器等)。
简言之:Spring Boot 是 "加速器",Spring MVC 是 "核心引擎"------Spring Boot 整合 Spring MVC 时,仅需引入 spring-boot-starter-web 依赖,即可自动完成 Spring MVC 的核心组件配置。
二、Spring MVC 核心原理:请求处理全流程
Spring MVC 的核心是 "前端控制器(DispatcherServlet)+ 组件化请求处理",理解请求从进入应用到响应返回的全流程,是掌握 Spring MVC 的关键。
1. 核心组件
Spring MVC 的请求处理依赖以下核心组件,各组件各司其职,通过 Spring 容器管理,可灵活替换:
| 组件名称 | 核心作用 | 关键实现 |
|---|---|---|
| DispatcherServlet(前端控制器) | 统一接收请求、分发请求、处理响应,是整个流程的 "中枢" | 继承自 HttpServlet,由 Spring MVC 核心包提供 |
| HandlerMapping(处理器映射器) | 根据请求 URL 匹配对应的处理器(Controller 方法),返回 HandlerExecutionChain(包含处理器 + 拦截器) | 常用实现:RequestMappingHandlerMapping(适配 @RequestMapping 注解) |
| HandlerAdapter(处理器适配器) | 适配不同类型的处理器,调用处理器方法并返回 ModelAndView | 常用实现:RequestMappingHandlerAdapter(适配注解式 Controller) |
| Handler(处理器) | 即 Controller 中的请求处理方法,负责处理业务逻辑,返回数据 / 视图 | 通常由 @Controller + @RequestMapping 注解定义 |
| ViewResolver(视图解析器) | 将逻辑视图名解析为物理视图(如 JSP 路径、Thymeleaf 模板) | 常用实现:InternalResourceViewResolver(JSP)、ThymeleafViewResolver(Thymeleaf) |
| HandlerInterceptor(处理器拦截器) | 拦截请求处理流程,实现前置 / 后置处理、完成后处理 | 自定义实现 HandlerInterceptor 接口 |
2. 请求处理完整流程
以 "用户访问 http://localhost:8080/api/users/1 查询用户信息" 为例,拆解请求处理的 10 个核心步骤:
- 请求进入 DispatcherServlet:客户端请求被 Tomcat 等 Servlet 容器接收后,转发给 DispatcherServlet(所有请求的统一入口);
- DispatcherServlet 调用 HandlerMapping:DispatcherServlet 向 HandlerMapping 发起请求,根据 URL(
/api/users/1)匹配对应的 Controller 方法; - HandlerMapping 返回处理器链:HandlerMapping(如 RequestMappingHandlerMapping)解析
@RequestMapping("/api/users/{id}")注解,匹配到UserController.getById(Integer id)方法,返回包含该方法 + 拦截器的 HandlerExecutionChain; - DispatcherServlet 调用 HandlerAdapter:DispatcherServlet 根据处理器类型,选择对应的 HandlerAdapter(如 RequestMappingHandlerAdapter);
- HandlerAdapter 预处理请求:HandlerAdapter 完成参数绑定(如将 URL 中的
id=1绑定到方法参数)、数据校验、文件解析等; - HandlerAdapter 调用处理器方法:执行
UserController.getById(1)方法,处理业务逻辑(如调用 Service 查询用户),返回 ModelAndView(或直接返回 JSON 数据); - HandlerAdapter 返回结果给 DispatcherServlet:若返回 ModelAndView(包含逻辑视图名 + 数据模型),则进入视图解析阶段;若返回 JSON(如 @ResponseBody 注解),则直接处理响应;
- DispatcherServlet 调用 ViewResolver:ViewResolver 将逻辑视图名(如
userDetail)解析为物理视图(如/WEB-INF/views/userDetail.jsp); - View 渲染视图:View 对象(如 JstlView)结合数据模型渲染视图,生成 HTML 响应;
- DispatcherServlet 返回响应:将渲染后的响应(或 JSON 数据)返回给客户端,完成请求处理。
关键细节:
- 若 Controller 方法添加
@ResponseBody注解(或使用@RestController),则步骤 7-9 会跳过,HandlerAdapter 直接将返回值转换为 JSON/XML,通过 DispatcherServlet 返回; - 拦截器会在 "HandlerAdapter 调用处理器方法前"(preHandle)、"处理器方法执行后"(postHandle)、"响应返回后"(afterCompletion)触发,实现通用逻辑拦截。
3. 核心配置(非 Spring Boot 环境)
在传统 Spring MVC 项目(非 Spring Boot)中,需手动配置核心组件,核心配置文件 spring-mvc.xml 示例:
XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 1. 扫描 Controller 所在包,注册 @Controller 注解的 Bean -->
<context:component-scan base-package="com.example.controller"/>
<!-- 2. 开启 MVC 注解驱动(自动注册 RequestMappingHandlerMapping、RequestMappingHandlerAdapter) -->
<mvc:annotation-driven/>
<!-- 3. 配置视图解析器(JSP) -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/> <!-- 视图前缀 -->
<property name="suffix" value=".jsp"/> <!-- 视图后缀 -->
</bean>
<!-- 4. 配置静态资源映射(如 CSS/JS/图片),避免 DispatcherServlet 拦截 -->
<mvc:resources mapping="/static/**" location="/static/"/>
<!-- 5. 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/api/**"/> <!-- 拦截 /api 下的所有请求 -->
<bean class="com.example.interceptor.LogInterceptor"/> <!-- 自定义拦截器 -->
</mvc:interceptor>
</mvc:interceptors>
</beans>
同时需在 web.xml 中注册 DispatcherServlet:
XML
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 注册 DispatcherServlet -->
<servlet>
<servlet-name>spring-mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定 Spring MVC 配置文件路径 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!-- 启动时加载 DispatcherServlet -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 映射所有请求到 DispatcherServlet -->
<servlet-mapping>
<servlet-name>spring-mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
而在 Spring Boot 环境中,上述配置全部由 spring-boot-starter-web 的自动配置类(如 WebMvcAutoConfiguration)完成,开发者无需手动编写 XML,仅需通过注解和配置文件微调即可。
三、Spring MVC 实战:从 0 到 1 开发 RESTful 接口
本节基于 Spring Boot 2.7.x(简化配置),开发一个完整的用户管理 RESTful 接口,覆盖请求映射、参数绑定、数据校验、响应处理等核心场景。
1. 环境搭建
(1)创建 Spring Boot 项目
通过 Spring Initializr(start.spring.io)创建项目,引入核心依赖:
- Spring Web(整合 Spring MVC);
- Lombok(简化 POJO 代码);
- Validation(数据校验)。
(2)Maven 依赖(pom.xml)
XML
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.10</version>
<relativePath/>
</parent>
<dependencies>
<!-- Spring MVC 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 数据校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Lombok -->
<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>
2. 核心开发
(1)定义实体类(User)
使用 Lombok 简化代码,添加 JSR 380 数据校验注解(如 @NotBlank、@Min):
java
package com.example.springmvc.entity;
import lombok.Data;
import javax.validation.constraints.*;
import java.time.LocalDateTime;
/**
* 用户实体类
*/
@Data
public class User {
private Integer id; // 主键ID
@NotBlank(message = "用户名不能为空") // 非空校验
@Size(min = 2, max = 20, message = "用户名长度必须在2-20之间")
private String name; // 用户名
@Min(value = 1, message = "年龄不能小于1") // 最小值校验
@Max(value = 150, message = "年龄不能大于150")
private Integer age; // 年龄
@Email(message = "邮箱格式不正确") // 邮箱格式校验
private String email; // 邮箱
private LocalDateTime createTime; // 创建时间
}
(2)定义 Service 层(模拟业务逻辑)
java
package com.example.springmvc.service;
import com.example.springmvc.entity.User;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 用户业务层
*/
@Service
public class UserService {
// 模拟数据库存储
private final ConcurrentHashMap<Integer, User> userMap = new ConcurrentHashMap<>();
private final AtomicInteger idGenerator = new AtomicInteger(1);
/**
* 新增用户
*/
public User save(User user) {
int id = idGenerator.getAndIncrement();
user.setId(id);
user.setCreateTime(LocalDateTime.now());
userMap.put(id, user);
return user;
}
/**
* 根据ID查询用户
*/
public User getById(Integer id) {
return userMap.get(id);
}
/**
* 查询所有用户
*/
public List<User> listAll() {
return new ArrayList<>(userMap.values());
}
/**
* 根据ID更新用户
*/
public boolean update(Integer id, User user) {
if (!userMap.containsKey(id)) {
return false;
}
user.setId(id);
user.setCreateTime(userMap.get(id).getCreateTime()); // 保留创建时间
userMap.put(id, user);
return true;
}
/**
* 根据ID删除用户
*/
public boolean delete(Integer id) {
return userMap.remove(id) != null;
}
}
(3)定义 Controller 层(核心请求处理)
使用 @RestController(= @Controller + @ResponseBody)定义 REST 接口,覆盖 GET/POST/PUT/DELETE 请求,添加参数校验、异常处理:
java
package com.example.springmvc.controller;
import com.example.springmvc.entity.User;
import com.example.springmvc.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
import java.util.stream.Collectors;
/**
* 用户管理REST接口
* 接口前缀:/api/users
*/
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
/**
* 新增用户:POST /api/users
* @Valid:触发参数校验,BindingResult:接收校验结果
*/
@PostMapping
public ResponseEntity<?> save(@Valid @RequestBody User user, BindingResult bindingResult) {
// 校验失败:返回错误信息
if (bindingResult.hasErrors()) {
String errorMsg = bindingResult.getFieldErrors().stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining("; "));
return new ResponseEntity<>(errorMsg, HttpStatus.BAD_REQUEST);
}
// 校验成功:新增用户
User savedUser = userService.save(user);
return new ResponseEntity<>(savedUser, HttpStatus.CREATED);
}
/**
* 根据ID查询用户:GET /api/users/{id}
* @PathVariable:绑定URL路径参数
*/
@GetMapping("/{id}")
public ResponseEntity<User> getById(@PathVariable Integer id) {
User user = userService.getById(id);
if (user == null) {
return ResponseEntity.notFound().build(); // 用户不存在,返回404
}
return ResponseEntity.ok(user);
}
/**
* 查询所有用户:GET /api/users
*/
@GetMapping
public ResponseEntity<List<User>> listAll() {
List<User> users = userService.listAll();
return ResponseEntity.ok(users);
}
/**
* 根据ID更新用户:PUT /api/users/{id}
*/
@PutMapping("/{id}")
public ResponseEntity<?> update(
@PathVariable Integer id,
@Valid @RequestBody User user,
BindingResult bindingResult) {
// 校验失败
if (bindingResult.hasErrors()) {
String errorMsg = bindingResult.getFieldErrors().stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining("; "));
return new ResponseEntity<>(errorMsg, HttpStatus.BAD_REQUEST);
}
// 更新用户
boolean success = userService.update(id, user);
if (!success) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(userService.getById(id));
}
/**
* 根据ID删除用户:DELETE /api/users/{id}
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Integer id) {
boolean success = userService.delete(id);
if (!success) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.noContent().build(); // 删除成功,返回204
}
}
(4)全局异常处理(可选)
上述代码中,参数校验的错误处理逻辑可通过 @ControllerAdvice+@ExceptionHandler 实现全局异常处理,简化 Controller 代码:
java
package com.example.springmvc.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.util.stream.Collectors;
/**
* 全局异常处理器
*/
@ControllerAdvice // 拦截所有@Controller/@RestController
public class GlobalExceptionHandler {
/**
* 处理参数校验异常
*/
@ExceptionHandler(BindException.class)
public ResponseEntity<String> handleBindException(BindException e) {
String errorMsg = e.getFieldErrors().stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining("; "));
return new ResponseEntity<>(errorMsg, HttpStatus.BAD_REQUEST);
}
/**
* 处理其他异常
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception e) {
return new ResponseEntity<>("服务器内部错误:" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
添加全局异常处理器后,Controller 中的参数校验代码可简化为:
java
@PostMapping
public ResponseEntity<User> save(@Valid @RequestBody User user) {
User savedUser = userService.save(user);
return new ResponseEntity<>(savedUser, HttpStatus.CREATED);
}
3. 测试接口
启动 Spring Boot 应用,使用 Postman/curl 测试接口:
- 新增用户:
POST http://localhost:8080/api/users,请求体{"name":"张三","age":25,"email":"zhangsan@example.com"},返回新增用户信息; - 新增用户(参数校验失败):请求体
{"name":"张","age":0,"email":"zhangsan"},返回错误信息name: 用户名长度必须在2-20之间; age: 年龄不能小于1; email: 邮箱格式不正确; - 查询用户:
GET http://localhost:8080/api/users/1,返回用户信息; - 其他接口测试逻辑类似。
四、Spring MVC 高级特性
1. 请求参数绑定的多种方式
Spring MVC 支持灵活的参数绑定,除了 @PathVariable、@RequestBody,还有以下常用方式:
| 注解 / 方式 | 适用场景 | 示例 |
|---|---|---|
| @RequestParam | 绑定 URL 查询参数 | @GetMapping("/search") public List<User> search(@RequestParam String name) → 访问 /api/users/search?name=张三 |
| @RequestHeader | 绑定请求头参数 | @GetMapping("/header") public String getHeader(@RequestHeader("User-Agent") String userAgent) |
| @CookieValue | 绑定 Cookie 参数 | @GetMapping("/cookie") public String getCookie(@CookieValue("JSESSIONID") String jsessionId) |
| @ModelAttribute | 绑定表单参数(POST 表单) | @PostMapping("/form") public User saveForm(@ModelAttribute User user) |
| 自动绑定 POJO | 多个参数自动绑定到 POJO | 若请求参数为 name=张三&age=25,方法参数为 User user,则自动绑定 user.setName("张三")、user.setAge(25) |
2. 拦截器(HandlerInterceptor)
拦截器可拦截请求处理流程,实现登录校验、日志记录、性能监控等通用功能。自定义拦截器需实现 HandlerInterceptor 接口:
(1)自定义拦截器
java
package com.example.springmvc.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 日志拦截器:记录请求URL、处理时间
*/
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
// 请求处理前执行(返回true:放行,false:拦截)
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 记录请求开始时间
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
// 记录请求信息
log.info("请求开始:URL={}, Method={}", request.getRequestURL(), request.getMethod());
return true; // 放行
}
// 请求处理后、视图渲染前执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("请求处理完成,准备渲染视图");
}
// 视图渲染后执行(响应返回后)
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 计算处理时间
long startTime = (long) request.getAttribute("startTime");
long costTime = System.currentTimeMillis() - startTime;
// 记录响应信息
log.info("请求结束:URL={}, Status={}, 耗时={}ms",
request.getRequestURL(), response.getStatus(), costTime);
}
}
(2)注册拦截器(Spring Boot 环境)
创建配置类,实现 WebMvcConfigurer 接口,注册拦截器:
java
package com.example.springmvc.config;
import com.example.springmvc.interceptor.LogInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Spring MVC 配置类
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 注册拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册日志拦截器,拦截所有请求,排除静态资源
registry.addInterceptor(new LogInterceptor())
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/static/**"); // 排除静态资源路径
}
}
启动应用后,访问任意接口,控制台会打印请求日志:
请求开始:URL=http://localhost:8080/api/users/1, Method=GET
请求处理完成,准备渲染视图
请求结束:URL=http://localhost:8080/api/users/1, Status=200, 耗时=10ms
3. 文件上传
Spring MVC 支持单文件 / 多文件上传,只需配置文件上传解析器(Spring Boot 自动配置),即可快速实现:
(1)配置文件上传(application.yml)
spring:
servlet:
multipart:
max-file-size: 10MB # 单个文件最大大小
max-request-size: 50MB # 单次请求最大文件大小
enabled: true # 开启文件上传(默认开启)
(2)编写文件上传接口
java
/**
* 单文件上传:POST /api/upload
*/
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(
@RequestParam("file") MultipartFile file, // 绑定上传文件
@RequestParam("desc") String desc) { // 绑定额外参数
// 判断文件是否为空
if (file.isEmpty()) {
return new ResponseEntity<>("文件不能为空", HttpStatus.BAD_REQUEST);
}
// 保存文件(示例:保存到项目根目录的 upload 目录)
try {
String uploadPath = System.getProperty("user.dir") + "/upload/";
File dir = new File(uploadPath);
if (!dir.exists()) {
dir.mkdirs(); // 创建目录
}
// 保存文件
String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
File destFile = new File(uploadPath + fileName);
file.transferTo(destFile);
return ResponseEntity.ok("文件上传成功:" + destFile.getAbsolutePath());
} catch (Exception e) {
return new ResponseEntity<>("文件上传失败:" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* 多文件上传:POST /api/uploads
*/
@PostMapping("/uploads")
public ResponseEntity<String> uploadFiles(@RequestParam("files") MultipartFile[] files) {
if (files == null || files.length == 0) {
return new ResponseEntity<>("文件不能为空", HttpStatus.BAD_REQUEST);
}
// 遍历保存文件
for (MultipartFile file : files) {
if (file.isEmpty()) {
continue;
}
try {
String uploadPath = System.getProperty("user.dir") + "/upload/";
File dir = new File(uploadPath);
if (!dir.exists()) {
dir.mkdirs();
}
String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
File destFile = new File(uploadPath + fileName);
file.transferTo(destFile);
} catch (Exception e) {
return new ResponseEntity<>("文件上传失败:" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
return ResponseEntity.ok("多文件上传成功,共上传" + files.length + "个文件");
}
4. 跨域处理(CORS)
前后端分离项目中,前端页面与后端接口部署在不同域名下,会触发跨域问题,Spring MVC 提供 @CrossOrigin 注解或全局 CORS 配置解决:
(1)局部跨域(@CrossOrigin)
在 Controller / 方法上添加 @CrossOrigin 注解:
java
// 整个Controller允许跨域
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "http://localhost:3000", maxAge = 3600) // 允许前端域名 http://localhost:3000 跨域
public class UserController {
// ...
}
// 单个方法允许跨域
@GetMapping("/{id}")
@CrossOrigin(origins = "*") // 允许所有域名跨域
public ResponseEntity<User> getById(@PathVariable Integer id) {
// ...
}
(2)全局跨域配置
在 WebMvcConfig 中配置全局 CORS 规则,替代局部注解:
java
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 对 /api 下的所有路径生效
.allowedOrigins("http://localhost:3000") // 允许的前端域名
.allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的请求方法
.allowedHeaders("*") // 允许的请求头
.allowCredentials(true) // 允许携带 Cookie
.maxAge(3600); // 预检请求缓存时间(秒)
}
五、常见问题与性能优化
1. 常见问题解决方案
(1)404 错误(请求路径找不到)
- 原因 1:Controller 的
@RequestMapping路径配置错误(如拼写错误); - 原因 2:请求方法不匹配(如前端用 POST 请求,后端用 @GetMapping);
- 原因 3:Spring Boot 的上下文路径配置错误(如配置
server.servlet.context-path=/demo,但请求未加/demo前缀); - 解决方案:检查路径拼写、请求方法、上下文路径,开启 Spring MVC 日志(
logging.level.org.springframework.web=DEBUG)排查映射匹配情况。
(2)参数绑定失败(参数为 null)
- 原因 1:请求参数名与方法参数名不匹配(如前端传
userName,后端参数为name); - 原因 2:JSON 请求的 Content-Type 不是
application/json; - 原因 3:POJO 类无无参构造器(JSON 解析器无法实例化);
- 解决方案:使用
@RequestParam(name = "userName")指定参数名,确保请求头 Content-Type 正确,POJO 类添加无参构造器(Lombok 的 @Data 默认包含)。
(3)跨域请求被拒绝
- 原因:未配置 CORS 规则,或配置的允许域名 / 方法不匹配;
- 解决方案:使用
@CrossOrigin或全局 CORS 配置,确保allowedOrigins包含前端域名,allowedMethods包含请求方法。
2. 性能优化技巧
(1)开启请求压缩
Spring MVC 支持 Gzip 压缩响应数据,减少网络传输量:
server:
compression:
enabled: true # 开启压缩
mime-types: application/json,text/html,text/css,application/javascript # 压缩的 MIME 类型
min-response-size: 1024 # 响应数据最小大小(字节),超过才压缩
(2)优化 DispatcherServlet 线程池
Tomcat 的线程池参数直接影响请求处理能力,可通过以下配置优化:
server:
tomcat:
threads:
max: 200 # 最大线程数(根据 CPU 核心数调整,建议 CPU 核心数*2)
min-spare: 50 # 最小空闲线程数
max-connections: 10000 # 最大连接数
accept-count: 100 # 连接队列长度
(3)减少不必要的拦截器
拦截器会增加请求处理耗时,需按需配置:
- 仅对核心路径(如
/api/**)添加拦截器; - 排除静态资源、健康检查等无需拦截的路径;
- 拦截器中避免执行耗时操作(如数据库查询)。
(4)使用异步请求(@Async)
对于耗时的请求(如调用第三方 API),可使用异步请求避免阻塞 Tomcat 线程:
java
// 1. 开启异步支持
@SpringBootApplication
@EnableAsync
public class SpringMvcDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringMvcDemoApplication.class, args);
}
}
// 2. Service 方法添加 @Async
@Service
public class UserService {
@Async
public CompletableFuture<User> getByIdAsync(Integer id) {
// 模拟耗时操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return CompletableFuture.completedFuture(userMap.get(id));
}
}
// 3. Controller 调用异步方法
@GetMapping("/async/{id}")
public CompletableFuture<ResponseEntity<User>> getByIdAsync(@PathVariable Integer id) {
return userService.getByIdAsync(id)
.thenApply(user -> {
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(user);
});
}
六、总结
Spring MVC 的核心是 "前端控制器 + 组件化请求处理",通过 DispatcherServlet 统一接收请求,再由 HandlerMapping、HandlerAdapter 等组件分工协作,实现灵活、松耦合的请求处理流程。它不仅解决了传统 Servlet 开发的痛点,还与 Spring 生态深度融合,成为 Java Web 开发的主流选择。
掌握 Spring MVC 的关键在于:
- 理解请求处理的全流程,熟悉核心组件的职责;
- 熟练使用
@RequestMapping、@PathVariable、@RequestBody等注解实现参数绑定与请求映射; - 掌握数据校验、拦截器、文件上传、跨域处理等高级特性;
- 结合性能优化技巧,确保应用在高并发场景下的稳定性。
无论是开发简单的 REST 接口,还是构建复杂的企业级 Web 应用,Spring MVC 都能通过灵活的配置和扩展能力,满足多样化的开发需求。而 Spring Boot 的自动配置进一步降低了 Spring MVC 的使用门槛,让开发者可以更专注于业务逻辑,而非繁琐的配置。