第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的通知后,根据自身实际情况,做出相应改变。
当然,观察者与被观察者指的都是具有某一类功能的对象,所以这里的观察者与被观察者都是指的接口,而真正的观察者对象与被观察者对象,是指实现了这些接口的类的对象。
项目:observePattern(Java Project)
1.2.1 定义观察者接口
1.2.2 定义被观察者接口
1.2.3 定义观察者
这里定义了两个观察者:1号与2号观察者。
1.2.4 定义被观察者
被观察者类除了要实现观察者接口IObservable外,还需要在类中声明并创建一个观察者集合,用于向其中添加观察者。
1.2.5 定义测试类
1.2.6 运行结果
1.3 监听器设计模式
监听器设计模式,是观察者设计模式的一种实现,它并不是23种设计模式之一。
这里的监听器实际对应的就是观察者,而被监听对象,则是指被观察者。当被监听对象的状态发生改变时,也需通知监听器,监听器在收到通知后会做出相应改变。
与观察者设计模式不同的是,被监听者的状态改变,被定义为了一个对象,称为事件;被监听对象有了个新的名子,称为事件源;对监听器的通知,称为触发监听器。其实质与观察者设计模式是相同的。
下面以对被监听者所执行的增删改查CURD操作进行监听为例,来演示监听器设计模式的用法。
1.3.1 定义事件接口
一般情况下,监听器对象被事件触发后,都是需要从事件中获取到事件源对象,然后再从事件源中获取一些数据。也就是说,在事件对象中一般是需要提供获取事件源对象的方法的。当然,除了获取事件源的方法外,根据业务需求,事件对象一般还需要提供一些其它数据,以便让监听器获取。
1.3.2 定义监听器接口
1.3.3 定义事件源接口
1.3.4 定义事件类
1.3.5 定义监听器
1.3.6 定义事件源
1.3.7 定义测试类
1.3.8 运行结果
1.3.9 再定义事件源
事件源是拥有自己的业务方法的,本例的业务方法为增删改查。应该是事件源对象在执行这些业务方法时触发监听器,而并非是前面测试类那么使用监听器。所以这里需要为事件源添加业务方法,在业务方法中触发监听器。
1.3.10 再定义测试类
第2章 监听器Listener
2.1 Servlet规范中的监听器
Servlet规范中已经定义好了八个监听器接口,它们要监听的对象分别是request、session、servletContext对象,触发监听器的事件是这三个对象的创建与销毁,它们的域属性空间中属性的添加、删除、修改,及session的钝化与活化操作。
在JavaWeb项目中使用监听器,需要在web.xml文件中对监听器进行注册。
下面分别对这八个监听器进行学习。
2.1.1 ServletRequestListener
该监听器用于完成对Request对象的创建及销毁的监听,即当Request对象被创建或被销毁时,会触发该监听器中相应方法的执行。
项目:requestListener
(1)定义并注册监听器
2.1.2 ServletRequestAttributeListener
该监听器用于完成对request域属性空间中属性的添加、修改、删除操作的监听。
项目:requestListener
(1)定义并注册监听器
注意ServletRequestAttributeEvent事件的方法getName()可以获取到被操作的属性名,方法getValue()可以获取到被操作的属性的值。
(2)定义index.jsp页面
2.1.3 HttpSessionListener
该监听器用于完成对Session对象的创建及销毁的监听。
项目:sessionListener
(1)定义并注册监听器
(2)定义index.jsp页面
2.1.4 HttpSessionAttributeListener
该监听器用于完成对session域属性空间中属性的添加、修改、删除操作的监听。
项目:sessionListener
(1)定义并注册监听器
(2)定义index.jsp页面
2.1.5 ServletContextListener
该监听器用于完成对ServletContext对象的创建及销毁的监听。不过需要注意,由于ServletContext在一个应用中只有一个,且是在服务器启动时创建。另外,ServletConetxt的生命周期与整个应用的相同,所以当项目重新部署,或Tomcat正常关闭(通过stop service关闭,不能是terminate关闭)时,可以销毁ServletContext。
项目:servletContextListener
(1)定义并注册监听器
2.1.6 ServletContextAttributeListener
(1)定义并注册监听器
(2)定义index.jsp页面
2.1.7 HttpSessionBindingListener
该监听器用于监听指定类型对象与Session的绑定与解绑,即该类型对象被放入到Session域中,或从Session域中删除该类型对象,均会引发该监听器中相应方法的执行。
它与HttpSessionAttributeListener的不同之处是,该监听器监听的是指定类型的对象在Session域中的操作,而HttpSessionAttributeListener监听的是 Session域属性空间的变化,无论是什么类型的对象。
另外,需要强调两点:①该监听器是由实体类实现;②该监听器无需在web.xml中注册。
项目:sessionBindingListener
(1)定义实现监听器接口的实体类
(2)定义index.jsp页面
2.1.8 HttpSessionActivationListener
该监听器用于监听在Session中存放的指定类型对象的钝化与活化。
钝化是指将内存中的数据写入到硬盘中,而活化是指将硬盘中的数据恢复到内存。当用户正在访问的应用或该应用所在的服务器由于种种原因被停掉,然后在短时间内又重启,此时用户在访问时Session中的数据是不能丢掉的,在应用关闭之前,需要将数据写入到硬盘,在重启后应可以立即重新恢复Session中的数据。这就称为Session的钝化与活化。
那么Session中的哪些数据能够钝化呢?只有存放在JVM堆内存中的实现了Serializable类的对象能够被钝化。也就是说,对于字符串常量、基本数据类型常量等存放在JVM方法区中常量池中的常量,是无法被钝化的。
对于监听Session中对象数据的钝化与活化,需要注意以下几点:
● 实体类除了要实现HttpSessionActivationListener接口外,还需要实现Serializable接口。
● 钝化指的是Session中对象数据的钝化,并非是Session的钝化。所以Session中有几个可以钝化的对象,就会发生几次钝化。
● HttpSessionActivationListener监听器是不需要在web.xml中注册的。
(1)定义实现监听器接口的实体类
(2)定义index.jsp页面
(3)访问该项目
启动服务器后,在浏览器地址栏输入:http://localhost:8080/HttpSessionActivationListener/
然后正常关闭服务器,控制台输出
当重新启动服务器时,控制台输出
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中删除。
项目:clientCount
(1)定义ServletContext监听器
在ServletContext初始化时创建用于存放IP信息的Map集合,并将创建好的Map存放到ServletContext域中。
Map的key为客户端IP,而value则为该客户端ip所发出的会话对象组成的List。
(2)定义Request监听器
Request监听器主要完成以下功能:
● 获取当前请求的客户端的IP。
● 获取当前IP所对应的全局域中的List。若这个List为空,则创建一个List。
● 将当前IP放入到List中,并将当前IP与List写入到Map中,然后再重新将Map写回ServletContext中。
● 将当前IP存放到当前请求所对应的Session域中,以备在Session销毁时使用。
(3)定义Session监听器
该监听器的功能主要是,当Session被销毁时,将当前Session对象从List中删除。在从List删除后,若List中没有了元素,则说明这个IP所发出的会话已全部关闭,则可以将该IP所对应的Entry从map中删除了。若List中仍有元素,则将变化过的List重新再写入回map。
- 注册监听器
(5)定义index.jsp页面
(6)定义并注册LogoutServlet
(7)定义message.jsp页面
2.2.2 管理员踢除用户
论坛管理员对于一些不守规矩的登录用户可以进行踢除。这里要完成的就是这个功能。这里有些细节需要注意:
● 什么是用户已经登录?就是用户信息写入到了Session中。
● 什么是对用户的踢除?就是使该用户信息所绑定的Session失效。
● 若要完成这个踢除功能,管理员首先应该可以看到所有在线用户。那么在线用户信息就应该保存在一个集合中。
● 这个集合既应该有用户信息,又应该有与用户信息绑定的Session对象。这样便于管理员获取到某一用户的Session后,将其失效。所以这个集合选用Map,key 为用户名(各站点都要求用户名是不能重复的,原因就是这个),value为与该用户绑定的Session。
● 这个Map集合中的数据应该在什么时候放进去?只要发生User对象与Session的绑定操作,就说明有用户登录。此时就应将数据放入到Map中。也就是说,应该为实体类User实现Session绑定监听器HttpSessionBindingListener。
● 这个Map集合应该什么时候创建?应该在应用启动时就创建,即在ServletConetxt被初始化时被创建。所以应该定义一个ServletContext监听器ServletContextListener,在ServletContext被初始化时创建这个Map集合。
项目:kickUsers
(1)定义用户登录页面index.jsp
(2)定义实体类User
只要发生User对象与Session的绑定操作,就说明有用户登录。此时就应将数据放入到Map中。也就是说,实体类User应实现Session绑定监听器HttpSessionBindingListener。
(3)定义并注册ServletContext监听器
(4)定义并注册LoginServlet
用户提交登录表单后,马上与Session进行绑定,以便将信息写入到Map中。
(5)定义welcome.jsp页面
(6)定义用户列表页面userList.jsp
这其中用到了显示序号、隔行着色。而这些功能的完成,是由的varStatus属性配合着完成的。
(7)定义并注册DeleteServlet
这里需要注意的是,当将当前user的Session失效后,还需要将当前user对应的Entry对象从Map中删除,否则,userList页面的列表中仍然会显示这个用户。因为Map仍存在,只不过该session失效而已。
将指定用户踢除后,重新返回到userList页面,显示更新过的列表。