什么是Servlet過濾器(filter)?
Servlet 過濾器是小型的 Web 組件,可以鏈在Servlet容器的處理過程中,攔截請求和響應(yīng),檢查和修在客戶機和Web應(yīng)用程序之間交換的數(shù)據(jù)。這意味著過濾器會在Servlet處理之前訪問一個進入的請求,并在外發(fā)的響應(yīng)回到客戶前訪問這些信息。
過濾器可以被添加到請求/響應(yīng)鏈中,或者在無需影響應(yīng)用程序中其他 Web 組件的情況下刪除它們。過濾器僅只是改動請求和響應(yīng)的運行時處理,因而不應(yīng)該將它們直接嵌入 Web 應(yīng)用程序框架。
Servlet可以與一個或者多個過濾器相關(guān)聯(lián),后者將形成一個過濾器鏈。

編寫一個 Servlet 過濾器
實現(xiàn)一個 Servlet 過濾器需要三個步驟:首先要編寫過濾器實現(xiàn)類的程序,然后要把該過濾器添加到 Web 應(yīng)用程序中(通過在 Web 部署描述符 web.xml 中聲明它),最后要把過濾器與應(yīng)用程序一起打包部署。

1. 編寫實現(xiàn)類的程序
過濾器 API 一共包含 3 個簡單的接口:Filter、FilterChain 和 FilterConfig。過濾器類必須需要實現(xiàn) Filter 接口:

init():這個方法在容器實例化過濾器時被調(diào)用,它主要設(shè)計用于使過濾器為處理做準(zhǔn)備。容器為這個方法傳遞一個FilterConfig對象,其中包含著配置信息。
doFilter():過濾器擁有單個用于處理請求和響應(yīng)的方法——doFilter()。這個方法接受三個輸入?yún)?shù):一個 ServletRequest、response 和一個 FilterChain 對象。FilterChain對于正確的過濾操作至關(guān)重要。doFilter()方法必須調(diào)用FilterChain的doFilter()方法,除非該方法用來攔截以后的下游處理。注意:過濾器的一個實例可以同時服務(wù)于多個請求,意味著任何共享的變量都必須通過同步塊(synchronized block)來訪問。
destroy():該方法由容器在銷毀過濾器實例之前調(diào)用。
例1 演示了一個簡單的過濾器,用來計算一個客戶機的 Web 請求所花的大致時間。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package cc.ejb.examples;
import java.io.IOException;
import java.io.StringWriter;
import java.io.PrintWriter;
import java.util.Date;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class PageTimerFilter implements Filter 
{
 private FilterConfig config = null;
 public void init(FilterConfig config) throws ServletException 
 {  
  this.config = config;
 }
 public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException
 {
  Date startTime, endTime;
  double duration;
  startTime = new Date();
  // Forward the request to the next resource in the chain
  chain.doFilter(request, response);
  // Calculate the duration between the start time and end time
  endTime = new Date();
  duration = (endTime.getTime() - startTime.getTime())/1000;//Convert from milliseconds to seconds
  StringWriter sw = new StringWriter();
  PrintWriter writer = new PrintWriter(sw);
  writer.println();
  writer.println("===============");
  writer.println("Total elapsed time is: " + duration + " seconds.");
  writer.println("===============");
  // Log the resulting string
  writer.flush();
  config.getServletContext().log(sw.getBuffer().toString());
 }
 public void destroy() {
  this.config=null;
 }
}

在doFilter()方法實現(xiàn)中,出現(xiàn)在FilterChain的doFilter()方法調(diào)用之前的代碼都被看成是預(yù)處理,Web資源(包括其他過濾器、Servlet等等)所做的處理還沒有發(fā)生。而在該方法之后的代碼則是后期處理,這時外發(fā)的響應(yīng)信息已經(jīng)包含了Web資源的完整響應(yīng)。也就是說,FilterChain的doFilter()將調(diào)用接下來的過濾器(在有鏈?zhǔn)疥P(guān)系的時候)或者其他Web資源。

2. 配置 Servlet 過濾器和配置Servlet類似,過濾器通過 web.xml 文件中的兩個 XML 標(biāo)簽<filter>和<filter-name>來聲明。<filter>標(biāo)簽負(fù)責(zé)把一個過濾器名和一個特定的類關(guān)聯(lián)起來,這種關(guān)聯(lián)是通過<filter-name>和<filter-class>元素指定。其 DTD定義如下:
1
(((description*,display-name*,icon*)),filter-name,filter-class,init-param*)

可以為過濾器指定初始化參數(shù),和Servlet類似,參數(shù)是使用<inti-param>和成對的<param-name>和<param-value>來指定的,如下所示:
1
2
3
4
<init-param>
    <param-name>counter</param-name>
       <param-value>100</param-value>
</init-param>

例 2 顯示了 web.xml 文件,它展示了如何聲明過濾器的包含關(guān)系:
1
2
3
4
5
6
7
8
 <filter>
  <filter-name>Page Timers</filter-name>
  <filter-class>cc.ejb.examples.PageTimerFilter</filter-class>
 </filter>
 <filter-mapping>
  <filter-name>Page Timers</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>

<filter>必須有一個<ulr-pattern>或者<servlet-name>元素。我們可以通過<ulr-pattern>來指定通配符,將過濾器應(yīng)用到Web資源范圍,在上面的例子中,Page Timer過濾器被應(yīng)用到每個Web資源。也可以通過<servlet-name>將過濾器指定到某一個特定的Servlet上。應(yīng)該注意這些聲明的順序,所引用的過濾器名必須在前面的過濾器定義給出。

