专题:监听器
1 监听器相关设计模式
在 Servlet 规范中存在三大组件:Servlet 接口、Listener 接口、Filter 接口。我们在这里要学习监听器接口 Listener。监听器是一种设计模式,是观察者设计模式的一种实现。所以我们需要先学习观察者设计模式,再学习监听器设计模式。

1.1 设计模式

设计模式是指,可以重复利用的解决方案。由 GoF(Gang of Four,四人组)于 1995 年提出。他们提出了三类 23 种设计模式。这三类分别为:

1.1.1 创建型

通过特定方式创建特定对象的设计模式。例如,工厂方法模式、单例模式等。

1.1.2 结构性

为了解决某一特定问题所搭建的特定代码结构的设计模式。例如,适配器模式、代理模式等。

1.1.3 行为型

通过构建不同的角色来完成某一特定功能的设计模式。例如,模板方法模式、观察者模式等。

1.2 观察者设计模式

从现实角度来说,我们每一个人都是一个观察者,同时也是一个被观察者。作为被观察者,我们会发出一些信息,观察者在接收到这些信息后,会做出相应的反映;而作为观察者,我们是可以被“被观察者”所发出的信息影响的。一个被观察者,可能存在多个观察者。也就是说,一个被观察者所发出的信息,可能会影响到多个观察者。
观察者设计模式,定义了一种一对多的关联关系。一个对象 A 与多个对象 B、C、D 之间建立“被观察与观察关系”。当对象 A 的状态发生改变时,通知所有观察者对象 B、C、D。当观察者对象 B、C、D 在接收到 A 的通知后,根据自身实际情况,做出相应改变。
当然,观察者与被观察者指的都是具有某一类功能的对象,所以这里的观察者与被观察者都是指的接口,而真正的观察者对象与被观察者对象,是指实现了这些接口的类的对象。

1.2.1 定义观察者接口

image.png

1.2.2 定义被观察者接口

image.png

1.2.3 定义观察者

这里定义了一号和二号观察者
image.png
image.png

1.2.4 定义被观察者

被观察者除了要实现被观察者接口 Iobserverable 之外,还需要在类中声明并创建一个观察者集合,用于向其中添加观察者。
image.png

1.2.5 定义测试类

image.png

1.2.6 运行结果

image.png

1.3 监听器设计模式

监听器设计模式,是观察者设计模式的一种实现,它并不是 23 种设计模式之一。
这里的监听器实际对应的就是观察者,而被监听对象,则是指被观察者。当被监听对象的状态发生改变时,也需要通知监听器,监听器在收到通知后会做出相应改变。
与观察者设计模式不同的是,被监听者的状态改变,被定义为了一个对象,称为事件**被监听对象有了个新的名子,称为事件源;对监听器的通知,称为触发监听器。**其本质与观察者设计模式是相同的。
下面以对被监听者所执行的增删改查 CURD 操作进行监听为例,来演示监听器设计模式的用法。

1.3.1 定义事件接口

一般情况下,监听器对象被事件触发后,都是需要从事件中获取到事件源对象,然后再从事件源中获取一些数据。也就是说,在事件对象中一般是需要提供获取事件源对象的方法的。当然,除了获取事件源的方法外,根据业务需求,事件对象一般还需要提供一些其它数据,以便让监听器获取。
image.png

1.2.2 定义事件源接口

image.png

1.3.3 定义监听器接口

image.png

1.3.4 定义事件类

image.png

1.3.5 定义事件源

事件源是拥有自己的业务方法的,本例的业务方法为增删改查。应该是事件源对象在执行这些业务方法时触发监听器。
image.png

1.3.6 定义监听器

image.png

1.3.7 定义测试类

image.png

1.3.8 运行结果

image.png
2 监听器 Listener

2.1 Servlet 规范中的监听器

Servlet 规范中已经定义好了八个监听器接口,它们要监听的对象分别是 request、session、 servletContext 对象,触发监听器的事件是这三个对象的创建与销毁,它们的域属性空间中属性的添加、删除、修改,及 session 的钝化与活化操作。
在 JavaWeb 项目中使用监听器,需要在 web.xml 文件中对监听器进行注册。
image.png
下面分别对这八个监听器对象进行学习

2.1.1 ServletRequestListener

该监听器用于完成对 Request 对象的创建及销毁的监听,即当 Request 对象被创建或被销毁时,会触发该监听器中相应方法的执行。
项目:MyRequestListener

(1) 定义监听器

image.png

(2) 注册监听器


Listener - 图18

