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


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

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


Servlet過濾器

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

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

Servlet過濾器的特點

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

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

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

Servlet過濾器的作用:

1.查詢請求并作出相應(yīng)的行動。

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

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

4.修改響應(yīng)頭和內(nèi)容,用戶可以通過提供定制的響應(yīng)版本實現(xiàn)。

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

Servlet過濾器的體系結(jié)構(gòu)

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

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

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

Servlet過濾器對請求的過濾

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

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

Servlet過濾器對響應(yīng)的過濾

Servlet過濾器對響應(yīng)的過濾過程如下:

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

Servlet過濾器的發(fā)布

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

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

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

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

實現(xiàn)一個Servlet過濾器

Servlet過濾器接口的構(gòu)成

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

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

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

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

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

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


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

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

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

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

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

編寫過濾器類

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

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



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

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

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

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

  //includeList:數(shù)組,受保護的資源。
  //logonList:數(shù)組,登錄頁面。
  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(";");//分割為數(shù)組
           includeList=includeStrings.split(";");//分割為數(shù)組
      }
      //必須重寫
      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);  //轉(zhuǎn)向登頁面 
          }else  {//該用戶已經(jīng)登錄
              chain.doFilter(request,response);
          }
      }
      //必須重寫
      public void destroy() {
          config=null;
      }
  }

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

配置部署過濾器

在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>

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

過濾器的應(yīng)用案例.

版權(quán)過濾器的應(yīng)用案例

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

編寫過濾器類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'>版權(quán)所有: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,然后通過輸出流向客戶端輸出版權(quán)信息,這樣,每個頁面的最后都會出現(xiàn)過濾器添加的版權(quán)信息。

修改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>

測試

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

禁止未授權(quán)的IP訪問站點過濾器的應(yīng)用案例

