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

    • 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章 扩展与异步机制

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

MVC 设计模式

本章是 Spring MVC 教程的思想基础。在讲解 DispatcherServlet 如何调度请求、Controller 如何映射 URL 之前,必须先理解 MVC(Model-View-Controller) 这一经典设计模式。Spring MVC 的命名本身就表明它是对 MVC 模式的 Web 化实现——不理解 MVC 的三层分离思想,就无法理解为什么 Spring MVC 要设计成"DispatcherServlet + Controller + ViewResolver"这样的结构。


定义与作用

MVC 是一种架构模式,它将应用程序划分为三个核心组件,各自承担明确的职责,通过定义良好的接口协作完成用户请求的处理。

**Model(模型)**负责封装业务数据和业务规则。它是应用程序的"真相来源"——用户看到的信息来自 Model,用户提交的数据最终也存入 Model。Model 不关心数据如何展示,也不关心用户通过什么界面操作,它只保证数据的完整性和一致性。

**View(视图)**负责将 Model 中的数据以特定形式呈现给用户。它只读取 Model,不修改 Model。同一个 Model 可以被不同的 View 渲染:HTML 页面供浏览器查看、JSON 字符串供移动端接口调用、PDF 报表供财务部门打印。

**Controller(控制器)**负责接收用户输入,调用 Model 处理业务,并选择合适的 View 呈现结果。它是 Model 和 View 之间的"协调者"——用户操作先到达 Controller,Controller 决定接下来该做什么、该展示什么。

MVC 模式之所以被提出,是因为早期的 GUI 程序(如 1970 年代 Smalltalk 语言的应用)中,界面代码、业务逻辑和数据存储全部混在一起。修改界面可能破坏业务规则,更换数据库可能需要重写界面。MVC 用关注点分离(Separation of Concerns)解决了这一混乱。

生活类比:餐厅后厨分工

想象一家正规餐厅的后厨:

  • Model 是食材与菜谱:冷库里存放着牛肉、蔬菜、调料(数据),菜谱上写着"牛排七分熟需要煎 4 分钟"(业务规则)。食材和菜谱不关心客人坐在大厅还是包间,也不关心服务员穿什么制服。
  • View 是摆盘与上菜:同样的牛排,商务套餐配黑椒汁和西兰花(Web 页面),儿童套餐配薯条和番茄酱(移动端 H5)。摆盘师只从厨房取成品,不会自己去煎牛排。
  • Controller 是传菜员:客人点单后,传菜员把订单送到厨房(调用 Model),煎好后端给摆盘师装饰(选择 View),最后送到客人桌上(返回响应)。传菜员不煎牛排,也不决定盘子花纹,他只负责"把对的东西送到对的地方"。

这个类比的关键在于:三层各司其职,通过标准接口协作,任何一层都可以独立替换而不影响其他层。


核心原理

MVC 三层职责对比

组件职责在 Web 开发中的典型实现不能做的事
Model封装数据与业务规则Entity 类、Service 层、Repository 层直接操作 HTTP 请求/响应
View数据可视化呈现JSP、Thymeleaf、FreeMarker、JSON 序列化直接调用数据库
Controller接收输入、调用 Model、选择 ViewServlet、Spring MVC 的 Controller包含复杂业务逻辑

MVC 在 Web 开发中的演变

MVC 模式诞生于桌面应用时代,进入 Web 领域后经历了三次重要演变:

演变解读:

  1. JSP + JavaBean 时代(1998 年左右):JSP 页面中直接嵌入 <% %> 脚本,既查数据库又拼 HTML。Model 和 View 完全混在一起,维护困难。

  2. Servlet + JSP + JavaBean 时代(2000 年左右):Sun 公司提出 Model 2 架构,用 Servlet 充当 Controller,JSP 只负责展示,JavaBean 负责业务。这是 MVC 在 Java Web 中的首次正规落地,但每个 URL 仍需一个独立 Servlet,配置繁琐。

  3. Spring MVC 时代(2004 年至今):DispatcherServlet 作为唯一入口,用注解声明请求映射,自动完成参数绑定和视图解析。Controller 只需关注"收到什么请求、调用什么业务、返回什么视图",所有 Web 层基础设施由框架统一提供。