2.1.2 ServletRequestAttributeListener

该监听器用于完成对 request 域属性空间中属性的添加、修改、删除操作的监听。

(1) 定义监听器

注意 ServletRequestAttributeEvent 事件的方法 getName()可以获取到被操作的属性名, 方法 getValue()可以获取到被操作的属性的值。
image.png

(2) 注册监听器

image.png

(3) 修改 show.jsp 页面

image.png

(4) 运行结果

修改得到的是修改之前的属性名和属性值
image.png

2.1.3 HttpSessionListener

该监听器用于完成对 Session 对象的创建及销毁的监听。

(1) 定义监听器

image.png

(2) 注册监听器

image.png
此时执行程序,结果如下:
image.png

(3) 修改 show.jsp 页面

也可以在 web.xml 中设置,默认为半小时自动销毁
以下方法为手动销毁…
image.png
运行结果如下:
image.png

2.1.4 HttpSessionAttributeListener

该监听器用于完成对 session 域属性空间中属性的添加、修改、删除操作的监听。

(1) 定义监听器

image.png

(2) 注册监听器

image.png

(3) 修改 show.jsp 页面

image.png

(4) 运行结果

image.png

2.1.5 ServletContextListener

该监听器用于完成对 ServletContext 对象的创建及销毁的监听。不过需要注意,由于 ServletContext 在一个应用中只有一个,且是在服务器启动时创建。另外,ServletConetxt 的生命周期与整个应用的相同,所以当项目重新部署,或 Tomcat 正常关闭(通过 stop service
关闭,不能是 terminate 关闭)时,可以销毁 ServletContext。

(1) 定义监听器

image.png

(2) 注册监听器

image.png

(3) 运行结果

当Tomcat服务器启动时,ServletContext对象被创建
image.png
当Tomcat服务器被正常 stop 而不是 terminate 中止时,ServletContext对象被销毁
image.png
image.png

2.1.6 ServletContextAttributeListener

(1) 定义监听器

image.png

(2) 注册监听器

image.png

(3) 修改 show.jsp 页面

image.png

(4) 运行结果

类似前面这两个,都是服务器自动添加的…
image.png

2.1.7 HttpSessionBindingListener

该监听器用于监听指定类型对象与 Session 的绑定与解绑,即该类型对象被放入到 Session 域中,或从 Session 域中删除该类型对象,均会引发该监听器中相应方法的执行。
它与 HttpSessionAttributeListener 的不同之处是,该监听器监听的是指定类型的对象在Session 域中的操作,而 HttpSessionAttributeListener 监听的是 Session 域属性空间的变化,无论是什么类型的对象。
另外,需要强调两点:

  • 该监听器是由实体类实现
  • 该监听器无需在web.xml 中注册

    (1) 定义实现监听器接口的实体类

    image.png

    (2) 修改 show.jsp 页面

    image.png

    (3) 运行结果

    image.png

    2.1.8 HttpSessionActivationListener

    该监听器用于监听在 Session 中存放的指定类型对象的钝化与活化
    钝化是指将内存中的数据写入到硬盘中,而活化是指将硬盘中的数据恢复到内存。当用户正在访问的应用或该应用所在的服务器由于种种原因被停掉,然后在短时间内又重启,此时用户在访问时 Session 中的数据是不能丢掉的,在应用关闭之前,需要将数据写入到硬盘,在重启后应可以立即重新恢复 Session 中的数据。这就称为 Session 的钝化与活化。
    那么 Session 中的哪些数据能够钝化呢?只有存放在 JVM **堆内存中的实现了 Serializable 类的对象能够被钝化。也就是说,对于字符串常量、基本数据类型常量等存放在 JVM 方法区中常量池中的常量,是无法被钝化的**。
    对于监听 Session 中对象数据的钝化与活化,需要注意以下几点:

  • 实体类除了要实现 HttpSessionActivationListener 接口外,还需要实现 Serializable 接口。

  • 钝化指的是 Session 中对象数据的钝化,并非是 Session 的钝化。所以 Session 中有几个可以钝化的对象,就会发生几次钝化。
  • HttpSessionActivationListener 监听器是不需要在 web.xml 中注册的。

    (1) 定义实现监听器接口的实体类

    image.png

    (2) 修改 show.jsp 页面

    image.png

    (3) 运行结果

    当 stop 服务器时:
    image.png
    image.png
    再次重启服务器时:
    image.png

    2.2 监听器应用举例

    2.2.1 在线客户端统计

    统计连接在应用上的客户端数量。客户端的唯一标识就是 IP,只需要将连接到服务器上 的 IP 数量进行统计,就可统计出客户端的数量。这里需要注意一些细节:
  • 从 Request 中可以获取到请求的 IP,而从 Session 中是获取不到的。
  • 从 Session 中是无法获取到 Request 对象的,因为 session 与 request 的关系是 1:n,即一 个会话中可以包含多个请求。
  • 一个客户端可以发出很多请求与会话,但从这些请求中获取到的 IP 都是相同的。可以将获取到的 IP 放入到 Map 集合中,且以 IP 为 key,可以保证集合中没有重复的 IP。而 value 则为该 IP 的机器上所发出的会话对象所组成的 List。
  • 当一个客户端的 Session 被销毁时,应从map 的当前 ip 所对应的 value 中,即 List 中删除当前的 Session 对象。然后再查看 Map 中该客户端 IP 所发出的会话 List 长度,若为0, 则可将该 IP 所对应的 Entry 对象从Map 中删除。

    (1) 定义 ServletContext 监听器

    在 ServletContext 初始化时创建用于存放 IP 信息的Map 集合,并将创建好的Map 存放 到 ServletContext 域中。
    Map 的 key 为客户端 IP,而 value 则为该客户端 ip 所发出的会话对象组成的 List。
    image.png

    (2) 定义 Request 监听器

    Request 监听器主要完成以下功能:

  • 获取当前请求的客户端的 IP

  • 获取当前 IP 所对应的全局域中的 List。若这个 List 为空,则创建一个 List。
  • 将当前 IP 放入到 List 中,并将当前 IP 与 List 写入到 Map 中,然后再重新将 Map 写回 ServletContext 中
  • 将当前 IP 存放到当前请求所对应的 Session 域中,以备在 Session 销毁时使用

