之前學習了很多涉及servlet的內容,本小結我們說一下監聽器,說起監聽器,編過桌面程序和手機App的都不陌生,常見的套路都是拖一個控件,然後給它綁定一個監聽器,即可以對該對象的事件進行監聽以便發生響應,從本質上來說這些都是觀察者模式的具體實現,在web程序中的監聽器也不例外。
在Java Web程序中使用監聽器可以通過以下兩種方法:
通過注解@WebListener來標識一個自定義的監聽器;
@WebListener
public class CustomListener implements Listener {
}
通過在web.xml中配置來使用監聽器;
org.springframework.web.context.ContextLoaderListener
在Java Web程序中,主要是對ServletContext、HttpSession和ServletRequest三個對象提供支持。
@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)。
@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銷毀時不是只是調用sessionDestroyed()方法,在執行完這個方法之後,還會調用attributeRemoved()方法,將其中保存的所有屬性全部清除。
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);
上述是錯誤的使用辦法,不能序列化HttpSession,此處的序列化只是將Session實例中的信息轉換成能在網絡中傳輸的數據形式即可,含義范圍要大於Java中的序列化概念,一般的理解是將對象轉化成字符串。
至於Session為什麼序列化,這就很明顯了,如果服務器宕機,客戶訪問服務器的所有信息因為存在於內存中,所以也就全部丟失了,將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");
}
向doGet()方法中的Session中添加屬性:
session.setAttribute("person", new Person("lmy", 23));
session.setMaxInactiveInterval(10);
發出請求之後,輸出如下:
HttpSessionBindingEvent source: org.apache.catalina.session.StandardSessionFacade@fdfaab5 person bound10秒之後Session失效,輸出:
HttpSessionBindingEvent source: org.apache.catalina.session.StandardSessionFacade@fdfaab5 person unbound
@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類接受請求代碼:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("MyHttpServlet1 request work");
System.out.println("current thread :" + Thread.currentThread().getName());
}
MyHttpServlet類接受請求代碼:
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("MyHttpServlet request work"); System.out.println("current thread :" + Thread.currentThread().getName()); }
向MyHttpServlet發送一個請求輸出如下:
可以看出接收一個請求後首先初始化ServletRequest對象,調用requestInitialized()方法,之後會發現進行了屬性的替換,使能異步處理,在之後進行處理請求的具體工作,最後銷毀該請求對象。
向MyHttpServlet1發送一個請求輸出如下:
處理的流程和向MyHttpServlet發送請求相同但是看出來請求的處理線程是不同的。
在請求的過程中添加屬性和ServletContext中的對屬性的監聽器是類似的:
req.setAttribute("area", "zh");
req.setAttribute("area", "ts");
req.removeAttribute("area");
輸出如下:
屬性替換時也是返回的替換之前的值,和ServletContext中屬性的替換是相同的。