Servlet過濾器和監(jiān)聽器


Servlet過濾器是 Servlet 程序的一種特殊用法,主要用來完成一些通用的操作,如編碼的過濾、判斷用戶的登錄狀態(tài)。過濾器使得Servlet開發(fā)者能夠在客戶端請求到達 Servlet資源之前被截獲,在處理之后再發(fā)送給被請求的Servlet資源,并且還可以截獲響應,修改之后再發(fā)送給用戶。

而Servlet監(jiān)聽器可以 監(jiān)聽客戶端發(fā)出的請求、服務器端的操作,通過監(jiān)聽器,可以自動激發(fā)一些操作,如監(jiān)聽在線人數。


Servlet過濾器

Servlet過濾器是在Java Servlet 2.3 規(guī)范中定義的,它是一種可以插入的Web組件,它能夠對Servlet 容器的接收到的客戶端請求和向客戶端發(fā)出的響應對象進行截獲,過濾器支持對Servlet程序和JSP頁面的基本請求處理功能,如日志、性能、安全、會話 處理、XSLT轉換等。

Servlet過濾器本身不產生請求和響應,它只提供過濾作用,Servlet過濾器能夠在Servlet程序(JSP頁面)被調用之前檢查 request對象,修改請求頭和請求內容,在Servlet程序(JSP頁面)被調用之后,檢查response對象,修改響應頭和響應內容。

Servlet過濾器的特點

1.Servlet過濾器可以檢查和修改request和response對象。 

2.Servlet過濾器可以被指定與特定的URL關聯(lián),只有當客戶請求訪問該特定的URL時,才會觸發(fā)過濾器。

3.Servlet過濾器可以被串聯(lián)成串,形成過濾鏈,協(xié)同修改請求和響應。

Servlet過濾器的作用:

1.查詢請求并作出相應的行動。

2.阻塞請求--響應對,使其不能進一步傳遞。

3.修改請求頭和內容,用戶可以提供自定義的請求。

4.修改響應頭和內容,用戶可以通過提供定制的響應版本實現。

5.與外部資源進行交互。

Servlet過濾器的體系結構

Servlet過濾器用于攔截傳入的請求和傳出的響應,并監(jiān)視、修改或以某種方式處理 正在通過的數據流。Servlet過濾器是自包含、模塊化的組件,可以將它們添加到請求/響應過濾鏈中,或者在不影響應用程序中其它Web組件的情況下刪 除它們。Servlet過濾器只在改動請求和響應的運行時處理,因而不應該將它們之間嵌入到Web應用程序框架,除非是通過Servlet API中良好定義的標準接口來實現。

Web資源可以配置成為沒有過濾器與之關聯(lián)(默認情況)、與單個過濾器關聯(lián)(典型情況),甚至是與一個過濾器鏈關聯(lián)。其功能與Servlet一 樣,主要是接收請求和響應對象,然后過濾器會檢查請求對象,并決定是將該請求轉發(fā)給鏈中的下一個過濾器,還是終止該請求并直接向客戶端發(fā)會一個響應,如果 請求被轉發(fā)了,它將被傳遞給過濾鏈中的下一個過濾器,或者Servlet程序(JSP頁面),在這個請求通過過濾器鏈并被服務器處理后,一個響應將以相反 的順序通過該過濾鏈發(fā)送回去,這樣就給每個Servlet過濾器提供了根據需要處理響應對象的機會。

當過濾器在Servlet 2.3規(guī)范中首次引入時,只能過濾客戶端和客戶端所訪問的指定Web資源之間的內容(請求/響應),如果該Web資源將請求轉發(fā)給其它Web資源時,那就 不能向幕后委托的任何請求應用過濾器。Servlet 2.4 規(guī)范消除了這個限制,Servlet過濾器現在可以應用于J2EE Web環(huán)境中存在請求和響應的任何地方。可見,Servlet過濾器可以應用在客戶端和Servlet程序之間、Servlet程序和Servlet程序 之間、Servlet程序和JSP頁面之間、JSP頁面和JSP頁面之間,具有強大的能力和靈活性。

Servlet過濾器對請求的過濾

  Servlet過濾器對請求的過濾過程如下:

  1.Servlet容器創(chuàng)建一個Servlet過濾器實例。
  2.Servlet過濾器實例調用init()方法得到初始化參數。
  3.Servlet過濾器實例調用doFilter()方法,根據初始化參數的值判斷該請求是否合法,如果該請求不合法,則阻塞該請求,如果是合法請求,則調用chain.doFilter(request,response)方法將該請求向后轉發(fā)。

Servlet過濾器對響應的過濾

