JavaWeb-监听器

序言

  Servlet监听器是Servlet规范中定义的一种特殊类,用于监听ServletContext、HttpSession和ServletRequest等域对象的创建与销毁事件,以及监听这些域对象中属性发生修改的事件。Listener对应观察者模式,事件发生的时候会自动触发该事件对应的Listener。 Listener 主要用于对 Session、request、context 进行监控。

Servlet和JS中的8个Listener和6个Event类
这里写图片描述

1、监听器基础

(1) 三大监听对象

1、ServletContext:application,整个应用只存在一个
2、HttpSession:session,针对每一个对话
3、ServletRequest:request,针对每一个客户请求

(2) 三大监听器

对应以上三大监听对象,存在三大监听器

1、ServletContextListener:对应所有用户
2、ServletSessionListener:对应于一个用户
3、ServletRequestListener:对应当前请求

(3) 监听内容

  监听内容:创建、销毁、属性改变事件。

(4) 监听作用

  监听作用:可以在事件发生前、发生后进行一些处理,一般可以用来统计在线人数和在线用户、统计网站访问量、系统启动时初始化信息等。

(5) 监听器分类

8种监听器可以分为3类

(1)监听 Session、request、context 的创建与销毁
  分别为:HttpSessionLister、ServletContextListener、ServletRequestListener
(2)监听对象属性变化
  分别为:HttpSessionAttributeLister、ServletContextAttributeListener、ServletRequestAttributeListener
(3)监听Session内的对象
  分别为HttpSessionBindingListener 和 HttpSessionActivationListener。
与上面六类不同,这两类 Listener 监听的是Session 内的对象,而非 Session 本身不需要在 web.xml中配置

感知性监听器 (2个):不需要注册。实现这些监听器的类,能感知自己的行为。

1、HttpSessionBindingListener:实现该接口的普通类,能感知自己什么时候被HttpSession绑了,什么时候被解绑了。
2、HttpSessionActivationListener:实现该接口的普通类,能感知自己什么时候随着session对象去了,什么时候复活了。(钝化--活化)

3、监听器的基本使用

  不同功能的Listener 需要实现不同的 Listener 接口,一个Listener也可以实现多个接口,这样就可以多种功能的监听器一起工作。

(1) 创建步骤

1、创建一个实现监听器接口的类

2、配置web.xml文件,注册监听器

<listener>
    <listener-class>完整类名</listener-class>
</listener>

监听器的启动顺序:按照web.xml的配置顺序来启动。

加载顺序:监听器>过滤器>Servlet。这三者被称为 Java Web的三大组件

(2) 具体实例

如下定义一个ServletContext监听器:

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
 * @title Context监听器
 */
public class MyListener implements ServletContextListener {

    public void contextDestroyed(ServletContextEvent sce) {

    }

    public void contextInitialized(ServletContextEvent sce) {

    }   
}

在Web.xml文件中配置监听器:

<!--监听器 -->
<listener>
    <listener-class>servlet.listener.MyListener</listener-class>
</listener>

4、详解三大监听器

(1) 监听三大对象的创建与销毁

  监听 Session、request、context 的创建于销毁: HttpSessionLister、ServletContextListener、ServletRequestListener。

1、三种监听器的触发时机及使用

这里写图片描述

2、 应用实例: 实现监听对象的创建与销毁:

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @title 监听对象的创建与销毁
 */
public class ListenerTest implements HttpSessionListener, ServletContextListener, ServletRequestListener {

    Log log = LogFactory.getLog(getClass());

    // 创建 session
    public void sessionCreated(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        log.info("新创建一个session, ID为: " + session.getId());
    }

