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

文章目录
- [Java Web核心技术应用(监听器+RBAC权限模型)](#Java Web核心技术应用(监听器+RBAC权限模型))
-
- 第一部分:Servlet监听器详解🥝
-
- 一、监听器核心概念🍋🟩
-
- [1. 什么是Servlet监听器?](#1. 什么是Servlet监听器?)
- [2. 核心监听对象与场景](#2. 核心监听对象与场景)
- [3. 本次焦点:ServletContextListener🍂](#3. 本次焦点:ServletContextListener🍂)
- 二、监听器实战:Druid数据源初始化🍋🟩
-
- [1. 核心需求](#1. 核心需求)
- [2. 创建resources资源依赖文件](#2. 创建resources资源依赖文件)
- [3. 配套配置(web.xml)](#3. 配套配置(web.xml))
- [4. 监听器完整代码](#4. 监听器完整代码)
- [5. JDBC工具类(配合监听器使用)](#5. JDBC工具类(配合监听器使用))
- [6. 监听器运行效果](#6. 监听器运行效果)
- 第二部分:RBAC权限模型完整实现🐦🔥
-
- 一、RBAC模型核心概念🤔
-
- [1. 什么是RBAC?](#1. 什么是RBAC?)
- [2. 核心术语与关系](#2. 核心术语与关系)
- [3. 核心关系示例](#3. 核心关系示例)
- 二、RBAC模型实现步骤
-
- [1. 数据库设计(5张核心表)](#1. 数据库设计(5张核心表))
- [2. 核心工具:结果集处理器(ResultHandler)](#2. 核心工具:结果集处理器(ResultHandler))
- [3. 数据访问层(DAO)](#3. 数据访问层(DAO))
- [4. 业务逻辑层(Service)](#4. 业务逻辑层(Service))
- [5. 核心控制:权限过滤器(Filter)](#5. 核心控制:权限过滤器(Filter))
- [6. 控制层(Servlet)](#6. 控制层(Servlet))
- [7. 前端页面(JSP+AJAX)](#7. 前端页面(JSP+AJAX))
- 三、上面的RBAC模型运行流程
-
- 简单逻辑
- 深入探寻🐦🔥🤔🐦🔥
-
- 一、整体架构概览(从上到下分层)
- 二、各模块运行逻辑拆解(按请求流程顺序)
-
- [1. 前端页面:发起请求的入口](#1. 前端页面:发起请求的入口)
- [2. 权限过滤器(PermissionFilter)](#2. 权限过滤器(PermissionFilter))
- [3. 控制层(Servlet):请求的分发](#3. 控制层(Servlet):请求的分发)
- [4. 业务层(Service):核心逻辑实现](#4. 业务层(Service):核心逻辑实现)
- [5. 数据访问层(DAO):数据库操作执行](#5. 数据访问层(DAO):数据库操作执行)
- [6. 工具层:简化代码量](#6. 工具层:简化代码量)
- [7. 数据库:](#7. 数据库:)
- 三、核心流程示例(以"查询学生"为例)
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. 核心关系示例
- 用户 ↔ 角色(多对多):通过
user_role表关联; - 角色 ↔ 权限(多对多):通过
role_permission表关联; - 权限 ↔ 资源(一对一):
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模型运行流程

简单逻辑
- 用户访问
login.jsp,输入用户名密码登录; - 登录成功后,用户名存入Session;
- 用户点击"查询/修改"按钮,请求被
PermissionFilter拦截; - 过滤器提取URI,先校验登录状态(Session是否有用户名);
- 登录通过后,查询用户-角色-权限关联关系,校验是否有权限访问该URI;
- 有权限则放行请求,执行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→ 返回"请先登录",拒绝放行;
- 从 Session 中获取
- 步骤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 处理。
- 查询学生
- (1)用户相关(
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 对象(如User、List<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"的权限记录,统计数量。
三、核心流程示例(以"查询学生"为例)
- 前端:用户登录成功后,在
main.jsp点击"查询学生"→ 向/search发 GET 请求; - 过滤器:拦截
/search请求 → 检查 Session 有username(已登录)→ 调用hasPermission()校验权限 → 有权限,放行; - 控制层:
SearchServlet接收请求 → 调用StudentService.searchStudents(); - 业务层:
StudentServiceImpl调用StudentDao.searchStudents(); - 数据访问层:
StudentDaoImpl执行 SQL(SELECT id,name,sex,age FROM student)→ 用MultiResultHandler将ResultSet映射为List<Student>; - 工具层:
JdbcUtil从 Druid 连接池获取连接 → 执行 SQL → 返回结果集给 DAO; - 数据库:执行 SQL 查询学生数据 → 返回结果集;
- 反向返回:
List<Student>→ Service → Servlet → 转 JSON → 前端 → 控制台打印学生列表。
如果我的内容对你有帮助,请 点赞 , 评论 , 收藏 。创作不易,大家的支持就是我坚持下去的动力!