使用過濾器禁止未授權(quán)的IP訪問站點是過濾器常見的應(yīng)用,本例演示了如何利用過濾器實現(xiàn)禁止未授權(quán)的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()方法內(nèi),通過request對象得到客戶端的IP地址,如果客戶端的IP是被禁止的IP,則使用request對象將請求轉(zhuǎn)發(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的客戶的所有請求(/*)都進行過濾,轉(zhuǎn)移到error.jsp頁面。

編寫出錯頁面error.jsp

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

網(wǎng)站不允許IP地址為192.168.1.1的計算機訪問。

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

過濾頁面內(nèi)容(響應(yīng)內(nèi)容)

本過濾器使用HttpServletResponseWrapper類 來實現(xiàn)頁面內(nèi)容的過濾,它的原理是讓W(xué)eb資源先將頁面內(nèi)容(響應(yīng)內(nèi)容)寫入到HttpServletResponseWrapper對象中,然后再在過 濾器中處理HttpServletResponseWrapper對象中的頁面內(nèi)容(響應(yīng)內(nèi)容),最后再將處理好的頁面內(nèi)容(響應(yīng)內(nèi)容)發(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; 

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

      //得到字符輸出流
      public PrintWriter getWriter() throws IOException { 
          if  (outputType == OT_STREAM) 
              throw  new IllegalStateException(); //已經(jīng)用了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(); //已經(jīng)用了Writer流
          else  if (outputType == OT_STREAM) 
              return  output; 
          else  { 
              outputType  = OT_STREAM; 
              output  = new WrappedOutputStream(buffer); 
              return  output; 
          } 
      } 

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

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

      //內(nèi)部類,將數(shù)據(jù)寫入自己的定義的緩沖區(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對象的關(guān)于輸出流(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); //讓服務(wù)器將響應(yīng)內(nèi)容寫到Wrapper中
          String  html = wrapperResponse.getResponseData(); //取出響應(yīng)內(nèi)容
          oldresponse.getWriter().print(html.replaceAll(oldword,  newword)); //替換頁面中的文字,然后發(fā)送給客戶
      } 
      public void init(FilterConfig config) throws ServletException {
          oldword=config.getInitParameter("oldword");
          newword=config.getInitParameter("newword");
      } 
  }

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

對該過濾器的配置

  <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應(yīng)用的上下文(環(huán)境)信息、Servlet請求信息、Servlet會話信息,并自動根據(jù)不同情況,在后臺調(diào)用相 應(yīng)的處理程序。通過監(jiān)聽器,可以自動激發(fā)一些操作,比如監(jiān)聽在線人數(shù),當(dāng)增加一個HttpSession時就激發(fā) sessionCreated(HttpSessionEvent)方法,這樣就可以給在線人數(shù)加1。

監(jiān)聽器的原理

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

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

監(jiān)聽器的類型

在Servlet 2.4 規(guī)范中,根據(jù)監(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對象)實現(xiàn)監(jiān)聽涉及2個接口:



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

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

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

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

被監(jiān)聽對象HttpSession

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



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

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

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

session對象被保存到磁盤時,激發(fā) sessionWillPassivate(HttpSessionEvent)
session對象被調(diào)入內(nèi)存時,激發(fā) sessionDidActivate(HttpSessionEvent)

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

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

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

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

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

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

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

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

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

被監(jiān)聽對象ServletRequest

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


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

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

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

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

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

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

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

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

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

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

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

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

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) {//重寫接口定義的方法,項目啟動是調(diào)用該方法
      ConnectDB db=new ConnectDB();
      con=db.getConnection();  //使用對象db創(chuàng)建數(shù)據(jù)庫連接
      e.getServletContext().setAttribute("con",con);//與數(shù)據(jù)庫的連接保存入application對象中
  }
  public void contextDestroyed(ServletContextEvent  e) {//重寫接口定義的方法,項目關(guān)閉時調(diào)用該方法
      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項目都會有一個可以使用的數(shù)據(jù)庫連接,并且這個數(shù)據(jù)庫連接會在該ServletContext對象關(guān)閉(結(jié) 束)的時候隨之關(guān)閉。

測試頁面testcon.jsp:

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

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

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

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


HttpSessionListener:監(jiān)督HttpSession對象的創(chuàng)建和撤消,統(tǒng)計人數(shù)。
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;

  //構(gòu)造函數(shù) 
  public OnLineCountListener()  {
      count=0;//人數(shù)
  }

  //重寫HttpSessionListener接口中的2個方法,完成對session對象創(chuàng)建和撤消的監(jiān)視
  public void  sessionCreated(HttpSessionEvent se) {//創(chuàng)建了一個session對象
      count++;//人數(shù)加1
      setContext(se);
  }
  public void  sessionDestroyed(HttpSessionEvent se){//撤消了一個session對象
      count--;//人數(shù)減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項目關(guān)閉
      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保存目前在線人數(shù),每增加一個session對象,人數(shù)加1,每撤消一個session對象,人數(shù)減1。人數(shù)保存在ServletContext對象中,使得任何頁面都可以使用。

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

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

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

listener.jsp頁面內(nèi)容

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

exit.jsp頁面內(nèi)容

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

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

HttpSessionBindingListener 接口的使用

設(shè)計一個學(xué)生對象Student,當(dāng)將該學(xué)生對象存入 session中時,他的年齡增加10歲,當(dāng)將這個學(xué)生對象從session中刪除時,他的年齡減少5歲。

學(xué)生類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時自動調(diào)用
      age+=10;
  }
  public void  valueUnbound(HttpSessionBindingEvent arg0) {//從session中刪除時自動調(diào)用
      age-=5;
  }
  public int getAge() {return  age;}
} 

測試頁面bind.jsp

<%@ page  contentType="text/html;charset=gb2312"  import="ch13.Student"%>
<%
  Student student=new  Student();
  out.println("學(xué)生年齡:"+student.getAge()+"<br>"); 
  session.setAttribute("st",student);
  out.println("存入session后,該學(xué)生年齡:"+student.getAge()+"<br>");
  session.removeAttribute("st");  
  out.println("從session刪除,該學(xué)生年齡:"+student.getAge()+"<br>");  
%> 
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容