0%

JavaWeb学习(十一)——监听器

监听器

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

监听器组件

监听器涉及三个组件:事件源,事件对象,事件监听器

当事件源发生某个动作的时候,它会调用事件监听器的方法,并在调用事件监听器方法的时候把事件对象传递进去。

我们在监听器中就可以通过事件对象获取得到事件源,从而对事件源进行操作!

简单的监听器

监听器定义为接口,监听的方法需要事件对象传递进来,从而在监听器上通过事件对象获取得到事件源,对事件源进行修改

1
2
3
4
5
6
7
8
9
10
/**
* 事件监听器
*
* 监听Person事件源的eat和sleep方法
*/
interface PersonListener{

void doEat(Event event);
void doSleep(Event event);
}

事件源

事件源是一个Person类,它有eat和sleep()方法。

事件源需要注册监听器(即在事件源上关联监听器对象)

如果触发了eat或sleep()方法的时候,会调用监听器的方法,并将事件对象传递进去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
   /**
*
* 事件源Person
*
* 事件源要提供方法注册监听器(即在事件源上关联监听器对象)
*/

public class Person {

//在成员变量定义一个监听器对象
private PersonListener personListener ;

//在事件源中定义两个方法
public void Eat() {

/**
* 调用监听器的doeat方法监听Person类对象eat(吃)这个动作,将事件对象Event传递给doeat方法,
* 事件对象封装了事件源,new Event(this)中的this代表的就是事件源
*/
if(personListener!=null)
personListener.doEat(new Event(this));
}

public void sleep() {


if(personListener!=null)
personListener.doSleep(new Event(this));
}

//注册监听器,该类没有监听器对象啊,那么就传递进来吧。
public void registerLister(PersonListener personListener) {
this.personListener = personListener;
}

}

事件对象

事件对象封装了事件源。

监听器可以从事件对象上获取得到事件源的对象(信息)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 事件对象Even
*
* 事件对象封装了事件源
*
* 在监听器上能够通过事件对象获取得到事件源
*
*
*/
class Event{
private Person person;

public Event() {
}

public Event(Person person) {
this.person = person;
}

public Person getResource() {
return person;
}

Servle监听器

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

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

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

和其它事件监听器略有不同的是,servlet监听器的注册不是直接注册在事件源上,而是由WEB容器负责注册,开发人员只需在web.xml文件中使用<listener>标签配置好监听器.

监听对象的创建和销毁

HttpSessionListener、ServletContextListener、ServletRequestListener分别监控着Session、Context、Request对象的创建和销毁

  • HttpSessionListener(可以用来收集在线者信息)
  • ServletContextListener(可以获取web.xml里面的参数配置)
  • ServletRequestListener

监听对象属性变化

ServletContextAttributeListener、HttpSessionAttributeListener、ServletRequestAttributeListener分别监听着Context、Session、Request对象属性的变化

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

  • attributeAdded()

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

  • attributeRemoved()

    当删除被监听对象中的一个属性时,web容器调用事件监听器的attributeRemoved方法进行响应

  • attributeReplaced()

    当监听器的域对象中的某个属性被替换时,web容器调用事件监听器的attributeReplaced方法进行响应

监听Session内的对象

除了上面的6种Listener,还有两种Linstener监听Session内的对象,分别是HttpSessionBindingListener和HttpSessionActivationListener,实现这两个接口并不需要在web.xml文件中注册

  • 实现HttpSessionBindingListener接口,JavaBean 对象可以感知自己被绑定到 Session 中和从 Session 中删除的事件【和HttpSessionAttributeListener的作用是差不多的】

  • 实现HttpSessionActivationListener接口,JavaBean 对象可以感知自己被活化和钝化的事件(当服务器关闭时,会将Session的内容保存在硬盘上【钝化】,当服务器开启时,会将Session的内容在硬盘式重新加载【活化】)

