前言:在Java Web开发领域,Spring MVC无疑是主流中的主流——无论是企业级后端开发、分布式系统搭建,还是日常接口开发,几乎都能看到它的身影。很多开发者入门时只会“照搬配置写接口”,却不懂其底层设计逻辑,遇到参数绑定异常、拦截器失效、视图解析出错等问题时无从下手。本文不搞“复制粘贴式”讲解,从Spring MVC的核心设计思想出发,拆解底层执行流程、核心组件、实战配置,再到生产环境中常见问题及解决方案,全程结合实际开发场景,帮你真正吃透Spring MVC,从“会用”到“精通”。
一、为什么Spring MVC能成为Java Web主流框架?
在Spring MVC出现之前,Java Web开发主流是Servlet+JSP的组合,但这种方式存在致命痛点:代码耦合度高(Servlet既要处理请求,又要处理业务逻辑)、配置繁琐(每个请求都要配置Servlet映射)、视图与逻辑混杂(JSP中嵌入大量Java代码),且难以应对复杂项目的扩展需求。
Spring MVC基于Spring框架的IOC和AOP特性,完美解决了上述问题,其核心优势体现在3点,也是它能取代传统Servlet、成为主流的关键:
松耦合设计:通过核心组件分工(处理器映射器、处理器适配器、视图解析器等),将请求处理、业务逻辑、视图渲染完全分离,符合“单一职责原则”,便于后期维护和扩展。
极简配置+强大功能:无论是XML配置还是注解配置,都能快速搭建框架;同时内置参数绑定、拦截器、异常处理、文件上传等常用功能,无需重复开发。
无缝集成Spring生态:与Spring Core、Spring Boot、Spring Security等组件无缝衔接,形成完整的Java后端开发生态,降低技术选型和集成成本(这也是企业首选Spring MVC的核心原因)。
补充:很多新手会混淆“Spring”和“Spring MVC”——Spring是一个一站式的轻量级Java开发框架,核心是IOC(控制反转)和AOP(面向切面编程);而Spring MVC是Spring框架的一个子模块,专门用于处理Web层请求,专注于“请求接收-逻辑处理-响应返回”的全流程。
二、Spring MVC底层核心原理(必懂,面试高频)
要精通Spring MVC,首先要搞懂它的“请求处理全流程”——这是所有功能的基础,也是面试中必问的考点(比如“请说说Spring MVC的执行流程”“DispatcherServlet的作用是什么”)。
核心前提:Spring MVC的所有请求,都会先经过DispatcherServlet(前端控制器),它相当于Spring MVC的“中枢神经”,负责协调所有核心组件,分发请求、处理响应。
2.1 核心组件(5个,缺一不可)
很多教程会罗列一堆组件,但实际开发和面试中,重点只有这5个,搞懂它们的职责,就能轻松理解执行流程:
DispatcherServlet(前端控制器):核心中的核心,负责接收客户端所有请求(如HTTP请求),并分发到对应的组件;同时接收组件的处理结果,统一返回响应(或跳转视图)。
注意:DispatcherServlet本身不处理业务逻辑,只负责“调度”,相当于餐厅的“服务员”,负责传菜、接单,不负责做菜。
HandlerMapping(处理器映射器):根据请求的URL、请求方式(GET/POST)等信息,找到对应的Handler(处理器,也就是我们写的Controller方法)。
比如:我们写的@RequestMapping("/user/list"),HandlerMapping会根据“/user/list”这个URL,找到对应的Controller方法。
HandlerAdapter(处理器适配器):因为Handler(Controller方法)的形式不固定(比如有的有参数,有的无参数,有的返回String,有的返回ModelAndView),HandlerAdapter负责“适配”,将Handler包装成统一的格式,让DispatcherServlet能够调用。
补充:Spring MVC内置了多种适配器,比如RequestMappingHandlerAdapter,专门适配带有@RequestMapping注解的Controller方法(这也是我们最常用的)。
Handler(处理器):就是我们自己写的Controller类和Controller方法,负责处理具体的业务逻辑(比如查询用户列表、新增用户),是请求处理的核心业务层。
ViewResolver(视图解析器):负责将Handler返回的视图名称(如“list”),解析成具体的视图对象(如JSP页面、HTML页面、JSON数据)。
比如:Handler返回“user/list”,ViewResolver会解析成“/WEB-INF/views/user/list.jsp”(默认配置下),如果是前后端分离项目,视图解析器会解析成JSON响应(无需跳转页面)。
2.2 完整执行流程(图文结合理解,记准这8步)
结合上面的核心组件,Spring MVC的请求处理流程共8步,每一步都有明确的职责,缺一不可(建议结合实际代码,对照流程理解):
客户端(浏览器/Postman)发送HTTP请求(如GET /user/list),请求首先到达DispatcherServlet(前端控制器)。
DispatcherServlet接收请求后,调用HandlerMapping(处理器映射器),请求HandlerMapping“找到”对应的Handler(Controller方法)。
HandlerMapping根据请求URL、请求方式等信息,找到对应的Handler,并将Handler的信息(如Controller方法对象)返回给DispatcherServlet。
DispatcherServlet调用HandlerAdapter(处理器适配器),将Handler适配成统一的格式(便于调用)。
HandlerAdapter调用Handler(具体的Controller方法),执行业务逻辑(如查询数据库、处理参数),并返回处理结果(可能是ModelAndView、String、JSON对象等)。
Handler将处理结果返回给HandlerAdapter,再由HandlerAdapter返回给DispatcherServlet。
DispatcherServlet调用ViewResolver(视图解析器),将处理结果中的视图名称,解析成具体的视图对象(如JSP、JSON)。
DispatcherServlet将视图对象渲染(如JSP页面填充数据、JSON格式化),并将最终的响应结果返回给客户端,请求处理完成。
面试小贴士:记住这8步,不用死记硬背,结合组件职责理解即可。比如:DispatcherServlet是“调度者”,每一步都要经过它;HandlerMapping负责“找方法”,HandlerAdapter负责“调方法”,ViewResolver负责“解析视图”。
三、Spring MVC实战配置(注解版,贴合当前开发主流)
当前企业开发中,Spring MVC的配置几乎都是“注解版”(取代了传统的XML配置),因为注解配置更简洁、更易维护。下面结合实际开发场景,讲解核心配置和常用注解,全程可直接复制到项目中使用。
前提:项目已集成Spring(如果是Spring Boot项目,无需手动配置Spring MVC,Spring Boot会自动自动配置DispatcherServlet、ViewResolver等核心组件,这也是Spring Boot简化开发的原因)。
3.1 核心配置类(替代XML配置)
传统XML配置需要写spring-mvc.xml,注解版只需编写一个配置类,继承WebMvcConfigurer,并重写对应的方法即可,核心配置如下(复制可用):
package com.example.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
/**
* Spring MVC核心配置类
* 注解@Configuration:标识该类是配置类,替代spring-mvc.xml
* 注解@EnableWebMvc:开启Spring MVC的注解支持(如@RequestMapping、@Controller等)
*/
@Configuration
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
// 1. 视图解析器配置(前后端分离项目可省略,若用JSP需配置)
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// 配置JSP视图解析器
registry.jsp("/WEB-INF/views/", ".jsp");
// 说明:前缀"/WEB-INF/views/",后缀".jsp",Handler返回"user/list",会解析成"/WEB-INF/views/user/list.jsp"
}
// 2. 静态资源放行(重点!否则CSS、JS、图片等静态资源会被Spring MVC拦截)
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// 放行静态资源,交给Tomcat的默认Servlet处理
configurer.enable();
}
// 3. 拦截器配置(实战常用,如登录拦截、权限拦截)
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册登录拦截器(自定义拦截器,下文会讲)
registry.addInterceptor(new LoginInterceptor())
// 拦截所有请求
.addPathPatterns("/**")
// 放行登录、注册接口,以及静态资源
.excludePathPatterns("/user/login", "/user/register", "/static/**");
}
// 4. 跨域配置(前后端分离项目必配,解决跨域问题)
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 允许所有请求跨域
.allowedOriginPatterns("*") // 允许所有来源(生产环境建议指定具体域名,如"http://localhost:8080")
.allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的请求方式
.allowedHeaders("*") // 允许的请求头
.allowCredentials(true) // 允许携带Cookie
.maxAge(3600); // 预检请求的缓存时间(秒)
}
}
说明:如果是Spring Boot项目,无需手动编写这个配置类,Spring Boot会自动配置大部分内容(如DispatcherServlet、静态资源放行),如果需要自定义拦截器、跨域等,只需编写上述配置类即可。
3.2 常用核心注解(实战高频,必掌握)
Spring MVC的注解非常多,但实际开发中,常用的只有这6个,掌握它们,就能应对大部分接口开发场景:
@Controller:标识该类是Spring MVC的处理器(Controller),Spring会自动扫描该类,并将其注入到IOC容器中。
注意:@Controller不能省略,否则Spring无法识别该类是Controller,无法接收请求。
@RequestMapping:用于映射请求URL和请求方式,可标注在类上,也可标注在方法上。
示例:
`// 类上标注:所有方法的URL都以"/user"开头
@Controller
@RequestMapping(“/user”)
public class UserController {
// 方法上标注:完整URL是"/user/list",支持GET和POST请求
@RequestMapping(value = “/list”, method = {RequestMethod.GET, RequestMethod.POST})
public String userList() {
// 业务逻辑
return “user/list”; // 返回视图名称
}
}
`
@GetMapping / @PostMapping:@RequestMapping的简化版,分别对应GET请求和POST请求,实战中更常用(简化代码)。
示例:@GetMapping("/list") 等价于 @RequestMapping(value = "/list", method = RequestMethod.GET)。
@RequestParam:用于接收请求参数(URL拼接的参数,如?name=zhangsan&age=20),可指定参数是否必传、默认值。
示例:
// 接收name和age参数,age非必传,默认值为18 @GetMapping("/get") public String getUser(@RequestParam("name") String username, @RequestParam(value = "age", required = false, defaultValue = "18") Integer age) { System.out.println("用户名:" + username + ",年龄:" + age); return "user/detail"; }
@PathVariable:用于接收URL路径中的参数(RESTful风格接口常用),比如URL为“/user/123”,接收123这个参数。
示例:
// RESTful风格,接收路径中的id参数(URL:/user/123) @GetMapping("/{id}") public String getUserById(@PathVariable("id") Integer userId) { System.out.println("用户ID:" + userId); return "user/detail"; }
@ResponseBody:用于将Handler的返回结果(如String、对象)直接转换成JSON格式,返回给客户端,无需经过视图解析器(前后端分离项目必用)。
补充:@RestController = @Controller + @ResponseBody,标注在类上,说明该类所有方法都返回JSON,无需每个方法都加@ResponseBody(实战中最常用)。
四、实战高频场景(避坑指南,生产环境常用)
掌握了原理和配置,接下来就是实战——下面讲解几个生产环境中最常用的场景,以及对应的实现方式和避坑点,这些都是我在实际开发中踩过的坑,分享给大家,避免重复踩坑。
4.1 场景1:前后端分离项目,返回JSON数据(最常用)
当前开发主流是“前后端分离”(前端用Vue、React,后端用Spring MVC提供接口),后端无需跳转视图,只需返回JSON数据,实现方式如下:
// @RestController = @Controller + @ResponseBody,所有方法返回JSON
@RestController
@RequestMapping("/user")
public class UserController {
// 模拟查询用户列表,返回JSON数组
@GetMapping("/list")
public List<User> getUserList() {
// 模拟数据库查询
List<User> userList = new ArrayList<>();
userList.add(new User(1, "zhangsan", 20));
userList.add(new User(2, "lisi", 22));
return userList; // 自动转换成JSON格式返回
}
// 模拟新增用户,接收JSON参数,返回结果
@PostMapping("/add")
public Result addUser(@RequestBody User user) {
// @RequestBody:接收前端传递的JSON参数,自动封装成User对象
System.out.println("新增用户:" + user);
// 返回自定义结果(包含状态码、消息、数据)
return Result.success("新增用户成功", user);
}
}
// 自定义返回结果类(实战必写,统一响应格式)
class Result {
private Integer code; // 状态码:200成功,500失败,404找不到
private String message; // 响应消息
private Object data; // 响应数据
// 静态方法,简化调用
public static Result success(String message, Object data) {
Result result = new Result();
result.setCode(200);
result.setMessage(message);
result.setData(data);
return result;
}
// 省略getter、setter方法
}
避坑点:1. 接收JSON参数必须加@RequestBody注解,否则Spring MVC无法将JSON转换成Java对象;2. 前后端分离项目,无需配置ViewResolver(配置了也没用);3. 跨域问题必须解决(参考3.1中的跨域配置),否则前端会报跨域错误。
4.2 场景2:拦截器实战(登录拦截、权限拦截)
实战中,我们需要拦截某些请求(如需要登录才能访问的接口),判断用户是否登录,未登录则跳转登录页面或返回未登录提示,这就需要用到Spring MVC的拦截器。
实现步骤(3步):
package com.example.demo.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* 登录拦截器:未登录用户无法访问需要权限的接口
*/
public class LoginInterceptor implements HandlerInterceptor {
/**
* 拦截请求(在Handler执行之前执行)
* 返回true:放行,继续执行Handler
* 返回false:拦截,不执行Handler,直接返回响应
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 获取Session
HttpSession session = request.getSession();
// 2. 判断Session中是否有登录用户(登录成功后,会将用户信息存入Session)
Object loginUser = session.getAttribute("loginUser");
if (loginUser == null) {
// 3. 未登录:前后端分离项目返回JSON提示,传统项目跳转登录页面
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("{\\"code\\":401,\\"message\\":\\"未登录,请先登录\\",\\"data\\":null}");
return false; // 拦截请求
}
// 已登录,放行
return true;
}
// 可选:Handler执行之后,视图渲染之前执行(可用于修改ModelAndView)
// @Override
// public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
// }
// 可选:视图渲染之后执行(可用于释放资源)
// @Override
// public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
// }
}
在Spring MVC配置类中注册拦截器(参考3.1中的addInterceptors方法);
测试:未登录时访问“/user/list”,会返回401未登录提示;登录成功后(将用户信息存入Session),即可正常访问。
注意:拦截器和过滤器(Filter)的区别,面试中常问——拦截器是Spring MVC的组件,只拦截Spring MVC处理的请求(如Controller接口),基于Java反射实现;过滤器是Servlet的组件,拦截所有请求(包括静态资源、Servlet),基于Servlet规范实现。
4.3 场景3:异常统一处理(实战必配)
开发中,Controller方法中难免会出现异常(如空指针异常、数据库异常),如果不处理,会返回默认的错误页面(如404、500页面),体验很差。Spring MVC提供了@ControllerAdvice + @ExceptionHandler注解,实现异常统一处理,所有Controller的异常都会被拦截,统一返回JSON提示。
实现代码(复制可用):
package com.example.demo.exception;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 全局异常处理器:统一处理所有Controller的异常
* @ControllerAdvice:标识该类是全局异常处理器,会扫描所有Controller
*/
@ControllerAdvice
public class GlobalExceptionHandler {
// 1. 处理所有运行时异常(最常用,如NullPointerException、IndexOutOfBoundsException)
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public Result handleRuntimeException(RuntimeException e) {
// 打印异常信息(便于排查问题)
e.printStackTrace();
// 统一返回错误提示
return Result.error("系统异常,请联系管理员!", e.getMessage());
}
// 2. 处理自定义异常(实战常用,如登录失败、参数错误)
@ExceptionHandler(LoginException.class)
@ResponseBody
public Result handleLoginException(LoginException e) {
// 自定义异常,无需打印堆栈(已知异常)
return Result.error(e.getMessage(), null);
}
// 3. 处理参数绑定异常(如前端传递的参数类型错误,int类型传了字符串)
@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody
public Result handleIllegalArgumentException(IllegalArgumentException e) {
return Result.error("参数错误:" + e.getMessage(), null);
}
// 可根据需求,添加更多异常处理方法(如IOException、SQLException等)
}
// 自定义异常(实战常用,区分不同业务异常)
class LoginException extends RuntimeException {
public LoginException(String message) {
super(message);
}
}
// 统一返回结果类(和4.1中的Result一致)
class Result {
private Integer code;
private String message;
private Object data;
// 错误返回
public static Result error(String message, Object data) {
Result result = new Result();
result.setCode(500);
result.setMessage(message);
result.setData(data);
return result;
}
// 省略getter、setter方法
}
优势:无需在每个Controller方法中try-catch,统一拦截、统一处理,代码更简洁,且返回格式统一,便于前端处理。
五、生产环境常见问题及避坑总结(重点!)
结合我多年的开发经验,整理了Spring MVC生产环境中最常出现的5个问题,以及对应的解决方案,避免大家踩坑:
问题1:静态资源(CSS、JS、图片)无法访问,报404错误
原因:Spring MVC的DispatcherServlet拦截了所有请求,包括静态资源,而静态资源没有对应的Handler处理。
解决方案:在Spring MVC配置类中,重写configureDefaultServletHandling方法,开启静态资源放行(参考3.1中的配置);或通过addResourceHandlers方法,指定静态资源路径。
问题2:前后端分离项目,前端传递JSON参数,后端接收不到,报400错误
原因:1. 后端未加@RequestBody注解;2. 前端传递的JSON格式与后端Java对象不匹配(如字段名不一致、类型不匹配);3. 后端Java对象没有无参构造方法。
解决方案:1. 接收JSON参数必须加@RequestBody注解;2. 保证前端JSON字段名与后端Java对象字段名一致(可使用@JsonProperty注解映射不同字段名);3. 给Java对象添加无参构造方法。
问题3:拦截器不生效
原因:1. 未在Spring MVC配置类中注册拦截器;2. 拦截路径配置错误(如addPathPatterns未拦截目标请求,或excludePathPatterns误放行);3. 拦截器类未被Spring扫描(如未加@Component注解,但手动new的拦截器无需加)。
解决方案:1. 确保在配置类中注册拦截器;2. 检查拦截路径配置,确保目标请求被拦截;3. 手动new的拦截器无需加@Component,若用@Autowired注入,需给拦截器加@Component注解。
问题4:@RequestMapping注解无法映射请求,报404错误
原因:1. 未给Controller类加@Controller或@RestController注解;2. 配置类未加@EnableWebMvc注解(Spring Boot项目可省略);3. 请求URL与@RequestMapping的value不一致(大小写敏感);4. Spring未扫描到Controller类(如Controller类不在Spring的扫描范围内)。
解决方案:1. 给Controller类加@RestController或@Controller注解;2. 配置类加@EnableWebMvc注解(非Spring Boot项目);3. 检查请求URL与@RequestMapping的value是否一致(注意大小写);4. 确保Controller类在Spring的扫描范围内(如@ComponentScan注解扫描的包包含Controller类)。
问题5:异常统一处理不生效
原因:1. 未给异常处理器类加@ControllerAdvice注解;2. @ExceptionHandler注解指定的异常类型错误(如要处理NullPointerException,却指定了RuntimeException的子类);3. 异常处理器类未被Spring扫描到。
解决方案:1. 给异常处理器类加@ControllerAdvice注解;2. 确保@ExceptionHandler指定的异常类型,是要处理的异常类型(或其父类);3. 确保异常处理器类在Spring的扫描范围内。
六、总结与拓展
本文从Spring MVC的核心原理、核心组件、执行流程,到实战配置、高频场景、避坑指南,全面讲解了Spring MVC的核心知识点,全程结合实际开发场景,适合Java Web开发者入门、进阶,也可作为面试复习资料。
最后补充2个拓展点,帮助大家进一步提升:
Spring MVC与Spring Boot的关系:Spring Boot是“约定大于配置”的框架,它内置了Spring MVC的核心组件(如DispatcherServlet),无需手动配置,只需引入spring-boot-starter-web依赖,即可快速搭建Spring MVC项目,简化了配置流程。
进阶学习方向:掌握Spring MVC后,可以进一步学习Spring Security(权限管理)、Spring Cloud(微服务),这些技术都基于Spring生态,学好Spring MVC是基础。


