zoukankan      html  css  js  c++  java
  • Listener 介绍

    当 web 应用在 web 容器中运行时,web 应用内部会不断地发生各种事件:如 web 应用启动、web 应用停止,用户 session 开始、用户 session 结束、用户请求到达等。

    实际上,Servlet API 提供了大量监听器来监听 web 应用的内部事件,从而允许当 web 内部事件发生时回调事件监听器内的方法。

    使用 Listener 只需两个步骤:

    1、定义 Listener 实现类

    2、通过注解或者 web.xml 文件中配置 Listener

    实现 Listener 类

    与 AWT 事件编程完全相似,监听不同 web 事件的监听器也不相同。常用的 web 事件监听器接口有如下几个:

    ServletContextListener:用于监听 web 应用的启动和关闭。

    ServletContextAttributeListener:用于监听 ServletContext 范围(application)内属性的改变。

    ServletRequestListener:用于监听用户请求。

    ServletRequestAttributeListener:用于监听 ServletRequest 范围(request)内属性的改变。

    HttpSessionListener:用于监听用户 session 的开始和结束。

    HttpSessionAttributeListener:用于监听 HttpSession 范围(session)内属性的改变。

    下面先以 ServletContextListener 为例来介绍 Listener 的开发和使用,ServletContextListener 用于监听 Web 应用的启动和关闭。该 Listener 类必须实现 ServletContextListener 接口,该接口包含如下两个方法:

    contextInitialized(ServletContextEvent sce):启动 web 应用时,系统调用 Listener 的该方法。

    contextDestroyed(ServletContextEvent sce):关闭 web 应用时,系统调用 Listener 的该方法。

    下面创建一个获取数据库连接的 Listener,该 Listener 会在应用启动时获取数据库连接,并将获取到的连接设置成 application 范围内的属性。

    GetConnectionListener.java

    package com.baiguiren;
    
    import java.sql.*;
    
    import javax.servlet.*;
    import javax.servlet.http.*;
    import javax.servlet.jsp.*;
    import javax.servlet.annotation.*;
    
    @WebListener
    public class GetConnectionListener implements ServletContextListener
    {
        // 应用启动时,该方法被调用
        public void contextInitialized(ServletContextEvent sce)
        {
            try {
                // 取得该应用的 ServletContext 实例
                ServletContext application = sce.getServletContext();
                // 从配置参数中获取驱动
                String driver = application.getInitParameter("driver");
                // 从配置参数中获取数据库 url
                String url = application.getInitParameter("url");
                // 从配置参数中获取数据库用户名
                String user = application.getInitParameter("user");
                // 从配置参数中获取密码
                String pass = application.getInitParameter("pass");
    
                // 注册驱动
                Class.forName(driver);
                // 获取数据库连接
                Connection connection = DriverManager.getConnection(url, user, pass);
                // 将数据库连接设置成 application 范围内的属性
                application.setAttribute("connection", connection);
            } catch (Exception ex) {
                System.out.println("Listener 中获取数据库连接出现异常:" + ex.getMessage());
            }
        }
    
        // 应用关闭时,该方法被调用
        public void contextDestroyed(ServletContextEvent sce)
        {
            // 取得该应用的 ServletContext 实例
            ServletContext application = sce.getServletContext();
            Connection connection = (Connection)application.getAttribute("connection");
            // 关闭数据库连接
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
    

      

    配置 Listener

    配置 Listener 只要向 Web 应用注册 Listener 实现类即可,无需配置参数之类的东西,因此十分简单。为 web 应用配置 Listener 也有两种方式。

    1、使用 @WebListener 注解修饰 Listener 实现类

    2、在 web.xml 文档中使用 <listener .../> 元素进行配置

    使用 @WebListener 时通常无需指定任何属性,只要使用该注解修饰 Listener 实现类即可向 Web 应用注册该监听器。

    在 web.xml 中使用 <listener .../> 元素进行配置时只要配置如下子元素即可。

    listener-class:指定 Listener 实现类

    <listener>
        <!-- 指定 Listener 的实现类 -->
        <listener-class>com.baiguiren.GetConnectionListener</listener-class>
    </listener>
    

      

    上面的配置片段向 web 应用注册了一个 Listener,其实现类为 com.baiguiren.GetConnectionListener。当 web 应用被启动时,该 Listener 的 contextInitialized 方法被触发,该方法会获取一个 JDBC Connection,并放入 application 范围内,这样所有的 JSP 页面都可通过 application 获取数据库连接,从而非常方便地进行数据库访问。

    上例中所有页面使用的都是同一个连接,较为实用的方法是:应用启动时将一个数据源(javax.sql.DataSource 实例)设置成 application 属性,而所有 JSP 页面都通过 DataSource 实例来取得数据库连接,再进行数据库访问。

    使用 ServletContextAttributeListener

    ServletContextAttribtuteListener 用于监听 ServletContext(application)范围内属性的变化,实现该接口的监听器需要实现如下三个方法。

    attributeAdded(ServletContextAttributeEvent event):当程序把一个属性存入 application 范围时触发该方法。

    attributeRemoved(ServletContextAttributeEvent event):当程序把一个属性从 application 范围删除时触发该方法。

    attributeReplaced(ServletContextAttributeEvent event):当程序替换 application 范围内的属性时将触发该方法。

    MyServletContextAttributeListener.java

    package com.baiguiren;
    
    import javax.servlet.*;
    import javax.servlet.http.*;
    import javax.servlet.jsp.*;
    import javax.servlet.annotation.*;
    
    @WebListener
    public class MyServletContextAttributeListener implements ServletContextAttributeListener
    {
        // 当程序向 application 范围添加属性时触发
        public void attributeAdded(ServletContextAttributeEvent event)
        {
            ServletContext application = event.getServletContext();
    
            // 获取添加的属性名和属性值
            String name = event.getName();
            Object value = event.getValue();
            System.out.println(application + "范围内添加了名为" + name + " , 值为" + value + "的属性!");
        }
    
        // 当程序从 application 范围删除属性时触发该方法
        public void attributeRemoved(ServletContextAttributeEvent event)
        {
            ServletContext application = event.getServletContext();
    
            // 获取被删除的属性名和属性值
            String name = event.getName();
            Object value = event.getValue();
            System.out.println(application + "范围内名为" + name + " , 值为" + value + "的属性被删除了!");
        }
    
        // 当 application 范围的属性被替换时触发该方法
        public void attributeReplaced(ServletContextAttributeEvent event)
        {
            ServletContext application = event.getServletContext();
    
            // 获取被替换的属性名和属性值
            String name = event.getName();
            Object value = event.getValue();
            System.out.println(application + "范围内名为" + name + " , 值为" + value + "的属性被替换了!");
        }
    }
    

      

    使用 ServletRequestListener 和 ServletRequestAttributeListener

    ServletRequestListener 用于监听用户请求的到达,实现该接口的监听器需要实现如下两个方法。

    1、requestInitialized(ServletRequestEvent event):用户请求到达、被初始化时触发该方法。

    2、requestDestroyed(ServletRequestEvent event):用户请求结束、被销毁时触发该方法。

    ServletRequestAttributeListener 则用于监听 ServletRequest(request)范围内属性的变化,实现该接口的监听器需要实现 attributeAdded、attributeRemoved、attributeReplaced 三个方法。由此可见,ServletRequestAttributeListener 与 ServletContextAttributeListener 的作用相似,都用于监听属性的改变,只是 ServletRequestAttributeListener 监听 request 范围内属性的改变,ServletContextAttributeListener 监听的是 application 内属性的改变。

    一个 Listener 可以监听多种事件,只要让该 Listener 实现多个接口就可以了:

    RequestListener.java

    package com.baiguiren;
    
    import javax.servlet.*;
    import javax.servlet.http.*;
    import javax.servlet.jsp.*;
    import javax.servlet.annotation.*;
    
    @WebListener
    public class RequestListener implements ServletRequestListener, ServletRequestAttributeListener
    {
        // 用户请求到达、被初始化时触发该方法
        public void requestInitialized(ServletRequestEvent event)
        {
            HttpServletRequest request = (HttpServletRequest)event.getServletRequest();
            System.out.println("----发向" + request.getRequestURI() + "请求被初始化----");
        }
    
        // 当用户请求结束、被销毁时触发该方法
        public void requestDestroyed(ServletRequestEvent event)
        {
            HttpServletRequest request = (HttpServletRequest)event.getServletRequest();
            System.out.println("----发向" + request.getRequestURI() + "请求被销毁----");
        }
    
        // 当程序向 request 范围内添加属性时触发该方法
        public void attributeAdded(ServletRequestAttributeEvent event)
        {
            ServletRequest request = event.getServletRequest();
            // 获取添加的属性名和属性值
            String name = event.getName();
            Object value = event.getValue();
            System.out.println(request + "范围内添加了名为" + name + " , 值为" + value + "的属性!");
        }
    
        // 当从 request 范围内删除属性时触发该方法
        public void attributeRemoved(ServletRequestAttributeEvent event)
        {
            ServletRequest request = event.getServletRequest();
            // 获取被删除的属性名和属性值
            String name = event.getName();
            Object value = event.getValue();
            System.out.println(request + "范围内名为" + name + " , 值为" + value + "的属性被删除了!");
        }
    
        // 当 request 范围内的属性被替换时触发该方法
        public void attributeReplaced(ServletRequestAttributeEvent event)
        {
            ServletRequest request = event.getServletRequest();
            // 获取被替换的属性名和属性值
            String name = event.getName();
            Object value = event.getValue();
            System.out.println(request + "范围内名为" + name + " , 值为" + value + "的属性被替换了!");
        }
    }
    

      

    使用 HttpSessionListener 和 HttpSessionnattributeListener

    HttpSessionListener 用于监听用户 session 的创建和销毁,实现该接口的监听器需要实现如下两个方法。

    sessionCreated(HttpSessionEvent se):用户与服务器的会话开始、创建时触发该方法。

    sessionDestroyed(HttpSessionEvent se):用户与服务器的会话断开、销毁时触发该方法。

    HttpSessionAttributeListener 则用于监听 HttpSession(session)范围内属性的变化,实现该接口的监听器需要实现 attributeAdded、attributeRemoved、attributeReplaced 三个方法。由此可见,HttpSessionAttributeListener 与 ServletContextAttributeListener 的作用相似,都用于监听属性的改变,只是 HttpSessionAttributeListener 监听 session 范围内属性的改变,而 ServletContextAttributeListener 监听的是 application 范围内属性的改变。

    实现 HttpSessionListener 接口的监听器可以监听每个用户会话的开始和断开,因此应用可以通过该监听器监听系统的在线用户。

    羡慕是该监听器的实现类:

    OnlineListener.java

    package com.baiguiren;
    
    import java.util.Hashtable;
    import java.util.Map;
    
    import javax.servlet.*;
    import javax.servlet.http.*;
    import javax.servlet.jsp.*;
    import javax.servlet.annotation.*;
    
    @WebListener
    public class OnlineListener implements HttpSessionListener
    {
        // 当用户与服务器之间开始 session 时触发该方法
        public void sessionCreated(HttpSessionEvent event)
        {
            HttpSession session = event.getSession();
            ServletContext application = session.getServletContext();
            // 获取 session ID
            String sessionID = session.getId();
            // 如果是一次新的会话
            if (session.isNew())
            {
                String user = (String)session.getAttribute("user");
                // 未登陆用户当游客处理
                user = (user == null) ? "游客" : user;
                Map<String, String> online = (Map<String, String>)application.getAttribute("online");
                if (online == null)
                {
                    online = new Hashtable<String, String>();
                }
    
                // 将用户在线信息放入 Map 中
                online.put(sessionID, user);
                application.setAttribute("online", online);
            }
        }
    
        // 当用户与服务器之间 session 断开时触发该方法
        public void sessionDestroyed(HttpSessionEvent event)
        {
            HttpSession session = event.getSession();
            ServletContext application = session.getServletContext();
            String sessionID = session.getId();
            Map<String, String> online = (Map<String, String>)application.getAttribute("online");
    
            if (online != null)
            {
                // 删除该用户的在线信息
                online.remove(sessionID);
            }
            application.setAttribute("online", online);
        }
    }
    

      

    online.jsp

    <%@ page contentType="text/html; charset=UTF-8" %>
    <%@ page import="java.util.Map" %>
    
    <html>
        <head>
            <title>用户在线信息</title>
        </head>
        <body>
            在线用户:
            <table border="1" width="400">
            <%
    Map<String, String> online = (Map<String, String>)application.getAttribute("online");
    if (online != null) {
        for (String sessionId : online.keySet())
        {
            %>
            <tr>
                <td><%=sessionId%></td>
                <td><%=online.get(sessionId)%></td>
            </tr>
            <%
        }
    }
            %>
            </table>
        </body>
    </html>
    

      

    上面即使设置了 session ,实际结果都是 "游客",不知道为何。上面的 sessionCreated 里面感觉这样不太妥,因为 created 的时候理论上 session 里面还没有数据。

    上面的统计在线用户的做法比较粗糙,我们可以通过 RequestListener 来更精准监控。

    1、定义一个 ServletRequestListener,这个监听器负责监听每个用户请求,当用户请求到达时,系统将用户请求的 session ID、用户名、用户 IP、正在访问的资源、访问的时间记录下来。

    2、启动一条后台线程,这条后台线程每隔一段时间检查上面的每条在线记录,如果某条在线记录的访问时间与当前时间相差超过了指定值,将这条记录删除即可。这条线程随着 web 应用启动而启动,可考虑使用 ServletContextListener 来完成。

    AnotherRequestListener.java

    package com.baiguiren;
    
    import java.sql.ResultSet;
    
    import javax.servlet.*;
    import javax.servlet.http.*;
    import javax.servlet.jsp.*;
    
    import org.omg.PortableServer.REQUEST_PROCESSING_POLICY_ID;
    
    import javax.servlet.annotation.*;
    
    @WebListener
    public class AnotherRequestListener implements ServletRequestListener
    {
        // 用户请求到达、被初始化时触发该方法
        public void requestInitialized(ServletRequestEvent event)
        {
            HttpServletRequest request = (HttpServletRequest)event.getServletRequest();
            HttpSession session = request.getSession();
            // 获取 session ID
            String sessionId = session.getId();
            // 获取访问的 IP 和正在访问的页面
            String ip = request.getRemoteAddr();
            String page = request.getRequestURI();
            String user = (String)session.getAttribute("user");
            // 未登录用户当游客处理
            user = (user == null) ? "游客" : user;
            try {
                DbDao dd = new DbDao("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/jsp", "root", "root");
                ResultSet rs = dd.query("select * from online_inf where session_id=?", true, sessionId);
                // 如果该用户对应的 session ID 存在,表明是旧的回话
                if (rs.next()) {
                    // 更新记录
                    rs.updateString(4, page);
                    rs.updateLong(5, System.currentTimeMillis());
                    rs.updateRow();
                    rs.close();
                } else {
                    // 插入该用户的在线信息
                    dd.insert("insert into online_inf values(?, ?, ?, ?, ?)", sessionId, user, ip, page, System.currentTimeMillis());
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    
        // 当用户请求结束、被销毁时触发该方法
        public void requestDestroyed(ServletRequestEvent event)
        {
            HttpServletRequest request = (HttpServletRequest)event.getServletRequest();
        }
    }
    

      

    AnotherOnlineListener.java

    package com.baiguiren;
    
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.sql.ResultSet;
    import java.util.Hashtable;
    import java.util.Map;
    
    import javax.servlet.*;
    import javax.servlet.http.*;
    import javax.servlet.jsp.*;
    
    import javax.servlet.annotation.*;
    
    @WebListener
    public class AnotherOnlineListener implements ServletContextListener
    {
        // 超过该时间(10分钟)没有访问本站即认为用户已经离线
        public final int MAX_MILLS = 10 * 60 * 1000;
    
        // 应用启动时触发该方法
        public void contextInitialized(ServletContextEvent sce)
        {
            // 每 5 秒检查一次
            new javax.swing.Timer(1000 * 5, new ActionListener(){
            
                @Override
                public void actionPerformed(ActionEvent e) {
                    try {
                        DbDao dd = new DbDao("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/online_inf", "root", "root");
                        ResultSet rs = dd.query("select * from online_inf", false);
                        StringBuffer beRemove = new StringBuffer("(");
                        while (rs.next()) {
                            // 如果距离上次访问时间超过了指定时间
                            if ((System.currentTimeMillis() - rs.getLong(5)) > MAX_MILLS) {
                                // 将需要被删除的 session ID 添加进来
                                beRemove.append("'");
                                beRemove.append(rs.getString(1));
                                beRemove.append("' , ");
                            }
                        }
                        // 有需要删除的记录
                        if (beRemove.length() > 3) {
                            beRemove.setLength(beRemove.length() - 3);
                            beRemove.append(")");
                            // 删除所有 "超过指定时间未重新请求的记录"
                            dd.modify("delete from online_inf where session_id in " + beRemove.toString());
                        }
    
                        dd.closeConnection();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            });
        }
    
        public void contextDestroyed(ServletContextEvent sce) {}
    }
    

      

    online1.jsp

    <%@ page contentType="text/html; charset=UTF-8" %>
    <%@ page import="java.util.Map" %>
    
    <html>
        <head>
            <title>用户在线信息</title>
        </head>
        <body>
            在线用户:
            <table border="1" width="640">
    
    <%
    DbDao dd = new DbDao("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/online_inf", "root", "root");
    // 查询 online_inf 表(在线用户表)的全部记录
    ResultSet rs = dd.query("select * from online_inf", false);
    while(rs.next*()) {
    %>
    
    <tr>
        <td><%=rs.getString(1)%></td>
        <td><%=rs.getString(2)%></td>
        <td><%=rs.getString(3)%></td>
        <td><%=rs.getString(4)%></td>
    </tr>
    
    <%
    }
    %>
            </table>
        </body>
    </html>
    

      

  • 相关阅读:
    spark 学习笔记 sample 算子
    spark 学习笔记 dataframe注册生成表
    hbase 的hdfs目录解析
    ldap用户创建
    phpldap部署
    ldap部署
    zookeeper 无法启动 ERROR org.apache.zookeeper.server.quorum.QuorumPeer: Unable to load database on disk java.io.EOFException
    数据采集flume kafka
    GraphQL教程(二) .net Core api 2.1
    GraphQL教程(一)。.net Core API2.1
  • 原文地址:https://www.cnblogs.com/eleven24/p/8654292.html
Copyright © 2011-2022 走看看