学习目标:
- 掌握 JSON 工具的使用,实现 Java 对象与 JSON 字符串之间的相互转换
- 深入理解 MVC 设计模式的核心思想
- 理解三层架构的设计理念及其与 MVC 的关系
- 掌握 MVC 设计模式和三层架构在实际开发中的应用
本章重点:
- MVC 中 Model、View、Controller 的职责划分
- 三层架构中表示层、业务层、数据访问层的调用关系
- MVC 与三层架构的联系与区别
- 前后端分离场景下控制层与接口设计的落地方式
01 / Section
1. 前置知识准备
- JSON数据格式
- Servlet开发基础(请求接收、响应处理)
- Java面向对象编程
- MyBatis持久层框架
02 / Section
2. MVC设计模式
2.1 场景引入:传统开发的问题
在没有使用MVC模式之前,我们的代码通常是这样的:
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>学生信息</title>
</head>
<body>
<%-- 直接在JSP中写Java代码处理业务逻辑 --%>
<%@ page import="java.sql.*, java.util.*" %>
<%
// 数据库连接
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root", "123456");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM student WHERE name = '" + request.getParameter("name") + "'");
Student student = null;
if(rs.next()) {
student = new Student();
student.setName(rs.getString("name"));
student.setClazz(rs.getString("clazz"));
}
rs.close();
stmt.close();
conn.close();
%>
hello,来自${student.clazz}的${student.name}
</body>
</html>问题分析:
- JSP页面既负责显示又负责业务逻辑处理
- 数据库连接代码散落在各个页面中
- 代码难以维护和复用
- 职责不清晰,开发协作困难
2.2 MVC模式的演进
2.2.1 改进后的实现
视图层(View):student.jsp
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>学生信息</title>
</head>
<body>
hello,来自${student.clazz}的${student.name}
</body>
</html>控制层(Controller):StudentServlet.java
java
@WebServlet("/student")
public class StudentServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 1. 接收请求参数
String name = req.getParameter("name");
// 2. 调用Model层处理业务逻辑
Student student = StudentHolder.getStudentMap().get(name);
// 3. 将数据存入Request域,供View使用
req.setAttribute("student", student);
// 4. 转发到视图层
req.getRequestDispatcher("/student.jsp").forward(req, resp);
}
}模型层(Model):StudentHolder.java
java
public class StudentHolder {
private static Map<String, Student> studentMap = new HashMap<>();
static {
studentMap.put("lilei", new Student("李雷", "三年二班"));
studentMap.put("hanmeimei", new Student("韩梅梅", "三年二班"));
studentMap.put("liziming", new Student("李子明", "三年六班"));
}
public static Map<String, Student> getStudentMap() {
return studentMap;
}
}实体类:Student.java
java
public class Student {
private String name;
private String clazz;
// 构造器、getter、setter省略...
}2.2.2 流程分析
访问请求示例:
http://localhost:8080/student?name=lileihttp://localhost:8080/student?name=hanmeimeihttp://localhost:8080/student?name=liziming
MVC职责划分:
| 步骤 | 组件 | 职责 | 对应代码 |
|---|---|---|---|
| 1 | Controller | 接收请求,协调调度 | StudentServlet |
| 2 | Model | 处理数据逻辑 | StudentHolder.getStudentMap() |
| 3 | Controller | 数据准备与传递 | req.setAttribute() |
| 4 | View | 数据渲染展示 | student.jsp |
2.3 MVC核心概念
2.3.1 三个核心组件
diagram
┌─────────────────────────────────────────────────────────────────┐ │ MVC架构图 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 客户端请求 │ │ │ │ │ ▼ │ │ ┌─────────────┐ 业务请求 ┌───────────────┐ │ │ │ │ ───────────────▶│ │ │ │ │ Controller │ │ Model │ │ │ │ (控制器) │◀─────────────── │ (数据模型) │ │ │ │ Servlet │ 业务数据 │ Service/DAO │ │ │ │ │ │ │ │ │ └──────┬──────┘ └───────────────┘ │ │ │ │ │ │ 渲染数据 │ │ ▼ │ │ ┌─────────────┐ │ │ │ View │ │ │ │ (视图层) │ │ │ │ JSP/HTML │ │ │ └──────┬──────┘ │ │ │ │ │ ▼ │ │ 响应客户端 │ │ │ └─────────────────────────────────────────────────────────────────┘
1. Model(模型层)
- 职责:处理应用程序的数据逻辑(业务逻辑 + 数据访问)
- 典型组件:Service、DAO、JavaBean/Entity
- 特点:不依赖于View和Controller,可独立测试
2. View(视图层)
- 职责:处理数据的展示和用户界面
- 典型组件:JSP、HTML、Thymeleaf模板
- 特点:只负责展示,不包含业务逻辑
3. Controller(控制器)
- 职责:接收用户请求,调用Model处理业务,选择View进行展示
- 典型组件:Servlet、SpringMVC的Controller
- 特点:承上启下,协调Model和View
2.3.2 MVC的核心思想
解耦(Decoupling):强制性地将应用程序的输入、处理和输出分离
饭店模型比喻:
| 饭店场景 | MVC组件 | 职责说明 |
|---|---|---|
| 顾客 | Client | 向系统发起请求 |
| 服务员 | Controller | 接收请求,协调各方 |
| 后厨 | Model | 负责具体业务处理(做菜/数据处理) |
| 餐桌 | View | 呈现最终结果 |
完整流程:
- 顾客点餐(Client发起请求)
- 服务员接单(Controller接收请求)
- 后厨做菜(Model处理数据)
- 服务员上菜(Controller转发结果)
- 顾客用餐(View展示给用户)
2.4 前后端分离模式
2.4.1 传统MVC vs 前后端分离
传统MVC模式:
text
浏览器 ──请求──▶ Servlet(Controller) ──▶ Service ──▶ DAO
│
▼
浏览器 ◀──JSP(View) ◀───────────────── Model数据前后端分离模式:
text
前端(HTML/CSS/JS) ◀────JSON────▶ 后端(SpringBoot/Servlet)
│ │
▼ ▼
视图渲染层 Controller+Service+DAO2.4.2 前后端分离详解
前端三要素: HTML、CSS、JavaScript
工作流程:
- 前端通过JavaScript发起Ajax异步请求
- 请求到达后端服务器,找到对应的Controller
- Controller调用Service和DAO处理业务
- 服务器将结果以JSON格式返回
- 前端JavaScript解析JSON,动态渲染页面
javascript
// 前端Ajax请求示例
fetch('/api/student?name=lilei')
.then(response => response.json())
.then(data => {
// 动态渲染页面
document.getElementById('name').textContent = data.name;
document.getElementById('clazz').textContent = data.clazz;
});后端响应JSON:
java
@WebServlet("/api/student")
public class StudentApiServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
String name = req.getParameter("name");
Student student = studentService.findByName(name);
// 将Java对象转换为JSON字符串
String json = JacksonUtil.write(student);
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().println(json);
}
}这还是MVC吗?
是的!服务器端承担 MC + 0.5V(Model + Controller + 部分View逻辑),前端承担 0.5V(真正的视图渲染)。
前后端分离的优势:
- 前后端可以并行开发,提高开发效率
- 前后端通信统一使用JSON格式
- 前端可以独立部署,甚至使用Vue/React等现代框架
- 后端专注于API设计,提高代码复用性
03 / Section
3. 三层架构
3.1 为什么需要三层架构?
MVC解决了视图和业务逻辑的分离,但在实际项目中,Controller直接调用DAO会导致:
- 业务逻辑散落在Controller中
- 代码复用性低
- 业务变更时需要修改多处代码
三层架构是在MVC基础上的进一步解耦。
3.2 三层架构详解
3.2.1 架构图
diagram
┌────────────────────────────────────────────────────────────────────┐ │ 三层架构与MVC对应关系 │ ├────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 表示层 (Presentation Layer) │ │ │ │ ┌──────────────────┐ │ │ │ │ │ Controller │ ◀── MVC的C │ │ │ │ │ Servlet/Handler│ │ │ │ │ └──────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ 调用 │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 业务逻辑层 (Business Logic Layer) │ │ │ │ ┌──────────────────┐ │ │ │ │ │ Service │ ◀── MVC的M(业务) │ │ │ │ │ 业务逻辑处理 │ │ │ │ │ └──────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ 调用 │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 数据访问层 (Data Access Layer) │ │ │ │ ┌──────────────────┐ │ │ │ │ │ DAO/Mapper │ ◀── MVC的M(数据) │ │ │ │ │ 数据库操作 │ │ │ │ │ └──────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 视图层 (View Layer) │ │ │ │ ┌──────────────────┐ │ │ │ │ │ JSP/HTML/前端框架 │ ◀── MVC的V │ │ │ │ └──────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ └────────────────────────────────────────────────────────────────────┘
3.2.2 各层职责
| 层级 | 英文名称 | 主要职责 | 对应代码 |
|---|---|---|---|
| 表示层 | Presentation Layer | 接收请求,调用业务层,返回响应 | Servlet、Controller |
| 业务逻辑层 | Business Logic Layer | 处理业务规则、事务控制 | Service接口及实现类 |
| 数据访问层 | Data Access Layer | 数据库CRUD操作 | DAO/Mapper接口及XML |
3.2.3 三层架构的优势
- 提高安全性:表示层不直接访问数据访问层,所有数据操作必须通过业务逻辑层
- 架构可替换:如果要将B/S架构改为C/S架构,只需替换表示层
- 分工明确:前端、后端、数据库开发人员可以各司其职
- 易于维护:业务逻辑集中在Service层,修改业务只需改一处
- 便于测试:各层可以独立进行单元测试
3.2.4 三层架构调用关系
java
// 表示层(Servlet) - 不直接操作数据库
@WebServlet("/user/*")
public class UserServlet extends HttpServlet {
// 注入Service层
private UserService userService = new UserServiceImpl();
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
// 1. 接收参数
String userId = req.getParameter("id");
// 2. 调用业务逻辑层(不直接调用DAO)
User user = userService.findById(userId);
// 3. 响应结果
resp.getWriter().println(JacksonUtil.write(user));
}
}
// 业务逻辑层(Service)
public class UserServiceImpl implements UserService {
// 注入DAO层
private UserDao userDao = new UserDaoImpl();
@Override
public User findById(String userId) {
// 可以添加业务逻辑:参数校验、缓存判断等
if (userId == null || userId.isEmpty()) {
throw new IllegalArgumentException("用户ID不能为空");
}
// 调用数据访问层
return userDao.selectById(userId);
}
}
// 数据访问层(DAO)
public class UserDaoImpl implements UserDao {
@Override
public User selectById(String userId) {
// 执行SQL操作
String sql = "SELECT * FROM user WHERE id = ?";
// ... JDBC操作
return user;
}
}04 / Section
4. 实战案例:用户登录系统
4.1 需求分析
4.1.1 接口设计
登录接口:
| 项目 | 说明 |
|---|---|
| 请求URL | http://localhost:8080/admin/auth/login |
| 请求方法 | POST |
| 请求参数 | JSON格式:{"username":"admin123","password":"admin123"} |
成功响应:
json
{
"errno": 0,
"errmsg": "成功",
"data": {
"adminInfo": {
"nickName": "admin123",
"avatar": "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"
},
"token": "58dfa18e-24e3-4ba4-91ef-8f7f092cd21f"
}
}失败响应:
json
{
"errno": 605,
"errmsg": "用户帐号或密码不正确"
}4.1.2 数据库表结构
sql
CREATE TABLE `market_admin` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL COMMENT '用户名',
`password` VARCHAR(50) NOT NULL COMMENT '密码',
`avatar` VARCHAR(255) COMMENT '头像URL',
`last_login_ip` VARCHAR(50) COMMENT '最后登录IP',
`last_login_time` DATETIME COMMENT '最后登录时间',
`add_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` TINYINT DEFAULT 0 COMMENT '是否删除',
`role_ids` VARCHAR(255) COMMENT '角色ID列表'
) COMMENT='管理员表';4.2 完整代码实现
4.2.1 实体类层
MarketAdmin.java
java
package com.example.entity;
import java.time.LocalDateTime;
public class MarketAdmin {
private Integer id;
private String username;
private String password;
private String avatar;
private String lastLoginIp;
private LocalDateTime lastLoginTime;
private LocalDateTime addTime;
private LocalDateTime updateTime;
private Boolean deleted;
private String roleIds;
// 构造器
public MarketAdmin() {}
// Getter和Setter方法
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getAvatar() { return avatar; }
public void setAvatar(String avatar) { this.avatar = avatar; }
public String getLastLoginIp() { return lastLoginIp; }
public void setLastLoginIp(String lastLoginIp) { this.lastLoginIp = lastLoginIp; }
public LocalDateTime getLastLoginTime() { return lastLoginTime; }
public void setLastLoginTime(LocalDateTime lastLoginTime) { this.lastLoginTime = lastLoginTime; }
public LocalDateTime getAddTime() { return addTime; }
public void setAddTime(LocalDateTime addTime) { this.addTime = addTime; }
public LocalDateTime getUpdateTime() { return updateTime; }
public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; }
public Boolean getDeleted() { return deleted; }
public void setDeleted(Boolean deleted) { this.deleted = deleted; }
public String getRoleIds() { return roleIds; }
public void setRoleIds(String roleIds) { this.roleIds = roleIds; }
}LoginData.java(登录响应数据包装类)
java
package com.example.vo;
import java.util.Map;
/**
* 登录成功后的响应数据
*/
public class LoginData {
private Map<String, String> adminInfo;
private String token;
public LoginData(Map<String, String> adminInfo, String token) {
this.adminInfo = adminInfo;
this.token = token;
}
// Getter和Setter
public Map<String, String> getAdminInfo() { return adminInfo; }
public void setAdminInfo(Map<String, String> adminInfo) { this.adminInfo = adminInfo; }
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
}BaseRespVo.java(统一响应包装类)
java
package com.example.vo;
/**
* 统一的响应结果包装类
* @param <T> 响应数据的类型
*/
public class BaseRespVo<T> {
/**
* 状态码:0表示成功,非0表示失败
*/
private Integer errno;
/**
* 提示信息
*/
private String errmsg;
/**
* 响应数据
*/
private T data;
/**
* 创建成功响应
*/
public static <T> BaseRespVo<T> ok(T data) {
BaseRespVo<T> respVo = new BaseRespVo<>();
respVo.setErrno(0);
respVo.setErrmsg("成功");
respVo.setData(data);
return respVo;
}
/**
* 创建登录失败响应
*/
public static BaseRespVo<Object> authFail() {
BaseRespVo<Object> respVo = new BaseRespVo<>();
respVo.setErrno(605);
respVo.setErrmsg("用户帐号或密码不正确");
return respVo;
}
/**
* 创建自定义失败响应
*/
public static BaseRespVo<Object> fail(int errno, String errmsg) {
BaseRespVo<Object> respVo = new BaseRespVo<>();
respVo.setErrno(errno);
respVo.setErrmsg(errmsg);
return respVo;
}
// Getter和Setter方法
public Integer getErrno() { return errno; }
public void setErrno(Integer errno) { this.errno = errno; }
public String getErrmsg() { return errmsg; }
public void setErrmsg(String errmsg) { this.errmsg = errmsg; }
public T getData() { return data; }
public void setData(T data) { this.data = data; }
}4.2.2 数据访问层(DAO/Mapper)
MarketAdminMapper.java(Mapper接口)
java
package com.example.mapper;
import com.example.entity.MarketAdmin;
import org.apache.ibatis.annotations.Param;
public interface MarketAdminMapper {
/**
* 根据用户名查询管理员信息
* @param username 用户名
* @return 管理员实体对象
*/
MarketAdmin selectByUsername(@Param("username") String username);
/**
* 根据ID查询管理员信息
* @param id 管理员ID
* @return 管理员实体对象
*/
MarketAdmin selectById(@Param("id") Integer id);
/**
* 更新管理员登录信息
* @param admin 管理员实体
* @return 影响行数
*/
int updateLoginInfo(MarketAdmin admin);
}MarketAdminMapper.xml(SQL映射文件)
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.MarketAdminMapper">
<!-- 结果映射 -->
<resultMap id="BaseResultMap" type="com.example.entity.MarketAdmin">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="last_login_ip" property="lastLoginIp"/>
<result column="last_login_time" property="lastLoginTime"/>
<result column="avatar" property="avatar"/>
<result column="add_time" property="addTime"/>
<result column="update_time" property="updateTime"/>
<result column="deleted" property="deleted"/>
<result column="role_ids" property="roleIds"/>
</resultMap>
<!-- 根据用户名查询 -->
<select id="selectByUsername" resultMap="BaseResultMap">
SELECT
id,
username,
password,
last_login_ip,
last_login_time,
avatar,
add_time,
update_time,
deleted,
role_ids
FROM market_admin
WHERE username = #{username}
AND deleted = 0
</select>
<!-- 根据ID查询 -->
<select id="selectById" resultMap="BaseResultMap">
SELECT
id,
username,
password,
last_login_ip,
last_login_time,
avatar,
add_time,
update_time,
deleted,
role_ids
FROM market_admin
WHERE id = #{id}
AND deleted = 0
</select>
<!-- 更新登录信息 -->
<update id="updateLoginInfo">
UPDATE market_admin
SET last_login_ip = #{lastLoginIp},
last_login_time = #{lastLoginTime},
update_time = NOW()
WHERE id = #{id}
</update>
</mapper>4.2.3 业务逻辑层(Service)
AuthService.java(Service接口)
java
package com.example.service;
import com.example.entity.MarketAdmin;
/**
* 认证服务接口
*/
public interface AuthService {
/**
* 用户登录
* @param username 用户名
* @param password 密码
* @return 登录成功返回管理员信息,失败返回null
*/
MarketAdmin login(String username, String password);
}AuthServiceImpl.java(Service实现类)
java
package com.example.service.impl;
import com.example.entity.MarketAdmin;
import com.example.mapper.MarketAdminMapper;
import com.example.service.AuthService;
import com.example.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
/**
* 认证服务实现类
*/
public class AuthServiceImpl implements AuthService {
@Override
public MarketAdmin login(String username, String password) {
// 参数校验
if (username == null || username.trim().isEmpty()) {
return null;
}
if (password == null || password.trim().isEmpty()) {
return null;
}
// 使用MyBatis查询用户信息
try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
MarketAdminMapper adminMapper = sqlSession.getMapper(MarketAdminMapper.class);
MarketAdmin marketAdmin = adminMapper.selectByUsername(username);
// 用户不存在
if (marketAdmin == null) {
return null;
}
// 密码校验(实际项目中应该使用加密比对)
if (password.equals(marketAdmin.getPassword())) {
return marketAdmin;
} else {
return null;
}
}
}
}4.2.4 表示层(Controller/Servlet)
CommonServlet.java(Servlet基类,提供通用处理逻辑)
java
package com.example.servlet;
import com.example.util.JacksonUtil;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Servlet基类,通过反射处理请求
* URL格式:/模块/功能/方法名
* 例如:/admin/auth/login 会调用login方法
*/
public class CommonServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
process(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
process(req, resp);
}
/**
* 统一处理方法
* 从URI中提取方法名,通过反射调用对应方法
*/
protected void process(HttpServletRequest request, HttpServletResponse response)
throws IOException {
// 设置编码
request.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=UTF-8");
// 获取方法名(URI最后一段)
String uri = request.getRequestURI();
String methodName = uri.substring(uri.lastIndexOf("/") + 1);
try {
// 通过反射获取方法
Method method = this.getClass().getMethod(
methodName,
HttpServletRequest.class,
HttpServletResponse.class
);
// 调用方法
method.invoke(this, request, response);
} catch (NoSuchMethodException e) {
response.setStatus(404);
response.getWriter().println("{\"error\":\"方法不存在\"}");
} catch (IllegalAccessException | InvocationTargetException e) {
response.setStatus(500);
response.getWriter().println("{\"error\":\"服务器内部错误\"}");
e.printStackTrace();
}
}
}AdminAuthServlet.java(登录控制器)
java
package com.example.servlet;
import com.example.entity.MarketAdmin;
import com.example.service.AuthService;
import com.example.service.impl.AuthServiceImpl;
import com.example.util.JacksonUtil;
import com.example.vo.BaseRespVo;
import com.example.vo.LoginData;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 管理员认证控制器
* URL模式:/admin/auth/*
*/
@WebServlet("/admin/auth/*")
public class AdminAuthServlet extends CommonServlet {
// 注入Service层
private AuthService authService = new AuthServiceImpl();
/**
* 登录方法
* 对应URL:/admin/auth/login
*/
public void login(HttpServletRequest request, HttpServletResponse response)
throws IOException {
// ========== 1. 接收请求参数 ==========
String jsonStr = request.getReader().readLine();
if (jsonStr == null || jsonStr.trim().isEmpty()) {
String responseJson = JacksonUtil.write(
BaseRespVo.fail(400, "请求参数不能为空")
);
response.getWriter().println(responseJson);
return;
}
// 将JSON字符串解析为Map
Map<String, Object> paramMap = JacksonUtil.read(jsonStr, Map.class);
String username = (String) paramMap.get("username");
String password = (String) paramMap.get("password");
// 参数校验
if (username == null || username.trim().isEmpty()) {
String responseJson = JacksonUtil.write(
BaseRespVo.fail(400, "用户名不能为空")
);
response.getWriter().println(responseJson);
return;
}
// ========== 2. 调用Service层处理业务 ==========
MarketAdmin marketAdmin = authService.login(username, password);
// ========== 3. 处理响应结果 ==========
BaseRespVo<LoginData> baseRespVo;
if (marketAdmin != null) {
// 登录成功
// 3.1 构造adminInfo
Map<String, String> adminInfo = new HashMap<>();
adminInfo.put("nickName", marketAdmin.getUsername());
adminInfo.put("avatar", marketAdmin.getAvatar());
// 3.2 获取Session并存储用户信息
HttpSession session = request.getSession();
session.setAttribute("adminId", marketAdmin.getId());
session.setAttribute("adminName", marketAdmin.getUsername());
// 3.3 获取token(使用sessionId作为token)
String token = session.getId();
// 3.4 构造响应数据
LoginData loginData = new LoginData(adminInfo, token);
baseRespVo = BaseRespVo.ok(loginData);
} else {
// 登录失败
baseRespVo = BaseRespVo.authFail();
}
// ========== 4. 返回JSON响应 ==========
String responseJson = JacksonUtil.write(baseRespVo);
response.getWriter().println(responseJson);
}
/**
* 登出方法
* 对应URL:/admin/auth/logout
*/
public void logout(HttpServletRequest request, HttpServletResponse response)
throws IOException {
// 销毁Session
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
// 返回成功响应
String responseJson = JacksonUtil.write(BaseRespVo.ok(null));
response.getWriter().println(responseJson);
}
/**
* 获取当前登录用户信息
* 对应URL:/admin/auth/info
*/
public void info(HttpServletRequest request, HttpServletResponse response)
throws IOException {
HttpSession session = request.getSession(false);
BaseRespVo<?> respVo;
if (session != null && session.getAttribute("adminId") != null) {
Integer adminId = (Integer) session.getAttribute("adminId");
String adminName = (String) session.getAttribute("adminName");
Map<String, Object> adminInfo = new HashMap<>();
adminInfo.put("adminId", adminId);
adminInfo.put("adminName", adminName);
respVo = BaseRespVo.ok(adminInfo);
} else {
respVo = BaseRespVo.fail(401, "未登录");
}
String responseJson = JacksonUtil.write(respVo);
response.getWriter().println(responseJson);
}
}4.2.5 登录认证过滤器
AuthFilter.java
java
package com.example.filter;
import com.example.util.JacksonUtil;
import com.example.vo.BaseRespVo;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* 登录认证过滤器
* 拦截所有请求,验证登录状态
*/
@WebFilter("/*")
public class AuthFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 过滤器初始化
System.out.println("AuthFilter初始化...");
}
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain)
throws IOException, ServletException {
// 转换为HTTP请求/响应
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 设置编码
request.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=UTF-8");
// 获取请求URI
String uri = request.getRequestURI();
// ========== 1. 白名单放行 ==========
// 登录接口放行
if (uri.endsWith("/admin/auth/login")) {
filterChain.doFilter(request, response);
return;
}
// 静态资源放行
if (uri.contains("/static/") ||
uri.endsWith(".html") ||
uri.endsWith(".js") ||
uri.endsWith(".css")) {
filterChain.doFilter(request, response);
return;
}
// ========== 2. 验证登录状态 ==========
HttpSession session = request.getSession(false);
boolean isLogin = session != null && session.getAttribute("adminId") != null;
if (isLogin) {
// 已登录,放行
filterChain.doFilter(request, response);
} else {
// 未登录,返回401
response.setStatus(401);
BaseRespVo<?> respVo = BaseRespVo.fail(401, "请先登录");
response.getWriter().println(JacksonUtil.write(respVo));
}
}
@Override
public void destroy() {
// 过滤器销毁
System.out.println("AuthFilter销毁...");
}
}4.3 项目结构组织
diagram
src/
├── main/
│ ├── java/
│ │ └── com/example/
│ │ ├── entity/ # 实体类(JavaBean)
│ │ │ └── MarketAdmin.java
│ │ ├── mapper/ # 数据访问层(Mapper接口)
│ │ │ └── MarketAdminMapper.java
│ │ ├── service/ # 业务逻辑层(Service接口)
│ │ │ └── AuthService.java
│ │ ├── service/impl/ # 业务逻辑层(Service实现)
│ │ │ └── AuthServiceImpl.java
│ │ ├── servlet/ # 表示层(Controller)
│ │ │ ├── CommonServlet.java
│ │ │ └── AdminAuthServlet.java
│ │ ├── filter/ # 过滤器
│ │ │ └── AuthFilter.java
│ │ ├── vo/ # 值对象(View Object)
│ │ │ ├── BaseRespVo.java
│ │ │ └── LoginData.java
│ │ └── util/ # 工具类
│ │ ├── JacksonUtil.java
│ │ └── MyBatisUtil.java
│ └── resources/
│ └── com/example/mapper/ # Mapper XML文件
│ └── MarketAdminMapper.xml
└── webapp/
└── WEB-INF/
└── web.xml05 / Section
5. 本章总复盘
5.1 MVC与三层架构对比
| 特性 | MVC设计模式 | 三层架构 |
|---|---|---|
| 关注角度 | 表现层设计模式 | 整体系统架构 |
| 核心目标 | 解耦视图和模型 | 分层职责明确 |
| Model含义 | 包含业务逻辑和数据 | 仅指数据实体 |
| Service层 | 属于Model的一部分 | 独立的一层 |
| 适用场景 | Web应用开发 | 企业级应用架构 |
5.2 架构演进关系
text
原始开发 ──▶ MVC ──▶ 三层架构 ──▶ 前后端分离
│ │ │ │
│ │ │ │
职责 视图与 业务与 前后端
混乱 逻辑分离 数据分离 完全分离5.3 最佳实践建议
5.3.1 分层开发规范
- Controller层(表示层)
- 只负责接收请求参数和返回响应
- 不做业务逻辑处理
- 调用Service层处理业务
- Service层(业务逻辑层)
- 处理所有业务规则
- 负责事务控制
- 调用DAO层进行数据操作
- DAO/Mapper层(数据访问层)
- 只负责数据库CRUD操作
- 不进行业务逻辑判断
- 返回原始数据给Service层
5.3.2 包结构规范
diagram
com.example.project/ ├── entity/ # 实体类(数据库表映射) ├── mapper/ # 数据访问层(MyBatis Mapper) ├── service/ # 业务逻辑层接口 │ └── impl/ # 业务逻辑层实现 ├── controller/ # 控制层(SpringMVC Controller) ├── dto/ # 数据传输对象(层间传递) ├── vo/ # 视图对象(响应给前端) ├── util/ # 工具类 └── config/ # 配置类
5.3.3 开发注意事项
- 分层调用原则:上层可以调用下层,下层不能调用上层
- 跨层调用禁止:Controller不能直接调用DAO,必须通过Service
- 异常处理:每层捕获处理本层异常,抛出自定义业务异常
- 事务管理:Service层负责事务控制,建议使用声明式事务
- 接口设计:层与层之间通过接口交互,便于解耦和测试
5.4 常见问题排查
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 请求404 | URL映射错误 | 检查@WebServlet配置 |
| JSON解析失败 | 缺少无参构造器 | 实体类添加无参构造器 |
| 中文乱码 | 编码设置问题 | 设置request/response编码为UTF-8 |
| 依赖注入为null | 手动new对象 | 使用依赖注入框架或手动注入 |
| 事务不生效 | 自调用或异常被捕获 | 使用AOP代理,正确抛出异常 |