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

【JavaWeb学习 | 第23篇】监听器(Listener)与 RBAC 权限模型

恭喜你坚持学到第23篇!前面我们已经掌握了 Filter过滤器AJAX+JSON文件上传下载 等实用技术。今天这篇将介绍两个重要内容:

  • Listener(监听器):基于事件驱动的组件,能监听应用、会话、请求的生命周期。
  • RBAC 权限模型:企业级系统中最常用的权限控制思想,常与 Filter 结合实现细粒度权限管理。

两者结合后,你就能搭建一个相对完整的“用户-角色-权限”后台管理系统框架。


一、什么是 Listener(监听器)?

Listener 是 Servlet 规范提供的事件监听机制,基于观察者模式。它可以在特定事件发生时(比如 Session 创建、应用启动等)自动执行代码,而无需手动调用。

Listener 不需要在请求中显式调用,容器会自动触发。

1. JavaWeb 中主要的 Listener 接口

Listener 接口监听对象主要方法常见应用场景
ServletContextListenerapplicationcontextInitialized / contextDestroyed应用启动时加载全局数据、初始化连接池
HttpSessionListenersessionsessionCreated / sessionDestroyed在线人数统计、Session 超时处理
ServletRequestListenerrequestrequestInitialized / requestDestroyed请求日志记录、统一处理
HttpSessionAttributeListenersession 属性attributeAdded / attributeRemoved / attributeReplacedSession 属性变化监控
ServletContextAttributeListenerapplication 属性attributeAdded / …全局属性变化监控
ServletRequestAttributeListenerrequest 属性attributeAdded / …请求属性变化监控

其中最常用的是前三个。

2. Listener 的开发步骤

步骤1:编写监听器类,实现对应接口。

步骤2:使用注解或 web.xml 配置(推荐注解)。

示例1:应用启动/销毁监听(ServletContextListener)

// com.example.listener.AppListener.java
@WebListener
public class AppListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // 应用启动时执行(服务器启动或项目部署)
        ServletContext context = sce.getServletContext();
        System.out.println("【应用启动】" + context.getContextPath() + " 已启动");

        // 初始化全局数据,例如加载字典、连接池等
        context.setAttribute("appName", "我的JavaWeb系统");
        context.setAttribute("onlineCount", 0);   // 在线人数初始值
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // 应用销毁时执行(服务器关闭或项目卸载)
        System.out.println("【应用销毁】系统正在关闭...");
    }
}

示例2:在线人数统计(HttpSessionListener) —— 经典案例

@WebListener
public class OnlineListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        // 新用户打开浏览器,Session 创建时
        ServletContext context = se.getSession().getServletContext();

        Integer count = (Integer) context.getAttribute("onlineCount");
        if (count == null) count = 0;
        count++;

        context.setAttribute("onlineCount", count);
        System.out.println("【在线人数 +1】 当前在线:" + count);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        // Session 超时或 invalidate() 时
        ServletContext context = se.getSession().getServletContext();

        Integer count = (Integer) context.getAttribute("onlineCount");
        if (count != null && count > 0) {
            count--;
            context.setAttribute("onlineCount", count);
        }
        System.out.println("【在线人数 -1】 当前在线:" + count);
    }
}

前端显示在线人数(任意 JSP 页面):

当前在线人数:${applicationScope.onlineCount}

注意:同一个用户在多个标签页仍算1人(因为共享同一个 Session)。若要统计“并发 Session 数”,可进一步记录用户 IP + SessionId。


二、RBAC 权限模型(Role-Based Access Control)

RBAC 是目前企业系统中最主流的权限控制模型,核心思想是:

权限不直接给用户,而是给角色;用户再关联角色

这样极大简化了权限管理(用户数量多时特别明显)。

1. RBAC 核心概念(RBAC0 基础模型)

  • 用户(User):系统登录者
  • 角色(Role):权限的集合(如 “管理员”、“普通用户”、“财务专员”)
  • 权限(Permission):具体操作(如 “用户列表查看”、“订单删除”)
  • 关系
  • 用户 ↔ 多对多 ↔ 角色
  • 角色 ↔ 多对多 ↔ 权限

经典数据库表结构(5张表):

  1. sys_user(用户表)
  2. sys_role(角色表)
  3. sys_permission(权限/菜单/URL 表)
  4. sys_user_role(用户-角色中间表)
  5. sys_role_permission(角色-权限中间表)

扩展模型

  • RBAC1:角色继承(管理员继承普通用户权限)
  • RBAC2:角色约束(互斥角色、基数约束等)
  • RBAC3:RBAC1 + RBAC2

2. JavaWeb 中 RBAC 的经典实现方式

