【JavaWeb学习 | 第23篇】监听器、RBAC权限模型


🌈 个人主页: Hygge_Code
🔥 热门专栏:从0开始学习Java | Linux学习| 计算机网络
💫 个人格言: "既然选择了远方,便不顾风雨兼程"

文章目录

Java Web核心技术应用(监听器+RBAC权限模型)

第一部分:Servlet监听器详解🥝

一、监听器核心概念🍋‍🟩

1. 什么是Servlet监听器?

Servlet监听器是Java Web的核心组件之一,用于监听Web应用中特定对象的状态变化,并在事件触发时执行预设逻辑。它就像一个"哨兵",实时监控目标对象的生命周期或属性变化,适用于资源初始化、状态监控等场景。

2. 核心监听对象与场景
监听对象 作用范围 常用场景
ServletContext 应用全局(唯一) 初始化数据源、加载全局配置、统计系统启动时间
HttpSession 用户会话 统计在线人数、监听会话超时、记录用户行为
ServletRequest 单次请求 记录请求参数、监控请求响应时间
3. 本次焦点:ServletContextListener🍂

ServletContextListener是最常用的监听器,专注于应用上下文的创建与销毁,核心方法:

  • contextInitialized(ServletContextEvent sce):应用启动时触发(Tomcat启动后自动执行),适合初始化全局资源;
  • contextDestroyed(ServletContextEvent sce):应用关闭时触发(Tomcat停止前执行),适合释放资源。

二、监听器实战:Druid数据源初始化🍋‍🟩

1. 核心需求

应用启动时自动初始化Druid数据库连接池,避免重复创建连接导致的资源浪费;应用关闭时自动关闭连接池,释放数据库资源。

2. 创建resources资源依赖文件

在resources目录下创建jdbc.properties:

properties 复制代码
druid.url=jdbc:mysql://localhost:3306/lesson?serverTimezone=UTC
druid.driverClassName=com.mysql.cj.jdbc.Driver
druid.username=root
druid.password=root
3. 配套配置(web.xml)

若需通过配置文件指定JDBC参数路径,在web.xml中添加如下配置:

xml 复制代码
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- 全局参数:JDBC配置文件路径 -->
    <context-param>
        <param-name>jdbcConfig</param-name>
        <param-value>/jdbc.properties</param-value> <!-- 配置文件放在类路径根目录 -->
    </context-param>

    <!-- 可选:若不使用@WebListener注解,可手动注册监听器 -->
    <!--
    <listener>
        <listener-class>com.listener.ApplicationContextListener</listener-class>
    </listener>
    -->
</web-app>
4. 监听器完整代码
java 复制代码
package com.listener;

import com.jdbc.JdbcUtil;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * Servlet上下文监听器
 * 作用:在Web应用启动时初始化JDBC数据源,在应用销毁时关闭数据源
 * 监听ServletContext的生命周期(初始化和销毁),实现资源的统一管理
 *
 * @WebListener 注解:表明该类是Servlet上下文监听器,容器启动时自动注册,无需在web.xml中手动配置
 */
@WebListener
public class ApplicationContextListener implements ServletContextListener {

    /**
     * 监听ServletContext初始化事件(Web应用启动时触发)
     * 核心职责:读取JDBC配置文件,初始化数据源,为整个应用提供数据库连接支持
     *
     * @param sce ServletContextEvent:上下文事件对象,用于获取ServletContext和相关事件信息
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("Servlet上下文初始化");

        // 1. 获取ServletContext(应用上下文对象),全局唯一,用于获取应用级配置和资源
        ServletContext context = sce.getServletContext();

        // 2. 从web.xml中读取名为"jdbcConfig"的上下文初始化参数(配置的是JDBC配置文件路径)
        String jdbcConfig = context.getInitParameter("jdbcConfig");

        // 3. 通过类加载器获取配置文件的输入流(读取类路径下的配置文件)
        // this.getClass():获取当前监听器类的Class对象
        // getResourceAsStream():根据配置的路径加载资源,返回输入流
        InputStream is = this.getClass().getResourceAsStream(jdbcConfig);

        // 4. 创建Properties对象,用于存储JDBC配置信息(如url、username、password等)
        Properties props = new Properties();

        try {
            // 5. 加载输入流中的配置信息到Properties对象
            props.load(is);

            // 6. 调用JdbcUtil工具类的初始化方法,传入配置参数,初始化数据源(如连接池)
            JdbcUtil.initDataSource(props);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 监听ServletContext销毁事件(Web应用停止时触发)
     * 核心职责:关闭JDBC数据源,释放数据库连接等资源,避免资源泄露
     */
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("Servlet上下文销毁");
        JdbcUtil.destroyDataSource();
    }
}
5. JDBC工具类(配合监听器使用)