    // 销毁 session
    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        log.info("销毁一个session, ID为: " + session.getId());
    }

    // 加载 context
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext servletContext = sce.getServletContext();
        log.info("即将启动" + servletContext.getContextPath());
    }

    // 卸载 context
    public void contextDestroyed(ServletContextEvent sce) {
        ServletContext servletContext = sce.getServletContext();
        log.info("即将关闭" + servletContext.getContextPath());
    }

    // 创建 request
    public void requestInitialized(ServletRequestEvent sre) {

        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();

        String uri = request.getRequestURI();
        uri = request.getQueryString() == null ? uri : (uri + "?" + request.getQueryString());

        request.setAttribute("dateCreated", System.currentTimeMillis());

        log.info("IP " + request.getRemoteAddr() + " 请求 " + uri);
    }

    // 销毁 request
    public void requestDestroyed(ServletRequestEvent sre) {

        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();

        long time = System.currentTimeMillis() - (Long) request.getAttribute("dateCreated");

        log.info(request.getRemoteAddr() + "请求处理结束, 用时" + time + "毫秒. ");
    }

}

(2) 监听对象属性变化

  监听对象属性变化,分别为HttpSessionAttributeLister、ServletContextAttributeListener、ServletRequestAttributeListener 。

1、三种监听器的触发时机及使用
这里写图片描述
2、应用实例:实现对象属性的监听

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
 * @title 监听Session对象的属性
 */
public class SessionAttributeListenerTest implements HttpSessionAttributeListener {

    Log log = LogFactory.getLog(getClass());

    // 添加属性
    public void attributeAdded(HttpSessionBindingEvent se) {
        HttpSession session = se.getSession();
        String name = se.getName();
        log.info("新建session属性:" + name + ", 值为:" + se.getValue());
    }

    // 删除属性
    public void attributeRemoved(HttpSessionBindingEvent se) {
        HttpSession session = se.getSession();
        String name = se.getName();
        log.info("删除session属性:" + name + ", 值为:" + se.getValue());
    }

    // 修改属性
    public void attributeReplaced(HttpSessionBindingEvent se) {
        HttpSession session = se.getSession();
        String name = se.getName();
        Object oldValue = se.getValue();
        log.info("修改session属性:" + name + ", 原值:" + oldValue + ", 新值:" + session.getAttribute(name));
    }
}

(3) 监听Session 内的对象

  监听Session内的对象,分别为HttpSessionBindingListener 和 HttpSessionActivationListener。

1、触发时机及使用:对象必须实现Listener接口,不需要在web.xml中配置。
这里写图片描述
2、应用实例:实现对象属性的监听

import java.io.Serializable;
import java.util.Date;

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionEvent;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
 * @title 同时实现多个接口
 * 被串行化,需要实现Serializable接口
 */
public class PersonInfo implements HttpSessionActivationListener, HttpSessionBindingListener, Serializable {

    private static final long serialVersionUID = -4780592776386225973L;

    Log log = LogFactory.getLog(getClass());

    private String name;
    private Date dateCreated;


    // 从硬盘加载后
    public void sessionDidActivate(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        log.info(this + "已经成功从硬盘中加载。sessionId: " + session.getId());
    }

    // 即将被钝化到硬盘时
    public void sessionWillPassivate(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        log.info(this + "即将保存到硬盘。sessionId: " + session.getId());
    }

    // 被放进session前
    public void valueBound(HttpSessionBindingEvent event) {
        HttpSession session = event.getSession();
        String name = event.getName();
        log.info(this + "被绑定到session \"" + session.getId() + "\"的" + name + "属性上");

        // 记录放到session中的时间
        this.setDateCreated(new Date());
    }

    // 从session中移除后
    public void valueUnbound(HttpSessionBindingEvent event) {
        HttpSession session = event.getSession();
        String name = event.getName();
        log.info(this + "被从session \"" + session.getId() + "\"的" + name + "属性上移除");
    }

    @Override
    public String toString() {
        return "PersonInfo(" + name + ")";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getDateCreated() {
        return dateCreated;
    }

    public void setDateCreated(Date dateCreated) {
        this.dateCreated = dateCreated;
    }
}

5、监听器的应用

(1) 单态登录

单态登录:实现一个账号只能在一台机器上登录。

1、Listener 的代码:

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * LoginSessionListener.jav
 * @title 实现单态登录的监听器
 */
public class LoginSessionListener implements HttpSessionAttributeListener {

