这是我参与8月更文挑战的第26天,活动详情查看:8月更文挑战
详细介绍了Java Web Servlet中的Listener监听器的原理以及常见用法。
此前我们学了Java Web中的Filter过滤器的原理以及常见用法:Java Web(11)—Filter过滤器的原理以及用法,现在我们来学习Java Web中的Listener监听器的原理以及用法。
Servlet、Filter、Listener被称为Java Web的三大组件!
1 监听器的概述
监听器就是一个java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即被执行。
详细的说,就是:监听器用于监听观察某个事件(程序)的发生情况,当被监听的事件真的发生了的时候,事件发生者(事件源) 就会给注册该事件的监听者(监听器)发送消息,告诉监听者某些信息,同时监听者也可以获得一份事件对象,根据这个对象可以获得相关属性和执行相关操作。
监听器可以看成是观察者模式的一种实现。监听器程序中有四种角色:
监听器(监听者):负责监听发生在事件源上的事件,它能够注册在对应的事件源上,当事件发生后会触发对应的处理方法(事件处理器)的执行。事件源(被监听对象,可以产生某些事件的对象):提供订阅与取消监听器的方法,并负责维护监听器列表,以及发送对应的事件给对应的监听器。事件对象:事件源发生某个动作时,比如某个增、删、改、查的动作,将该动作将封装为一个事件对象,并且事件源在通知事件监听器时会把这个事件对象传递过去。事件处理器:可以是作为监听器的成员方法,也可以独立出来注册到监听器中,当监听器接受到对应的事件时,将会调用对应的方法,或者事件处理器来处理该事件!
2 Servlet监听器
Servlet中同样存在监听器程序,下面简单的介绍Servlet的的监听器体系!
2.1 事件源
在Servlet规范中定义了多种类型的监听器,它们用于监听的事件源分别是ServletContext,HttpSession和ServletRequest这三个域对象。
2.2 生命周期监听器
生命周期监听器专门用于监听域对象的创建和销毁事件,主要有三个ServletContextListener、HttpSessionListener、ServletRequestListener。
ServletContextListener:Tomcat启动和关闭时调用下面两个方法default public void contextInitialized(ServletContextEvent evt):ServletContext对象被创建后调用;default public void contextDestroyed(ServletContextEvent evt):ServletContext对象被销毁前调用;
HttpSessionListener:开始会话和结束会话时调用下面两个方法default public void sessionCreated(HttpSessionEvent evt):HttpSession对象被创建后调用;default public void sessionDestroyed(HttpSessionEvent evt):HttpSession对象被销毁前调用;
ServletRequestListener:开始请求和结束请求时调用下面两个方法default public void requestInitiallized(ServletRequestEvent evt):ServletRequest对象被创建后调用;default public void requestDestroyed(ServletRequestEvent evt):ServletRequest对象被销毁前调用。
2.2.1 生命周期事件
当监听域对象触发了生命周期事件时,将会被封装为ServletContextEvent、HttpSeessionEvent、ServletRequestEvent事件。
ServletContextEvent:具有getServletContext()方法可以获取ServletContext;HttpSeessionEvent:具有getSession()方法可以获取HttpSession;ServletRequestEvent:具有getServletRequest()方法可以获取ServletRequest和getServletContext()方法可以获取ServletContext;
2.3 属性监听器
属性监听器专门用于监听域对象的域属性的增、删、改事件,主要有三个:ServletContextAttributeListener、HttpSessionAttributeListener、ServletRequestAttributeListener。
ServletContextAttributeListener:在ServletContext域进行增、删、改属性时调用下面方法。- default public void attributeAdded(ServletContextAttributeEvent evt):添加域属性
- default public void attributeRemoved(ServletContextAttributeEvent evt) :删除域属性
- default public void attributeReplaced(ServletContextAttributeEvent evt) :修改域属性
HttpSessionAttributeListener:在HttpSession域进行增、删、改属性时调用下面方法- default public void attributeAdded(HttpSessionBindingEvent evt) :添加域属性
- default public void attributeRemoved (HttpSessionBindingEvent evt) :删除域属性
- default public void attributeReplaced (HttpSessionBindingEvent evt) :修改域属性
ServletRequestAttributeListener:在ServletRequest域进行增、删、改属性时调用下面方法- default public void attributeAdded(ServletRequestAttributeEvent evt) :添加域属性
- default public void attributeRemoved (ServletRequestAttributeEvent evt) :删除域属性
- default public void attributeReplaced (ServletRequestAttributeEvent evt) :修改域属性
3 Listener的简单使用
Servlet中监听器的使用很简单,开发人员只需要实现对应的监听器接口,并且只需在web.xml文件中使用<listener>标签或者使用@WebListener注解配置好实现的监听器,由Web容器在Web应用启动时负责统一注册,在时间触发时,将会自动回调监听器的方法。
新建一个项目listener,下面是三个自定义的监听器:
public class MyHttpSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("Session创建: " + se.getSession());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("Session销毁: " + se.getSession());
}
}
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContext创建: " + sce.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContext销毁: " + sce.getServletContext());
}
}
public class MyServletRequestListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("ServletRequest创建: " + sre.getServletRequest());
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("ServletRequest销毁: " + sre.getServletRequest());
}
}
定义了监听器之后,虽然不需要我们手动注册到事件源中,但是仍然需要告诉Web容器,到底有哪些监听器,我们可以使用传统的web.xml配置。
基于web.xml的配置:
<!--部署监听器-->
<listener>
<listener-class>com.example.listener.MyHttpSessionListener</listener-class>
</listener>
<listener>
<listener-class>com.example.listener.MyServletContextListener</listener-class>
</listener>
<listener>
<listener-class>com.example.listener.MyServletRequestListener</listener-class>
</listener>
在Servlet 3.0之后还可以直接在实现类上使用@WebListener注解,非常方便!
在webapp下准备一个index.html:
<html>
<head>
<title>listener</title>
</head>
<body>
<h1> Hello</h1>
</body>
</html>
准备两个Servlet,用于测试Session:
@WebServlet("/hello-servlet")
public class HelloServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("hello");
out.close();
}
}
@WebServlet("/session-servlet")
public class SessionServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
//获取Session,将会创建Session
request.getSession();
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("session");
out.close();
}
}
启动项目,可以看到ServletContext创建的事件被触发:
访问index.html,可以看到ServletRequest的创建和销毁,并且每发起一个请求都会有ServletRequest的创建和销毁事件被触发:
访问/hello-servlet没有获取session的Servlet,可以看到ServletRequest的创建和销毁,并且每发起一个请求都会有ServletRequest的创建和销毁事件被触发:
多次调用请求,我们还会发现,实际上ServletRequest的“创建”和“销毁”事件被触发时,可能并没有一个真正的ServletRequest对象被创建和销毁,这里的“创建”和“销毁”事件仅仅是为了表示一个请求已经开始处理以及处理完毕。实际上这里的request是org.apache.catalina.connector.Request对象,是由tomcat服务器创建的,可以被tomcat复用。
在访问/hello-servlet时,并没有session被创建,我们接着通过 调用/session-servlet访问可以获取session的Servlet,可以看到Session和ServletRequest的创建和销毁:
这里的Session,就是代码中的request.getSession()方法触发创建的,当我们再次访问这个链接时:
此时,Session并没有创建,因为处于同一个会话中,我们在另一个浏览器打开,或者将当前浏览器的cookie清理(主要是JSESSIONID的Cookie清理掉),再次访问该链接:
我们发现,此时又创建了Session,因为请求头中没有JSESSIONID,那么服务器就把这次请求当作是一个新的会话,从而创建新的Session。
等待大约一分钟之后,由于Session的超时时间到了,控制台将会自动输出Session销毁的信息:
当我们再次访问该链接时,虽然携带了JSESSIONID,但是由于对应的Session已经因为超时而被销毁了,此时又会创建新的Session对象:
此时我们直接关闭tomcat服务器,将会看到ServletContext销毁的信息:
注意,如果关闭服务器,是不会触发Session销毁事件的!
4 HttpSession的监听器
还有两个与HttpSession相关的特殊的监听器,HttpSessionBindingListener和HttpSessionActivationListener这两个监听器的特点如下:
- 不用在web.xml文件中或者通过注解部署;
- 这两个监听器不是给Session添加,而是给Bean添加。即让Bean类实现监听器接口,然后再把Bean对象添加到Session域中;
或许这两个接口可以看作早期的、原始的Spring的Aware感知接口!
4.1 HttpSessionBindingListener
当某个类实现了该接口后,可以感知本类对象(Aware,即感知自己)被添加到Session中,以及感知从session中移除(包括Session销毁时的移除)。 例如让Person类实现HttpSessionBindingListener接口,那么当把Person对象添加到Session中,或者把Person对象从Session中移除时会调用下面两个方法:
default public void valueBound(HttpSessionBindingEvent event):当把监听器对象添加到Session中会调用监听器对象的本方法;default public void valueUnbound(HttpSessionBindingEvent event):当把监听器对象从Session中移除时会调用监听器对象的本方法;
这里要注意,HttpSessionBindingListener监听器的使用与前面介绍的都不相同,当该监听器的实现类对象添加到Session中,或把该监听器对象从Session移除时才会调用监听器中的方法(包括Session销毁时的移除),并且无需在web.xml文件中部署这个监听器。
下面简单测试一下!
编写Person类,让其实现HttpSessionBindingListener监听器接口:
/**
* @author lx
*/
public class Person implements HttpSessionBindingListener {
/*
* HttpSessionBindingListener的方法
*/
@Override
public void valueBound(HttpSessionBindingEvent evt) {
System.out.println("把Person对象存放到session中:" + evt.getValue());
}
@Override
public void valueUnbound(HttpSessionBindingEvent evt) {
System.out.println("从session中移除Person对象:" + evt.getValue());
}
/*
* Person的属性
*/
private String name;
private int age;
private String sex;
public Person(String name, int age, String sex) {
super();
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
}
编写SessionBindingServlet类,一个方法向session中添加Person对象,另一个从session中移除Person对象,通过name参数是否等于add来区分:
/**
1. @author lx
*/
@WebServlet("/SessionBindingServlet")
public class SessionBindingServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
if ("add".equals(name)) {
addPerson(req, resp);
} else {
removePerson(req, resp);
}
}
private void addPerson(HttpServletRequest request, HttpServletResponse response) {
//创建Person并且存入Session中
Person p = new Person("zhangSan", 23, "male");
request.getSession().setAttribute("person", p);
}
private void removePerson(HttpServletRequest request, HttpServletResponse response) {
//从Session中移除Person对象
request.getSession().removeAttribute("person");
}
}
首先我们通过调用/SessionBindingServlet?name=add尝试向session中添加Person,可以发现Session属性绑定事件被触发了,也就是Person的valueBound方法被执行了!
接着通过调用/SessionBindingServlet?name=del尝试向session中移除Person,可以发现Session属性移除事件被触发了,也就是Person的valueUnbound方法被执行了!
如果我们再次尝试执行移除,那么我们会发现由于这个属性已经被移除了,那么属性解绑事件也就不会被触发了。
如果我们的属性没有被移除,并且Sesssion超时了,那么在Session移除事件触发之后,将会触发该Sesssion中的属性的解绑事件(如果可以):
4.2 HttpSessionActivationListener
Tomcat会在session时间不被使用时钝化session对象,所谓钝化session,就是把session通过序列化的方式保存到硬盘文件中。当用户再使用session时,Tomcat还会把钝化的对象再活化session,所谓活化就是把硬盘文件中的session在反序列化回内存。当session被Tomcat钝化时,session中存储的对象和属性也被纯化,当session被活化时,也会把session中存储的对象活化。
如果某个类实现了HttpSessionActivationListener接口后并且被加入到Session中之后,该类对象可以感知到自己随着session被钝化和活化的事件,并且分别调用该对象的以下两个方法:
public void sessionWillPassivate(HttpSessionEvent se):当对象感知到Session被活化时调用本方法;public void sessionDidActivate(HttpSessionEvent se):当对象感知到Session被钝化时调用本方法;
HttpSessionActivationListener监听器与HttpSessionBindingListener监听器相似,都是感知型的监听器,例如让Person类实现了HttpSessionActivationListener监听器接口,并把Person对象添加到了session中后,当Tomcat钝化session时,同时也会钝化session中的Person对象,这时Person对象就会感知到自己被钝化了,其实就是调用Person对象的sessionWillPassivate()方法。当用户再次使用session时,Tomcat会活化session,这时Person会感知到自己被活化,其实就是调用Person对象的sessionDidActivate()方法。
注意,因为钝化和活化session,其实就是使用序列化和反序列化技术把session从内存保存到硬盘,和把session从硬盘加载到内存。这说明如果Person类没有实现Serializable接口,那么当session钝化时就不会钝化Person,而是把Person从session中移除再钝化!这也说明session活化后,session中就不在有Person对象了。
由于不能钝化而被移除的时候,如果属性对象实现了HttpSessionBindingListener接口,那么该对象同样会触发valueUnbound事件。另外,Session活化的时候,将会创建新的Session对象,因此会触发Session创建事件。另外,Session钝化并不会影响Session的超时时间,时间到了,Session还是会被销毁。
Session的钝化,需要修改的配置文件,我们在项目的webapp下新增的META-INF目录,并且新增的context.xml文件:
在context.xml文件中添加下面的配置:
<Context>
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="listener"/>
</Manager>
</Context>
创建Animal类,让Animal类实现HttpSessionActivationListener和Serializable接口:
/**
* @author lx
*/
public class Animal implements HttpSessionActivationListener, Serializable {
/*
* HttpSessionActivationListener的方法
*/
@Override
public void sessionDidActivate(HttpSessionEvent evt) {
System.out.println("session已经活化: " + evt.getSession());
}
@Override
public void sessionWillPassivate(HttpSessionEvent evt) {
System.out.println("session被钝化: " + evt.getSession());
}
/*
* Animal的属性
*/
private String name;
private int age;
private String sex;
public Animal(String name, int age, String sex) {
super();
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Animal [name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
}
创建SessionActivationServlet用于向Session中添加Animal并观察钝化、活化现象:
@WebServlet("/SessionActivationServlet")
public class SessionActivationServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
String name = req.getParameter("name");
if ("add".equals(name)) {
addAnimal(req, resp);
} else if ("del".equals(name)) {
removeAnimal(req, resp);
} else if ("get".equals(name)) {
getSession(req, resp);
}
}
private void addAnimal(HttpServletRequest request, HttpServletResponse response) {
//创建Animal并且存入Session中
Animal animal = new Animal("Animal", 12, "male");
request.getSession().setAttribute("animal", animal);
}
private void getSession(HttpServletRequest request, HttpServletResponse response) {
//获取Session
request.getSession();
}
private void removeAnimal(HttpServletRequest request, HttpServletResponse response) {
//从Session中移除removeAnimal对象
request.getSession().removeAttribute("animal");
}
}
添加Animal到Session中之后,等待一分钟,这时session会被钝化(Session超时时间可以设置为2分钟),也就会调用Anmal的sessionWillPassivate()方法,访问这个Session这会使session活化,会调用Anmal的sessionDidActivate()方法。
5 监听器的应用
监听器机制通常可以实现网站在线人数统计、监听用户的行为(管理员踢人)等功能。当然监听器实际上能做很多事情,比如用于实现业务逻辑解耦,这在Spring中由被称为“事件发布机制”,并且Spring框架提供了更加完善的监听器机制供我们直接使用。
5.1 在线人数统计
在线人数使用HttpSessionListener监听器统计,每当一个session会话建立 在线用户人数+1,每当一个session会话销毁 在线用户人数-1,在线人数的数据采用Context域对象保存。
监听器:
/**
* @author lx
*/
@WebListener
public class CountListener implements HttpSessionListener {
@Override
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);
}
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
ServletContext context = se.getSession().getServletContext();
Integer num = (Integer) se.getSession().getAttribute("num");
context.setAttribute("num", --num);
}
}
人数统计Servlet:
@WebServlet("/CountServlet")
public class CountServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.getSession();
ServletContext servletContext = getServletContext();
Object num = servletContext.getAttribute("num");
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println(num);
}
}
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!