Spring MVC 与 MVC 模式的对应关系

对应关系解读:

  • Controller 层:Spring MVC 做了更细的分工。DispatcherServlet 是"总调度员",Controller 方法是"具体办事员"。两者共同承担 MVC 中 Controller 的职责——接收输入、协调后续流程。
  • Model 层:Spring MVC 本身不定义 Model 的具体形态。开发者用 Service 处理业务、用 Repository 访问数据,这些由 Spring 容器管理,Spring MVC 只负责在 Controller 中注入它们。
  • View 层:Spring MVC 引入 ViewResolver 作为"视图查找器"。Controller 返回逻辑视图名 "employeeList",ViewResolver 根据配置将其解析为 /WEB-INF/views/employeeList.jsp 或 templates/employeeList.html。这种解耦让更换视图技术无需修改 Controller。

完整示例

场景

飞翔科技要开发一个产品信息展示系统。CTO 大翔要求团队严格遵循 MVC 分层原则,禁止任何一层越权操作。架构师白歌负责设计分层架构,小崔负责 Controller 和 Service,黄俪负责前端页面,李眉负责部署和配置。

需求

用户访问 /products/1001 时,系统展示编号为 1001 的产品详情,包括名称、价格和库存。

分层实现

Model 层(小崔)

// Product.java - 数据模型
package com.feixiang.model;

public class Product {
    private Long id;
    private String name;
    private Double price;
    private Integer stock;
    
    // 构造器、getter、setter 省略
}
// ProductService.java - 业务逻辑
package com.feixiang.service;

import com.feixiang.model.Product;
import org.springframework.stereotype.Service;

@Service
public class ProductService {
    
    public Product findById(Long id) {
        // 模拟从数据库查询
        Product product = new Product();
        product.setId(id);
        product.setName("Spring MVC 实战指南");
        product.setPrice(89.00);
        product.setStock(200);
        return product;
    }
}

白歌 review 意见:Service 层只操作 Product 对象,不涉及 HttpServletRequest 或 ResponseEntity。符合 Model 层职责。

View 层(黄俪)

<!-- productDetail.html (Thymeleaf) -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>产品详情</title>
</head>
<body>
    <h1>产品详情</h1>
    <p>名称:<span th:text="${product.name}">产品名称</span></p>
    <p>价格:¥<span th:text="${product.price}">0.00</span></p>
    <p>库存:<span th:text="${product.stock}">0</span> 件</p>
</body>
</html>

黄俪的设计原则:Thymeleaf 模板只读取 product 对象属性,不调用数据库,不写业务判断。如果以后换成 FreeMarker 或 Vue 前端渲染,只需替换模板文件,Controller 完全不用改。

Controller 层(小崔)

// ProductController.java
package com.feixiang.web;

import com.feixiang.model.Product;
import com.feixiang.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Controller
@RequestMapping("/products")
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping("/{id}")
    public String detail(@PathVariable Long id, Model model) {
        // 1. 调用 Model 层获取数据
        Product product = productService.findById(id);
        
        // 2. 将数据放入 Model 容器,供 View 读取
        model.addAttribute("product", product);
        
        // 3. 返回逻辑视图名,由 ViewResolver 解析
        return "productDetail";
    }
}

代码分析:

  • Controller 不直接创建 Product 对象的数据,而是委托给 ProductService(Model 层)
  • Controller 不拼接 HTML 字符串,而是返回逻辑视图名 "productDetail"(View 层解耦)
  • org.springframework.ui.Model 是 Spring MVC 提供的"数据搬运工",负责把数据从 Controller 送到 View

李眉的配置