封装Druid连接池操作,提供查询和增删改方法:

java 复制代码
package com.jdbc;

import com.alibaba.druid.pool.DruidDataSource;
import com.handler.ResultHandler;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

public class JdbcUtil {
    private static final DruidDataSource dataSource = new DruidDataSource();

    // 初始化数据源
    public static void initDataSource(Properties props) {
        dataSource.configFromPropety(props);
    }

    // 关闭数据源
    public static void destroyDataSource() {
        dataSource.close();
    }


    // 封装的万能查询
    public static <T> T query(String sql, ResultHandler<T> handler, Object... params) {

        try {
            Connection conn = dataSource.getConnection();
            // 预处理SQL(防注入)
            PreparedStatement ps = conn.prepareStatement(sql);
            // 设置SQL参数(JDBC参数索引从1开始)
            if (params != null && params.length > 0) {
                for (int i = 0; i < params.length; i++) {
                    ps.setObject(i + 1, params[i]);
                }
            }
            ResultSet rs = ps.executeQuery();
            T t = handler.handle(rs);
            rs.close();
            ps.close();
            conn.close();
            return t;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static int update(String sql, Object... params) throws SQLException {
        Connection conn = null;
        try {
            conn = dataSource.getConnection();
            PreparedStatement ps = conn.prepareStatement(sql);
            if (params != null && params.length > 0) {
                for (int i = 0; i < params.length; i++) {
                    ps.setObject(i + 1, params[i]);
                }
            }
             // 执行操作,返回受影响行数
            int affectedRows = ps.executeUpdate();
            //conn.commit();
            ps.close();
            conn.close();
            return affectedRows;
        } catch (SQLException e) {
            e.printStackTrace();
            if(conn != null){
                conn.close();
            }
        }
        return -1;
    }
}
6. 监听器运行效果

开启服务器和关闭服务器,查看控制台消息:

-->由此可以看出,Servelt上下文监听器可以读取到上下文参数,这些参数可以用来配置开发中所需要的环境。


第二部分:RBAC权限模型完整实现🐦‍🔥

一、RBAC模型核心概念🤔

1. 什么是RBAC?

RBAC(Role-Based Access Control,基于角色的访问控制)是一种灵活的权限管理模型,核心思想是通过角色关联用户与权限**,而非直接给用户分配权限,降低权限管理复杂度。**

RBAC的设计主要是控制服务器端的资源访问。RBAC怎么与用户建立联系?服务器感知用户是通过session来感知的,因此,RBAC的实现需要与session配合。前提是用户需要登录,登录后将用户信息存储在session中,这样才能在session中获取用户的信息

RBAC模型简单结构图示意:

2. 核心术语与关系
  • 用户(User):系统操作者(如"李四""张三");
  • 角色(Role):权限的集合(如"管理员""普通用户");
  • 权限(Permission):访问资源的资格(如"查询学生""修改学生"),与URL绑定;
  • 资源(Resource):系统可访问的内容(服务器上的一切数据都是资源,比如静态文件,查询的动态数据等)。
3. 核心关系示例
  1. 用户 ↔ 角色(多对多):通过user_role表关联;
  2. 角色 ↔ 权限(多对多):通过role_permission表关联;
  3. 权限 ↔ 资源(一对一):permission表存储权限与URL的映射。

二、RBAC模型实现步骤

1. 数据库设计(5张核心表)
sql 复制代码
-- 1. 创建用户表(user)
CREATE TABLE `user` (
  `username` VARCHAR(50) NOT NULL COMMENT '用户名(主键)',
  `password` VARCHAR(50) NOT NULL COMMENT '密码',
  `name` VARCHAR(50) COMMENT '用户姓名',
  PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


-- 2. 创建角色表(role)
CREATE TABLE `role` (
  `id` INT NOT NULL AUTO_INCREMENT COMMENT '角色ID(主键)',
  `name` VARCHAR(50) NOT NULL COMMENT '角色名称',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_role_name` (`name`) -- 角色名称唯一
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


-- 3. 创建权限表(permission)
CREATE TABLE `permission` (
  `id` INT NOT NULL AUTO_INCREMENT COMMENT '权限ID(主键)',
  `name` VARCHAR(50) NOT NULL COMMENT '权限名称',
  `url` VARCHAR(50) COMMENT '权限对应的URL路径',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_permission_name` (`name`) -- 权限名称唯一
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


-- 4. 创建用户-角色关联表(user_role)【多对多关系】
CREATE TABLE `user_role` (
  `username` VARCHAR(50) NOT NULL COMMENT '关联用户表的username',
  `role_id` INT NOT NULL COMMENT '关联角色表的id',
  PRIMARY KEY (`username`, `role_id`), -- 复合主键,避免重复关联
  -- 外键约束:关联user表的username
  FOREIGN KEY (`username`) REFERENCES `user`(`username`) ON DELETE CASCADE,
  -- 外键约束:关联role表的id
  FOREIGN KEY (`role_id`) REFERENCES `role`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


-- 5. 创建角色-权限关联表(role_permission)【多对多关系】
CREATE TABLE `role_permission` (
  `role_id` INT NOT NULL COMMENT '关联角色表的id',
  `permission_id` INT NOT NULL COMMENT '关联权限表的id',
  PRIMARY KEY (`role_id`, `permission_id`), -- 复合主键,避免重复关联
  -- 外键约束:关联role表的id
  FOREIGN KEY (`role_id`) REFERENCES `role`(`id`) ON DELETE CASCADE,
  -- 外键约束:关联permission表的id
  FOREIGN KEY (`permission_id`) REFERENCES `permission`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

五张数据表之间的物理关系表如下图所示:

2. 核心工具:结果集处理器(ResultHandler)

将数据库查询结果(ResultSet)转换为Java对象,简化数据映射:

java 复制代码
// ResultHandler接口
package com.handler;

import java.sql.ResultSet;
import java.sql.SQLException;

//对查询的结果集进行处理的接口,具体怎么处理需要用户来实现

public interface ResultHandler<T> {

    T handle(ResultSet rs) throws SQLException;

}


// 单个结果处理器(适配:用户查询、权限计数)
package com.handler;

import org.apache.commons.beanutils.BeanUtils;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

public class SingleResultHandler<T> implements ResultHandler {
    private Class<T> clazz;

    public SingleResultHandler(Class<T> clazz) {
        this.clazz = clazz;
    }

    @Override
    public Object handle(ResultSet rs) throws SQLException {
        int count = 0;
        while (rs.next()) {
            count++;
            if (count > 1) throw new RuntimeException("查询结果不唯一");
            try {
                // 处理基础类型/包装类(如COUNT(*)返回的Integer)
                if (clazz.isPrimitive() || Integer.class == clazz || Long.class == clazz) {
                    return rs.getObject(1, clazz);
                }
                // 处理复杂对象(如User)
                T t = clazz.newInstance();
                Map<String, Object> values = new HashMap<>();
                ResultSetMetaData rsmd = rs.getMetaData();
                for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                    values.put(rsmd.getColumnLabel(i), rs.getObject(i));
                }
                // 使用工具类对我们的对象的属性进行注入
                BeanUtils.populate(t, values);
                return t;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}


// 多个结果处理器(适配:学生列表查询)
package com.handler;

import org.apache.commons.beanutils.BeanUtils;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MultiResultHandler<T> implements ResultHandler<List<T>> {
    private Class<T> clazz;

    public MultiResultHandler(Class<T> clazz) {
        this.clazz = clazz;
    }

    @Override
    public List<T> handle(ResultSet rs) throws SQLException {
        List<T> dataList = new ArrayList<>();
        while (rs.next()) {
            try {
                T t = clazz.newInstance();
                Map<String, Object> values = new HashMap<>();
                ResultSetMetaData rsmd = rs.getMetaData();
                for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                    values.put(rsmd.getColumnLabel(i), rs.getObject(i));
                }
                BeanUtils.populate(t, values);
                dataList.add(t);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return dataList;
    }
}
3. 数据访问层(DAO)

实现用户、学生、权限相关的数据库操作:

java 复制代码
// 用户DAO:处理用户查询与权限计数
public class UserDaoImpl implements UserDao {
    // 根据用户名查询用户(登录用)
    @Override
    public User getUserByUsername(String username) {
        String sql = "SELECT username,password,name FROM user WHERE username=?";
        return (User) JdbcUtil.query(sql, new SingleResultHandler<>(User.class), username);
    }

    // 查询用户拥有目标URL的权限数量(核心权限校验SQL)
    @Override
    public Integer getUrlCount(String username, String url) {
        String sql = "SELECT COUNT(*) FROM user_role a " +
                     "INNER JOIN role b ON a.role_id = b.id " +
                     "INNER JOIN role_permission c ON b.id = c.role_id " +
                     "INNER JOIN permission d ON c.permission_id = d.id " +
                     "WHERE a.username=? AND d.url=?";
        return (Integer) JdbcUtil.query(sql, new SingleResultHandler<>(Integer.class), username, url);
    }
}

// 学生DAO:处理学生查询与修改
public class StudentDaoImpl implements StudentDao {
    @Override
    public List<Student> searchStudents() {
        String sql = "SELECT id,name,sex,age FROM student";
        return JdbcUtil.query(sql, new MultiResultHandler<>(Student.class));
    }

    @Override
    public int updateStudent(String id, String name, String sex, String age) throws SQLException {
        String sql = "UPDATE student SET name=?, sex=?, age=? WHERE id=?";
        return JdbcUtil.update(sql, name, sex, age, id);
    }
}
4. 业务逻辑层(Service)

封装登录校验与权限校验核心逻辑:

java 复制代码
// 用户Service:登录+权限校验
public class UserServiceImpl implements UserService {
    private UserDao userDao = new UserDaoImpl();

    /**
     * 登录校验:1-成功,-1-用户不存在,0-密码错误
     */
    @Override
    public int login(String username, String password) {
        User user = userDao.getUserByUsername(username);
        if (user == null) return -1;
        return password.equals(user.getPassword()) ? 1 : 0;
    }

    /**
     * 权限校验:true-有权限,false-无权限
     */
    @Override
    public boolean hasPermission(String username, String url) {
        Integer count = userDao.getUrlCount(username, url);
        return count != null && count > 0; // 处理null避免空指针
    }
}

// 学生Service:业务逻辑封装
public class StudentServiceImpl implements StudentService {
    private StudentDao studentDao = new StudentDaoImpl();

    @Override
    public List<Student> searchStudents() {
        return studentDao.searchStudents();
    }

    @Override
    public int updateStudent(String id, String name, String sex, String age) throws SQLException {
        return studentDao.updateStudent(id, name, sex, age);
    }
}
5. 核心控制:权限过滤器(Filter)

拦截所有请求,实现登录校验与权限校验,是RBAC模型的核心:

java 复制代码
package com.filter;

import com.service.UserService;
import com.service.impl.UserServiceImpl;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * RBAC权限过滤器:拦截所有请求,校验登录与权限
 */
@WebFilter("/*")
public class PermissionFilter extends HttpFilter {
    private UserService userService = new UserServiceImpl();

    @Override
    protected void doFilter(HttpServletRequest request,
                            HttpServletResponse response,
                            FilterChain chain) throws IOException, ServletException {
        // 1. 统一处理URI(去掉项目前缀、参数、末尾斜杠)
        String requestURI = request.getRequestURI();
        String contextPath = request.getContextPath();
        String uri = requestURI.replace(contextPath, "");
        uri = uri.split("\\?")[0]; // 去掉查询参数
        if (uri.endsWith("/") && uri.length() > 1) {
            uri = uri.substring(0, uri.length() - 1); // 去掉末尾斜杠
        }

        // 2. 放行无需权限的资源
        if ("/".equals(uri) || "/login".equals(uri) || "/login.jsp".equals(uri) || uri.startsWith("/js/")) {
            chain.doFilter(request, response);
            return;
        }

        // 3. 登录校验:未登录则拦截
        HttpSession session = request.getSession();
        String username = (String) session.getAttribute("username");
        if (username == null) {
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter writer = response.getWriter();
            writer.print("请先登录!");
            writer.flush();
            writer.close();
            return;
        }

        // 4. 权限校验:无权限则拦截
        if (userService.hasPermission(username, uri)) {
            chain.doFilter(request, response); // 有权限,放行
        } else {
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter writer = response.getWriter();
            writer.print("没有访问权限!");
            writer.flush();
            writer.close();
        }
    }
}
6. 控制层(Servlet)

处理前端请求,对接业务逻辑层:

java 复制代码
// 登录Servlet
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    private UserService userService = new UserServiceImpl();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        int result = userService.login(username, password);
        if (result == 1) {
            req.getSession().setAttribute("username", username); // 登录成功存Session
        }
        resp.getWriter().print(result);
    }
}

// 查询学生Servlet
@WebServlet("/search")
public class SearchServlet extends HttpServlet {
    private StudentService studentService = new StudentServiceImpl();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        List<Student> students = studentService.searchStudents();
        resp.setCharacterEncoding("UTF-8");
        resp.getWriter().print(JSONObject.toJSON(students));
    }
}

// 修改学生Servlet
@WebServlet("/update")
public class UpdateServlet extends HttpServlet {
    private StudentService studentService = new StudentServiceImpl();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String id = req.getParameter("id");
        String name = req.getParameter("name");
        String sex = req.getParameter("sex");
        String age = req.getParameter("age");
        try {
            int result = studentService.updateStudent(id, name, sex, age);
            resp.getWriter().print(result);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
7. 前端页面(JSP+AJAX)
jsp 复制代码
<!-- 登录页面(login.jsp) -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录</title>
</head>
<body>
    <h3>RBAC权限系统 - 登录</h3>
    <input type="text" id="username" placeholder="用户名">
    <input type="password" id="password" placeholder="密码">
    <input type="button" value="登录" id="loginBtn">

    <script src="js/jquery-3.6.0.js"></script>
    <script>
        $("#loginBtn").click(function(){
            $.ajax({
                url: "login",
                type: "post",
                data: {username: $("#username").val(), password: $("#password").val()},
                success: function(resp){
                    if(resp === "1") window.location.href = "main.jsp";
                    else if(resp === "-1") alert("账号不存在");
                    else alert("密码错误");
                }
            });
        });
    </script>
</body>
</html>

<!-- 主页面(main.jsp) -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>主页面</title>
</head>
<body>
    <h3>系统主页面</h3>
    <input type="button" value="查询学生" id="searchBtn">
    <input type="button" value="修改学生" id="updateBtn">

    <script src="js/jquery-3.6.0.js"></script>
    <script>
        $("#searchBtn").click(function(){
            $.ajax({url: "search", type: "get", success: function(resp){console.log("学生列表:", resp);}});
        });

        $("#updateBtn").click(function(){
            $.ajax({
                url: "update",
                type: "post",
                data: {id:3, name:"王五", sex:"女", age:30},
                success: function(resp){alert(resp === "1" ? "修改成功" : "修改失败");}
            });
        });
    </script>
</body>
</html>

三、上面的RBAC模型运行流程

简单逻辑
  1. 用户访问login.jsp,输入用户名密码登录;
  2. 登录成功后,用户名存入Session;
  3. 用户点击"查询/修改"按钮,请求被PermissionFilter拦截;
  4. 过滤器提取URI,先校验登录状态(Session是否有用户名);
  5. 登录通过后,查询用户-角色-权限关联关系,校验是否有权限访问该URI;
  6. 有权限则放行请求,执行Servlet逻辑;无权限则返回"没有访问权限"。
深入探寻🐦‍🔥🤔🐦‍🔥
一、整体架构概览(从上到下分层)
复制代码
前端页面(JSP+AJAX)→ 权限过滤器(Filter)→ 控制层(Servlet)→ 业务层(Service)→ 数据访问层(DAO)→ 工具层(JdbcUtil+ResultHandler)→ 数据库
  • 每一层只做自己的事(单一职责),上层依赖下层,下层不感知上层;
  • 核心核心是 PermissionFilter 过滤器,它拦截所有请求,是权限控制的"大门";
  • 关键工具是 ResultHandler 结果集处理器,简化数据库结果到 Java 对象的映射,避免重复代码。
二、各模块运行逻辑拆解(按请求流程顺序)
1. 前端页面:发起请求的入口
  • 核心页面:login.jsp(登录)、main.jsp(系统主页面);
  • 交互方式:通过 AJAX(jQuery) 发起异步请求(登录、查询学生、修改学生),不刷新页面就能获取结果;
  • 关键逻辑:
    • 登录页面:输入用户名/密码,点击登录按钮 → 向 /login 接口发 POST 请求;
    • 主页面:登录成功后跳转,点击"查询学生"→ 向 /search 发 GET 请求,点击"修改学生"→ 向 /update 发 POST 请求。
2. 权限过滤器(PermissionFilter)

这是整个系统的核心控制层,拦截所有请求(@WebFilter("/*")),执行「登录校验」和「权限校验」,决定请求是否能放行:

  • 步骤1:统一处理请求 URI(去掉项目前缀、参数、末尾斜杠),避免因 URL 格式不一致导致权限校验失败(比如 /search/search?name=xxx 视为同一个接口);
  • 步骤2:放行"白名单"资源(无需登录/权限的资源):
    • 根路径 /、登录接口 /login、登录页面 /login.jsp、静态资源 /js/(jQuery 文件);
  • 步骤3:登录校验(未登录则拦截):
    • 从 Session 中获取 username(登录成功后会存入);
    • 若 Session 中没有 username → 返回"请先登录",拒绝放行;
  • 步骤4:权限校验(已登录但无权限则拦截):
    • 调用 UserService.hasPermission(username, uri),检查当前用户是否有访问目标接口(URI)的权限;
    • 有权限 → 放行请求,继续执行后续流程;
    • 无权限 → 返回"没有访问权限",拒绝放行。
3. 控制层(Servlet):请求的分发

接收前端请求,对接业务层,处理请求参数和响应结果:

  • 每个 Servlet 对应一个核心接口,通过 @WebServlet("/xxx") 标注接口路径;
  • 核心逻辑:
    • LoginServlet:接收 /login 请求 → 获取用户名/密码参数 → 调用 UserService.login() 校验 → 返回结果(1=成功,-1=用户不存在,0=密码错误);
    • SearchServlet:接收 /search 请求 → 调用 StudentService.searchStudents() 获取学生列表 → 转 JSON 格式返回给前端;
    • UpdateServlet:接收 /update 请求 → 获取学生 ID/姓名/性别/年龄参数 → 调用 StudentService.updateStudent() 执行修改 → 返回受影响行数(1=成功,0=失败)。
4. 业务层(Service):核心逻辑实现

封装业务规则,调用 DAO 层操作数据库,不直接接触 JDBC:

  • 两大核心业务:
    • (1)用户相关(UserServiceImpl):
      • 登录校验 login():调用 UserDao.getUserByUsername() 查询用户 → 对比密码 → 返回结果;
      • 权限校验 hasPermission():调用 UserDao.getUrlCount() 查询用户拥有目标 URL 的权限数量 → 数量>0 则有权限;
    • (2)学生相关(StudentServiceImpl):
      • 查询学生 searchStudents():直接调用 StudentDao.searchStudents() 获取列表;
      • 修改学生 updateStudent():直接调用 StudentDao.updateStudent() 执行修改,抛出异常给 Servlet 处理。
5. 数据访问层(DAO):数据库操作执行

调用 JdbcUtil 工具类,执行 SQL 语句,是唯一直接操作数据库的层:

  • 每个 DAO 对应一类数据操作(用户、学生);
  • 核心逻辑:
    • UserDaoImpl
      • getUserByUsername():执行查询 SQL(根据用户名查用户信息)→ 使用 SingleResultHandler 将结果映射为 User 对象;
      • getUrlCount():执行多表关联的 COUNT SQL(用户→角色→权限→URL)→ 使用 SingleResultHandler 将结果映射为 Integer(权限数量);
    • StudentDaoImpl
      • searchStudents():执行查询 SQL(查所有学生)→ 使用 MultiResultHandler 将结果映射为 List<Student>
      • updateStudent():执行更新 SQL(修改学生信息)→ 调用 JdbcUtil.update() 返回受影响行数。
6. 工具层:简化代码量
  • (1)JdbcUtil:基于 Druid 连接池的 JDBC 工具类,封装了「数据源初始化/销毁」「查询(query)」「增删改(update)」通用逻辑,避免重复写 JDBC 样板代码;
  • (2)ResultHandler 接口及实现类:
    • 核心作用:将数据库返回的 ResultSet(结果集)自动映射为 Java 对象(如 UserList<Student>Integer);
    • SingleResultHandler:映射为单个对象(如登录查询的 User、权限计数的 Integer),多行会抛异常;
    • MultiResultHandler:映射为对象列表 (如学生列表 List<Student>);
    • 底层依赖 BeanUtils 工具类,自动将"列名-列值"映射到 JavaBean 的属性(需保证数据库列名与 JavaBean 属性名一致)。
7. 数据库:

基于 RBAC 模型设计 5 张表(用户、角色、权限、用户-角色关联、角色-权限关联),通过多表关联实现权限控制:

  • 核心关联逻辑:用户 ↔ 角色(多对多)、角色 ↔ 权限(多对多);
  • 权限校验的 SQL 核心:通过 INNER JOIN 串联 4 张表(user_role → role → role_permission → permission),筛选出"指定用户"且"指定 URL"的权限记录,统计数量。
三、核心流程示例(以"查询学生"为例)
  1. 前端:用户登录成功后,在 main.jsp 点击"查询学生"→ 向 /search 发 GET 请求;
  2. 过滤器:拦截 /search 请求 → 检查 Session 有 username(已登录)→ 调用 hasPermission() 校验权限 → 有权限,放行;
  3. 控制层:SearchServlet 接收请求 → 调用 StudentService.searchStudents()
  4. 业务层:StudentServiceImpl 调用 StudentDao.searchStudents()
  5. 数据访问层:StudentDaoImpl 执行 SQL(SELECT id,name,sex,age FROM student)→ 用 MultiResultHandlerResultSet 映射为 List<Student>
  6. 工具层:JdbcUtil 从 Druid 连接池获取连接 → 执行 SQL → 返回结果集给 DAO;
  7. 数据库:执行 SQL 查询学生数据 → 返回结果集;
  8. 反向返回:List<Student> → Service → Servlet → 转 JSON → 前端 → 控制台打印学生列表。

如果我的内容对你有帮助,请 点赞 , 评论 , 收藏 。创作不易,大家的支持就是我坚持下去的动力!

相关推荐
顶点多余2 小时前
继承和多态
c++·servlet
宋情写2 小时前
Springboot基础篇01-创建一个SpringBoot项目
java·spring boot·后端
悟能不能悟2 小时前
java map<String,List>判断是否有key,get(key.add(x),否则put(key,new list(){x})的新写法
java·list
sbc-study2 小时前
comsol例题学习-旋转晶片电镀-稀物质传递+二次电流分布+电极,壳+层流
学习·comsol·二次电流分布·稀物质传递·电极,壳·多物理场耦合·层流
智算菩萨2 小时前
【Python基础】AI的“重复学习”:循环语句(for, while)的奥秘
人工智能·python·学习
想学后端的前端工程师2 小时前
【Java JVM虚拟机深度解析:从原理到调优】
java·jvm·python
Ricardo_03242 小时前
关于死锁问题的学习总结
android·java
az3132 小时前
Spring Bean初始化机制详解
java·spring·bean·初始化
stars-he2 小时前
二极管峰值包络检波电路仿真学习笔记
笔记·学习