    活化:javabean对象和Session一起被反序列化(活化)到内存中。
    钝化:javabean对象存在Session中,当服务器把session序列化到硬盘上时,如果Session中的javabean对象实现了Serializable接口
    那么服务器会把session中的javabean对象一起序列化到硬盘上,javabean对象和Session一起被序列化到硬盘中的这个操作称之为钝化
    如果Session中的javabean对象没有实现Serializable接口,那么服务器会先把Session中没有实现Serializable接口的javabean对象移除
    然后再把Session序列化(钝化)到硬盘中
    当绑定到 HttpSession对象中的javabean对象将要随 HttpSession对象被钝化之前,
    web服务器调用该javabean对象对象的 void sessionWillPassivate(HttpSessionEvent event)方法
    这样javabean对象就可以知道自己将要和 HttpSession对象一起被序列化(钝化)到硬盘中
    当绑定到HttpSession对象中的javabean对象将要随 HttpSession对象被活化之后,
    web服务器调用该javabean对象的 void sessionDidActive(HttpSessionEvent event)方法
    这样javabean对象就可以知道自己将要和 HttpSession对象一起被反序列化(活化)回到内存中

应用

统计网站在线人数

  1. 监听Session是否被创建了:在网站中一般使用Session来标识某用户是否登陆了,如果登陆了,就在Session域中保存相对应的属性。如果没有登陆,那么Session的属性就应该为空。
  2. 如果Session被创建了,那么在Context的域对象的值就应该+1:在线人数用context对象保存
  3. 如果Session从内存中移除了,那么在Context的域对象的值就应该-1
  • 监听器代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class CountOnline implements HttpSessionListener {

public void sessionCreated(HttpSessionEvent se) {

//获取得到Context对象,使用Context域对象保存用户在线的个数
ServletContext context = se.getSession().getServletContext();

//直接判断Context对象是否存在这个域,如果存在就人数+1,如果不存在,那么就将属性设置到Context域中
Integer num = (Integer) context.getAttribute("num");

if (num == null) {
context.setAttribute("num", 1);
} else {
num++;
context.setAttribute("num", num);
}
}
public void sessionDestroyed(HttpSessionEvent se) {

ServletContext context = se.getSession().getServletContext();
Integer num = (Integer) se.getSession().getAttribute("num");

if (num == null) {
context.setAttribute("num", 1);
} else {
num--;
context.setAttribute("num", num);
}
}
}
  • 显示页面代码:
1
在线人数:${num}

自定义Session扫描器

移除长时间没有人使用的session。

  1. 创建一个容器装在站点的所有session。
  2. 隔一段时间去扫描一下全部的session,当长时间没有使用的话就将其从内存中移除。
  3. 并发访问的问题:监听Session创建的方法就会被并发访问了定时器扫描容器的时候,可能是获取不到所有的Session的
  • 监听器代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Listener1 implements ServletContextListener,
HttpSessionListener {



//服务器一启动,就应该创建容器。我们使用的是LinkList(涉及到增删)。容器也应该是线程安全的。
List<HttpSession> list = Collections.synchronizedList(new LinkedList<HttpSession>());

//定义一把锁(Session添加到容器和扫描容器这两个操作应该同步起来)
private Object lock = 1;

public void contextInitialized(ServletContextEvent sce) {


Timer timer = new Timer();
//执行我想要的任务,0秒延时,每10秒执行一次
timer.schedule(new MyTask(list, lock), 0, 10 * 1000);

}
public void sessionCreated(HttpSessionEvent se) {

//只要Session一创建了,就应该添加到容器中
synchronized (lock) {
list.add(se.getSession());
}
System.out.println("Session被创建啦");

}

public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("Session被销毁啦。");
}
public void contextDestroyed(ServletContextEvent sce) {

}
}
  • 任务代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* 在任务中应该扫描容器,容器在监听器上,只能传递进来了。
*
* 要想得到在监听器上的锁,也只能是传递进来
*
* */
class MyTask extends TimerTask {

private List<HttpSession> sessions;
private Object lock;

public MyTask(List<HttpSession> sessions, Object lock) {
this.sessions = sessions;
this.lock = lock;
}

@Override
public void run() {

synchronized (lock) {
//遍历容器
for (HttpSession session : sessions) {

面试题

过滤器监听器面试题都在这里 - SegmentFault 思否