核心思路

  1. 用户登录成功后,在 LoginServlet 中查询该用户拥有的所有权限(或 URL 列表),存入 Session
  2. 使用 Filter(权限过滤器)拦截请求,检查当前请求的 URL 是否在用户 Session 的权限列表中。
  3. 如果没有权限 → 跳转到“无权限提示页”或返回 JSON 错误。

示例:权限过滤器(结合之前学习的 Filter)

@WebFilter("/*")
public class PermissionFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        String uri = request.getRequestURI();

        // 1. 放行静态资源和登录相关
        if (uri.contains("/login") || uri.contains("/static/") 
            || uri.endsWith(".css") || uri.endsWith(".js") || uri.endsWith(".png")) {
            chain.doFilter(request, response);
            return;
        }

        // 2. 检查是否登录(结合之前的 LoginFilter)
        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute("user") == null) {
            request.setAttribute("errorMsg", "请先登录!");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
            return;
        }

        // 3. RBAC 权限校验
        @SuppressWarnings("unchecked")
        List<String> userPermissions = (List<String>) session.getAttribute("userPermissions");

        // 假设权限存储为 URL 列表,如 ["/user/list", "/order/add"]
        boolean hasPermission = false;
        if (userPermissions != null) {
            for (String perm : userPermissions) {
                if (uri.contains(perm)) {
                    hasPermission = true;
                    break;
                }
            }
        }

        if (hasPermission || isAdmin(session)) {   // 管理员特殊放行
            chain.doFilter(request, response);
        } else {
            // 无权限
            if (request.getHeader("X-Requested-With") != null) {
                // AJAX 请求返回 JSON
                response.setContentType("application/json");
                response.getWriter().write("{\"code\":403,\"msg\":\"无权限访问!\"}");
            } else {
                request.setAttribute("errorMsg", "抱歉,您没有权限访问该页面!");
                request.getRequestDispatcher("/error/noPermission.jsp").forward(request, response);
            }
        }
    }

    private boolean isAdmin(HttpSession session) {
        // 简单判断是否为管理员角色
        return "admin".equals(session.getAttribute("userRole"));
    }
}

登录时加载权限(LoginServlet 片段)

// 登录成功后
User user = ...;
List<String> perms = permissionService.getUserPermissions(user.getId());  // 查询数据库
session.setAttribute("user", user);
session.setAttribute("userPermissions", perms);
session.setAttribute("userRole", user.getRoleCode());

三、实际开发建议

  1. Listener 适合做全局初始化被动事件响应,不要放复杂业务逻辑。
  2. 在线人数统计建议结合 HttpSessionBindingListener(让 User 对象实现该接口,销毁时自动减数)更精确。
  3. RBAC 在中小项目中常用 URL 权限 + Filter 实现;大型项目推荐引入 Spring SecurityShiro
  4. 菜单权限:登录后把用户有权限的菜单也存入 Session,前端用 JSTL/c:if 动态显示菜单。
  5. 权限颗粒度:可以细化到按钮级别(用自定义标签或 JS 控制按钮显示)。

四、练习建议(强烈推荐完成)

  1. 实现在线人数统计(Listener + application 作用域),并在首页实时显示。
  2. 实现Session 监听,当用户注销(session.invalidate())时自动减在线人数。
  3. 搭建简易 RBAC
  • 创建用户、角色、权限表(可用 MySQL)
  • 登录时加载权限到 Session
  • 编写 PermissionFilter 实现 URL 权限控制
  1. 结合之前 Filter 系列,做一个登录过滤器 + 权限过滤器链。
  2. 进阶:实现角色继承(管理员自动拥有普通用户所有权限)。

完成这些,你的 JavaWeb 项目已经具备了“用户管理 + 权限控制 + 在线监控”的基础骨架,非常接近真实后台管理系统!


系列文章导航(持续更新中):

  • 第21篇:AJAX与JSON详解
  • 第22篇:文件上传下载与 Excel 导入导出
  • 第23篇:监听器与 RBAC 权限模型(本文)
  • 第24篇:综合案例 —— 基于 Servlet + JSP + MVC + Filter + Listener + RBAC 的简易后台管理系统(推荐)

有任何疑问(比如想看更完整的在线用户列表代码、RBAC 数据库表设计、或如何把权限细化到按钮级别),欢迎在评论区留言,我会尽快补充详细示例!

掌握了 Listener 和 RBAC,你已经具备了独立开发中小型 JavaWeb 管理系统的能力。继续加油,下一篇文章我们将把前面所有知识点整合起来,做一个完整的小项目!💪

下一篇文章见~ 🚀

文章已创建 5295

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部