[Servlet]HttpSession的事件监听、HttpServletRequest的事件监听

时间:2021-04-24 22:43:35

1. HttpSession不同事件对应不同监听器:

    1) 关于HttpSession总共有4种监听器,分别是HttpSessionListener、HttpSessionAttributeListener/HttpSessionBindingListener和HttpSessionActivationListener;

    2) HttpSessionListener:生命周期监听器,负责监听并响应HttpSession的创建和销毁;

    3) HttpSessionAttributeListener/HttpSessionBindingListener:两者协同工作,负责监听并响应HttpSession属性的添加、删除和替换;

    4) HttpSessionActivationListener:负责应用程序的分布式迁移,在分布式环境下应用程序可能分散在不同的JVM中,要从一个JVM迁移到另一个JVM时需要使用该监听器,一般用不到;


2. HttpSessionListener:

    1) 接口原型:

public interface HttpSessionListener extends EventListener {
public void sessionCreated(HttpSessionEvent se);
public void sessionDestroyed(HttpSessionEvent se);
}
    2) 响应时机是HttpSession创建后和结束前;

    3) HttpSessionEvent类只包装了一个方法,即getSession(),来获取触发事件的会话对象,在两个方法中可以调用该方法获取Session句柄并进行进一步处理;

    4) HttpSessionListener典型应用——防止重登陆:

         i. 为了防止用户重复登录通常会在数据库中设置一个是否登录的字段,如果用户登录就设置该字段,如果用户注销就清除该字段,如果用户再次登录时该字段为已登录则要拒绝重复登录;

         ii. 但现在问题来了,有些用户登陆后未点击注销就关闭了浏览器,那么这就意味着数据库中的登录字段永远也无法被清除了,那么用户以后就再也都登录不了了;

         iii. 但还好,我们可以利用Session,因为客户端浏览器对话通常会产生一个HttpSession,而在关闭浏览器后HttpSession可以存活一段时间,那么就可以在HttpSession被销毁的时候用Listener监听一下,并在sesssionDestroyed中检查一下数据库中登录字段是否被清除,如果没被清除就清除一下,这样就能万无一失了;

         iv. 例如:这里用户名保存在Session的login属性中

@WebListener
public class Test implements HttpSessionListener {
@Override
public void sessionCreate(HttpSessionEvent se) {}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
String userName = session.getAttribute("login");

拿userName到数据库中检查登录字段,如果还登着就清除该字段
}
}
    5) 典型案例2——统计在线人数:

         i. 专门用一个类来统计在线人数,通过监听HttpSession创建和销毁事件来实现;

         ii. 创建Session时人数+1,销毁时人数-1;

         iii. 例如:

@WebListener
public class OnlineUserCounter implements HttpSessionListener {
private static int counter;
public static int getCounter() {
return counter;
}
@Override
public void sessionCreated(HttpSessionEvent se) {
OnlineUserCounter.counter++;
}
@Override void sessionDestroyed(HttpSessionEvent se) {
OnlineUserCounter.counter--;
}
}
!!当然这只是一个大致的逻辑,里面没有考虑是否重复登录等,否则就还要在这两个方法里面加入检查数据库字段的代码;


3. HttpSessionAttributeListener/HttpSessionBindingListener:

    1) 先来看一下HttpSessionAttributeListener接口:

public interface HttpSessionAttributeListener extends EventListener {
public void attributeAdded(HttpSessionBindingEvent se);
public void attributeRemoved(HttpSessionBindingEvent se);
public void attributeReplaced(HttpSessionBindingEvent se);
}
!可以看到还是那老三样,但是传入的参数是HttpSessionBinding事件对象;

!!但是HttpSessionBindingListenerEvent和ServletContextAttributeEvent一样都只有getName和getValue来获取被设置的属性名和值;

