`
zengshaotao
  • 浏览: 777693 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

监听器详解

    博客分类:
  • java
 
阅读更多

一、各监听器顺序
1、Web应用服务器启动
    ServletContextAttributeListener —— attributeAdded
    ServletContextListener —— contextInitialized

2、客户端第一次访问网站
    ServletContextAttributeListener    —— attributeAdded
    HttpSessionListener —— sessionCreated

3、提交操作
    HttpSessionAttributeListener —— attributeAdded
        每执行一句session.setAttribute(),
        就会执行一次attributeAdded

4、同IP重复提交
    HttpSessionAttributeListener —— attributeReplaced
        与attributeAdded一样,每执行一句session.setAttribute(),也会执行一次attributeReplaced

5、移除session
    HttpSessionAttributeListener —— attributeRemoved
        但执行session.removeAttribute()后,就会执行attributeRemoved

6、当一个session失效或过期
    HttpSessionListener —— sessionDestroyed
    HttpSessionAttributeListener —— attributeRemoved


二、监听器介绍

    ServletContextAttributeListener
        用于监听WEB应用属性改变的事件,包括:增加属性、删除属性、修改属性,监听器类需要实现
    方法
        public void attributeAdded(ServletContextAttributeEvent scae)
        public void attributeRemoved(ServletContextAttributeEvent scae)
        public void attributeReplaced(ServletContextAttributeEvent scae)


    ServletContextListener
        用于监听WEB应用启动和销毁的事件。
    方法
        public void contextInitialized(ServletContextEvent se)
        public void contextDestroyed(ServletContextEvent se)


    HttpSessionListener
        用于监听Session对象的创建和销毁
    方法
        public void sessionCreated(HttpSessionEvent se)
        public void sessionDestroyed(HttpSessionEvent se)


    HttpSessionAttributeListener
        用于监听Session对象属性的改变事件
    方法
        public void attributeAdded(HttpSessionBindingEvent se)
        public void attributeRemoved(HttpSessionBindingEvent se)
        public void attributeReplaced(HttpSessionBindingEvent se)

    其他监听
    HttpSessionActivationListener
        用于监听Session对象的钝化、活化事件
    方法
        public void sessionDidActivate(HttpSessionEvent se)
        public void sessionWillPassivate(HttpSessionEvent se)


    HttpSessionBindingListener
        用于监听Session对象的绑定和解除事件
    方法
        public void valueBound(HttpSessionBindingEvent se)
        public void valueUnbound(HttpSessionBindingEvent se)

 

注:以上监听器的作用说明参考

http://linweihan.javaeye.com/blog/132715

 

 

三、监听器实现禁止不同IP相同用户的重复登录
JDK1.5 + MyEclipse5.5 + Struts2.1.8 + Tomcat6

1、登录用户Model
    /**
     * 登录用户Model
     * 用于记录登录用户
     * @author Administrator
     *
     */
public class LogonUser {
    private String userID;
    private String name;
    private Date logonTime; //保存用户登录时间,用来判断登录用户先后顺序
   
    public String getUserID() {
        return userID;
    }
   
    public void setUserID(String userID) {
        this.userID = userID;
    }

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

    public Date getLogonTime() {
        return logonTime;
    }
   
    public void setLogonTime(Date logonTime) {
        this.logonTime = logonTime;
    }
}   


2、编写登录统计类LogonStatistics
public class LogonStatistics {
    private static int count = 0;
    // 登录用户集合,设置为线程安全
    private static Map<String, LogonUser> logonMap = Collections.synchronizedMap(new HashMap<String, LogonUser>());
    //线程锁
    private static Lock lock = new ReentrantLock();

    private static LogonStatistics instance = null;

    private LogonStatistics() {
    }

    public static LogonStatistics getInstance() {
        lock.lock();
        try {
            if (instance == null) {
                instance = new LogonStatistics();
            }
            return instance;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 将登录用户保存到登录用户集合中
     *
     * @param lu 登录用户Model
     * @return 如果保存到登录用户集合中,则返回true
     */
    public void logon(LogonUser lu) {
        lock.lock();
        try {
            // 如果在登录用户容器logonMap中指定的Key已存在,就覆盖旧的
            if (logonMap.containsKey(lu.getUserID())) {
                logonMap.remove(lu.getUserID());
                logonMap.put(lu.getUserID(), lu);
            } else {
                logonMap.put(lu.getUserID(), lu);
                count++;
            }// else
        } finally {
            lock.unlock();
        }
    }

    /**
     * 退出登录,删除登录用户集合中的相关用户记录
     *
     * @param userID 退出用户ID
     */
    public void unLogon(String userID) {
        lock.lock();
        try {
            if (logonMap.containsKey(userID)) {
                logonMap.remove(userID);
                count--;
            }
        } finally {
            lock.unlock();
        }
    }

    public int getLogonCount() {
        return count;
    }

    /**
     * 根据用户的登录时间判断登录的先后顺序
     * @param lu
     * @return
     */
    public static boolean isOldLogon(LogonUser lu) {
        lock.lock();
        try {
            // 如果集合为空的,则默认这个用户为新用户(也就是没有可比较的对象),则返回false
            if(logonMap.containsKey(lu.getUserID())) {
                LogonUser old = logonMap.get(lu.getUserID());
                //如果用户名一致,登录时间lu在old之前,则返回true
                if((lu.getName().equals(old.getName())) && lu.getLogonTime().before(old.getLogonTime())){
                    return true;
                }//if
            }//if
            return false;
        } finally {
            lock.unlock();
        }//finally
    }
}
   

3、实现HttpSessionAttributeListener接口
public class LogonSessionListener implements HttpSessionAttributeListener {
    private final String LOGONKEY = "logonkey"; //登录用户在session中的Key名

    private final String LOGON = "logon";
    private final String UNLOGON = "unlogon";
   
    public void attributeAdded(HttpSessionBindingEvent se) {
        //用户登录
        userLogonAndUnLogon(se, this.LOGON);
    }

    public void attributeRemoved(HttpSessionBindingEvent se) {
        //用户退出
        userLogonAndUnLogon(se, this.UNLOGON);
    }

    public void attributeReplaced(HttpSessionBindingEvent se) {
        //当用户在同一IP上登录两个不同的帐号,则会进入该方法,所以这里也要写上登录方法
        userLogonAndUnLogon(se, this.LOGON);
    }

    /*
    * 用户登录与退出方法——由于登录与退出大部分代码相同,所以代码写在同一个方法中,通过选项决定登录还是退出
    * @param se
    * @param choose
    * @return
    */
    private void userLogonAndUnLogon(HttpSessionBindingEvent se, String choose){
        //判断是否为用户登录session
        if(this.LOGONKEY.equals(se.getName())){//getName() 给出Key
            LogonUser lu = (LogonUser)se.getValue();
            if(lu != null){
                LogonStatistics ls = LogonStatistics.getInstance();
                if(choose.equals(LOGON)){
                    ls.logon(lu);
                }else if(choose.equals(UNLOGON)){
                    ls.unLogon(lu.getUserID());
                }//else if
                System.out.println("当前已登录人数:" + ls.getLogonCount());
            }//if
        }//if
    }
}

4、向session中加入登陆用户Model

public class LogonAction extends ActionSupport implements ServletRequestAware{
    private static final long serialVersionUID = 1L;
   
    private HttpSession session;
   
    private String userID;
    private String password;
   
    private final String LOGONKEY = "logonkey"; //登录用户在session中的Key名
   
    public String logon() throws Exception {
        IMobileUserManager uManager = new MobileUserManagerImpl();
        MobileUser user = uManager.login(userID, password);
        if(user == null){
            this.request.setAttribute("fail", "登录失败,请检查您的输入");
            return "fail";
        }
       
        LogonUser logonUser = new LogonUser();
        logonUser.setUserID(user.getUserID());
        logonUser.setName(user.getUserSignName());
        logonUser.setLogonTime(new Date());
       
        this.session.setAttribute(this.LOGONKEY, logonUser);
        //this.session.setMaxInactiveInterval(60 * 20);//失效时间 SEC * minute
       
        return SUCCESS;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setUserID(String userID) {
        this.userID = userID;
    }

    public void setValidate(String validate) {
        this.validate = validate;
    }
   
    public void setServletRequest(HttpServletRequest request) {
        this.session = request.getSession();
    }

}


5、利用AbstractInterceptor拦截器来实现旧用户的强迫退出
public class LoginStateInterceptor extends AbstractInterceptor {
    private static final long serialVersionUID = 1L;
   
    private final String LOGONKEY = "logonkey";

    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        Map session = ActionContext.getContext().getSession();
        LogonUser lu = (LogonUser)session.get(this.LOGONKEY);
        if(lu == null){
             ActionSupport action = (ActionSupport)invocation.getAction();
             action.addActionError("您尚未登录或已超时,请先登录");
             return "unlogin";//action.LOGIN;
        }else{
            LogonStatistics ls = LogonStatistics.getInstance();
            //判断当前使用帐户已经在别处重新登录
            if(LogonStatistics.isOldLogon(lu)){
                ActionSupport action = (ActionSupport)invocation.getAction();
                action.addActionError("您的帐号" + lu.getUserID() + "已在别的地方登录,您已被迫退出. 若有疑问请联系管理员,谢谢!");
                return "unlogin";
            }
            System.out.println("拦截器");
            return invocation.invoke();
        }//else
    }

}   

6、在web.xml中加入监听
    <listener>
        <listener-class>com.zg.listener.LogonSessionListener</listener-class>
    </listener>

7、在struts.xml中写入拦截器
    <package namespace="/" extends="struts-default">
        <interceptors>
            <interceptor class="com.zg.interceptor.LoginStateInterceptor"/>

            <interceptor-stack>
                <interceptor-ref/>
                <interceptor-ref/>
            </interceptor-stack>
        </interceptors>
       
        <global-results>
            <result>/login/login.jsp</result>
        </global-results>
    </package>

 

以上代码参考

http://dev.firnow.com/course/3_program/java/javajs/20100719/461986.html

感谢分享

 


四、关于session的失效与过期这个问题
    如果session失效或过期,会进入HttpSessionListener监听的sessionDestroyed(HttpSessionEvent se)方法,
    但最后还会进入 HttpSessionAttributeListener(上边实现的监听接口)监听的attributeRemoved(HttpSessionBindingEvent se)方法
    所以,在[实现禁止不同IP相同用户的重复登录]这里,HttpSessionListener这个接口可以不实现


五、关于用户登录session的Key相同名称的问题

    第一次学写网站的时候,这个问题也困扰了我,查了些资料,才大概的理解。
   开始的理解:每次保存到session的用户登录的key的都叫"logonkey",就算是不同的用户登录,Key名也是一样的,这样不就覆盖了之前登录用户的信息了吗?
   看资料后的理解:servlet的 session 是多例的,每一个访问用户,都会产生一个新的session,两个不同IP的用户就会有两个session,所以不会产生覆盖之前用户信息的情况


六、IE和火狐浏览器对session的生成测试

看了以下的问题

http://topic.csdn.net/u/20100503/18/f50d4f11-ae1f-4bb1-ac63-5991a2980371.html

自己好奇,就用以上的测试程序在自己的机子上做了以下测试。基本上每次都是重启TomCat


    1、监听执行情况
        相同IP的情况下,两个火狐或两个IE访问同一个网站,都会执行以下监听器,
        ServletContextAttributeListener —— attributeAdded
        HttpSessionListener —— sessionCreated
    当再有浏览器打开,则只会执行
        HttpSessionListener —— sessionCreated

测试结果:
火狐
    两个窗体同时打开
        不同帐号
            窗体1的sessionID: AAB8A48784BD4C6788AD3DF515DE9346
            窗体2的sessionID:AAB8A48784BD4C6788AD3DF515DE9346

    两个标签同时打开
        不同帐号
            标签1的sessionID:088FBC8398755E89D6E1F4C6DDE6A451
            标签2的sessionID:088FBC8398755E89D6E1F4C6DDE6A451

    两个窗体同时打开
        相同帐号
            窗体1的sessionID:96D56B493D7309D3E58F68DBD7A21773
            窗体2的sessionID:96D56B493D7309D3E58F68DBD7A21773

    两个标签同时打开
        相同帐号
            标签1的sessionID:BA9CEA585F0A462669DC9451B0FD131E
            标签2的sessionID:BA9CEA585F0A462669DC9451B0FD131E

    先打开一个窗体,关闭后再打开另一个窗体
        不同帐号
            窗体1的sessionID:C2CBD2F3A3BCF343A5C9F304AA07293E
            窗体2的sessionID:C2CBD2F3A3BCF343A5C9F304AA07293E

        相同帐号(本次没有重启TomCat,所以与上边一次的ID一样)
            窗体1的sessionID:C2CBD2F3A3BCF343A5C9F304AA07293E
            窗体2的sessionID:C2CBD2F3A3BCF343A5C9F304AA07293E
       
           
IE6
    两个窗体同时打开
        不同帐号
            窗体1的sessionID:FB90001D16BD935BF110659F3486B9FC
            窗体2的sessionID:9D3BA24FC1D16F65EF61D07B90D457C5
       
        相同帐号       
            窗体1的sessionID:D82D0E4E393B1936CD364FF09ED6996B
            窗体2的sessionID:F3469B1AD89401E1F4F11CD96DD7375D


    先打开一个窗体,关闭后再打开另一个窗体
        不同帐号
            窗体1的sessionID:627C441F48AF1D3628578E7E584D64C7
            窗体2的sessionID:BE4296FD0C48920EADA301985309C6BE

分享到:
评论

相关推荐

    java监听器的实现和原理详解

    Java监听器的实现和原理详解 Java监听器是一种非常重要的设计模式,在Java中广泛应用于事件驱动编程。监听器模式的主要思想是将事件源和事件处理器分离,使得事件源可以独立于事件处理器,提高了系统的灵活性和可...

    java监听器和过滤器详解

    ### Java监听器和过滤器详解 #### 监听器概念及分类 监听器在Java Web开发中扮演着非常重要的角色,主要用于监听特定容器事件的发生,比如服务器对象的创建与销毁等,并根据这些事件做出相应的处理。它的工作原理...

    Java设计模式之监听器模式实例详解

    监听器模式是Java设计模式中的一种行为型模式,主要用于监听某些事件的发生,并在事件发生时自动执行预定义的操作。在实际开发中,监听器模式可以用于多种场景,如UI事件处理、事件广播、异步通知等。 在监听器模式...

    javaweb_高级_过滤器_监听器

    【JavaWeb 高级:过滤器与监听器详解】 在JavaWeb开发中,过滤器(Filter)和监听器(Listener)是两种非常重要的组件,它们能够帮助我们实现更精细的控制和管理应用程序的行为。本篇将详细介绍过滤器的原理、使用...

    Filter过滤器和Listener监听器详解

    Filter过滤器和Listener监听器详解 Filter过滤器 Filter的简介 对资源的访问进行过滤,相当于小区的保安,进去要检查,出去还要检查。 Filter的使用 编写一个类,继承并实现javax.servlet.Filter。 package ...

    监听器 过滤器 详解

    在Java Web开发中,监听器(Listeners)和过滤器(Filters)是两个非常重要的概念,它们主要用于增强应用程序的功能和性能。这两个组件都是Servlet规范的一部分,能够帮助开发者在Web应用程序的不同阶段进行介入,...

    STRUTS:listener监听器

    ### STRUTS:Listener监听器详解 #### 一、引言 在Java Web开发中,监听器(Listener)是十分重要的组成部分,它们主要用于监听特定事件的发生,并执行相应的处理逻辑。Struts框架作为早期流行的MVC架构之一,充分...

    JAVA培训Servlet监听器.pdf

    JAVA Servlet 监听器详解 JAVA Servlet监听器是指在Servlet容器中,用于监听一些重要事件的发生,监听器对象可以在事情发生前、发生后做一些必要的处理。监听器可以分为多种类型,每种类型都有其特定的应用场景和...

    Java基础 Servlet监听器详解

    Java Servlet监听器是Java Servlet API中的一个重要组成部分,它们允许开发者在Web应用的生命周期内的某些关键点添加自定义行为。具体来说,监听器可以监控Web应用中的三个主要对象:ServletContext(代表整个Web...

    Java监听器

    ### Java监听器详解 在Java编程语言中,监听器(Listener)是一种常用的设计模式,用于实现事件处理机制。本文将围绕“Java监听器”的概念、原理及其应用进行深入探讨。 #### 一、Java监听器的基本概念 监听器是...

    JAVA监听器 绝对有用

    ### JAVA监听器详解 在Java Web开发中,监听器(Listeners)是一种非常重要的机制,用于监控应用程序中的特定事件,如应用程序启动、停止等,并在这些事件发生时执行相应的操作。本文将详细介绍Java监听器的基本...

    JSP监听器用法分析.docx

    **JSP监听器详解** JSP监听器是JavaServer Pages(JSP)技术中的一个重要组成部分,主要用于监控和处理Web应用程序中的特定事件。监听器通过实现Servlet API提供的接口,可以在特定事件发生时执行自定义逻辑,如...

    libev手册及常用监听器讲解及实例

    **libev手册及常用监听器详解** Libev是一个高性能、跨平台的事件库,它提供了异步事件处理的能力,广泛应用于服务器开发和其他需要高效率事件循环的场景。本篇文章将基于"libev手册"和"libev学习笔记"这两份文档,...

    【动力节点】Javaweb开发视频教程之监听器

    教程名称: 【动力节点】Javaweb开发视频教程之监听器 动力节点推出的Java视频教程包含两大部分内容:第一部分为监听器相关设计模式的详解。其中包括观察者设计模式、监听器设计模式。第二部分为监听器用法。详细...

    oracle监听启动详解

    - **作用**:用于配置Oracle监听器进程,该进程负责接收远程连接请求并将之转发给Oracle服务器进程。 - **示例配置**: ```plaintext SID_LIST_LISTENER= (SID_LIST= (SID_DESC= (GLOBAL_DBNAME=boway) ...

    oracle9i企业管理器详解

    6. **配置管理**:通过企业管理器,你可以轻松地修改数据库参数,配置初始化参数文件,以及管理数据库的网络连接和监听器设置。 7. **应用程序开发**:Oracle 9i EM还支持应用程序开发,允许开发者创建和管理数据库...

    监听广播使用方法的详解

    监听广播使用方法的详解,监听广播使用方法的详解,监听广播使用方法的详解,监听广播使用方法的详解,

    我收集的servlet中事件监听器机制we吧xml配置详解

    在Servlet中,事件监听器机制和`web.xml`配置是两个关键的概念,它们极大地增强了应用的灵活性和可扩展性。 事件监听器机制在Servlet中扮演着重要角色,它允许程序员对特定的事件进行响应,例如请求到达、会话创建...

    Java中的拦截器、过滤器、监听器用法详解

    Java中的拦截器、过滤器、监听器用法详解 Java中的拦截器、过滤器、监听器是三个重要的概念,它们在Java Web开发中扮演着非常重要的角色。本文将详细介绍Java中的拦截器、过滤器、监听器的用法,包括它们的功能、...

    Spring Boot的listener(监听器)简单使用实例详解

    "Spring Boot的listener(监听器)简单使用实例详解" 在Spring Boot中,listener(监听器)是一种非常重要的组件,它可以帮助我们在应用程序启动和停止时执行一些特定的任务。今天,我们将详细介绍Spring Boot的...

Global site tag (gtag.js) - Google Analytics