WD
Classnote Docs课程课件
09

09 MVC架构

学习目标:

  • 掌握 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=lilei
  • http://localhost:8080/student?name=hanmeimei
  • http://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 响应客户端
┌─────────────────────────────────────────────────────────────────┐
│                          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 呈现最终结果

完整流程:

  1. 顾客点餐(Client发起请求)
  2. 服务员接单(Controller接收请求)
  3. 后厨做菜(Model处理数据)
  4. 服务员上菜(Controller转发结果)
  5. 顾客用餐(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+DAO

2.4.2 前后端分离详解

前端三要素: HTML、CSS、JavaScript

工作流程:

  1. 前端通过JavaScript发起Ajax异步请求
  2. 请求到达后端服务器,找到对应的Controller
  3. Controller调用Service和DAO处理业务
  4. 服务器将结果以JSON格式返回
  5. 前端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
┌────────────────────────────────────────────────────────────────────┐
│                      三层架构与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 三层架构的优势

  1. 提高安全性:表示层不直接访问数据访问层,所有数据操作必须通过业务逻辑层
  2. 架构可替换:如果要将B/S架构改为C/S架构,只需替换表示层
  3. 分工明确:前端、后端、数据库开发人员可以各司其职
  4. 易于维护:业务逻辑集中在Service层,修改业务只需改一处
  5. 便于测试:各层可以独立进行单元测试

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.xml
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.xml
05 / Section

5. 本章总复盘

5.1 MVC与三层架构对比

特性 MVC设计模式 三层架构
关注角度 表现层设计模式 整体系统架构
核心目标 解耦视图和模型 分层职责明确
Model含义 包含业务逻辑和数据 仅指数据实体
Service层 属于Model的一部分 独立的一层
适用场景 Web应用开发 企业级应用架构

5.2 架构演进关系

text
原始开发 ──▶ MVC ──▶ 三层架构 ──▶ 前后端分离
   │           │           │            │
   │           │           │            │
  职责        视图与      业务与        前后端
  混乱        逻辑分离     数据分离       完全分离

5.3 最佳实践建议

5.3.1 分层开发规范

  1. Controller层(表示层)
  • 只负责接收请求参数和返回响应
  • 不做业务逻辑处理
  • 调用Service层处理业务
  1. Service层(业务逻辑层)
  • 处理所有业务规则
  • 负责事务控制
  • 调用DAO层进行数据操作
  1. DAO/Mapper层(数据访问层)
  • 只负责数据库CRUD操作
  • 不进行业务逻辑判断
  • 返回原始数据给Service层

5.3.2 包结构规范

diagram
com.example.project/ entity/ # 实体类(数据库表映射) mapper/ # 数据访问层(MyBatis Mapper) service/ # 业务逻辑层接口 impl/ # 业务逻辑层实现 controller/ # 控制层(SpringMVC Controller) dto/ # 数据传输对象(层间传递) vo/ # 视图对象(响应给前端) util/ # 工具类 config/ # 配置类
com.example.project/
├── entity/        # 实体类(数据库表映射)
├── mapper/        # 数据访问层(MyBatis Mapper)
├── service/       # 业务逻辑层接口
│   └── impl/      # 业务逻辑层实现
├── controller/    # 控制层(SpringMVC Controller)
├── dto/           # 数据传输对象(层间传递)
├── vo/            # 视图对象(响应给前端)
├── util/          # 工具类
└── config/        # 配置类

5.3.3 开发注意事项

  1. 分层调用原则:上层可以调用下层,下层不能调用上层
  2. 跨层调用禁止:Controller不能直接调用DAO,必须通过Service
  3. 异常处理:每层捕获处理本层异常,抛出自定义业务异常
  4. 事务管理:Service层负责事务控制,建议使用声明式事务
  5. 接口设计:层与层之间通过接口交互,便于解耦和测试

5.4 常见问题排查

问题现象 可能原因 解决方案
请求404 URL映射错误 检查@WebServlet配置
JSON解析失败 缺少无参构造器 实体类添加无参构造器
中文乱码 编码设置问题 设置request/response编码为UTF-8
依赖注入为null 手动new对象 使用依赖注入框架或手动注入
事务不生效 自调用或异常被捕获 使用AOP代理,正确抛出异常