// WebConfig.java
@Configuration
@EnableWebMvc
@ComponentScan("com.feixiang")
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public ViewResolver viewResolver() {
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(templateEngine());
        resolver.setCharacterEncoding("UTF-8");
        return resolver;
    }
    
    // Thymeleaf 引擎配置省略...
}

李眉的部署笔记:ViewResolver 把 "productDetail" 解析为 templates/productDetail.html。如果客户要求换成 JSP,只需修改 ViewResolver 配置,Controller 代码零改动——这就是 MVC 分层带来的可替换性。


易错场景与面试考点

误区一:Controller 里写业务逻辑

错误代码:

@Controller
public class OrderController {

    @Autowired
    private OrderRepository orderRepository;

    @PostMapping("/orders")
    public String create(OrderForm form) {
        // ❌ 错误:Controller 里直接写业务规则
        if (form.getQuantity() > 100) {
            throw new IllegalArgumentException("单次购买不能超过 100 件");
        }
        
        double finalPrice = form.getPrice() * form.getQuantity();
        if (form.getVip()) {
            finalPrice *= 0.9;  // VIP 折扣逻辑也写在 Controller 里
        }
        
        Order order = new Order();
        order.setPrice(finalPrice);
        orderRepository.save(order);  // 直接访问数据层
        
        return "orderSuccess";
    }
}

纠正:Controller 应该只负责"接收请求、调用 Service、返回视图"。业务规则(限购、折扣计算)属于 Model 层,应该封装在 Service 中:

@Service
public class OrderService {
    public Order createOrder(OrderForm form) {
        // 业务规则集中在这里
        if (form.getQuantity() > 100) {
            throw new IllegalArgumentException("单次购买不能超过 100 件");
        }
        double finalPrice = calculatePrice(form);
        // ...
    }
}

后果:如果 Controller 里写满业务逻辑,当需求变更(如折扣规则调整)时,需要修改所有相关的 Controller。而按 MVC 分层,只需修改 Service 层。

误区二:Model 层直接操作 HTTP 对象

错误代码:

@Service
public class ReportService {
    
    public void exportReport(HttpServletResponse response) {  // ❌ Service 依赖 Web 层 API
        response.setContentType("application/pdf");
        // ...
    }
}

纠正:Model 层(Service)应该对 Web 层完全无感知。如果需要输出文件,Service 返回 byte[] 或 InputStream,由 Controller 写入 ResponseEntity:

@Service
public class ReportService {
    public byte[] generateReport() {  // ✅ 返回纯数据
        // ...
    }
}

面试高频:Spring MVC 中 MVC 各层分别对应什么?

标准回答:

MVC 层Spring MVC 对应组件职责
ModelService + Repository + Entity业务逻辑、数据访问、数据封装
ViewThymeleaf / JSP / JSON 视图数据可视化呈现
ControllerDispatcherServlet + @Controller 方法请求分发、参数绑定、调用 Model、选择 View

加分点:强调 Spring MVC 对 Controller 层的细化——DispatcherServlet 负责"找对人",Controller 方法负责"办对事"。这种分工让框架处理基础设施,开发者专注业务映射。


小结

MVC 设计模式的核心价值是关注点分离:Model 管数据与规则,View 管展示,Controller 管协调。Spring MVC 并非发明了新的架构,而是将 MVC 模式在 Web 领域做了工程化落地——用 DispatcherServlet 统一入口、用注解声明映射、用 ViewResolver 解耦视图技术。

理解 MVC 分层后,你就能判断代码应该写在哪一层:业务规则进 Service,页面渲染进模板,请求映射进 Controller。任何一层的越权,都会破坏分层的可维护性和可测试性。

本章与全局的关系:本章建立了"三层分离"的思想框架。下一章"前端控制器模式"将深入讲解 Spring MVC 如何在 Controller 层内部做进一步分工——DispatcherServlet 作为唯一入口的设计原理。

上一页
Spring MVC 是什么
下一页
前端控制器模式