Java进阶全套教程(四)------ SpringMVC框架详解
一、SpringMVC核心认知与企业定位
SpringMVC是Spring生态原生的MVC分层Web框架,是目前Java后端Web开发的绝对主流框架,所有SpringBoot Web项目、SpringCloud微服务接口项目的底层Web核心均为SpringMVC。它基于Spring IOC、AOP核心特性构建,完美适配Spring全家桶生态,彻底替代老旧的Struts2框架,解决了原生Servlet开发代码冗余、耦合严重、参数解析繁琐、视图跳转混乱、无统一异常处理的核心痛点。
SpringMVC严格遵循MVC架构设计模式 ,实现数据、视图、业务控制的彻底解耦:Model(模型层)负责封装业务数据与业务逻辑、View(视图层)负责数据展示、Controller(控制层)负责请求分发与流程调度。相较于原生Servlet,SpringMVC提供了全自动参数绑定、统一响应封装、全局异常处理、拦截器、文件操作、跨域支持等企业级通用能力,大幅提升Web项目开发效率与代码规范性。
本教程聚焦Spring6适配的最新SpringMVC特性、底层运行原理、全场景实战、生产避坑、企业规范配置 ,摒弃老旧XML冗余配置、过时API和应试习题,全部案例采用纯注解开发+自定义业务场景,代码原创可直接用于生产项目,适配单体Web项目、前后端分离接口项目、微服务网关接口场景。
官方权威地址 :SpringMVC官方文档 https://docs.spring.io/spring-framework/reference/web/webmvc.html
二、SpringMVC整体架构与执行流程(底层核心)
2.1 核心组件详解(企业必备)
SpringMVC整套框架基于七大核心组件协同工作,组件职责单一、各司其职,理解组件协作机制是吃透SpringMVC底层的关键,所有Web请求的处理流程均围绕这些组件展开:
-
DispatcherServlet(前端控制器) :SpringMVC的核心中枢,全局唯一入口,所有客户端请求(浏览器、Ajax、Postman)都会先经过该组件,负责统一接收请求、分发请求、调度其他组件、响应结果,相当于整个框架的"总指挥"。
-
HandlerMapping(处理器映射器):请求路由匹配组件,负责根据客户端请求的URL地址,匹配对应的Controller处理器方法,返回处理器执行链,解决"请求找哪个方法处理"的问题。
-
HandlerAdapter(处理器适配器):方法执行适配组件,SpringMVC支持多种处理器写法,适配器负责适配不同类型的处理器,统一调用规则,执行目标Controller方法,解决"如何执行找到的方法"的问题。
-
Handler(处理器):开发者自定义的Controller控制器方法,是真正处理业务逻辑的核心,负责接收参数、调用Service业务层、封装返回数据。
-
ViewResolver(视图解析器):视图渲染组件,负责解析视图路径、拼接视图前缀后缀、匹配视图资源,完成服务端页面跳转渲染,适配JSP、Thymeleaf等视图模板。
-
View(视图):数据展示载体,如JSP页面、HTML页面,负责渲染Controller返回的模型数据,展示给客户端。
-
ModelAndView(模型视图):数据与视图封装对象,统一封装业务数据(Model)和视图名称(View),实现数据与视图的绑定传递。
2.2 完整请求执行流程(图解式梳理)
SpringMVC的请求工作流程是整个框架的核心底层原理 ,也是面试高频核心考点、线上接口异常排查的核心依据。其整体运行严格遵循客户端 → Web容器(Tomcat) → SpringMVC核心组件联动 → 业务处理 → 视图/数据渲染 → 客户端响应的标准化链路。
结合Spring官方规范,一次完整的HTTP请求从发起、处理到响应结束,共分为10个标准步骤,全程由SpringMVC九大核心组件自动协同完成,开发者仅需专注业务逻辑开发,下面结合组件职责、底层机制、参数流转做逐行精细化拆解:
步骤1:请求接入容器,交由前端控制器统一接收
客户端(浏览器、Postman、前端Axios等)发起HTTP/HTTPS请求,请求首先被Web容器(Tomcat)捕获。Tomcat读取请求报文、封装原生HttpServletRequest和HttpServletResponse对象,根据项目Servlet映射规则,将所有请求统一转发给SpringMVC的核心总入口 DispatcherServlet(前端控制器)。
DispatcherServlet是整个SpringMVC的唯一全局入口、流程总指挥,所有请求必须经过它统一调度,彻底实现请求集中管控,规避原生Servlet分散处理的混乱问题。
步骤2:处理器映射器完成URL路由匹配
DispatcherServlet接收请求后,不直接处理业务,而是调用 HandlerMapping(处理器映射器) 进行路由解析。映射器会读取当前请求的URL、请求方式,扫描项目中所有标注@RequestMapping、@GetMapping、@PostMapping的Controller接口,完成精准匹配。
匹配成功后,HandlerMapping会封装处理器执行链(HandlerExecutionChain),包含目标Controller方法、拦截器链等信息;若匹配失败(无对应接口),直接返回404请求资源不存在。该步骤解决了「请求该交给哪个方法处理」的核心问题。
步骤3:处理器适配器适配执行规则
DispatcherServlet拿到处理器执行链后,调用HandlerAdapter(处理器适配器) 。由于SpringMVC支持多种处理器写法(注解式Controller、传统Controller、函数式端点等),不同处理器的执行规则不同,适配器的核心作用是统一适配、统一调用规范,屏蔽底层差异,让框架可以通用执行所有类型的处理器方法。
适配完成后,适配器准备执行目标Controller方法,是衔接路由匹配与业务执行的关键中间组件。
步骤4:全自动参数解析、类型转换与参数绑定(核心能力)
这是SpringMVC相较于原生Servlet最大的优势所在。HandlerAdapter会调用内置的参数解析器 ,自动完成全流程参数处理:解析URL参数、表单参数、JSON请求体、路径变量,自动完成数据类型转换、日期格式化、参数校验、对象封装。
最终将处理完成的合法参数,自动注入到目标Controller方法的入参中,全程无需开发者手动获取request、手动转型、手动封装对象,彻底简化参数处理逻辑。
步骤5:执行控制器业务逻辑,封装模型视图数据
参数注入完成后,适配器正式执行目标Controller的业务方法。开发者自定义的Controller逻辑开始执行,调用Service业务层、Dao持久层,完成数据库查询、新增、修改、删除等业务操作,处理业务规则、权限校验、数据组装。
业务执行完成后,Controller会返回数据(Model模型数据)和视图名称,SpringMVC自动将其封装为 ModelAndView 对象:Model存储业务响应数据,View存储视图跳转路径,实现数据与视图的绑定。
步骤6:视图解析器解析真实视图路径
Controller执行完毕,HandlerAdapter将ModelAndView对象回传给核心总指挥DispatcherServlet。随后DispatcherServlet调用 ViewResolver(视图解析器),根据配置的视图前缀、后缀,对ModelAndView中的视图名称进行解析,拼接出完整、真实的服务端视图资源路径(如JSP、Thymeleaf模板页面路径)。
如果是前后端分离场景(使用@RestController),方法直接返回JSON数据,不会进入视图解析流程,直接响应数据给客户端。
步骤7:视图渲染,数据填充页面
视图解析器匹配到真实View视图资源后,启动视图渲染流程。框架将Model中封装的所有业务数据,自动填充到视图页面的对应位置,完成页面数据渲染、动态内容替换,生成完整的静态页面内容。
步骤8:统一封装HTTP响应,返回客户端
视图渲染完成后,DispatcherServlet将渲染后的完整页面、或者接口JSON数据,封装为标准的HTTP响应报文,设置响应状态码、响应头、响应体,通过Web容器响应通道返回给前端客户端。
步骤9:资源释放与后置处理
请求响应完成后,SpringMVC执行后置操作:触发拦截器afterCompletion后置方法、销毁本次请求的临时参数资源、释放Tomcat工作线程、清理请求上下文,避免内存泄漏,保证服务高并发稳定性。
步骤10:单次请求流程彻底结束
客户端接收响应数据/页面,完成一次完整的SpringMVC请求交互,等待下一次请求触发。
💡 核心总结(面试必背) :SpringMVC工作流程核心就是 DispatcherServlet调度 + HandlerMapping路由 + HandlerAdapter执行 + 业务处理 + 视图/数据响应,所有通用底层操作全部由框架自动化实现,开发者仅聚焦业务开发,这也是SpringMVC高效、规范、适配企业级开发的核心原因。
三、SpringMVC环境搭建(Spring6 纯注解企业版)
摒弃传统繁琐的XML全局配置,基于Spring6 + 纯注解配置 + Maven搭建标准SpringMVC开发环境,适配前后端分离、传统Web项目双场景,是目前企业新项目的标准搭建方案。
3.1 全套Maven核心依赖(适配Spring6)
统一版本管控,引入SpringMVC核心、Web容器、JSON解析、单元测试全套依赖,兼容JDK17+、Jakarta EE新标准,无冗余依赖:
xml
<properties>
<spring.version>6.0.11</spring.version>
<junit.version>4.13.2</junit.version>
<jackson.version>2.15.2</jackson.version>
</properties>
<dependencies>
<!-- Spring6核心容器依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- SpringMVC核心Web依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Jakarta Web规范依赖(Spring6必备) -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet.jsp</groupId>
<artifactId>jakarta.servlet.jsp-api</artifactId>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
<!-- JSON序列化解析依赖(前后端分离必备) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- 单元测试依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
3.2 纯注解核心配置类(替代所有XML)
通过三组核心配置类,彻底消灭XML配置,分别实现Spring容器初始化、SpringMVC Web配置、Web容器全局配置,结构清晰、便于后期维护扩展。
3.2.1 Spring根容器配置类
负责扫描业务层、持久层组件,不扫描Web控制器组件,实现容器分层管理:
java
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
// Spring根容器全局配置
@Configuration
// 扫描除Controller外的所有业务组件
@ComponentScan(value = "com.business",
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class))
public class SpringRootConfig {
}
3.2.2 SpringMVC Web配置类
开启MVC注解驱动、配置视图解析器、放行静态资源、注册拦截器、跨域配置,整合所有Web能力:
java
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
// SpringMVC Web配置类
@Configuration
@EnableWebMvc // 开启SpringMVC全注解驱动
@ComponentScan("com.business.controller") // 仅扫描控制器层
public class SpringMvcConfig implements WebMvcConfigurer {
// 放行静态资源:css、js、img、html
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 匹配前端静态资源请求路径
registry.addResourceHandler("/static/**")
// 映射项目资源目录
.addResourceLocations("classpath:/static/");
}
// 全局跨域配置
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*") // 允许所有域名跨域
.allowedMethods("GET","POST","PUT","DELETE") // 允许所有请求方式
.maxAge(3600); // 跨域缓存时长
}
}
3.2.3 Web容器初始化配置类
替代web.xml,实现容器启动时自动加载Spring、SpringMVC配置,初始化前端控制器:
java
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
// Web容器初始化配置,替代传统web.xml
public class WebAppInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
// 加载Spring根容器配置
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringRootConfig.class};
}
// 加载SpringMVC Web配置
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
// 配置前端控制器拦截所有请求
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
四、SpringMVC核心请求处理与参数绑定
参数绑定是SpringMVC的核心能力,相较于原生Servlet手动获取参数、类型转换、封装对象的繁琐操作,SpringMVC实现了全自动参数解析、类型转换、数据封装,支持所有主流请求参数类型,适配企业全场景接口开发。
4.1 基础参数绑定(普通类型、字符串、日期)
自动绑定URL路径参数、表单提交参数,支持int、double、String、Date等基础类型,SpringMVC自动完成类型转换,无需手动处理。针对日期类型,提供全局格式化解决方案,彻底解决日期转换异常问题。
java
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
// 基础参数测试控制器
@RestController
@RequestMapping("/param/basic")
public class BasicParamController {
/**
* 普通基础类型参数绑定
* @param userId 用户ID
* @param userName 用户名
* @param userScore 用户积分
* @return 响应结果
*/
@RequestMapping("/info")
public String getBasicParam(Integer userId, String userName, Double userScore) {
return "接收参数:用户ID=" + userId + ",用户名=" + userName + ",用户积分=" + userScore;
}
/**
* 日期类型参数绑定(全局通用格式化)
* @param createTime 创建时间
* @return 响应结果
*/
@RequestMapping("/date")
public String getDateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date createTime) {
return "接收日期参数:" + createTime;
}
}
4.2 实体类参数绑定(表单自动封装)
支持直接将前端表单参数自动封装为Java实体类对象,要求表单参数名与实体类属性名一致,SpringMVC自动反射赋值,彻底省略手动set赋值操作,适配新增、修改表单场景。
4.2.1 自定义用户实体类
java
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
// 自定义用户实体类
public class User {
private Integer userId;
private String userName;
private String userPhone;
private Integer userAge;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date registerTime;
// 无参构造
public User() {}
// 全参构造
public User(Integer userId, String userName, String userPhone, Integer userAge, Date registerTime) {
this.userId = userId;
this.userName = userName;
this.userPhone = userPhone;
this.userAge = userAge;
this.registerTime = registerTime;
}
// 完整getter、setter方法
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPhone() {
return userPhone;
}
public void setUserPhone(String userPhone) {
this.userPhone = userPhone;
}
public Integer getUserAge() {
return userAge;
}
public void setUserAge(Integer userAge) {
this.userAge = userAge;
}
public Date getRegisterTime() {
return registerTime;
}
public void setRegisterTime(Date registerTime) {
this.registerTime = registerTime;
}
// 重写toString
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userPhone='" + userPhone + '\'' +
", userAge=" + userAge +
", registerTime=" + registerTime +
'}';
}
}
4.2.2 实体类参数绑定控制器
java
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/param/entity")
public class EntityParamController {
/**
* 自动封装前端表单参数为User实体对象
* @param user 前端参数自动封装的实体对象
* @return 实体对象信息
*/
@RequestMapping("/user/add")
public String addUser(User user) {
// 直接使用封装后的实体对象,无需手动赋值
return "自动封装用户信息:" + user.toString();
}
}
4.3 JSON格式参数绑定(前后端分离核心)
前后端分离项目中,前端统一通过JSON格式传递参数,SpringMVC通过**@RequestBody**注解自动解析JSON字符串,封装为实体类、List、Map对象,是现代接口开发最常用的参数接收方式。
java
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/param/json")
public class JsonParamController {
/**
* JSON对象参数绑定
* @param user 解析后的用户实体对象
* @return 响应结果
*/
@PostMapping("/user/save")
public String saveUserByJson(@RequestBody User user) {
return "JSON解析用户信息:" + user;
}
/**
* JSON数组参数绑定
* @param userList 用户集合
* @return 响应结果
*/
@PostMapping("/user/batch")
public String batchSaveUser(@RequestBody List<User> userList) {
return "批量解析用户数量:" + userList.size() + ",数据:" + userList;
}
/**
* JSON通用Map参数绑定(适配动态参数)
* @param paramMap 动态参数集合
* @return 响应结果
*/
@PostMapping("/param/map")
public String getDynamicParam(@RequestBody Map<String,Object> paramMap) {
return "接收动态JSON参数:" + paramMap;
}
}
4.4 路径变量参数绑定(RESTful风格)
适配RESTful接口设计规范,通过**@PathVariable**注解获取URL路径中的动态参数,替代传统问号传参,接口更简洁、规范,是企业微服务接口标准写法。
java
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/rest")
public class RestParamController {
/**
* 单路径变量接收:根据用户ID查询用户
* @param userId 用户ID
* @return 响应结果
*/
@RequestMapping("/user/{userId}")
public String getUserById(@PathVariable Integer userId) {
return "RESTful查询用户,ID:" + userId;
}
/**
* 多路径变量接收:根据订单ID和状态查询订单
* @param orderId 订单ID
* @param orderStatus 订单状态
* @return 响应结果
*/
@RequestMapping("/order/{orderId}/{orderStatus}")
public String getOrderInfo(@PathVariable String orderId, @PathVariable Integer orderStatus) {
return "查询订单:订单号=" + orderId + ",订单状态=" + orderStatus;
}
}
五、SpringMVC视图跳转与响应封装
SpringMVC提供两种核心响应模式:服务端视图跳转(传统Web项目)和JSON数据响应(前后端分离项目),全面适配不同业务场景,同时支持重定向、请求转发、全局统一响应封装。
5.1 传统视图跳转(转发/重定向)
支持请求转发(服务器内部跳转,地址栏不变、携带参数)和重定向(客户端跳转,地址栏改变、不携带原请求参数),适配JSP、HTML视图页面渲染。
java
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/view")
public class ViewJumpController {
/**
* 普通请求转发(默认跳转方式)
* @param model 模型对象,用于传递页面数据
* @return 视图名称
*/
@RequestMapping("/forward")
public String forwardView(Model model) {
// 向视图传递数据
model.addAttribute("projectName","SpringMVC企业项目");
model.addAttribute("author","Java进阶开发");
// 转发到success.jsp页面
return "success";
}
/**
* 重定向跳转
* @return 重定向路径
*/
@RequestMapping("/redirect")
public String redirectView() {
// 重定向到指定接口/页面
return "redirect:/view/forward";
}
}
5.2 前后端分离JSON统一响应
企业前后端分离项目中,禁止直接返回零散数据,必须封装全局统一响应结果实体,统一返回格式、状态码、提示信息、业务数据,方便前端统一解析处理。
5.2.1 全局统一响应工具类
java
// 全局统一接口响应实体
public class Result<T> {
// 响应状态码:200成功,500失败
private Integer code;
// 响应提示信息
private String msg;
// 响应业务数据
private T data;
// 成功响应(带数据)
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMsg("请求成功");
result.setData(data);
return result;
}
// 成功响应(无数据)
public static <T> Result<T> success() {
return success(null);
}
// 失败响应
public static <T> Result<T> error(Integer code, String msg) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMsg(msg);
return result;
}
// getter、setter
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
5.2.2 统一响应实战控制器
java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/response")
public class ResponseController {
/**
* 查询单个用户信息-统一响应
*/
@GetMapping("/user/get")
public Result<User> getUserInfo() {
User user = new User(1001,"张三","13800138000",24,null);
return Result.success(user);
}
/**
* 查询用户列表-统一响应
*/
@GetMapping("/user/list")
public Result<List<User>> getUserList() {
List<User> userList = new ArrayList<>();
userList.add(new User(1001,"张三","13800138000",24,null));
userList.add(new User(1002,"李四","13900139000",26,null));
return Result.success(userList);
}
/**
* 业务异常响应
*/
@GetMapping("/user/error")
public Result<Void> userError() {
return Result.error(500,"用户信息查询失败,用户不存在");
}
}
六、SpringMVC文件上传与下载(企业全场景)
文件上传下载是项目通用核心功能,SpringMVC封装了极简的文件操作API,支持单文件上传、多文件批量上传、异步无刷新上传、跨服务器上传、文件批量下载,适配头像上传、附件上传、资料导出等生产场景。
6.1 核心文件上传配置
在SpringMVC配置类中注册文件上传解析器,配置文件大小限制、临时缓存、编码格式,解决大文件上传、乱码问题:
java
import org.springframework.context.annotation.Bean;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
// 新增文件上传解析器Bean
@Bean
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
6.2 单文件上传实战
接收前端文件流,自动封装为MultipartFile对象,通过UUID重命名文件避免文件名冲突,统一存储到服务器指定目录:
java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.util.UUID;
@RestController
@RequestMapping("/file")
public class FileUploadController {
// 定义文件存储根目录
private static final String UPLOAD_BASE_PATH = "D:/project_upload/springmvc_file/";
/**
* 单文件上传接口
* @param file 前端上传的文件对象
* @param request 请求对象
* @return 上传结果
*/
@PostMapping("/upload/single")
public Result<String> singleFileUpload(MultipartFile file, HttpServletRequest request) {
try {
// 判断文件是否为空
if (file.isEmpty()) {
return Result.error(500,"上传文件不能为空");
}
// 获取原始文件名
String originalFilename = file.getOriginalFilename();
// 生成唯一文件名,避免覆盖
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
String newFileName = UUID.randomUUID().toString().replace("-","") + suffix;
// 创建存储目录
File dir = new File(UPLOAD_BASE_PATH);
if (!dir.exists()) {
dir.mkdirs();
}
// 拼接完整文件路径
File targetFile = new File(dir, newFileName);
// 文件写入服务器磁盘
file.transferTo(targetFile);
// 返回文件访问路径
String fileUrl = "/static/upload/" + newFileName;
return Result.success(fileUrl);
} catch (Exception e) {
e.printStackTrace();
return Result.error(500,"文件上传失败:" + e.getMessage());
}
}
}
6.3 多文件批量上传实战
通过MultipartFile数组接收多文件,遍历批量保存,适配批量上传图片、批量导入附件等场景:
java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@PostMapping("/upload/batch")
public Result<List<String>> batchFileUpload(MultipartFile[] files) {
List<String> fileUrlList = new ArrayList<>();
try {
// 遍历所有上传文件
for (MultipartFile file : files) {
if (file.isEmpty()) {
continue;
}
// 文件名处理
String originalFilename = file.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
String newFileName = UUID.randomUUID().toString().replace("-","") + suffix;
// 保存文件
File dir = new File(UPLOAD_BASE_PATH);
if (!dir.exists()) {
dir.mkdirs();
}
File targetFile = new File(dir, newFileName);
file.transferTo(targetFile);
// 记录文件路径
fileUrlList.add("/static/upload/" + newFileName);
}
return Result.success(fileUrlList);
} catch (Exception e) {
e.printStackTrace();
return Result.error(500,"批量上传失败:" + e.getMessage());
}
}
6.4 文件下载实战(浏览器直接下载)
读取服务器本地文件,通过响应流写入浏览器,设置响应头实现文件强制下载,支持图片、文档、压缩包等所有格式文件:
java
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@RestController
@RequestMapping("/file")
public class FileDownloadController {
private static final String DOWNLOAD_BASE_PATH = "D:/project_upload/springmvc_file/";
/**
* 文件下载接口
* @param fileName 文件名
* @return 文件响应实体
*/
@GetMapping("/download")
public ResponseEntity<byte[]> fileDownload(@RequestParam String fileName) {
try {
// 拼接文件完整路径
File file = new File(DOWNLOAD_BASE_PATH, fileName);
if (!file.exists()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("文件不存在".getBytes());
}
// 读取文件字节流
byte[] bytes = new byte[(int) file.length()];
FileInputStream fis = new FileInputStream(file);
fis.read(bytes);
fis.close();
// 设置响应头,触发浏览器下载
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition","attachment;filename=" + fileName);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 返回文件字节数据
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("文件下载失败".getBytes());
}
}
}
七、SpringMVC全局异常处理(生产级方案)
传统开发中,控制器异常需要逐个捕获,代码冗余、异常返回格式不统一、前端无法统一处理。SpringMVC提供全局异常处理器,基于AOP思想统一拦截所有控制器异常,实现异常统一捕获、统一封装、友好提示,是生产项目必备优化方案。
7.1 自定义业务异常类
自定义业务异常,区分系统异常和手动抛出的业务异常,精准返回异常状态码和提示信息:
java
// 自定义业务异常
public class BusinessException extends RuntimeException {
private Integer code;
private String msg;
public BusinessException(Integer code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
7.2 全局统一异常处理器
通过@ControllerAdvice声明全局异常类,拦截所有控制器异常,区分自定义业务异常、空指针异常、算术异常、通用系统异常,分类处理、统一返回格式:
java
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
// 全局异常处理器,拦截所有控制器异常
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
/**
* 处理自定义业务异常
*/
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
return Result.error(e.getCode(), e.getMsg());
}
/**
* 处理空指针异常
*/
@ExceptionHandler(NullPointerException.class)
public Result<Void> handleNullPointException() {
return Result.error(500,"系统异常:数据为空,操作失败");
}
/**
* 处理算术运算异常
*/
@ExceptionHandler(ArithmeticException.class)
public Result<Void> handleArithmeticException() {
return Result.error(500,"系统异常:运算错误");
}
/**
* 处理所有未知通用异常
*/
@ExceptionHandler(Exception.class)
public Result<Void> handleAllException(Exception e) {
e.printStackTrace();
return Result.error(500,"服务器繁忙,请稍后重试");
}
}
7.3 异常测试控制器
java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/exception")
public class ExceptionTestController {
// 测试自定义业务异常
@GetMapping("/business")
public Result<Void> testBusinessException() {
throw new BusinessException(4001,"用户权限不足,无法操作");
}
// 测试空指针异常
@GetMapping("/null")
public Result<Void> testNullException() {
String str = null;
str.length();
return Result.success();
}
// 测试算术异常
@GetMapping("/math")
public Result<Void> testMathException() {
int num = 1 / 0;
return Result.success();
}
}
八、SpringMVC拦截器(Interceptor)实战精讲
SpringMVC拦截器是基于AOP思想的请求增强组件,专门用于拦截控制器请求,实现登录校验、权限拦截、日志记录、敏感词过滤、请求限流等通用功能,无侵入业务代码,是企业权限管控的核心组件。
拦截器与过滤器核心区别(面试重点):拦截器是SpringMVC组件、仅拦截控制器请求、可获取Spring容器Bean、不依赖Web容器;过滤器是Servlet组件、拦截所有请求(静态资源+控制器)、无法直接获取Spring Bean、依赖Web容器。
8.1 拦截器核心三大方法
自定义拦截器需实现HandlerInterceptor接口,包含三个核心方法,对应请求全生命周期:
-
preHandle:控制器方法执行前执行,返回true放行、false拦截,用于权限校验、登录验证;
-
postHandle:控制器方法执行完毕、视图渲染前执行,用于修改模型数据、响应数据;
-
afterCompletion:视图渲染完成、请求结束后执行,用于资源释放、请求日志记录。
8.2 登录权限拦截器实战
实现企业通用的登录拦截功能:未登录用户禁止访问业务接口,已登录用户正常放行,放行登录、注册、静态资源接口:
8.2.1 自定义登录拦截器
java
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class LoginInterceptor implements HandlerInterceptor {
// 请求前置拦截:登录权限校验
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取请求地址
String requestUri = request.getRequestURI();
// 放行登录、注册接口
if (requestUri.contains("/user/login") || requestUri.contains("/user/register") || requestUri.contains("/static/")) {
return true;
}
// 从session获取登录用户信息
Object loginUser = request.getSession().getAttribute("loginUser");
// 未登录则拦截请求,返回提示信息
if (loginUser == null) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"msg\":\"用户未登录,请先登录\"}");
return false;
}
// 已登录放行
return true;
}
// 控制器执行后、视图渲染前
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
// 请求完全结束后
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
8.2.2 注册拦截器到Spring容器
在SpringMVC配置类中注册拦截器,配置拦截路径与放行路径:
java
// 重写拦截器注册方法
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/user/login","/user/register","/static/**"); // 放行指定请求
}
8.3 多拦截器执行顺序规则
项目中可配置多个拦截器形成拦截器链,执行顺序遵循核心规则:preHandle顺序执行,postHandle、afterCompletion逆序执行;任意拦截器preHandle返回false,后续所有拦截器和控制器方法均不执行,已执行的preHandle对应的afterCompletion会正常执行。
九、SpringMVC跨域请求解决方案
浏览器同源策略会限制跨域请求:协议、域名、端口任意一个不同,即为跨域,前端Ajax请求会被浏览器拦截。SpringMVC提供三种企业级跨域解决方案,适配不同开发场景。
9.1 注解式跨域(单接口生效)
通过@CrossOrigin注解作用于控制器类/方法,精准控制单个接口跨域,适合少量接口需要跨域的场景:
java
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/cors")
// 当前类所有接口允许跨域
@CrossOrigin(origins = "*",maxAge = 3600)
public class CorsController {
@GetMapping("/test")
public Result<String> corsTest() {
return Result.success("跨域请求成功");
}
}
9.2 全局跨域配置(全项目生效)
前文已配置全局跨域,作用于项目所有接口,适合前后端分离项目统一跨域处理,无需逐个接口配置,是企业主流方案。
9.3 过滤器跨域配置(兼容老旧项目)
通过自定义跨域过滤器实现全局跨域,兼容低版本Spring项目:
java
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request = (HttpServletRequest) servletRequest;
// 设置跨域响应头
response.setHeader("Access-Control-Allow-Origin","*");
response.setHeader("Access-Control-Allow-Methods","GET,POST,PUT,DELETE,OPTIONS");
response.setHeader("Access-Control-Allow-Headers","*");
response.setHeader("Access-Control-Max-Age","3600");
// 放行OPTIONS预请求
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(200);
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
十、SSM整合企业完整实战(Spring6+MyBatis3.5+SpringMVC)
整合Spring6+SpringMVC+MyBatis3.5 三大核心框架,搭建标准企业级SSM架构,实现数据库CRUD、事务管控、请求处理、视图渲染、异常拦截、权限校验全套能力,完全贴合生产项目架构规范。
整合核心思路:Spring容器管理所有业务Bean(Service、Dao、事务)、MyBatis负责数据库持久化操作、SpringMVC负责Web请求调度与响应,三层架构完全解耦。
10.1 项目分层架构
标准企业分层结构,职责清晰、规范统一:
-
entity实体层:封装数据库表对应实体类
-
dao持久层:数据库CRUD接口,MyBatis实现数据访问
-
service业务层:封装业务逻辑,Spring声明式事务管控
-
controller控制层:接收前端请求、调用业务层、封装响应数据
-
config配置层:所有框架注解配置类
-
common公共层:统一响应、全局异常、工具类
10.2 数据库初始化脚本
sql
CREATE DATABASE IF NOT EXISTS ssm_enterprise DEFAULT CHARACTER SET utf8mb4;
USE ssm_enterprise;
-- 员工业务表
CREATE TABLE emp (
emp_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工ID',
emp_name VARCHAR(30) NOT NULL COMMENT '员工姓名',
emp_gender VARCHAR(10) COMMENT '员工性别',
emp_salary DECIMAL(10,2) COMMENT '员工薪资',
emp_dept VARCHAR(30) COMMENT '所属部门',
create_time DATETIME DEFAULT NOW() COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工信息表';
-- 初始化测试数据
INSERT INTO emp(emp_name,emp_gender,emp_salary,emp_dept)
VALUES ('张三','男',8500.00,'研发部'),
('李四','女',7200.00,'测试部'),
('王五','男',9800.00,'架构部');
10.3 完整业务代码实现
10.3.1 员工实体类
java
import java.util.Date;
public class Emp {
private Integer empId;
private String empName;
private String empGender;
private Double empSalary;
private String empDept;
private Date createTime;
// 无参、全参构造、getter、setter、toString
public Emp() {}
public Emp(Integer empId, String empName, String empGender, Double empSalary, String empDept, Date createTime) {
this.empId = empId;
this.empName = empName;
this.empGender = empGender;
this.empSalary = empSalary;
this.empDept = empDept;
this.createTime = createTime;
}
// 省略getter/setter
}
10.3.2 Dao持久层接口
java
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface EmpDao {
// 查询所有员工
@Select("select * from emp")
List<Emp> findAllEmp();
// 根据ID查询员工
@Select("select * from emp where emp_id = #{empId}")
Emp findEmpById(Integer empId);
// 新增员工
@Insert("insert into emp(emp_name,emp_gender,emp_salary,emp_dept) values(#{empName},#{empGender},#{empSalary},#{empDept})")
int addEmp(Emp emp);
// 修改员工
@Update("update emp set emp_name=#{empName},emp_gender=#{empGender},emp_salary=#{empSalary},emp_dept=#{empDept} where emp_id=#{empId}")
int updateEmp(Emp emp);
// 删除员工
@Delete("delete from emp where emp_id = #{empId}")
int deleteEmp(Integer empId);
}
10.3.3 Service业务层接口与实现类
业务层封装CRUD业务逻辑,添加Spring声明式事务,保证数据库操作原子性:
java
import java.util.List;
// 业务接口
public interface EmpService {
// 查询全部员工
List<Emp> findAllEmp();
// 根据ID查询员工
Emp findEmpById(Integer empId);
// 新增员工
int addEmp(Emp emp);
// 修改员工
int updateEmp(Emp emp);
// 删除员工
int deleteEmp(Integer empId);
}
java
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
@Service
public class EmpServiceImpl implements EmpService {
@Resource
private EmpDao empDao;
@Override
public List<Emp> findAllEmp() {
return empDao.findAllEmp();
}
@Override
public Emp findEmpById(Integer empId) {
return empDao.findEmpById(empId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int addEmp(Emp emp) {
return empDao.addEmp(emp);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int updateEmp(Emp emp) {
return empDao.updateEmp(emp);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteEmp(Integer empId) {
return empDao.deleteEmp(empId);
}
}
10.3.4 Controller控制层接口(前后端分离)
编写全套员工CRUD接口,使用全局统一响应格式,适配前后端分离开发:
java
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping("/emp")
public class EmpController {
@Resource
private EmpService empService;
/**
* 查询所有员工
*/
@GetMapping("/list")
public Result<List<Emp>> getEmpList(){
List<Emp> empList = empService.findAllEmp();
return Result.success(empList);
}
/**
* 根据ID查询员工
*/
@GetMapping("/{empId}")
public Result<Emp> getEmpById(@PathVariable Integer empId){
Emp emp = empService.findEmpById(empId);
return Result.success(emp);
}
/**
* 新增员工
*/
@PostMapping("/add")
public Result<Void> addEmp(@RequestBody Emp emp){
int rows = empService.addEmp(emp);
if(rows > 0){
return Result.success();
}
return Result.error(500,"新增员工失败");
}
/**
* 修改员工
*/
@PutMapping("/update")
public Result<Void> updateEmp(@RequestBody Emp emp){
int rows = empService.updateEmp(emp);
if(rows > 0){
return Result.success();
}
return Result.error(500,"修改员工失败");
}
/**
* 删除员工
*/
@DeleteMapping("/delete/{empId}")
public Result<Void> deleteEmp(@PathVariable Integer empId){
int rows = empService.deleteEmp(empId);
if(rows > 0){
return Result.success();
}
return Result.error(500,"删除员工失败");
}
}
10.4 SSM全套核心配置类(纯注解、无XML)
SSM整合核心在于三大容器配置解耦整合,补齐MyBatis、事务、数据源完整配置,适配Spring6新标准。
10.4.1 数据源与MyBatis配置类
java
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.io.IOException;
@Configuration
@PropertySource("classpath:db.properties") // 加载数据库配置文件
@MapperScan("com.business.dao") // 扫描Dao接口
@EnableTransactionManagement // 开启事务管理
public class MyBatisConfig {
@Resource
private Environment env;
// 数据源Bean
@Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("db.driver"));
dataSource.setUrl(env.getProperty("db.url"));
dataSource.setUsername(env.getProperty("db.username"));
dataSource.setPassword(env.getProperty("db.password"));
return dataSource;
}
// SqlSession工厂Bean
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
// 设置数据源
factoryBean.setDataSource(dataSource);
// 设置实体类别名包
factoryBean.setTypeAliasesPackage("com.business.entity");
// 加载Mapper映射文件
Resource[] resources = new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml");
factoryBean.setMapperLocations(resources);
return factoryBean;
}
}
10.4.2 数据库配置文件 db.properties
properties
# MySQL8.0+ 驱动配置(适配Spring6、JDK17)
db.driver=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/ssm_enterprise?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
db.username=root
db.password=123456
10.5 统一整合配置说明
至此,Spring6 + SpringMVC + MyBatis3.5 企业级SSM架构完全整合完成,整体整合逻辑闭环:
1、容器分层
Spring根容器:管理Service、DataSource、事务、MyBatis核心Bean,不扫描Web控制器;
SpringMVC子容器:仅管理Controller、拦截器、Web视图、文件解析等Web组件,父子容器隔离,解耦彻底。
2、全链路闭环
前端请求 → DispatcherServlet → 路由匹配 → 参数绑定 → Controller → Service(事务)→ Dao → MyBatis → MySQL数据库,完美实现完整Web业务链路。
3、生产级特性全覆盖
包含:纯注解零XML配置、全局统一响应、全局异常拦截、事务控制、参数自动绑定、跨域处理、文件上传下载、拦截器权限校验,完全适配企业项目上线标准。
💡 本章核心总结 :SSM整合的本质是Spring管业务与事务、MyBatis管数据持久化、SpringMVC管Web请求调度,三者各司其职,是Java后端最经典、最通用、面试必问的企业级基础架构,也是SpringBoot底层核心雏形。