image.png
image.png

(3) 定义 Session 监听器

该监听器的功能主要是,当 Session 被销毁时,将当前 Session 对象从 List 中删除。在从 List 删除后,若 List 中没有了元素,则说明这个 IP 所发出的会话已全部关闭,则可以将该 IP 所对应的 Entry 从map 中删除了。若 List 中仍有元素,则将变化过的 List 重新再写入回map。
image.png
image.png

(4) 注册监听器

image.png

(5) 定义 index.jsp 页面

image.png

(6) 定义 logoutServlet

image.png

(7) 注册 logoutServlet

image.png

(8) 定义 message.jsp 页面

image.png

2.2.2 管理员踢除用户

论坛管理员对于一些不守规矩的登录用户可以进行踢除。这里要完成的就是这个功能。 这里有些细节需要注意:

  • 什么是用户已经登录?就是用户信息写入到了 Session 中。
  • 什么是对用户的踢除?就是使该用户信息所绑定的 Session 失效。
  • 若要完成这个踢除功能,管理员首先应该可以看到所有在线用户。那么在线用户信息就应该保存在一个集合中。
  • 这个集合既应该有用户信息,又应该有与用户信息绑定的 Session 对象。这样便于管理员获取到某一用户的 Session 后,将其失效。所以这个集合选用Map,key 为用户名(各站点都要求用户名是不能重复的,原因就是这个),value 为与该用户绑定的 Session。
  • 这个 Map 集合中的数据应该在什么时候放进去?只要发生 User 对象与 Session 的绑定操作,就说明有用户登录。此时就应将数据放入到 Map 中。也就是说,应该为实体类 User 实现 Session 绑定监听器 HttpSessionBindingListener。
  • 这个Map 集合应该什么时候创建?应该在应用启动时就创建,即在 ServletConetxt 被初始化时被创建。所以应该定义一个 ServletContext 监听器 ServletContextListener,在 ServletContext 被初始化时创建这个 Map 集合。

    (1) 定义 ServletContext 监听器

    image.png

    (2) 注册 ServletContext 监听器

    image.png

    (3) 定义实体类User

    只要发生 User 对象与 Session 的绑定操作,就说明有用户登录。此时就应将数据放入到 Map 中。也就是说,实体类 User 应实现 Session 绑定监听器 HttpSessionBindingListener。
    image.png
    image.png
    image.png

    (4) 定义用户登录页面 login.jsp

    image.png

    (5) 定义 LoginServlet

    用户提交登录表单后,马上与 Session 进行绑定,以便将信息写入到Map 中
    image.png

    (6) 创建 welcome.jsp

    image.png

    (7) 创建管理员页面 index.jsp

    image.png

    (8) 定义 kickServlet

    image.png