!!那HttpSessionBindingEvent和HttpSessionBindingListener起什么作用呢?又和HttpSessionAttributeListener如何合作呢?

    2) 这要说到设计模式了:

         i. Java程序的最大特点就是各大模块分工合作,各模块之间耦合度小;

         ii. 有时候在用户登录的时候用户只提供了一个用户名(当然需要密码),这时Servlet可以将用户名记录在Session中临时存放;

         iii. 但是通常情况下只保存用户名是不够的,往往需要在Session保存更多的信息,而这些额外的信息往往保存在数据库中,也就是说最好将用户名以及其它额外信息组成一个专门存放在Session中的“用户类User”,而该类的构造函数可以接受一个用户名,在函数体中会利用该用户名去检索数据库,将额外信息用数据库的查询结果初始化;

         iv. 最后将该对象作为树形添加到Session中保存即可;

!!那么现在问题来了,现在都流行将业务逻辑和数据库查询等分开,这里要将数据库查询代码写在构造器里显然是不合理的,可能会带来一系列隐患,有没有方法可以将它们分开呢?

         v. 使用HttpSessionBindingListener就可以实现了;

    3) BindingListener接口提供了两种方法,一种是值的绑定,另一种是值的解绑定:

public interface HttpSessionBindingListener extends EventListener {
public void valueBound(HttpSessionBindingEvent event);
public void valueUnbound(HttpSessionBindingEvent event);
}
!!上例中的User就需要实现该接口,上例中处理的顺序是这样的:

        a. 获取用户名String userName;

        b. 但是想将数据库中的其它信息和数据和用户名一并临时保存在Session中,因此需要一个User类,类中有两个字段,一个是String name,还有一个字段表示一类数据,即datas,即额外要添加的数据;

        c. 先不急着查询数据库,而是先只初始化User的userName字段:User user = new User(userName);  // 构造器是public User(String name) { this.name = name; }

!!但User必须实现HttpSessionBindingListener接口

        d. 接着直接将User对象插入到Session中:request.getSession().setAttribute("user", user);

        e. 此时会触发一个事件,那就是HttpSessionBindingEvent事件,该事件表示有树形插入到Session中,并且有值需要绑定,也就是包含了两层意思,而这个事件也会先后被两个监听器听见,第一个是HttpSessionBindingListener;

        f. BindingListener监听到后就会调用User的valueBound接口方法,然后在该方法中查询数据库将其余额外的数据初始化,例如:

public void valueBound(HttpSessionBindingEvent event) {
this.datas = 从数据库查询;
}

        g. 在valueBound调用结束后,HttpSessionAttributeListener在监听到该BindingEvent,此时在AttributeListener中获取的value已经是添加过额外信息的完整值了,接着可以进行后面的操作了;

!!解绑定valueUnbound会在removeAttribute时触发,用来解除绑定;

    4) 小结:一句话,就是BindingListener将属性中的字段绑定或解绑后再交给AttributeListener处理,它俩都只处理BindingEvent这一种事件;

!!从分工角度来看,就是BindingListener负责属性中字段的绑定和解绑,AttributeListener负责将处理绑定和解绑后的属性;


!!!这里主要应该理解HttpSessionBindingListener,它就是用来初始化或解除Session属性中的额外信息的!而HttpSessionAttributeListener的作用和用法和ServletContextAttributeListener无异,只不过在AttributeListener中获得的属性值都是BindingListener处理过的!!


4. HttpServletRequest的事件、监听器:

    1) 对于Request的事件共有三种监听器,前两种还是传统的生命周期监听器ServletRequestListener和属性改变监听器ServletRequestAttributeListener,还有一个是和异步处理有关的AsyncListener,这里先不讲,会在异步处理的章节具体讲解;

    2) 生命周期监听器:

public interface ServletRequestListener extends EventListener {
public void requestDestroyed (ServletRequestEvent sre);
public void requestInitialized (ServletRequestEvent sre);
}
!!ServletRequestEvent包装了getServletRequest()和getSerlvetContext()方法来获取ServletRequest对象和ServletContext对象;

    3) 属性改变监听器:

public interface ServletRequestAttributeListener extends EventListener {
public void attributeAdded(ServletRequestAttributeEvent srae);
public void attributeRemoved(ServletRequestAttributeEvent srae);
public void attributeReplaced(ServletRequestAttributeEvent srae);
}
!!还是老三样,Event里也只包装了getName和getValue两种方法;