29、servlet事件监听器(JavaEE笔记)

一、概述

监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即被执行。

二、监听器经典案例:监听windows窗口的事件监听器

(工程day20
请描述java时间监听机制:

  • 1.事件监听涉及到三个组件:事件源、事件对象、事件监听器
  • 2.当事件源上发生某个动作时,它会调用事件监听器的一个方法,并在调用该方法时把事件对象传递进去,开发人员在监听器中通过事件对象,就可以拿到事件源,从而对事件源进行操作。事件对象封装事件源和动作,而监听器对象通过事件对象对事件源进行处理。

Demo1.java

package cn.itcast.demo;
import java.awt.Frame;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

public class Demo1 {

    public static void main(String[] args) {
        Frame f = new Frame();
        f.setSize(400, 400);
        f.setVisible(true);
        
        f.addWindowListener(new WindowListener() {
            
            public void windowOpened(WindowEvent arg0) {}
            public void windowIconified(WindowEvent arg0) {}
            public void windowDeiconified(WindowEvent arg0) {}
            public void windowDeactivated(WindowEvent arg0) {}

            @Override
            public void windowClosing(WindowEvent e) {
                System.out.println("关闭");
                Frame f = (Frame) e.getSource();//得到关闭窗口的事件源
                f.dispose();//关闭窗口  
            }
            public void windowClosed(WindowEvent arg0) {}
            public void windowActivated(WindowEvent arg0) {}
        });
    }
}

说明:这里我们产生一个窗口,当我们点击窗口右上角的叉时,使用监听器监测此事件,点击的时候就会监测到,执行关闭操作,这是一个经典的监听器使用例子。上例中使用方法addWindowListener注册一个监听器,在监听器中使用相关方法对事件源进行处理,当然我们会将事件源WindowEvent传递进去。

三、自己设计一个类让别人监听

Demo2.java

package cn.itcast.demo;
//设计一个事件源,被监听器监听,Observer(观察者设计模式)
public class Demo2 {

    public static void main(String[] args) {
        Person p = new Person();
        p.registerListener(new PersonListener() {
            
            @Override
            public void dorun(Event e) {
                Person person = e.getSource();
                System.out.println(person + "吃饭");
            }
            
            @Override
            public void doeat(Event e) {
                Person person = e.getSource();
                System.out.println(person + "跑步");
            }
        });
        p.eat();
    }
}

class Person{//让这个类被其他类监听
    
    private PersonListener listener;//定义一个监听器接口,记住传递进来的监听器对象
    
    public void eat(){
        if(listener != null){
            listener.doeat(new Event(this));
        }
        
    }
    public void run(){
        if(listener != null){
            listener.dorun(new Event(this));
        }
    }
    public void registerListener(PersonListener listener){
        this.listener = listener;
    }
}

interface PersonListener{
    public void doeat(Event e);
    public void dorun(Event e);
}
class Event{//用于封装事件源
    private Person source;
    
    public Event() {
        super();
    }

    public Event(Person source) {
        super();
        this.source = source;
    }

    public Person getSource() {
        return source;
    }

    public void setSource(Person source) {
        this.source = source;
    }   
}

说明:首先我们定义事件源对象Event和一个监听器接口PersonListener,然后我们想让某个类(这里是Person)被监听,于是需要在类中维护一个监听器接口PersonListener,我们可以使用一个方法(registerListener)将此接口传递进来,然后我们就可以使用监听器接口中的相关方法对事件源进行处理了。

四、servlet监听器

  • 在servlet规范中定义了多种类型的监听器,它们用于监听的事件源分别为ServletContext、HttpSession 和 ServletRequest这三个域对象。

  • Servlet规范针对这三个对象上的操作,又把这多种类型的监听器划分为三种类型:

    • 1.监听三个域对象创建和销毁的事件监听器;
    • 2.监听域对象中属性的增加和删除的事件监听器;
    • 3.监听绑定到HttpSession域中的某个对象的状态的事件监听器。
  • 监听servletContext域对象创建和销毁

    • ServletContextListener接口用于监听ServletContext对象的创建和销毁事件。
    • ServletContext对象被创建时,激发````contextInitialized(ServletContextEvent sce)```方法。
    • ServletContext对象被销毁时,激发contextDestroyed(ServletContextEvent sce)方法。

注意:ServletContext域对象何时创建和销毁?

  • 创建:服务器启动针对每一个web应用创建一个ServletContext
  • 销毁:服务器关闭前先关闭代表每一个web应用的ServletContext

4.1 示例:监听ServletContext对象

MyServletContextListener.java

package cn.itcast.web.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
//这里我们只需要在web.xml文件中将此监听器配置就可以了,当服务器启动时就会创建ServletContext
public class MyServletContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ServletContext创建");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ServletContext销毁");
    }
}

web.xml中进行注册:

<listener>
    <listener-class>cn.itcast.web.listener.MyServletContextListener</listener-class>
</listener>

说明:因为在服务器启动的时候ServletContext就会创建,这时我们可以监测到其创建。

4.2 监听HttpSession域对象创建和销毁

这里HttpSessionListener接口用于监听HttpSession的创建和销毁。

MyHttpSessionListener.java

package cn.itcast.web.listener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class MyHttpSessionListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        
        System.out.println(se.getSession() + "session创建了");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("session销毁了");
    }
}

说明:在访问index.jsp的时候会创建一个session,服务器关闭的时候是不会摧毁session的,我们可以设置失效时间,在配置文件中进行配置,单位是分钟,可以用来统计当前在线多少用户,但是不是特别准确。

Session域对象创建和销毁的时机
创建:用户每一次访问时,服务器创建Session
销毁:如果用户的Session三十分钟(默认)没有使用,服务器就会销毁Session,我们在web.xml里面也可以配置Session失效时间。

4.3 监听HttpRequest域对象创建和销毁

这里ServletRequestListener接口用于监听ServletRequest对象的创建和销毁。
MyServletRequestListener .java

package cn.itcast.web.listener;
//ServletRequestListener可以用来检测网站性能
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;

public class MyServletRequestListener implements ServletRequestListener {

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println(sre.getServletRequest() + "销毁了");

    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println(sre.getServletRequest() + "创建了");

    }
}

说明:ServletRequest域对象创建和销毁的时机
创建:用户每一次访问,都会创建一个Request。
销毁:当前访问结束,Request对象就会销毁。

五、案例:统计当前在线人数

OnlineCountListener.java

package cn.itcast.web.listener;
//统计当前在线用户个数
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
//监听器和过滤器一样,Servlet中只存在一个,所以num不需要设置成静态的
public class OnlineCountListener implements HttpSessionListener {
    //如果我们要将num值传递到页面,则不能使用Request和session,而只能通过Application(ServletContext)
    /*int num = 0;*/

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        ServletContext context = se.getSession().getServletContext();
        Integer num = (Integer) context.getAttribute("num");
        if(num == null){
            context.setAttribute("num", 1);
        }else{
            num++;
            context.setAttribute("num", num);
        }
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        ServletContext context = se.getSession().getServletContext();
        Integer num = (Integer) context.getAttribute("num");
        if(num == null){
            context.setAttribute("num", 1);
        }else{
            num--;
            context.setAttribute("num", num);
        }
    }
}

说明:当服务器启动时只有这个监听器只有一个,所以我们可以在方法中定义一个变量来统计在线人数。而这个变量我们如果要传递到前台,不能使用request和session,因为会有多个。这里我们通过servletContext域来将此统计值传递到前台。

index.jsp

<body>
    当前在线用户个数:${applicationScope.num} 
</body>

六、案例:自定义Session扫描器

在开发中我们有时候需要管理session,比如当session多长时间没用之后我们就将其销毁,减小服务器的压力。
SessionScannerListener.java

package cn.itcast.web.listener;
//Session的默认失效时间是三十分钟
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

//自定义Session扫描器
public class SessionScannerListener implements HttpSessionListener, ServletContextListener{
    private List<HttpSession> list = Collections.synchronizedList(new LinkedList());//使得集合成为一个线程安全的集合
    private Object lock;//定义一把锁
    
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        synchronized (lock) {
            list.add(session);
        }
        //list.add(session);//这样做容易出现两个Session抢一个list位置的情况,即集合不是线程安全的
        System.out.println("被创建了");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("被销毁了");

    }

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        Timer timer = new Timer() ;
        timer.schedule(new MyTask(list,lock), 0, 1000*15);//延时为0,每隔15秒扫描一次  
        
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {}
}
class MyTask extends TimerTask{
    
    private List<HttpSession> list ;
    private Object lock;//定义一把锁用于记住传递进来的锁
    
    public MyTask(List list, Object lock) {//将要扫描的集合传递进来
        this.list = list;
        this.lock = lock;
    }
    @Override
    public void run() {
        synchronized (lock) {
            ListIterator<HttpSession> it = list.listIterator();
            while(it.hasNext()){
                HttpSession session = (HttpSession) it.next();
                if((System.currentTimeMillis() - session.getLastAccessedTime()) > 1000*60){//表示此Session十五秒没人用,就将其摧毁
                    session.invalidate();//摧毁此Session
                    //list.remove(session);//将其从当前的list中移除
                    it.remove();//调用迭代器将其移除
                }
            }
        }
        /*ListIterator<HttpSession> it = list.listIterator();
        while(it.hasNext()){
            HttpSession session = (HttpSession) it.next();
            if((System.currentTimeMillis() - session.getLastAccessedTime()) > 1000*60){//表示此Session十五秒没人用,就将其摧毁
                session.invalidate();//摧毁此Session
                //list.remove(session);//将其从当前的list中移除
                it.remove();//调用迭代器将其移除
            }
        }*/
    }
}

说明:

  • 1.我们定义一个集合来保存所有session,但是当两个用户同时访问的时候,有可能在创建session的时存入集合的同一个位置,为了避免这种情况,我们将集合做成一个线程安全的,java中为我们提供了一个集合帮助类Collections类,可以将集合做成一个线程安全的集合。

  • 2.我们要扫描在线用户,所以需要定义一个定时器,而此定时器是在服务器一启动就需要开启,于是我们还需要一个servletContext的监听器,我们直接让定义的监听器继承两个监听器接口,同时监听HttsSessionservletContext

  • 3.我们在遍历集合的时候是不能执行add操作的,这会出现并发问题,所以我们需要给迭代器和add方法都加上一把锁,防止并发问题。将一段代码做成同步是只需要加关键字synchronized即可,但是如果要把两段代码做成同步的就需要用到锁。

我们还可以指定服务器在某个时间发送邮件:
SendMailListener.java

package cn.itcast.web.listener;
import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
//我们设置一个时间,让监听器在设置的时间点干什么事情
public class SendMailListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        
        Calendar c = Calendar.getInstance();
        c.set(2015, 11, 7, 15, 11, 0);//设置一个时间是2015.12.7 15:11:00
        
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            
            @Override
            public void run() {
                System.out.println("aaaaaaaa");
            }
        }, c.getTime());
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {}
}

七、监听三个域对象属性的变化

  • Servlet规范定义了监听ServletContext、HttpSession
    HttpServletRequest这三个对象中的属性变更信息事件的监听器。

  • 这三个监听器接口分别是ServletContextAttributeListener、HttpSessionAttributeListener、ServletRequestAttributeListener

  • 这三个接口中都定义了三个方法来处理被监听对象中的属性的增加,删除和替换的事件,同一个事件在这三个接口中对应的方法名称完全相同,只是接受的参数类型不同。

八、相关方法

8.1attributeAdded方法

  • 当向被监听对象中增加一个属性时,web容器就调用事件监听器的attributeAdded方法进行添加操作,这个方法接受一个事件类型的参数,监听器可以通过这个参数来获得正在增加属性的域对象和被保护到域中的属性对象。

  • 各个域属性监听器中的完整语法定义

public void attributeAdded(ServletContextAttributeEvent scae)
public void attributeAdded(HttpSessionBindingEvent se)
public void attributeAdded(ServletRequestAttributeEvent srae)

8.2 attributeRemoved方法

  • 当删除被监听对象中的一个属性时,web容器调用事件监听器的这个方法进行相应的操作。
  • 各个域属性监听器中的完整语法定义
public void attributeRemoved(ServletContextAttributeEvent scab)
public void attributeRemoved(HttpSessionBindingEvent se)
public void attributeRemoved(ServletRequestAttributeEvent srae)

8.3 attributeReplace方法

  • 当监听器的域对象中的某一个属性被替换时,web容器调用事件监听器的这个方法进行相应的操作。
  • 各个域属性监听器中的完整语法定义
public void attributeReplaced(ServletContextAttributeEvent scab)
public void attributeReplaced(HttpSessionBindingEvent se)
public void attributeReplaced(ServletRequestAttributeEvent srae)

8.4 感知Session绑定的事件监听器

  • 保存在Session域中的对象可以有多种状态。绑定到Session中:从Session域中解决绑定;随Session对象持久化到一个存储设备中;随Session对象从一个存储设备中恢复。

  • servlet规范中定义两个特殊的监听器接口来帮助javaBean对象了解自己在Session域中的这些状态:HttpSessionBindingListener接口和HttpSessionActivationListener接口,实现这两个接口的类不需要在web.xml文件中进行注册。

  • HttpSessionBindingListener接口
    实现了此接口的javaBean对象可以感知自己被绑定到Session中和从Session中删除的事件。
    例:MyBean .java

package cn.itcast.domain;
//这个监听器用来监听自己,所以不需要在配置文件中进行配置
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

public class MyBean implements HttpSessionBindingListener {
    
    private String name;
    
    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        System.out.println("自己被添加到Session");
    }

    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        System.out.println("自己被从Session删除");
    }
}

index.jsp

<% session.setAttribute("bean", new MyBean()); %>
  • HttpSessionActivationListener接口
    实现了此接口的javaBean对象可以感知自己被活化和钝化的事件。
    MyBean2.java
package cn.itcast.domain;
import java.io.Serializable;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionEvent;
//注意:Session被钝化和活化都是由tomcat管理,默认是三十分钟,但是我们也可以自己进行设置。更改服务器的配置
public class MyBean2 implements HttpSessionActivationListener,Serializable {

    @Override
    public void sessionWillPassivate(HttpSessionEvent se) {
        System.out.println("钝化");//即从内存中序列化到硬盘
    }

    @Override
    public void sessionDidActivate(HttpSessionEvent se) {
        System.out.println("活化");//从硬盘中回到内存
    }
}

同时我们需要一个配置文件context.xml,放在META-INF中。

<Context>
    <manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">1表示一分钟
        <Store className="org.apache.catalina.session.FileStore" directory="it315"/> it315这个目录在tomcat的work目录中找到
    </manager>
</Context>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • 监听器(listener) 监听器简介 :监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个...
    奋斗的老王阅读 2,488评论 0 53
  • 本文包括:1、Listener简介2、Servlet监听器3、监听三个域对象创建和销毁的事件监听器4、监听三个域对...
    廖少少阅读 6,000评论 6 28
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,184评论 11 349
  • 雨夜 哗啦啦的作响 心情在这一刻泛起涟漪 望向雨夜 仿佛看到了你的面容 伸手想去抚摸 却又遥不可及 想问你声,你过...
    Mr丶鹿阅读 173评论 0 0
  • 因为口语内测组的调试,好一阵忙活,昨天的作业都拉下了。 群主的原图省略 直接抄写加注释 运行地址 http...
    蜗牛0718阅读 260评论 1 1