Servlet過濾器對響應的過濾過程如下:

1.過濾器截獲客戶端的請求。
2.重新封裝ServletResponse,在封裝后的ServletResponse中提供客戶端自定義的輸出流。
3.將請求向后轉發(fā)。
4.Web組件產生響應。
5.過濾器從被封裝的ServletResponse中獲取客戶自定義的輸出流。
6.將響應內容通過客戶自定義的輸出流寫入緩沖流。
7.在緩沖流中修改響應內容后清空緩沖流,輸出響應內容。

Servlet過濾器的發(fā)布

Seevlet過濾器設計完畢之后,必須對該過濾器進行發(fā)布(配置), 發(fā)布一個Servlet過濾器時,必須在項目的web.xml文件中加入<filter>元素和<filter- mapping>元素,<filter>元素用來定義一個過濾器,該元素的屬性有:

屬性 描述
filter-name 指定過濾器的名字
filter-class 指定過濾器類
init-param 指定過濾器的初始化參數

<filter-mapping>元素用于將過濾器與URL關聯(lián),其屬性有:

屬性 描述
filter-name 指定過濾器的名字
url-pattern 指定與過濾器關聯(lián)的URL

實現一個Servlet過濾器

Servlet過濾器接口的構成

所有的Servlet過濾器都必須實現javax.servlet.filter接口,該接口中定義了3個過濾器必須實現的方法:

1.void  init(FilterConfig):過濾器的初始化方法,Servlet容器在創(chuàng)建過濾器實例時調用這個方法,在這個方法中可以讀出在web.xml文件中為該過濾器配置的初始化參數。

2.void  doFilter(ServletRequest,ServletResponse,FilterChain):用于完成實際的過濾操作,當客戶請求訪問與過濾器相關聯(lián)的URL時,Servlet容器將先調用過濾器的這個方法,FilterChain參數用于訪問后續(xù)過濾器。

3.void  destroy():過濾器在被取消前執(zhí)行這個方法,釋放過濾器申請的資源。

Servlet過濾器的創(chuàng)建步驟

創(chuàng)建一個Servlet過濾器需要下面的步驟:


1.創(chuàng)建一個實現了javax.servlet.Filter接口的類。

2.重寫init(FilterConfig)方法,讀入為過濾器配置的初始化參數,申請過濾器需要的資源。

3.重寫方法doFilter(ServletRequest,ServletResponse,FilterChain),完成過濾操作,可以  從ServletRequest參數中得到全部的請求信息,從ServletResponse參數中得到全部的響應信息。

4.在doFilter()方法的最后,使用FilterChain參數的doFilter()方法將請求和響應后傳。

5.對響應的Servlet程序和JSP頁面注冊過濾器,在部署描述文件(web.xml)中使用<filter-apping>和<filter>元素對過濾器進行配置。

編寫過濾器類

在過濾器中,需要使用3個簡單的接口,它們是:分別是Filter、FilterChain、FilterConfig,全部包含在javax.servlet包中。從編程的角度看,過濾器類要實現Filter接口,然后使用實現了FilterChain和FilterConfig接口的對象來工作,FilterChain對象負責將請求和響應后傳,FilterConfig對象負責為過濾器讀初始化參數。

為了與過濾器的三步模式(創(chuàng)建、工作、撤消)保持一致,過濾器必須重寫Filter接口中的三個方法:



init():在容器實例化過濾器市時被調用,主要為過濾器做初始化,該方法有一個FilterConfig類型的形參。

doFilter():這個方法用來完成真正的過濾操作,它有3個形式參數:ServletRequest參數包含請求信息,ServletResponse參數包含響應信息,FilterChain參數用來將請求和響應向后傳遞。

destroy():過濾器被撤消時調用這個方法,釋放過濾器所 占有的資源。

