MVC 是最经典、也是很多架构模式的源头——MVVM、MVI 都是它的"后代",Spring MVC 更是 Java 后端的标配。平时写 Spring Boot 天天用,但 MVC 的"三部分职责、数据怎么流转、为什么后来又演化出 MVVM/MVI"一直没系统梳理过。这篇把 MVC 从头理清,作为架构模式系列的第一篇。
一、解决什么问题
想象一个没有分层的小程序:界面渲染、用户交互、数据读写、业务逻辑,全写在一个类/一个文件里。结果是——改一处 UI 可能碰坏数据逻辑,改业务逻辑又担心影响展示,代码越堆越乱。
MVC 的核心思想一句话:把"数据"、"展示"、"控制"三个关注点拆开,各管各的。
二、三部分职责
| 部分 | 职责 | 不做什么 |
|---|---|---|
| Model(模型) | 数据 + 业务逻辑 | 不碰界面 |
| View(视图) | 展示数据 + 接收用户输入 | 不碰业务逻辑 |
| Controller(控制器) | 协调两者:接收输入、调 Model、选 View | 不碰具体数据 |
关键在分层:Model 不知道 View 长什么样,View 不知道数据从哪来,Controller 是中间协调者。
三、数据流
MVC 的数据流是一条环:
用户操作 View → Controller 收到指令 → 更新 Model → Model 变化 → View 刷新以"提交订单"为例:
- 用户在页面上点"下单"(View 层)
- Controller 收到请求,调 Service 处理(Model 层)
- Service 更新订单数据(Model 变化)
- 结果返回给页面,页面刷新展示(View 更新)
四、Spring MVC 落地
Spring MVC 是 MVC 在 Java Web 的标准实现,三层对应:
@Controller // Controller 层:接收请求、调 Service、返回视图
public class OrderController {
@GetMapping("/order/{id}")
public String detail(@PathVariable Long id, Model model) {
Order order = orderService.getById(id); // 调 Service(业务层)拿数据
model.addAttribute("order", order);
return "order/detail"; // 返回视图名
}
}
@Service // 业务层:业务逻辑
public class OrderService {
public Order getById(Long id) { return orderMapper.selectById(id); }
}- Controller(表现层):
@Controller/@RestController,接收请求、返回视图或数据 - View:模板(Thymeleaf)或前后端分离时的 JSON 数据
- DispatcherServlet:前端控制器,统一接收请求、分发给具体 Controller(这是 Spring MVC 的入口)
注意:MVC 的"Model"是个抽象概念,Spring 落地时把它拆成了更细的三层。对应关系如下:
| MVC(抽象概念) | Spring 实际落地 |
|---|---|
| Model(数据 + 业务逻辑) | 拆成三层:@Service(业务逻辑)/ @Mapper(数据访问)/ Entity(数据实体) |
| View(展示) | Thymeleaf 模板 / JSON |
| Controller(协调) | @Controller / @RestController |
所以代码里不叫"Model 层",而是具体叫"业务层 / 持久层 / 实体"——@Service 只是 Model 的一部分(业务逻辑那一层),不是整个 Model。
五、MVC 的问题(为什么后来有 MVVM/MVI)
MVC 用了这么多年,但有两个痛点,正是后续模式要解决的:
痛点 1:Controller 和 View 耦合紧
Controller 本该只做"协调",却被迫记住了 View 的一堆细节:
// 这个 Controller 里,耦合了 View 的好几处细节:
@GetMapping("/order/{id}")
public String detail(@PathVariable Long id, Model model) {
Order order = orderService.getById(id);
model.addAttribute("order", order); // 耦合点①:塞什么数据、字段名叫 "order"
return "order/detail"; // 耦合点②:视图文件名写死
}前端一改——比如页面重命名成 order/show.html、字段从 order 改成 data——Controller 就要跟着改两处。Controller 和 View 绑在一起,前端动后端就动。
痛点 2:View 里逻辑越堆越重
View 本该只做"展示",结果业务逻辑全塞了进来:
<!-- 反面教材:View(JSP)里塞满了业务逻辑,MVC 的 "V" 名存实亡 -->
<%
List<Order> orders = orderDao.findAll(); // View 里查数据库
double total = 0;
for (Order o : orders) { // View 里做业务计算
if ("PAID".equals(o.getStatus())) {
total += o.getAmount();
}
}
%>
<p>已支付总额:<%= total %></p> <!-- View 里既算又展示 -->查库、循环、判断全在 View 里——Model 和 Controller 的活儿都被 View 抢了,这就是"V 名存实亡"。
所以有了演化:
- MVP:用 Presenter 替代 Controller,把 View 和业务彻底隔离(见《MVP 模式》)
- MVVM:用 ViewModel + 双向绑定,View 只声明"绑谁",不用手动更新(见《MVVM 模式》)
- MVI:单向数据流,状态不可变,彻底消灭"状态散落各处"(见《MVI 模式》)
小结
- MVC 把"数据/展示/控制"拆成三层,各管各的,是架构分层的鼻祖
- 数据流:用户操作 → Controller → Model → View 刷新
- Spring MVC 落地:Controller(@Controller)+ Model(@Service/@Repository)+ View(模板)+ DispatcherServlet 入口
- MVC 的痛点(Controller 和 View 耦合、View 过重)催生了 MVP/MVVM/MVI
下一步看《MVVM 模式》——它用"双向绑定"解决 MVC 里 View 要手动更新的问题,是 Vue、React、Android DataBinding 的核心思想。