    Log log = LogFactory.getLog(this.getClass());

    Map<String, HttpSession> map = new HashMap<String, HttpSession>();

    public void attributeAdded(HttpSessionBindingEvent event) {

        String name = event.getName();

        // 登录
        if (name.equals("personInfo")) {

            PersonInfo personInfo = (PersonInfo) event.getValue();

            if (map.get(personInfo.getAccount()) != null) {

                // map 中有记录,表明该帐号在其他机器上登录过,将以前的登录失效
                HttpSession session = map.get(personInfo.getAccount());
                PersonInfo oldPersonInfo = (PersonInfo) session.getAttribute("personInfo");//map已经存在的旧的信息

                log.info("帐号" + oldPersonInfo.getAccount() + "在" + oldPersonInfo.getIp() + "已经登录,该登录将被迫下线。");

                session.removeAttribute("personInfo");
                session.setAttribute("msg", "您的帐号已经在其他机器上登录,您被迫下线。");
            }

            // 将session以用户名为索引,放入map中
            map.put(personInfo.getAccount(), event.getSession());
            log.info("帐号" + personInfo.getAccount() + "在" + personInfo.getIp() + "登录。");
        }
    }

    public void attributeRemoved(HttpSessionBindingEvent event) {

        String name = event.getName();

        // 注销
        if (name.equals("personInfo")) {
            // 将该session从map中移除
            PersonInfo personInfo = (PersonInfo) event.getValue();
            map.remove(personInfo.getAccount());
            log.info("帐号" + personInfo.getAccount() + "注销。");
        }
    }

    public void attributeReplaced(HttpSessionBindingEvent event) {

        String name = event.getName();

        // 没有注销的情况下,用另一个帐号登录
        if (name.equals("personInfo")) {

            // 移除旧的的登录信息
            PersonInfo oldPersonInfo = (PersonInfo) event.getValue();
            map.remove(oldPersonInfo.getAccount());

            // 新的登录信息
            PersonInfo personInfo = (PersonInfo) event.getSession().getAttribute("personInfo");

            // 也要检查新登录的帐号是否在别的机器上登录过
            if (map.get(personInfo.getAccount()) != null) {
                // map 中有记录,表明该帐号在其他机器上登录过,将以前的登录失效
                HttpSession session = map.get(personInfo.getAccount());
                session.removeAttribute("personInfo");
                session.setAttribute("msg", "您的帐号已经在其他机器上登录,您被迫下线。");
            }
            map.put("personInfo", event.getSession());
        }

    }

}

2、实体类:

import java.io.Serializable;
import java.util.Date;

public class PersonInfo implements Serializable {

    private static final long serialVersionUID = 4063725584941336123L;

    // 帐号
    private String account;

    // 登录IP地址
    private String ip;

    // 登录时间
    private Date loginDate;

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public Date getLoginDate() {
        return loginDate;
    }

    public void setLoginDate(Date loginDate) {
        this.loginDate = loginDate;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((account == null) ? 0 : account.hashCode());
        result = prime * result + ((ip == null) ? 0 : ip.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        PersonInfo other = (PersonInfo) obj;
        if (account == null) {
            if (other.account != null)
                return false;
        } else if (!account.equals(other.account))
            return false;
        if (ip == null) {
            if (other.ip != null)
                return false;
        } else if (!ip.equals(other.ip))
            return false;
        return true;
    }
}

3、Web.xml文件中配置监听器

<!-- 单态登录监听器 -->
<listener>
    <listener-class>servlet.listener.singleton.LoginSessionListener</listener-class>
</listener>

(2) 显示在线人数

需要3个监听器:

1、ContextListener:获取服务启动的时间等。
2、RequestListener:获取客户端的IP、访问地址,访问次数等。
3、SessionListener:需要监听 Session 的创建与属性变化。

代码如下:

package com.helloweenvsfei.listener;

import java.util.Date;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import com.helloweenvsfei.util.ApplicationConstants;

public class MyContextListener implements ServletContextListener {

    public void contextInitialized(ServletContextEvent event) {
        // 启动时,记录服务器启动时间
        ApplicationConstants.START_DATE = new Date();
    }

