Java Servlet学习笔记——8. 监听器的实例

写在前面

这几天学习有点懈怠,或者说这段时间都有所懈怠。还是要自制。这一章列举几个例子,说明一下。

ServletContextListener实例

创建一个AppListener类实现了ServletContextListener 接口, 它在ServletContext刚创建时,通过函数contextInitialized(ServletContextEvent sce), 将一个保存国家编码和国家名的Map放置到ServletContext中。代码如下:

package app08.listener;

import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class AppListener implements ServletContextListener {

    @Override
    public void contextDestroyed(ServletContextEvent sce) {} 

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext servletContext = sce.getServletContext();
        Map<String, String> countries = new HashMap<String, String>();
        countries.put("ca", "Canada");
        countries.put("us", "United States");
        servletContext.setAttribute("countries", countries);
    }
}

编写JSP代码来测试,代码如下:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Country List</title>
</head>
<body>
We operate in these countries:
<ul>
    <c:forEach items="${countries}" var="country">
        <li>${country.value}</li>
    </c:forEach>
</ul>
</body>
</html>

countries.jsp页面使用了JSTL的forEach标签来迭代地读取名为countries的map里的数据。 因此需要在app08应用的WEB-INF/lib路径下加入JSTL的相关库(jstl-1.2.jar、standard-1.1.2.jar)才能够运行。

HttpSessionListener的实例

创建一个SessionListener类。 这个监听器来统计HttpSession的数量。 它使用了一个AtomicInteger对象来统计, 并且将这个对象保存成ServletContext范围的属性。 每当有一个HttpSession被创建时, 这个AtomicInteger对象就会加一。 每当有一个HttpSession被销毁时, 这个AtomicInteger对象就会减一。 所以这个对象会保存着当前存活的HttpSession数量。 这里使用了AtomicInteger 来代替Integer类型是为了保证能同步进行加减的操作。

AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。AtomicInteger提供原子操作来进行Integer的使用,因此十分适合高并发情况下的使用。

SessionListener类实现了ServletContextListener和HttpSessionListener接口。 所以需要实现这两个接口的所有方法。其中继承自ServletContextListener接口的contextInitialized方法创建了一个AtomicInteger对象并将其保存在ServletContext属性中。 由于是在应用启动的时候创建, 因此这个AtomicInteger对象的初始值为0。 这个ServletContext属性的名字为userCounter。

package app08.listener;

import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

@WebListener
public class SessionListener implements HttpSessionListener, ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext servletContext = sce.getServletContext();
        servletContext.setAttribute("userCounter", new AtomicInteger());
    } 
    @Override
    public void contextDestroyed(ServletContextEvent sce) {} 

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        HttpSession session = se.getSession();ServletContext servletContext = session.getServletContext();
        AtomicInteger userCounter = (AtomicInteger) servletContext.getAttribute("userCounter");
        int userCount = userCounter.incrementAndGet();
        System.out.println("userCount incremented to :" + userCount);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        ServletContext servletContext = session.getServletContext();
        AtomicInteger userCounter = (AtomicInteger) servletContext.getAttribute("userCounter");
        int userCount = userCounter.decrementAndGet();
        System.out.println("---------- userCount decremented to:"+ userCount);
    }
}

第一次访问时, 控制台会打印如下信息:

userCount incremented to :1

用同一个浏览器再次访问这个URL并不会改变userCounter, 因为这属于同一个HttpSession。 使用不同的浏览器访问才能增加userCounter的值。

如果你有时间等待HttpSession过期的话, 在控制台也能看到HttpSession销毁时打印的信息。

HttpSessionBindingListener的实例

当有属性绑定或者解绑到HttpSession上时,HttpSessionBindingListener 监听器会被调用。 如果对HttpSession属性的绑定和解绑动作感兴趣, 就可以实现HttpSessionBindingListener 来监听。 例如可以在HttpSession属性绑定时更新状态, 或者在属性解绑时释放资源。

下面实现一个Product类,这个监听器会在HttpSession属性绑定和解绑时在控制台打印信息。代码如下:

package app08.model;

import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

public class Product implements HttpSessionBindingListener {
    private String id;
    private String name;
    private double price;

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getPrice() {
        return price;
    }
    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        String attributeName = event.getName();
        System.out.println(attributeName + " valueBound");
    }

    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        String attributeName = event.getName();
        System.out.println(attributeName + " valueUnbound");
    }
}

ServletRequestListener的实例

PerfStatListener实现了ServletRequestListener接口, 来计算每个HTTP请求的完成时间。 由于容器在请求创建时会调用ServletRequestListener的requestInitialized方法, 在销毁时会调用requestDestroyed, 因此很容易就可以计算出时间。 只需要在记录下两个事件的事件, 并且相减, 就可以计算出一次HTTP请求的完成时间了。代码如下:

package app08.listener;

import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;

@WebListener
public class PerfStatListener implements ServletRequestListener{

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        ServletRequest servletRequest = sre.getServletRequest();
        servletRequest.setAttribute("start", System.nanoTime());
    } 

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        ServletRequest servletRequest = sre.getServletRequest();
        Long start = (Long) servletRequest.getAttribute("start");
        Long end = System.nanoTime();
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String uri = httpServletRequest.getRequestURI();
        System.out.println("time taken to execute " + uri + ":" + ((end - start) / 1000) + "microseconds");
    }
}

equestInitialized 方法调用System.nanoTime()获取当前系统时间的数值(Long类型) , 并将这个数值保存到ServletRequest中,nanoTime返回一个long类型的数值来表示任意时间。 这个数值和系统或是时钟时间都没什么关系, 但是同一个JVM上调用两次nanoTime得到的数值可以计算出两次调用之间的时间。

所以, 在requestDestroyed方法中再次调用nanoTime方法, 并且减去第一次调用获得的数值, 就得到HTTP请求的完成时间了。

写在后面

这一部分的实例还是有一些没有弄得很清楚,主要是对Servlet这个容器与实际的Http通信机制并没有完全弄明白。后续还需要多看一些其他的书籍。相互佐证,取长补短。

猜你喜欢

转载自blog.csdn.net/zy2317878/article/details/80561418