一、概述
监听器就是一个实现特定接口的普通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
的监听器,我们直接让定义的监听器继承两个监听器接口,同时监听HttsSession
和servletContext
。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>