    public void contextDestroyed(ServletContextEvent event) {
        // 关闭时,将结果清除。也可以将结果保存到硬盘上。
        ApplicationConstants.START_DATE = null;
        ApplicationConstants.MAX_ONLINE_COUNT_DATE = null;
    }
}
package com.helloweenvsfei.listener;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class MyRequestListener implements ServletRequestListener {

    public void requestDestroyed(ServletRequestEvent event) {
    }

    public void requestInitialized(ServletRequestEvent event) {

        HttpServletRequest request = (HttpServletRequest) event
                .getServletRequest();

        HttpSession session = request.getSession(true);

        // 记录IP地址
        session.setAttribute("ip", request.getRemoteAddr());

        // 记录访问次数,只记录访问 .html, .do, .jsp, .action 的累计次数
        String uri = request.getRequestURI();
        String[] suffix = { ".html", ".do", ".jsp", ".action" };
        for (int i=0; i<suffix.length; i++) {
            if (uri.endsWith(suffix[i])) {
                break;
            }
            if(i == suffix.length-1)
                return;
        }

        Integer activeTimes = (Integer) session.getAttribute("activeTimes");

        if (activeTimes == null) {
            activeTimes = 0;
        }

        session.setAttribute("activeTimes", activeTimes + 1);
    }
}
package com.helloweenvsfei.listener;

import java.util.Date;

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import com.helloweenvsfei.util.ApplicationConstants;

public class MySessionListener implements HttpSessionListener,
        HttpSessionAttributeListener {

    public void sessionCreated(HttpSessionEvent sessionEvent) {

        HttpSession session = sessionEvent.getSession();

        // 将 session 放入 map
        ApplicationConstants.SESSION_MAP.put(session.getId(), session);
        // 总访问人数++
        ApplicationConstants.TOTAL_HISTORY_COUNT++;

        // 如果当前在线人数超过历史记录,则更新最大在线人数,并记录时间
        if (ApplicationConstants.SESSION_MAP.size() > ApplicationConstants.MAX_ONLINE_COUNT) {
            ApplicationConstants.MAX_ONLINE_COUNT = ApplicationConstants.SESSION_MAP
                    .size();
            ApplicationConstants.MAX_ONLINE_COUNT_DATE = new Date();
        }
    }

    public void sessionDestroyed(HttpSessionEvent sessionEvent) {
        HttpSession session = sessionEvent.getSession();
        // 将session从map中移除
        ApplicationConstants.SESSION_MAP.remove(session.getId());
    }

    public void attributeAdded(HttpSessionBindingEvent event) {

        if (event.getName().equals("personInfo")) {

            // 当前登录用户数++
            ApplicationConstants.CURRENT_LOGIN_COUNT++;
            HttpSession session = event.getSession();

            // 查找该帐号有没有在其他机器上登录
            for (HttpSession sess : ApplicationConstants.SESSION_MAP.values()) {

                // 如果该帐号已经在其他机器上登录,则以前的登录失效
                if (event.getValue().equals(sess.getAttribute("personInfo"))
                        && session.getId() != sess.getId()) {
                    sess.invalidate();
                }
            }
        }
    }

    public void attributeRemoved(HttpSessionBindingEvent event) {

        // 注销 当前登录用户数--
        if (event.getName().equals("personInfo")) {
            ApplicationConstants.CURRENT_LOGIN_COUNT--;
        }
    }

    public void attributeReplaced(HttpSessionBindingEvent event) {

        // 重新登录
        if (event.getName().equals("personInfo")) {
            HttpSession session = event.getSession();
            for (HttpSession sess : ApplicationConstants.SESSION_MAP.values()) {
                // 如果新帐号在其他机器上登录过,则以前登录失效
                if (event.getValue().equals(sess.getAttribute("personInfo"))
                        && session.getId() != sess.getId()) {
                    sess.invalidate();
                }
            }
        }
    } 
}

猜你喜欢

转载自blog.csdn.net/weixin_39190897/article/details/82454928