3. 部署 Servlet 過濾器事實上, 部署過濾器是非常簡單的事情。只需把過濾器類和其他 Web 組件類包括在一起,并像通常所做的那樣把 web.xml 文件(連同過濾器定義和過濾器映射聲明)放進 Web 應(yīng)用程序結(jié)構(gòu)中,servlet 容器將處理之后的其他所有事情。

Servlet 2.4中的新特性
我們通過下面的例子來研究Servlet 2.4的新特性。例3和例4是兩個很簡單的程序片斷,JSP程序?qū)碜钥蛻舻恼埱骹orward到Thank.html。
1
2
3
4
5
6
<%@ page language="java" %>
<html>  
<body>
<jsp:forward page="/Thank.html"/>
</body>
</html>

1
2
3
4
5
<html>
  <body>
    Thank you for coming Filter worlds<br>
  </body>
</html>

將這兩個程序和上面的過濾器一起打包部署并運行Test.jsp,我們發(fā)現(xiàn)控制臺只有一行而不是兩行輸出:
1
2
3
4
11:06:57,045 INFO  [Engine] StandardContext[/Test]
===============
Total elapsed time is: 0.0 seconds.
===============

因為,在Servlet 2.3 規(guī)范中的過濾器只能過濾 Web 客戶機和其所訪問的指定 Web 資源之間的內(nèi)容。如果該資源然將請求調(diào)度給其他 Web 資源(這里是Thank.html),就不能向幕后委托的任何請求應(yīng)用過濾器。

2.4 規(guī)范消除了這個限制,通過增強filter和request dispatcher的配合,過濾器可以根據(jù)請求分發(fā)器(request dispatcher)所使用的方法有條件地對Web請求進行過濾。該功能是通過中的元素來實現(xiàn)的:

只有當(dāng)request直接來自客戶,過濾器才生效,對應(yīng)為REQUEST條件。
只有當(dāng)request被一個請求分發(fā)器使用forward()方法轉(zhuǎn)到一個Web構(gòu)件時(采用或定義),對應(yīng)稱為FORWARD條件。
類似地,只有當(dāng)request被一個請求分發(fā)器使用include()方法轉(zhuǎn)到一個Web構(gòu)件時(采用或定義),對應(yīng)稱為INCLUDE條件。
只有當(dāng)request被一個請求分發(fā)器使用“錯誤信息頁”機制方法轉(zhuǎn)到一個Web構(gòu)件時,對應(yīng)稱為ERROR條件。
以上四種條件的組合使用。
修改之后web.xml的如下所示:
1
2
3
4
5
6
7
8
9
10
 <filter>
  <filter-name>Page Timers</filter-name>
  <filter-class>cc.ejb.examples.PageTimerFilter</filter-class>
 </filter>
 <filter-mapping>
  <filter-name>Page Timers</filter-name>
  <url-pattern>/*</url-pattern>
  <dispatcher>REQUEST</dispatcher>
  <dispatcher>FORWARD</dispatcher>
 </filter-mapping>

再次部署運行這個Web應(yīng)用,結(jié)果如下:
1
2
3
4
5
6
7
8
11:17:51,165 INFO  [Engine] StandardContext[/Test]
===============
Total elapsed time is: 0.0 seconds.
===============
11:17:51,165 INFO  [Engine] StandardContext[/Test]
===============
Total elapsed time is: 10.0 seconds.
===============

過濾器的運用
在適合使用裝飾過濾器模式或者攔截器模式的任何地方,您都可以使用過濾器。過濾器的一些最普遍的應(yīng)用如下:

加載:對于到達系統(tǒng)的所有請求,過濾器收集諸如瀏覽器類型、一天中的時間、轉(zhuǎn)發(fā) URL 等相關(guān)信息,并對它們進行日志記錄。
性能:過濾器在內(nèi)容通過線路傳來并在到達 servlet 和 JSP 頁面之前解壓縮該內(nèi)容,然后再取得響應(yīng)內(nèi)容,并在將響應(yīng)內(nèi)容發(fā)送到客戶機機器之前將它轉(zhuǎn)換為壓縮格式。
安全:過濾器處理身份驗證令牌的管理,并適當(dāng)?shù)叵拗瓢踩Y源的訪問,提示用戶進行身份驗證和/或?qū)⑺麄冎敢降谌竭M行身份驗證。過濾器甚至能夠管理訪問控制列表(Access Control List,ACL),以便除了身份驗證之外還提供授權(quán)機制。將安全邏輯放在過濾器中,而不是放在 servlet 或者 JSP 頁面中,這樣提供了巨大的靈活性。在開發(fā)期間,過濾器可以關(guān)閉(在 web.xml 文件中注釋掉)。在生產(chǎn)應(yīng)用中,過濾器又可以再次啟用。此外還可以添加多個過濾器,以便根據(jù)需要提高安全、加密和不可拒絕的服務(wù)的等級。
會話處理:將 servlet 和 JSP 頁面與會話處理代碼混雜在一起可能會帶來相當(dāng)大的麻煩。使用過濾器來管理會話可以讓 Web 頁面集中精力考慮內(nèi)容顯示和委托處理,而不必?fù)?dān)心會話管理的細(xì)節(jié)。
XSLT 轉(zhuǎn)換:不管是使用移動客戶端還是使用基于 XML 的 Web 服務(wù),無需把邏輯嵌入應(yīng)用程序就在 XML 語法之間執(zhí)行轉(zhuǎn)換的能力都絕對是無價的。