在下面的例子中實現了一個簡單的Servlet過濾器(SessionFilter.Java),它實現的功能是判斷客戶是否成功登錄,如果成功登錄,轉向正確頁面,否則返回一個錯誤頁面,提示客戶應該進行登錄。該過濾器代碼如下:

  //includeList:數組,受保護的資源。
  //logonList:數組,登錄頁面。
  package ch13;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.io.*;
  public class SessionFilter implements Filter{
      String  logonStrings,includeStrings,redirectPath,disabletestfilter;
      String[]  logonList,includeList;

      private boolean  isContains(String containers,String[] regx) {
          boolean  result=false;
          for(int  i=0;i<regx.length;i++) {
              if  (containers.indexOf(regx[i])!=-1)
                  return  true;
          }
          return  result;
      }

      public FilterConfig config;
      private void  setFilterConfig(FilterConfig config) {
          this.config=config;
      }

      private FilterConfig  getFilterConfig(){
          return  config;
      }

      //必須重寫
      public void init(FilterConfig filterConfig) throws ServletException{
          this.config=filterConfig;
          logonStrings=config.getInitParameter("logonStrings");
           includeStrings=config.getInitParameter("includeStrings");
           redirectPath=config.getInitParameter("redirectPath");
           disabletestfilter=config.getInitParameter("disabletestfilter");
          logonList=logonStrings.split(";");//分割為數組
           includeList=includeStrings.split(";");//分割為數組
      }
      //必須重寫
      public void  doFilter(ServletRequest request,ServletResponse response,FilterChain 
                                  chain)  throws ServletException, IOException {
          HttpServletRequest  httpreq=(HttpServletRequest)request;
          HttpServletResponse  httpres=(HttpServletResponse)response;
          HttpServletResponseWrapper  wrapper=new HttpServletResponseWrapper(
          (HttpServletResponse)response);
          if  (disabletestfilter.toUpperCase().equals("Y")){
              chain.doFilter(request,response);//如果不過濾
              return;
          }
          Object  user=httpreq.getSession().getAttribute("userinfo");
          if  (user==null){//該用戶沒有登錄
              if  (!isContains(httpreq.getRequestURI(),includeList)){
                  chain.doFilter(request,response);
                  return;//訪問的是不受保護的頁面,可以
              }
              if  (isContains(httpreq.getRequestURI(),logonList)){
                  chain.doFilter(request,response);
                  return;  //訪問的是登錄頁面,可以
              }
              wrapper.sendRedirect(redirectPath);  //轉向登頁面 
          }else  {//該用戶已經登錄
              chain.doFilter(request,response);
          }
      }
      //必須重寫
      public void destroy() {
          config=null;
      }
  }

在上面的這個Servlet過濾器程序中,根據用戶session對象中有無userinfo這個屬性來確定該用戶是否已經登錄。

配置部署過濾器

在WEB-INF/web.xml文件中用以下代碼配置過濾器:

  <filter>
      <filter-name>SessionFilter</filter-name>
          <filter-class>ch13.SessionFilter</filter-class>  
      <init-param>
          <param-name>logonStrings</param-name>
          <param-value>Login.jsp</param-value>  
      </init-param> 
      <init-param>
          <param-name>includeStrings</param-name>
          <param-value>.jsp;.html;.htm</param-value> 
      </init-param> 
      <init-param>
          <param-name>redirectPath</param-name>
          <param-value>./Login.jsp</param-value>  
      </init-param> 
      <init-param>
          <param-name>disabletestfilter</param-name>
          <param-value>n</param-value>  
      </init-param> 
  </filter> 
  <filter-mapping>
      <filter-name>SessionFilter</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>

