写在前面
这几天学习有点懈怠,或者说这段时间都有所懈怠。还是要自制。这一章列举几个例子,说明一下。
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通信机制并没有完全弄明白。后续还需要多看一些其他的书籍。相互佐证,取长补短。