Java Web基础知识之Listener:监控Servlet的每个动作
之前学习了很多涉及servlet的内容,本小结我们说一下监听器,说起监听器,编过桌面程序和手机App的都不陌生,常见的套路都是拖一个控件,然后给它绑定一个监听器,即可以对该对象的事件进行监听以便发生响应,从本质上来说这些都是观察者模式的具体实现,在web程序中的监听器也不例外。
在Java Web程序中使用监听器可以通过以下两种方法:
- 通过注解@WebListener来标识一个自定义的监听器;
- @WebListener
- public class CustomListener implements Listener {
- }
- 通过在web.xml中配置来使用监听器;
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
在Java Web程序中,主要是对ServletContext、HttpSession和ServletRequest三个对象提供支持。
一、 ServletContext监听器
有两类监听器用来监听ServletContext对象:
- ServletContextListener:该监听器用来监听ServletContext的初始化和销毁事件;
- ServletContextAttributeListener:该监听器用来监听ServletContext中属性的删除、添加和替换事件;
实例如下:
- @WebListener
- public class MyServletListener implements ServletContextListener, ServletContextAttributeListener {
- public void contextInitialized(ServletContextEvent sce) {
- System.out.println("ServletContextEvent source: " + sce.getSource());
- System.out.println("context created");
- }
- public void contextDestroyed(ServletContextEvent sce) {
- System.out.println("ServletContextEvent source: " + sce.getSource());
- System.out.println("context destroyed");
- }
- public void attributeAdded(ServletContextAttributeEvent event) {
- System.out.println("ServletContextAttributeEvent source: " + event.getSource());
- System.out.println("attribute added: " + event.getName() + "--" + event.getValue());
- }
- public void attributeRemoved(ServletContextAttributeEvent event) {
- System.out.println("ServletContextAttributeEvent source: " + event.getSource());
- System.out.println("attribute removed: "+ event.getName() + "--" + event.getValue());
- }
- public void attributeReplaced(ServletContextAttributeEvent event) {
- System.out.println("ServletContextAttributeEvent source: " + event.getSource());
- System.out.println("attribute replaced: "+ event.getName() + "--" + event.getValue());
- }
- }
该类实现了上述的两个接口,启动Servlet容器,分析结果,输出如下:
可以看到在启动的过程中首先创建了ServletContext,这激发了contextInitialized监听器方法,然后调用了每一个Servlet的init()方法对每一个Servlet进行初始化,对所有的Servlet(这里有两个自定义Servlet,MyServlet,TestServlet)初始化完成后则会向ServletContext添加一个属性,不管是事件对象还是添加的属性都是Servlet容器提供的。
当关闭Servlet容器的时候,分析结果,输出如下:
首先会调用所有Servlet的destroy方法,然后销毁ServletContext实例。
在对属性进行增加、替换、删除时,会调用相应的监听器,代码如下:
- ServletContext context = req.getServletContext();
- context.setAttribute("country", "zn");
- context.setAttribute("country", "en");
- context.removeAttribute("country");
代码输出:
这里要注意的是:当替换属性时也是使用setAttribute(),只不过是key的值是相同的。当替换属性后,监听器得到的值是旧值(我觉得旧值和新值都应提供相应API)。
二、 HttpSession监听器
有四类监听器用来监听HttpSession对象:
- HttpSessionListener:用来监听HttpSession的创建和销毁;
- HttpSessionActivationListener:这个监听器是在分布式环境下使用的,当HttpSession实例要在不同的虚拟机之间进行迁移或者序列化时则会用到这个类,当HttpSession对象被迁移到新的虚拟机上时或者反序列化时则会调用sessionDidActivate()方法,当HttpSession实例从旧的虚拟机迁出或者序列化时则会调用sessionWillPassivate()方法;
- HttpSessionAttributeListener:用来监听HttpSession中属性的删除、增加和替换事件;
- HttpSessionBindingListener:
其实在新出来的Servlet3.1规范中还有一个Session监听器,javax.servlet.http.HttpSessionIdListener用于监听SessionId的改变,由于我用的是Tomcat7还不支持Servlet3.1所以会找不到这个类,所以这里就没写,如果有兴趣的可以自己尝试一下。
1、 HttpSessionListener和HttpSessionAttributeListener:
- @WebListener
- public class MyHttpSessionListener implements HttpSessionListener, HttpSessionAttributeListener {
- private Long startTime;
- public void attributeAdded(HttpSessionBindingEvent event) {
- startTime = (Long) event.getSession().getAttribute("startTime");
- System.out.println("HttpSessionBindingEvent source: " + event.getSource());
- System.out.println("add attribute: " + event.getName() + "--" + event.getValue());
- }
- public void attributeRemoved(HttpSessionBindingEvent event) {
- System.out.println("HttpSessionBindingEvent source: " + event.getSource());
- System.out.println("remove attribute: " + event.getName() + "--" + event.getValue());
- }
- public void attributeReplaced(HttpSessionBindingEvent event) {
- System.out.println("HttpSessionBindingEvent source: " + event.getSource());
- System.out.println("replace attribute: " + event.getName() + "--" + event.getValue());
- }
- public void sessionCreated(HttpSessionEvent se) {
- System.out.println("HttpSessionEvent source: " + se.getSource());
- System.out.println("session id: " + se.getSession().getId());
- System.out.println("session create");
- }
- public void sessionDestroyed(HttpSessionEvent se) {
- System.out.println("HttpSessionEvent source: " + se.getSource());
- System.out.println("session id: " + se.getSession().getId());
- System.out.println("active time: " + (System.nanoTime() - startTime));
- System.out.println("session destroy");
- }
- }
对应的Servlet如下:
- @WebServlet(name="myHttpSessionServlet", urlPatterns="/myHttpSessionServlet", loadOnStartup=1)
- public class MyHttpSessionServlet extends HttpServlet {
- private static final long serialVersionUID = 5687825632187950599L;
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- HttpSession session = req.getSession();
- session.setAttribute("startTime", System.nanoTime());
- session.setAttribute("visitTimes", 0);
- session.setAttribute("visitTimes", 1);
- session.removeAttribute("visitTimes");
- session.setMaxInactiveInterval(10);
- resp.getWriter().write("session listene demo");
- }
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- }
- }
可以看出第一次首先创建HttpSession,调用了sessionCreated()方法。之后是添加方法时触发了对应的属性监听器,替换属性时还是监听器返回的替换前的值。
由于设置了存活时间,之后会触发销毁Session的方法,输出如下:
由于设置了存活时间,之后会触发销毁Session的方法,输出如下:
从中可见,Session销毁时不是只是调用sessionDestroyed()方法,在执行完这个方法之后,还会调用attributeRemoved()方法,将其中保存的所有属性全部清除。
2、 HttpSessionActivationListener
该监听器就像上面说过的一样,是在分布式环境下或者当要序列HttpSession实例时才起作用。
- 由于Session是存储在内存中的,所以有时候服务器为了节省内存,会把访问较少的Session 持久化到存储设备中,包括文件系统和数据库,这样就能节省内存空间,另一方面也告诉我们不要在Session中放过多的数据,以免造成服务器的负担;
- 当在分布式环境下时,如果一台机器发生宕机不能访问,可以将Session序列化进行传输到别的服务器中的JVM中进行工作,这其中就要求存储在Session中对数据要可以序列化,这时候是会使用到这个监听器的;
- 对Session进行序列化一般是Servlet容器进行的,我们可以进行配置;
ERROR,错误使用:
- HttpSession session = req.getSession();
- session.setAttribute("startTime", System.nanoTime());
- session.setAttribute("visitTimes", 0);
- session.setAttribute("visitTimes", 1);
- session.removeAttribute("visitTimes");
- ObjectOutputStream output =new ObjectOutputStream(new FileOutputStream("C:\\demo"));
- output.writeObject(session);
至于Session为什么序列化,这就很明显了,如果服务器宕机,客户访问服务器的所有信息因为存在于内存中,所以也就全部丢失了,将Session序列化到存储设备之后可以在服务器恢复运行之后将用户信息也同时恢复。
3、 HttpSessionBindingListener
首先这个监听器的作用和之前的HttpSessionAttributeListener很像,使用起来也很类似,但是它们有如下区别:
- HttpSessionAttributeListener要进行注册,或者通过注解或者通过web.xml,它是用来监听整个应用程序中的所有的Session实例,和所有Session实例对应的属性,属于一个通用的监听器;
- HttpSessionBindingListener则是针对于一个特定的Session和一个特定的属性的监听器,是量身定制的,并且它也不需要进行注册;
使用实例如下:
- public class Person implements Serializable, HttpSessionBindingListener{
- private static final long serialVersionUID = -4972586177179694554L;
- private String name;
- private Integer age;
- public Person(){}
- public Person(String name, Integer age) {
- super();
- this.name = name;
- this.age = age;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public Integer getAge() {
- return age;
- }
- public void setAge(Integer age) {
- this.age = age;
- }
- @Override
- public String toString() {
- return "Person [name=" + name + ", age=" + age + "]";
- }
- public void valueBound(HttpSessionBindingEvent event) {
- System.out.println("person bound");
- }
- public void valueUnbound(HttpSessionBindingEvent event) {
- System.out.println("person unbound");
- }
- session.setAttribute("person", new Person("lmy", 23));
- session.setMaxInactiveInterval(10);
- HttpSessionBindingEvent source: org.apache.catalina.session.StandardSessionFacade@fdfaab5
- person bound
- HttpSessionBindingEvent source: org.apache.catalina.session.StandardSessionFacade@fdfaab5
- person unbound
三、 ServletRequest监听器
有三类监听器用来监听ServletRequest对象:
- ServletRequestListener:用来监听ServletRequest的创建和销毁,注意Servlet容器是通过池来重用ServletRequest的,创建一个ServletRequest实例其实就是从池中取出一个实例,销毁该对象也就是将该实例放回池中;
- ServletRequestAttributeListener:用来监听ServletRequest中属性的删除、增加和替换事件;
- AsyncListener:这个后面提到异步编程时再详细说;
监听器代码如下:
- @WebListener
- public class MyServletRequestListener implements ServletRequestListener, ServletRequestAttributeListener {
- public void attributeAdded(ServletRequestAttributeEvent srae) {
- System.out.println("ServletRequestAttributeEvent source: " + srae.getSource());
- System.out.println("request attribute added: " + srae.getName() + "--" + srae.getValue());
- }
- public void attributeRemoved(ServletRequestAttributeEvent srae) {
- System.out.println("ServletRequestAttributeEvent source: " + srae.getSource());
- System.out.println("request attribute removed: " + srae.getName() + "--" + srae.getValue());
- }
- public void attributeReplaced(ServletRequestAttributeEvent srae) {
- System.out.println("ServletRequestAttributeEvent source: " + srae.getSource());
- System.out.println("request attribute replaced: " + srae.getName() + "--" + srae.getValue());
- }
- public void requestDestroyed(ServletRequestEvent sre) {
- System.out.println("ServletRequestEvent source: " + sre.getSource());
- System.out.println("request destroy");
- }
- public void requestInitialized(ServletRequestEvent sre) {
- System.out.println("ServletRequestEvent source: " + sre.getSource());
- System.out.println("request init");
- }
- }
MyHttpServlet1类接受请求代码:
- </pre><pre name="code" class="java">@Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- System.out.println("MyHttpServlet1 request work");
- <span style="white-space:pre"> </span>System.out.println("current thread :" + Thread.currentThread().getName());
- }
- </pre><div>MyHttpServlet类接受请求代码:</div><pre name="code" class="java"><pre name="code" class="java">@Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- System.out.println("MyHttpServlet request work");
- <span style="white-space:pre"> </span>System.out.println("current thread :" + Thread.currentThread().getName());
- }
可以看出接收一个请求后首先初始化ServletRequest对象,调用requestInitialized()方法,之后会发现
进行了属性的替换,使能异步处理,在之后进行处理请求的具体工作,最后销毁该请求对象。
向MyHttpServlet1发送一个请求输出如下:
处理的流程和向MyHttpServlet发送请求相同但是看出来请求的
处理线程是不同的。
在请求的过程中添加属性和ServletContext中的对属性的监听器是类似的:
- req.setAttribute("area", "zh");
- req.setAttribute("area", "ts");
- req.removeAttribute("area");
属性替换时也是返回的替换之前的值,和ServletContext中属性的替换是相同的。
相关文章:
- How are Threads allocated to handle Servlet request?
- Java序列化(一)
- HttpSessionActivationListener Example Use Case
- 理解Java对象序列化
- java tomcat实现Session对象的持久化原理及配置方法介绍
- Using Sessions and Session Persistence
- Practical Usage of HttpSessionBindingListener And HttpSessionAttributeListener
- What is difference between HttpSessionAttributeListener and HttpSessionBindingListener?