本文收录于「Java 学习日记」专栏,聚焦 Java Web 核心设计模式 ------MVC,从底层原理拆解到手动实现简易 MVC 框架,帮你理解 SpringMVC 的底层逻辑,夯实架构基础~
一、为什么要学 MVC 设计模式?
在前面的 Servlet+JSP 开发中,我们遇到了一个核心问题:职责混乱。
- 一个 Servlet 既处理请求参数、又写业务逻辑、还负责跳转页面,代码臃肿不堪;
- JSP 中混合 Java 代码,展示和逻辑耦合严重,维护成本极高;
- 新增功能时,需要修改大量代码,不符合 "开闭原则"。
而MVC 设计模式(Model-View-Controller) 就是为解决这些问题而生:
- 核心目标:将 "数据处理、页面展示、请求控制" 三者分离,降低代码耦合度,提高可维护性和扩展性;
- 实际应用:SpringMVC、Struts2 等主流 Web 框架都是基于 MVC 模式设计的,理解 MVC 是掌握这些框架的关键;
- 学习价值:手动实现简易 MVC 框架,能让你从 "使用框架" 升级到 "理解框架",建立架构思维。
今天这篇日记,我们先拆解 MVC 的核心原理,再手把手实现一个简易的 MVC 框架,彻底搞懂 MVC 的工作方式。
二、MVC 设计模式核心原理
1. MVC 三大组件的职责
MVC 将 Web 应用分为三个核心部分,各司其职、相互协作:
| 组件 | 中文名称 | 核心职责 | 对应技术实现(Java Web) |
|---|---|---|---|
| Model(模型) | 数据模型 | 封装数据、处理业务逻辑(如数据库操作) | JavaBean、Service、DAO |
| View(视图) | 视图展示 | 展示数据、接收用户输入,仅负责页面渲染 | JSP、HTML、Thymeleaf |
| Controller(控制器) | 控制器 | 接收请求、分发请求、协调 Model 和 View | Servlet |
2. MVC 工作流程(核心)
MVC 的核心是 "控制器居中调度,模型处理数据,视图只做展示",完整流程:

3. MVC 模式的核心优势
- 职责分离:数据处理、页面展示、请求控制分开,代码结构清晰;
- 可维护性高:修改页面样式只需改 View,修改业务逻辑只需改 Model,互不影响;
- 可复用性强:Model 层的业务逻辑可被多个 Controller/View 复用;
- 便于扩展:新增功能只需新增对应的 Controller/Model/View,符合开闭原则。
三、传统开发的问题(反例)
先看一个传统 Servlet 开发的反例,感受 MVC 要解决的问题:
java
运行
java
// 传统Servlet:职责混乱(接收请求+业务逻辑+页面跳转)
@WebServlet("/user/query")
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 接收请求参数(控制器职责)
String userId = request.getParameter("id");
// 2. 处理业务逻辑(模型职责)
User user = null;
if ("1".equals(userId)) {
user = new User("admin", 18);
}
// 3. 存储数据(控制器职责)
request.setAttribute("user", user);
// 4. 跳转页面(控制器职责)
request.getRequestDispatcher("/user.jsp").forward(request, response);
}
}
这个 Servlet 既做了控制器的工作,又做了模型的工作,代码耦合度极高 ------ 如果业务逻辑变复杂,这个类会变得臃肿不堪。
四、手动实现简易 MVC 框架(核心实战)
我们基于 MVC 思想,手动实现一个极简版的 MVC 框架,核心目标:
- 控制器(Controller)只负责请求分发,不写业务逻辑;
- 模型(Model)专注处理业务逻辑;
- 视图(View)只负责展示数据;
- 新增功能只需新增 Controller 和 Model,无需修改核心代码。
步骤 1:定义核心规范(接口)
1.1 模型层规范:UserService(处理用户业务)
java
运行
java
// src/main/java/com/mvc/service/UserService.java
package com.mvc.service;
import com.mvc.model.User;
// Model层:业务逻辑接口
public interface UserService {
// 根据ID查询用户
User getUserById(String userId);
// 用户登录
boolean login(String username, String password);
}
1.2 模型层实现:UserServiceImpl
java
运行
java
// src/main/java/com/mvc/service/impl/UserServiceImpl.java
package com.mvc.service.impl;
import com.mvc.model.User;
import com.mvc.service.UserService;
// Model层:业务逻辑实现(模拟数据库操作)
public class UserServiceImpl implements UserService {
@Override
public User getUserById(String userId) {
// 模拟数据库查询
if ("1".equals(userId)) {
return new User("Java日记", 18, "admin@test.com");
} else {
return new User("游客", 0, "guest@test.com");
}
}
@Override
public boolean login(String username, String password) {
// 模拟登录校验
return "admin".equals(username) && "123456".equals(password);
}
}
1.3 数据模型:User(封装数据)
java
运行
java
// src/main/java/com/mvc/model/User.java
package com.mvc.model;
// Model层:数据模型(JavaBean)
public class User {
private String username;
private Integer age;
private String email;
// 无参构造
public User() {}
// 有参构造
public User(String username, Integer age, String email) {
this.username = username;
this.age = age;
this.email = email;
}
// getter/setter
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
步骤 2:控制器层(核心调度)
2.1 基础控制器:BaseController(封装通用逻辑)
java
运行
java
// src/main/java/com/mvc/controller/BaseController.java
package com.mvc.controller;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
// 所有控制器的父类,封装通用方法
public class BaseController extends HttpServlet {
// 转发到指定页面
protected void forward(HttpServletRequest request, HttpServletResponse response, String path) throws ServletException, IOException {
request.getRequestDispatcher(path).forward(request, response);
}
// 重定向到指定页面
protected void redirect(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
response.sendRedirect(request.getContextPath() + path);
}
// 解决POST请求中文乱码
protected void setPostEncoding(HttpServletRequest request) {
try {
request.setCharacterEncoding("UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.2 用户控制器:UserController(仅负责调度)
java
运行
java
// src/main/java/com/mvc/controller/UserController.java
package com.mvc.controller;
import com.mvc.model.User;
import com.mvc.service.UserService;
import com.mvc.service.impl.UserServiceImpl;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
// Controller层:仅负责请求接收和调度
@WebServlet("/user/*")
public class UserController extends BaseController {
// 注入Model层对象(实际框架中是IOC容器管理,这里手动new)
private UserService userService = new UserServiceImpl();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取请求路径,分发请求
String uri = request.getRequestURI();
String action = uri.substring(uri.lastIndexOf("/") + 1);
// 根据不同action调用不同方法
if ("query".equals(action)) {
queryUser(request, response);
} else if ("toLogin".equals(action)) {
toLoginPage(request, response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
setPostEncoding(request);
String uri = request.getRequestURI();
String action = uri.substring(uri.lastIndexOf("/") + 1);
if ("login".equals(action)) {
login(request, response);
}
}
// 查询用户
private void queryUser(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 接收参数(控制器职责)
String userId = request.getParameter("id");
// 2. 调用Model层处理业务(不写业务逻辑,只调度)
User user = userService.getUserById(userId);
// 3. 存储数据到域对象
request.setAttribute("user", user);
// 4. 转发到视图
forward(request, response, "/userInfo.jsp");
}
// 跳转到登录页
private void toLoginPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
forward(request, response, "/login.jsp");
}
// 用户登录
private void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 接收参数
String username = request.getParameter("username");
String password = request.getParameter("password");
// 2. 调用Model层校验
boolean isLogin = userService.login(username, password);
if (isLogin) {
// 登录成功,存储用户信息到session
request.getSession().setAttribute("loginUser", username);
// 重定向到首页
redirect(request, response, "/index.jsp");
} else {
// 登录失败,存储错误信息,转发回登录页
request.setAttribute("errorMsg", "用户名或密码错误!");
forward(request, response, "/login.jsp");
}
}
}
步骤 3:视图层(仅展示数据)
3.1 登录页面:login.jsp
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>登录页(View)</title>
</head>
<body>
<h1>用户登录</h1>
<c:if test="${not empty errorMsg}">
<font color="red">${errorMsg}</font><br>
</c:if>
<form action="${pageContext.request.contextPath}/user/login" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
3.2 用户信息页面:userInfo.jsp
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>用户信息(View)</title>
</head>
<body>
<h1>用户信息展示</h1>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<th>用户名</th>
<th>年龄</th>
<th>邮箱</th>
</tr>
<tr>
<td>${user.username}</td>
<td>${user.age}</td>
<td>${user.email}</td>
</tr>
</table>
</body>
</html>
3.3 首页:index.jsp
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>首页(View)</title>
</head>
<body>
<c:if test="${empty sessionScope.loginUser}">
<c:redirect url="/user/toLogin"></c:redirect>
</c:if>
<h1>欢迎你,${sessionScope.loginUser}!</h1>
<a href="${pageContext.request.contextPath}/user/query?id=1">查看用户信息</a>
</body>
</html>
步骤 4:项目结构(规范)
最终项目结构符合 MVC 分层规范,清晰易维护:
plaintext
src/main/
├── java/
│ └── com/mvc/
│ ├── controller/ # 控制器层(Servlet)
│ │ ├── BaseController.java
│ │ └── UserController.java
│ ├── model/ # 数据模型层(JavaBean)
│ │ └── User.java
│ └── service/ # 业务逻辑层(Model核心)
│ ├── UserService.java
│ └── impl/
│ └── UserServiceImpl.java
└── webapp/ # 视图层(View)
├── login.jsp
├── userInfo.jsp
└── index.jsp
步骤 5:测试运行
- 将项目部署到 Tomcat,启动服务器;
- 访问
http://localhost:8080/mvc/user/toLogin(跳转到登录页); - 输入
admin/123456登录,跳转到首页; - 点击 "查看用户信息",展示用户数据;
- 输入错误密码,登录页显示错误信息。
五、简易 MVC 框架的核心优化(贴近 SpringMVC)
我们实现的简易 MVC 还比较基础,SpringMVC 在此基础上做了更多优化,核心点:
1. 前端控制器(DispatcherServlet)
- 我们的
UserController只能处理 /user/* 的请求,而 SpringMVC 的DispatcherServlet是全局控制器,接收所有请求,再分发到不同的 Controller; - 核心:通过配置(注解 / XML)映射请求路径和 Controller 方法,无需手动解析 URI。
2. 注解驱动
-
我们用
@WebServlet("/user/*")配置路径,SpringMVC 用@Controller、@RequestMapping注解,更灵活; -
示例: java
运行
java@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/query") public String queryUser(String id, Model model) { User user = userService.getUserById(id); model.addAttribute("user", user); return "userInfo"; // 视图名,由视图解析器解析路径 } }
3. 视图解析器
-
我们手动写转发路径
/userInfo.jsp,SpringMVC 通过视图解析器统一配置前缀 / 后缀,只需返回视图名即可; -
示例: xml
XML<!-- 视图解析器配置 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean>
4. IOC 容器
- 我们手动
new UserServiceImpl(),SpringMVC 通过 IOC 容器管理 Bean,自动注入依赖,降低耦合。
六、MVC 模式避坑指南
- 控制器职责越界:Controller 中不要写业务逻辑,只做请求分发,否则回到 "职责混乱" 的问题;
- 视图层写逻辑:JSP 中不要写 Java 脚本片段,仅用 EL+JSTL 展示数据,保持 View 的纯粹性;
- 模型层耦合视图:Model 层(Service/DAO)不要依赖 View 层(JSP),否则无法复用;
- 数据传递混乱:Controller 向 View 传递数据时,统一用 request/session 域,避免直接在 Model 中存储域数据。
七、今日实战小任务
- 基于我们实现的简易 MVC 框架,新增 "用户退出" 功能(Controller 新增 logout 方法,Model 无需修改,View 新增退出按钮);
- 新增 "修改用户信息" 功能,实现完整的 "查询 - 修改 - 展示" 流程,严格遵循 MVC 分层;
- 尝试给简易 MVC 添加 "视图解析器" 功能(封装前缀 / 后缀,Controller 只需返回视图名)。
总结
- MVC 设计模式将 Web 应用分为 Model(数据 / 业务)、View(展示)、Controller(控制)三层,核心是 "职责分离、控制器居中调度";
- 手动实现的简易 MVC 框架中,Controller 仅负责请求分发,Service(Model)处理业务逻辑,JSP(View)只做展示,彻底解决代码耦合问题;
- SpringMVC 是 MVC 模式的高级实现,核心优化包括前端控制器(DispatcherServlet)、注解驱动、视图解析器、IOC 容器等,理解简易 MVC 是掌握 SpringMVC 的关键。
下一篇【Day38】预告:SpringMVC 入门:核心组件、注解使用与实战案例,关注专栏从手写 MVC 过渡到主流框架~若本文对你有帮助,欢迎点赞 + 收藏 + 关注,你的支持是我更新的最大动力💖!