Spring MVC 是什么
本章是 Spring MVC 教程的起点。在讲解任何一个注解或组件之前,必须先回答三个根本问题:Spring MVC 为什么存在?它解决了什么问题?它在 Java Web 生态中处于什么位置? 理解这些,后续学习 DispatcherServlet、请求映射、数据绑定等所有机制才有根基。
产生背景:传统 Servlet 开发的痛点
在 Spring MVC 出现之前,Java Web 开发的主流方式是原生 Servlet API。每个功能对应一个独立的 Servlet 类,开发者需要手动处理请求分发、参数解析、响应拼接等所有细节。
一个典型的传统 Servlet 项目
// LoginServlet.java
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 1. 手动解析请求参数
String username = req.getParameter("username");
String password = req.getParameter("password");
// 2. 手动处理编码
req.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
// 3. 调用业务逻辑(手动获取 Service)
UserService userService = (UserService) getServletContext()
.getAttribute("userService");
User user = userService.login(username, password);
// 4. 手动拼接响应
if (user != null) {
req.getSession().setAttribute("user", user);
resp.sendRedirect("/home"); // 手动重定向
} else {
req.setAttribute("error", "用户名或密码错误");
req.getRequestDispatcher("/login.jsp").forward(req, resp); // 手动转发
}
}
}
// RegisterServlet.java
public class RegisterServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 同样的重复代码:解析参数、处理编码、获取 Service、拼接响应...
}
}
web.xml 配置:
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>com.example.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>register</servlet-name>
<servlet-class>com.example.RegisterServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>register</servlet-name>
<url-pattern>/register</url-pattern>
</servlet-mapping>
<!-- 每增加一个功能,就要在这里加一对 servlet + servlet-mapping -->
传统模式的五大痛点
| 痛点 | 表现 | 后果 |
|---|---|---|
| 重复劳动 | 每个 Servlet 都要写 doGet/doPost、解析参数、处理编码 | 代码冗余,维护困难 |
| 配置膨胀 | web.xml 中每增加一个 URL 就要加一对 servlet-mapping | 配置文件成百上千行 |
| 紧耦合 | Servlet 直接操作 HttpServletRequest/Response,与业务逻辑混在一起 | 无法单元测试,难以复用 |
| 手动依赖查找 | 通过 ServletContext.getAttribute() 获取 Service | 没有依赖注入,代码僵硬 |
| 视图硬编码 | 转发路径写死在代码里("/login.jsp") | 更换视图技术(JSP→Thymeleaf)需要改所有 Servlet |
Spring MVC 的解决方案
Spring MVC 并非凭空创造了一套新框架,而是站在 Servlet API 之上,用一套设计良好的架构解决上述所有痛点。它的核心思想可以概括为:一个入口、统一调度、声明式映射、自动绑定、解耦视图。
对比:传统 Servlet vs Spring MVC
上图展示了 Spring MVC 的核心改进:
- 一个入口:所有请求统一由 DispatcherServlet 接收,无需为每个 URL 配置独立 Servlet
- 自动路由:HandlerMapping 根据 URL 自动找到对应的 Controller 方法,无需手动写路由逻辑
- 声明式映射:用
@RequestMapping("/login")替代 web.xml 中的 servlet-mapping - 自动参数绑定:
@RequestParam String username自动从请求中提取参数,无需手动调用req.getParameter() - 解耦视图:返回逻辑视图名
"login",由 ViewResolver 解析为具体模板路径,视图技术可替换
生活类比:从"手写快递单"到"智能物流系统"
想象你要寄 20 个包裹:
- 传统 Servlet:你手写 20 张快递单,每张都要填收件人、地址、电话、物品描述。每张单的格式还不一样(有的用中文、有的用英文)。快递员来了,你一张一张交给他,还要自己记录哪张给了谁。
- Spring MVC:你使用一个智能物流平台(DispatcherServlet)。你在平台上声明"这 20 个包裹分别要寄给谁"(@RequestMapping),平台自动帮你生成标准化快递单(自动参数绑定)、自动分配给最合适的快递员(HandlerMapping)、自动选择运输方式(ViewResolver),最后统一打包发出(渲染响应)。你只需关注"包裹里装什么"(业务逻辑),不用关心"怎么寄"(Web 层细节)。
Spring MVC 在 Java Web 生态中的位置
Spring MVC 不是孤立的框架,它处于 Java Web 技术栈的中间层:
关键理解:
- Spring MVC 运行在 Servlet 容器之上(Tomcat 调用 DispatcherServlet,DispatcherServlet 调用 Controller)
- Spring MVC 依赖 Spring 容器(Controller 中的 Service 由容器注入,本教程默认此机制已就绪)
- Spring MVC 只负责 Web 层:接收 HTTP 请求、解析参数、调用业务层、包装响应。业务逻辑和数据访问不在其职责范围内
完整示例
场景
飞翔科技要开发一个员工信息查询系统。CTO 大翔要求技术团队评估"用传统 Servlet 写"和"用 Spring MVC 写"的差异。
传统 Servlet 方案
// EmployeeServlet.java
public class EmployeeServlet extends HttpServlet {
private EmployeeService employeeService;
@Override
public void init() throws ServletException {
// 手动从 ServletContext 获取 Service
employeeService = (EmployeeService) getServletContext()
.getAttribute("employeeService");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setContentType("application/json;charset=UTF-8");
// 手动解析参数
String idStr = req.getParameter("id");
int id = Integer.parseInt(idStr); // 可能抛 NumberFormatException
// 调用业务
Employee emp = employeeService.findById(id);
// 手动序列化 JSON
String json = "{\"id\":" + emp.getId() + ",\"name\":\"" + emp.getName() + "\"}";
resp.getWriter().write(json);
}
}
<!-- web.xml -->
<servlet>
<servlet-name>employee</servlet-name>
<servlet-class>com.feixiang.EmployeeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>employee</servlet-name>
<url-pattern>/employee</url-pattern>
</servlet-mapping>
问题:
- 小崔每增加一个接口(如按部门查询、分页查询),就要新建一个 Servlet + 修改 web.xml
- 白歌review代码时发现,参数解析、编码处理、JSON拼接在每个 Servlet 里重复出现
- 黄俪前端调用时发现,错误响应没有统一格式,有时是 HTML 404 页面,有时是空白页
Spring MVC 方案
// EmployeeController.java
@RestController
@RequestMapping("/employees")
public class EmployeeController {
@Autowired
private EmployeeService employeeService; // 容器自动注入
@GetMapping("/{id}")
public Employee getById(@PathVariable int id) {
return employeeService.findById(id); // 自动序列化为 JSON
}
@GetMapping
public List<Employee> listByDept(@RequestParam String dept) {
return employeeService.findByDept(dept);
}
}
变化分析:
- 一个 Controller 类处理多个 URL,无需修改 web.xml
@PathVariable和@RequestParam自动完成参数提取和类型转换@RestController自动将返回值序列化为 JSON,无需手动拼接字符串- 错误处理由全局异常处理器统一处理(后续章节讲解)
易错场景与面试考点
误区一:Spring MVC 替代了 Servlet
错误认知:"用了 Spring MVC 就不用学 Servlet 了。"
纠正:Spring MVC 建立在 Servlet API 之上,DispatcherServlet 本身就是一个 Servlet。不理解 Servlet 生命周期(init/service/destroy)、不理解 HttpServletRequest/Response 的作用,就无法真正理解 Spring MVC 的底层行为。例如:
- 为什么
@RequestBody只能读取一次请求体?因为 HttpServletRequest 的输入流是单向的 - 为什么
forward和redirect行为不同?因为前者是 Servlet 容器内部转发,后者是 HTTP 302 重定向
误区二:Spring MVC 和 Spring Boot 是一回事
错误认知:"我用 Spring Boot 写 Web,所以学 Spring Boot 就够了,不用学 Spring MVC。"
纠正:Spring Boot 是快速启动工具,它自动配置了 DispatcherServlet、组件扫描、默认视图解析器等。但当你需要:
- 自定义拦截器路径
- 配置跨域策略
- 添加自定义参数解析器
- 控制 JSON 序列化行为
这些操作的本质都是在配置 Spring MVC。Spring Boot 帮你省了配置代码,但没有替你理解配置背后的原理。
面试高频:Spring MVC 解决了传统 Servlet 的哪些问题?
标准回答:
- 统一入口:一个 DispatcherServlet 替代 N 个独立 Servlet,消除 web.xml 膨胀
- 声明式路由:注解替代 XML 配置,URL 映射与代码在一起,可读性高
- 自动参数绑定:无需手动调用
req.getParameter()和类型转换 - 依赖注入:Controller 通过
@Autowired获取 Service,无需手动查找 - 解耦视图:逻辑视图名 + ViewResolver,视图技术可替换(JSP/Thymeleaf/FreeMarker)
- 统一异常处理:
@ControllerAdvice全局处理异常,无需每个 Servlet 写 try-catch
小结
Spring MVC 是基于 Servlet API 的 Web 框架,它的核心价值不是"创造新东西",而是用一套优雅的架构解决传统 Servlet 开发的重复劳动、配置膨胀、紧耦合等问题。理解"为什么需要 Spring MVC",是后续学习 DispatcherServlet 调度机制、请求映射原理、数据绑定流程等所有细节的思想基础。
本章与全局的关系:本章回答了"Spring MVC 是什么、为什么存在"。下一章"前端控制器模式"将深入讲解 Spring MVC 的架构设计思想——为什么用 DispatcherServlet 作为唯一入口,以及这种设计模式的优势。