乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 SpringMVC概述与DispatcherServlet

    • 本章导读:Spring MVC概述与DispatcherServlet
    • Spring MVC 是什么
    • MVC 设计模式
    • 前端控制器模式
    • DispatcherServlet
    • 核心组件协作
  • 第2章 控制器与请求映射

    • 本章导读:控制器与请求映射
    • Controller
    • RestController
    • RequestMapping
    • GetMapping
    • PostMapping
    • PutMapping
    • DeleteMapping
    • PathVariable
    • RESTful
    • 请求映射原理
  • 第3章 请求参数获取与转换

    • 本章导读:请求参数获取与转换
    • RequestParam
    • RequestBody
    • RequestHeader
    • CookieValue
    • Model
    • ModelAttribute
    • 数据绑定原理
    • 数据校验
  • 第4章 响应数据与视图解析

    • 本章导读:响应数据与视图解析
    • ResponseBody
    • ResponseEntity
    • ModelAndView
    • ViewResolver
    • HttpMessageConverter
    • forward与redirect
  • 第5章 拦截器过滤器与跨域

    • 本章导读:拦截器、过滤器与跨域
    • HandlerInterceptor
    • WebMvcConfigurer
    • CrossOrigin
    • 登录验证实战
  • 第6章 文件上传与异常处理

    • 本章导读:文件上传与异常处理
    • MultipartFile
    • 文件下载
    • ExceptionHandler
    • ControllerAdvice
    • RestControllerAdvice
    • ResponseStatus
  • 第7章 高级特性与最佳实践

    • 本章导读:高级特性与最佳实践
    • SessionAttributes
    • SessionAttribute
    • RedirectAttributes
    • MockMvc测试
    • 国际化
    • 最佳实践
  • 第8章 扩展与异步机制

    • 本章导读:扩展与异步机制
    • 异步请求处理
    • 自定义参数解析器
    • 内容协商

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 的核心改进:

  1. 一个入口:所有请求统一由 DispatcherServlet 接收,无需为每个 URL 配置独立 Servlet
  2. 自动路由:HandlerMapping 根据 URL 自动找到对应的 Controller 方法,无需手动写路由逻辑
  3. 声明式映射:用 @RequestMapping("/login") 替代 web.xml 中的 servlet-mapping
  4. 自动参数绑定:@RequestParam String username 自动从请求中提取参数,无需手动调用 req.getParameter()
  5. 解耦视图:返回逻辑视图名 "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 的哪些问题?

标准回答:

  1. 统一入口:一个 DispatcherServlet 替代 N 个独立 Servlet,消除 web.xml 膨胀
  2. 声明式路由:注解替代 XML 配置,URL 映射与代码在一起,可读性高
  3. 自动参数绑定:无需手动调用 req.getParameter() 和类型转换
  4. 依赖注入:Controller 通过 @Autowired 获取 Service,无需手动查找
  5. 解耦视图:逻辑视图名 + ViewResolver,视图技术可替换(JSP/Thymeleaf/FreeMarker)
  6. 统一异常处理:@ControllerAdvice 全局处理异常,无需每个 Servlet 写 try-catch

小结

Spring MVC 是基于 Servlet API 的 Web 框架,它的核心价值不是"创造新东西",而是用一套优雅的架构解决传统 Servlet 开发的重复劳动、配置膨胀、紧耦合等问题。理解"为什么需要 Spring MVC",是后续学习 DispatcherServlet 调度机制、请求映射原理、数据绑定流程等所有细节的思想基础。

本章与全局的关系:本章回答了"Spring MVC 是什么、为什么存在"。下一章"前端控制器模式"将深入讲解 Spring MVC 的架构设计思想——为什么用 DispatcherServlet 作为唯一入口,以及这种设计模式的优势。

上一页
本章导读:Spring MVC概述与DispatcherServlet
下一页
MVC 设计模式