监听器详解

一、各监听器顺序
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

扫描二维码关注公众号,回复: 602268 查看本文章

感谢分享


四、关于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

猜你喜欢

转载自zengshaotao.iteye.com/blog/1895409
今日推荐