在上面的配置中,參數logonStrings指定可以訪問的登錄頁面,參數includeStrings指定受保護的資源的后綴,參數redirectPath表示沒有登錄時轉向的登錄頁面,參數disabletestfilter表示過濾器是否有效。而 /* 表示過濾器與所有的URL都關聯(lián)(對所有的訪問請求都進行過濾)。在瀏覽器中訪問任意的資源時,都要通過這個過濾器的過濾。

過濾器的應用案例.

版權過濾器的應用案例

在一個Web應用中的所有頁面的下面添加上版權信息,通常的做法是采用<%@ include>指令或<c:import> 標簽,使用過濾器也是一個好辦法。

編寫過濾器類CopyrightFilter.java

  package ch13;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.io.*;
  public class CopyrightFilter implements Filter{
      private String date;
      public FilterConfig config;
      //必須重寫
      public void init(FilterConfig filterConfig) throws ServletException{
          this.config=filterConfig;
          date=config.getInitParameter("date");
      }
      //必須重寫
      public void doFilter(ServletRequest request,ServletResponse response,FilterChain 
                                          chain)  throws ServletException, IOException {
          chain.doFilter(request,response);
          PrintWriter  out=response.getWriter();
          out.print("<br><center><font  size='3' color='red'>版權所有:yanglc
                                                          </center></font>");
          if  (date!=null) 
              out.print("<br><center><font  color='blue'>"+date+"</center></font>");
          out.flush();
      }
      //必須重寫
      public void destroy() {
          config=null;
      }
  }

在這個過濾器中,在doFilter()方法的最后,通過response對象得到一個輸出流out,然后通過輸出流向客戶端輸出版權信息,這樣,每個頁面的最后都會出現過濾器添加的版權信息。

修改web.xml,配置該過濾器

  <filter>
      <filter-name>CopyrightFilter</filter-name>
          <filter-class>ch13.CopyrightFilter</filter-class>  
      <init-param>
          <param-name>date</param-name>
          <param-value>2010-9</param-value>  
      </init-param> 
  </filter> 
  <filter-mapping>
      <filter-name>CopyrightFilter</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>

測試

在瀏覽器中任意訪問一個頁面,都可以在看到在頁面的下部出現過濾器添加的版權信息。

禁止未授權的IP訪問站點過濾器的應用案例

使用過濾器禁止未授權的IP訪問站點是過濾器常見的應用,本例演示了如何利用過濾器實現禁止未授權的IP訪問站點。

編寫過濾器類FilterIP.java

  package ch13;
  import javax.servlet.*;
  import javax.servlet.http.*;
  import java.io.*;
  public class FilterIP implements Filter{
      private String  filterIP,error;
      public FilterConfig config;
      //必須重寫
      public void init(FilterConfig filterConfig) throws  ServletException{
          this.config=filterConfig;
          filterIP=config.getInitParameter("FilterIP");
          if  (filterIP==null) filterIP="";
          error=config.getInitParameter("ERROR");
          if  (error==null) error="error.jsp";
      }
      //必須重寫
      public void doFilter(ServletRequest request,ServletResponse response,FilterChain 
                                          chain)  throws ServletException, IOException {
          RequestDispatcher  dispatcher=request.getRequestDispatcher("ErrorInfo.jsp");
          String remoteIP=request.getRemoteAddr();//得到客戶的IP地址
          if  (remoteIP.equals(filterIP)) {
              dispatcher.forward(request,response);
              return;
          }  else
              chain.doFilter(request,response);
      }
      //必須重寫
      public void destroy() {
          config=null;
      }
  }

在這個過濾器中,在doFilter()方法內,通過request對象得到客戶端的IP地址,如果客戶端的IP是被禁止的IP,則使用request對象將請求轉發(fā)給一個出錯頁面。

修改web.xml,配置過濾器

  <filter>
      <filter-name>FilterIP</filter-name>
          <filter-class>ch13.FilterIP</filter-class>  
      <init-param>
          <param-name>FilterIP</param-name>
          <param-value>192.168.1.1</param-value>  
      </init-param> 
      <init-param>
          <param-name>ERROR</param-name>
          <param-value>error.jsp</param-value>  
      </init-param>
  </filter> 
  <filter-mapping>
      <filter-name>FilterIP</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>

對來自192.168.1.1的客戶的所有請求(/*)都進行過濾,轉移到error.jsp頁面。

編寫出錯頁面error.jsp

  <%@  page contentType="text/html;charset=gb2312" %>

網站不允許IP地址為192.168.1.1的計算機訪問。

在IP地址為 192.168.1.1 的計算機上訪問網站的任何一個資源,都會轉移到error.jsp頁面。

過濾頁面內容(響應內容)

本過濾器使用HttpServletResponseWrapper類 來實現頁面內容的過濾,它的原理是讓Web資源先將頁面內容(響應內容)寫入到HttpServletResponseWrapper對象中,然后再在過 濾器中處理HttpServletResponseWrapper對象中的頁面內容(響應內容),最后再將處理好的頁面內容(響應內容)發(fā)送給客戶。

編寫HttpServletResponseWrapper類的子類.java

  package ch13;
  import java.io.ByteArrayOutputStream; 
  import java.io.OutputStreamWriter; 
  import java.io.IOException; 
  import java.io.PrintWriter; 
  import java.io.UnsupportedEncodingException; 
  import javax.servlet.http.HttpServletResponse; 
  import javax.servlet.http.HttpServletResponseWrapper;
  import javax.servlet.ServletOutputStream;
  public class WrapperResponse extends  HttpServletResponseWrapper { 
      public static final int  OT_NONE = 0, OT_WRITER = 1, OT_STREAM = 2; 
      private int outputType =  OT_NONE; 
      private ServletOutputStream  output = null; 
      private PrintWriter writer =  null; 
      private ByteArrayOutputStream  buffer = null; 

      //構造函數 
      public  WrapperResponse(HttpServletResponse resp) throws IOException { 
          super(resp);  
          buffer  = new ByteArrayOutputStream(); 
      } 

      //得到字符輸出流
      public PrintWriter getWriter() throws IOException { 
          if  (outputType == OT_STREAM) 
              throw  new IllegalStateException(); //已經用了OutputStream流
          else  if (outputType == OT_WRITER) 
              return  writer; 
          else  { 
              outputType  = OT_WRITER; 
              writer  = new PrintWriter(new  OutputStreamWriter(buffer, getCharacterEncoding())); 
              return  writer; 
          } 
      }

      //得到字節(jié)輸出流
      public ServletOutputStream getOutputStream() throws IOException { 
          if  (outputType == OT_WRITER) 
              throw  new IllegalStateException(); //已經用了Writer流
          else  if (outputType == OT_STREAM) 
              return  output; 
          else  { 
              outputType  = OT_STREAM; 
              output  = new WrappedOutputStream(buffer); 
              return  output; 
          } 
      } 

      //刷新輸出內容
      public void flushBuffer() throws IOException { 
          if  (outputType == OT_WRITER) 
              writer.flush();  
          if  (outputType == OT_STREAM) 
              output.flush();  
      } 

      //輸出緩沖區(qū)復位
      public void reset() { 
          outputType  = OT_NONE; 
          buffer.reset();  
      } 
      public String  getResponseData() throws IOException { 
          flushBuffer();  
          return  new String(buffer.toByteArray()); 
      } 

      //內部類,將數據寫入自己的定義的緩沖區(qū)
      class WrappedOutputStream extends ServletOutputStream { 
              private  ByteArrayOutputStream buffer; 
              public  WrappedOutputStream(ByteArrayOutputStream buffer) { 
                  this.buffer  = buffer; 
              }  
              public  void write(int b) throws IOException { 
                  buffer.write(b);  
              }  
              public  byte[] toByteArray() { 
                  return  buffer.toByteArray(); 
              }  
      } 
  } 

在這個類中,一定要重寫response對象的關于輸出流(outputStream、writer)操作的方法:getOutputStream()、getWriter()、flushBuffer()、reset()。

編寫過濾器GavinFilter.java

 package ch13;
  import java.io.IOException; 
  import javax.servlet.*; 
  import javax.servlet.http.HttpServletRequest; 
  import javax.servlet.http.HttpServletResponse; 
  public class GavinFilter implements Filter { 
      private String  oldword="%" , newword="百分號"; 
      public void destroy(){}
      public void  doFilter(ServletRequest request, ServletResponse response, FilterChain
                                              chain)  throws IOException, ServletException { 
          HttpServletResponse  oldresponse = (HttpServletResponse)response; 
          WrapperResponse  wrapperResponse = new WrapperResponse(oldresponse); 
          chain.doFilter(request,  wrapperResponse); //讓服務器將響應內容寫到Wrapper中
          String  html = wrapperResponse.getResponseData(); //取出響應內容
          oldresponse.getWriter().print(html.replaceAll(oldword,  newword)); //替換頁面中的文字,然后發(fā)送給客戶
      } 
      public void init(FilterConfig config) throws ServletException {
          oldword=config.getInitParameter("oldword");
          newword=config.getInitParameter("newword");
      } 
  }

該過濾器將頁面內容(響應內容)中的字符 % 替換為百分號三個漢字,由此可見,實現了對響應內容的過濾。

對該過濾器的配置

  <filter> 
      <filter-name>gavinFilter</filter-name>  
      <filter-class>ch13.GavinFilter</filter-class>  
      <init-param> 
          <param-name>oldword</param-name>  
          <param-value>%</param-value> 
      </init-param> 
      <init-param> 
          <param-name>newword</param-name>  
          <param-value>百分號</param-value> 
      </init-param> 
  </filter>
  <filter-mapping> 
      <filter-name>gavinFilter</filter-name>  
      <url-pattern>/*</url-pattern>  
  </filter-mapping> 

Servlet監(jiān)聽器

Servlet監(jiān)聽器也叫做 listener,通過它可以監(jiān)聽Web應用的上下文(環(huán)境)信息、Servlet請求信息、Servlet會話信息,并自動根據不同情況,在后臺調用相 應的處理程序。通過監(jiān)聽器,可以自動激發(fā)一些操作,比如監(jiān)聽在線人數,當增加一個HttpSession時就激發(fā) sessionCreated(HttpSessionEvent)方法,這樣就可以給在線人數加1。

監(jiān)聽器的原理

Servlet監(jiān)聽器是Web應用開發(fā)的一個重要組成部分,Servlet監(jiān)聽器是在Servlet2.3規(guī)范中和Servlet過濾器一起引入的。在 Servlet2.4 規(guī)范中對其進行了比較大的改進。主要就是用來對Web應用進行監(jiān)督和控制,極大地增強了Web應用的事件處理能力。

Servlet監(jiān)聽器的功能比較類似于Java中的GUI程序的監(jiān)聽器,可以監(jiān)聽由于Web應用中的狀態(tài)改變而引起的Servlet容器產生的相應事件,然后接收并處理這些事件。

監(jiān)聽器的類型

在Servlet 2.4 規(guī)范中,根據監(jiān)聽對象的類型和范圍,將監(jiān)聽器分為3類:ServletRequest監(jiān)聽器(請求監(jiān)聽器)、HttpSession監(jiān)聽器(會話監(jiān)聽器)、ServletContext監(jiān)聽器(上下文監(jiān)聽器),其中請求監(jiān)聽器(ServletRequest監(jiān)聽器)是 Servlet 2.4 規(guī)范中新增加的監(jiān)聽器,可以用來監(jiān)聽客戶的端請求,在Servlet 2.4

規(guī)范中包含了8個監(jiān)聽器接口和6個監(jiān)聽器事件類,具體的監(jiān)聽器接口和事件如下表:

被監(jiān)聽對象ServletContext

對ServletContext對象(JSP頁面中稱為application對象)實現監(jiān)聽涉及2個接口:



(1)ServletContextListener接口:用于監(jiān)聽ServletContext對象的創(chuàng)建和刪除:接口中定義的回調方法有:

  當創(chuàng)建一個ServletContext對象時,激發(fā)  contextInitialzed(ServletContextEvent)方法。
  當撤消一個ServletContext對象時,激發(fā)  contextDestroyed(ServletContextEvent)方法。

(2)ServletContextAttributeListener接口:用于監(jiān)聽ServletContext對象的屬性操作。接口中定義的回調方法有:

  增加屬性時,激發(fā) attributeAdded(ServletContextAttributeEvent)
  刪除屬性時,激發(fā) attributeRemoved(ServletContextAttributeEvent) 
  修改屬性時,激發(fā) attributeReplaced(ServletContextAttributeEvent) 

被監(jiān)聽對象HttpSession

對HttpSession對象(session)實現監(jiān)聽涉及4個接口:



(1)HttpSessionListener接口:這個接口監(jiān)聽Http會話的創(chuàng)建和撤消,并在某個session對象建立和銷毀之前調用某個方法。接口中定義的回調方法有:

創(chuàng)建一個session對象時,激發(fā)  sessionCreated(HttpSessionEvent)
刪除一個session對象時,激發(fā) sessionDestroyed(HttpSessionEvent)

(2)HttpSessionActivationListener接口:監(jiān)聽Http會話的active和passivate狀態(tài)。接口中定義的回調方法有:

session對象被保存到磁盤時,激發(fā) sessionWillPassivate(HttpSessionEvent)
session對象被調入內存時,激發(fā) sessionDidActivate(HttpSessionEvent)

Activate與Passivate是用于置換session對象的動作,當Web服務器因為資源利用或負載平衡等原因要將內存中的 session對象暫時儲存至硬盤或其它儲存器時(通過對象序列化),所作的動作稱之為Passivate,而硬盤或儲存器上的session對象重新加 載到JVM中時所采的動作稱之為Activate。sessionDidActivate()方法與 sessionWillPassivate()方法分別于Activeate后與Passivate前被調用。 

(3)HttpSessionAttributeListener接口:監(jiān)聽Http會話中屬性的設置信息。接口中定義的回調方法有:

向某個session對象中增加新屬性時,激發(fā)  attributeAdded(HttpSessionBindingEvent)
刪除某個session對象中的屬性時,激發(fā) attributeRemoved(HttpSessionBindingEvent)
修改某個session對象中的屬性時,激發(fā) attributeReplaced(HttpSessionBindingEvent)

使用HttpSessionBindingEvent事件類對象的getSession()方法可以得到這個session對象,使用 HttpSessionBindingEvent對象的getName()方法得到屬性的名字,使用getValue()方法得到屬性的值。

若有屬性加入到某個會話(HttpSession)對象,則會調用attributeAdded(),同理在替換屬性與移除屬性時,會分別調用attributeReplaced()、attributeRemoved()。

(4)HttpSessionBindingListener接口:這是唯一一個不需要在web.xml中進行配置的監(jiān)聽器接口,監(jiān)聽Http會話中屬性的變化情況。接口中定義的回調方法有:

屬性被加入到session中時,激發(fā)屬性的 valueBound(HttpSessionBindingEvent)
屬性被從session中刪除時,激發(fā)屬性的 valueUnbound(HttpSessionBindingEvent)

使用HttpSessionBindingEvent事件類對象的getSession()方法可以得到這個session對象,使用 HttpSessionBindingEvent對象的getName()方法得到屬性的名字,使用getValue()方法得到屬性的值。

如果一個對象object實現了HttpSessionBindingListener接口時,當把object對象保存到session中時, 就會自動調用object對象的valueBound()方法,如果對象object被從session(HttpSession)移除時,則會調用 object對象的valueUnbound()方法。使用這個接口,可以讓一個對象自己知道它自己是被保存到了session中,還是從session 中被刪除了。

被監(jiān)聽對象ServletRequest

對ServletRequest對象(request)實現監(jiān)聽涉及2個接口:


(1)ServletRequestListener接口:監(jiān)聽請求的創(chuàng)建和撤消,該接口用來監(jiān)聽請求到達和結束,因此可以在請求達到前和請求結束前執(zhí)行一些用戶行為。  接口中定義的回調方法有: 

請求對象初始化時,激發(fā) requestInitialized(ServletRequestEvent)
請求對象被撤消時,激發(fā)  requestDestroyed(ServletRequestEvent)

在request(HttpServletRequest)對象建立或被消滅時,會分別調用requestInitialized()和requestDestroyed()方法。

(2)ServletRequestAttributeListener接口:監(jiān)聽請求中(request對象中)的屬性變化。接口中定義的回調方法有:

向某個request對象中增加屬性時被調用attributeAdded(ServletRequestAttributeEvent)方法。
從某個request對象中刪除屬性時被調用attributeRemoved(ServletRequestAttributeEvent)方法。
修改某個request中的屬性時被調用attributeReplaced(ServletRequestAttributeEvent)方法。

使用ServletRequestEvent類的getServletRequest()方法可以得到這個被監(jiān)聽的請求對象,使用  ServletRequestAttributeEvent類的getName()方法可以得到屬性名,getValue()方法可以得到屬性的值。
若有屬性加入到某個request對象中時則會調用attributeAdded(),同理在替換屬性與刪除屬性時,會分別調用attributeReplaced()、 attributeRemoved()。 

當Web應用程序啟動后,在處理任何請求之前,調用contextInitialzed()方法和getInitParamter()方法,返回 在配置文件中為定義的環(huán)境初始化信息。不同的組件,如Servlet、JSP、監(jiān)聽器和過濾器等,通過ServletRequest、 HttpSession 和 ServletContext達到數據共享,這些類都提供了下面的一組方法,可以使用這組方法來設置、獲取、刪除屬性:

public void  setAttribute("屬性名",屬性值);
public Object  getAttribute("屬性名");
public void  removeAttribute("屬性名");

監(jiān)聽器管理共享數據庫連接

在web.xml中,使用<listener>來配置監(jiān)聽器,語法是:

<listener>
  <listener-class>包名.類名</listener-class>
</listener>

比如:創(chuàng)建一個ServletContext對象監(jiān)聽器,在一個Web項目一啟動就創(chuàng)建一個與數據庫的連接,保存在application對象中,這個連接一直保存到Web項目關閉時為止。程序代碼如下:

package ch13;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.sql.*;
//import ch7.db.*; 
public final class MyConnectionManager implements ServletContextListener {
  Connection con=null;
  public void  contextInitialized(ServletContextEvent e) {//重寫接口定義的方法,項目啟動是調用該方法
      ConnectDB db=new ConnectDB();
      con=db.getConnection();  //使用對象db創(chuàng)建數據庫連接
      e.getServletContext().setAttribute("con",con);//與數據庫的連接保存入application對象中
  }
  public void contextDestroyed(ServletContextEvent  e) {//重寫接口定義的方法,項目關閉時調用該方法
      try {
          con.close();
      }
      catch(Exception  e1){}
  }
}

在web.xml文件對這個ServletContext類型的監(jiān)聽器進行配置:

<listener>
  <listener-class>ch13.MyConnectionManager</listener-class>
</listener> 

這個監(jiān)聽器能保證每新創(chuàng)建一個ServletContext對象時(一個Web項目只有一個 ServletContext對象),該Web項目都會有一個可以使用的數據庫連接,并且這個數據庫連接會在該ServletContext對象關閉(結 束)的時候隨之關閉。

測試頁面testcon.jsp:

<%@ page contentType="text/html" pageEncoding="GB18030"%>
<br><%= "得到的數據庫連接:"+application.getAttribute("con") %>
<br><h1>請注意安裝數據庫的驅動程序</h1>

監(jiān)聽器的應用案例

在線用戶數量監(jiān)聽器

下面是一個在線用戶數量監(jiān)聽器,這個監(jiān)聽器可以實時統(tǒng)計在線人數,在 ServletContext初始化和撤消時,在服務器控制臺打印出對應信息,當ServletContext對象里的屬性增加、修改、刪除時,在服務器 控制臺打印相應的信息。要完成上面的監(jiān)聽功能,需要使用3個接口:


HttpSessionListener:監(jiān)督HttpSession對象的創(chuàng)建和撤消,統(tǒng)計人數。
ServletContextListener:監(jiān)督ServletContext對象的創(chuàng)建和撤消。
ServletContextAttributeListener:監(jiān)督ServletContext的屬性變化。

監(jiān)聽器程序代碼OnLineCountListener.java

package ch13;
import javax.servlet.*;
import javax.servlet.http.*;
public final class OnLineCountListener implements  HttpSessionListener,
                          ServletContextAttributeListener,  ServletContextListener {
  private int count;
  private ServletContext  context=null;

  //構造函數 
  public OnLineCountListener()  {
      count=0;//人數
  }

  //重寫HttpSessionListener接口中的2個方法,完成對session對象創(chuàng)建和撤消的監(jiān)視
  public void  sessionCreated(HttpSessionEvent se) {//創(chuàng)建了一個session對象
      count++;//人數加1
      setContext(se);
  }
  public void  sessionDestroyed(HttpSessionEvent se){//撤消了一個session對象
      count--;//人數減1
      setContext(se);
  }

  private void  setContext(HttpSessionEvent se){
      se.getSession().getServletContext().setAttribute("onLine",new  Integer(count));
  }

  //重寫ServletContextAttributeListener接口中的3個方法
  public void  attributeAdded(ServletContextAttributeEvent event) {//添加了屬性
      log("attributeAdded("+event.getName()+","+event.getValue()+")");
  }
  public void  attributeRemoved(ServletContextAttributeEvent event) {//刪除了屬性
      log("attributeRemove("+event.getName()+","+event.getValue()+")");
  }
  public void attributeReplaced(ServletContextAttributeEvent  event) {//替換了原有的屬性
      log("attributeReplaced("+event.getName()+","+event.getValue()+")");
  }

  //重寫ServletContextListener接口中的2個方法
  public void  contextDestroyed(ServletContextEvent event) {//Web項目關閉
      log("contextDestroyed()");
      context=null;
  }
  public void  contextInitialized(ServletContextEvent event) {//Web項目啟動
      this.context=event.getServletContext();
      log("contextInitialized()");
  }

  //顯示信息
  private void log(String  message){
      System.out.println("ContextListener:"+message);
  }
}

在OnLineCountListener類中,用count保存目前在線人數,每增加一個session對象,人數加1,每撤消一個session對象,人數減1。人數保存在ServletContext對象中,使得任何頁面都可以使用。

在web.xml文件中配置監(jiān)聽器

<listener>
  <listener-class>ch13.OnLineCountListener</listener-class>
</listener> 

編寫測試頁面(2個) listener.jsp------>exit.jsp

listener.jsp頁面內容

<%@ page  contentType="text/html;charset=gb2312" %>
目前在線人數:<font  color="red"><%=application.getAttribute("onLine")%></font><br>
退出會話:
<form action="exit.jsp"  method="post">
<input type="submit"  value="exit">
</form> 

exit.jsp頁面內容

<%@ page  contentType="text/html;charset=gb2312" %>
你已經退出會話<%  session.invalidate(); %> 

可以單獨啟動5個瀏覽器窗口,每個窗口代表一個客戶,因此在線人數是5。

HttpSessionBindingListener 接口的使用

設計一個學生對象Student,當將該學生對象存入 session中時,他的年齡增加10歲,當將這個學生對象從session中刪除時,他的年齡減少5歲。

學生類Student.java

package ch13;
import javax.servlet.*;
import javax.servlet.http.*;
public class Student implements  HttpSessionBindingListener {
  private int age=30;
  public void valueBound(HttpSessionBindingEvent  arg0) {//存入session時自動調用
      age+=10;
  }
  public void  valueUnbound(HttpSessionBindingEvent arg0) {//從session中刪除時自動調用
      age-=5;
  }
  public int getAge() {return  age;}
} 

測試頁面bind.jsp

<%@ page  contentType="text/html;charset=gb2312"  import="ch13.Student"%>
<%
  Student student=new  Student();
  out.println("學生年齡:"+student.getAge()+"<br>"); 
  session.setAttribute("st",student);
  out.println("存入session后,該學生年齡:"+student.getAge()+"<br>");
  session.removeAttribute("st");  
  out.println("從session刪除,該學生年齡:"+student.getAge()+"<br>");  
%> 
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容