Servlet 過濾器是可插入的 Web 組件,它允許我們實現 Web 應用程序中的預處理和後 期處理邏輯。過濾器支持 servlet 和 JSP 頁面的基本請求處理功能,比如日志記錄、性 能、安全、會話處理、XSLT 轉換,等等。 過濾器最初是隨 Java Servlet 2.3 規范發布 的,最近定稿的 2.4 規范對它進行了重大升級。在這 J2EE 探索者系列文章的最後一篇中 ,我將向您介紹 Servlet 過濾器的基礎知識 ―― 比如總體的體系結構設計、實現細節, 以及在 J2EE Web 應用程序中的典型應用,還會涉及一些預計最新的 Servlet 規范將會提 供的擴展功能。
Servlet 過濾器是什麼?
Servlet 過濾器是小型的 Web 組件,它們攔截請求和響應,以便查看、提取或以某種 方式操作正在客戶機和服務器之間交換的數據。過濾器是通常封裝了一些功能的 Web 組件 ,這些功能雖然很重要,但是對於處理客戶機請求或發送響應來說不是決定性的。典型的 例子包括記錄關於請求和響應的數據、處理安全協議、管理會話屬性,等等。過濾器提供 一種面向對象的模塊化機制,用以將公共任務封裝到可插入的組件中,這些組件通過一個 配置文件來聲明,並動態地處理。
Servlet 過濾器中結合了許多元素,從而使得過濾器成為獨特、強大和模塊化的 Web 組件。也就是說,Servlet 過濾器是:
聲明式的:過濾器通過 Web 部署描述符(web.xml)中的 XML 標簽來聲明。這樣允許 添加和刪除過濾器,而無需改動任何應用程序代碼或 JSP 頁面。
動態的:過濾器在運行時由 Servlet 容器調用來攔截和處理請求和響應。
靈活的:過濾器在 Web 處理環境中的應用很廣泛,涵蓋諸如日志記錄和安全等許多最 公共的輔助任務。過濾器還是靈活的,因為它們可用於對來自客戶機的直接調用執行預處 理和後期處理,以及處理在防火牆之後的 Web 組件之間調度的請求。最後,可以將過濾器 鏈接起來以提供必需的功能。
模塊化的:通過把應用程序處理邏輯封裝到單個類文件中,過濾器從而定義了可容易地 從請求/響應鏈中添加或刪除的模塊化單元。
可移植的:與 Java 平台的其他許多方面一樣,Servlet 過濾器是跨平台和跨容器可移 植的,從而進一步支持了 Servler 過濾器的模塊化和可重用本質。
可重用的:歸功於過濾器實現類的模塊化設計,以及聲明式的過濾器配置方式,過濾器 可以容易地跨越不同的項目和應用程序使用。
透明的:在請求/響應鏈中包括過濾器,這種設計是為了補充(而不是以任何方式替代 )servlet 或 JSP 頁面提供的核心處理。因而,過濾器可以根據需要添加或刪除,而不會 破壞 servlet 或 JSP 頁面。
所以 Servlet 過濾器是通過一個配置文件來靈活聲明的模塊化可重用組件。過濾器動 態地處理傳入的請求和傳出的響應,並且無需修改應用程序代碼就可以透明地添加或刪除 它們。最後,過濾器獨立於任何平台或者 Servlet 容器,從而允許將它們容易地部署到任 何相容的 J2EE 環境中。
在接下來的幾小節中,我們將進一步考察 Servlet 過濾器機制的總體設計,以及實現 、配置和部署過濾器所涉及的步驟。我們還將探討 Servlet 過濾器的一些實際應用,最後 簡要考察一下模型-視圖-控制器(MVC)體系結構中包含的 Servlet 過濾器,從而結束本 文的討論。
Servlet 過濾器體系結構
正如其名稱所暗示的, Servlet 過濾器用於攔截傳入的請求和/或傳出的響應,並監視 、修改或以某種方式處理正在通過的數據流。過濾器是自包含、模塊化的組件,可以將它 們添加到請求/響應鏈中,或者在無需影響應用程序中其他 Web 組件的情況下刪除它們。 過濾器僅只是改動請求和響應的運行時處理,因而不應該將它們直接嵌入 Web 應用程序框 架,除非是通過 Servlet API 中良好定義的標准接口來實現。
Web 資源可以配置為沒有過濾器與之關聯(這是默認情況)、與單個過濾器關聯(這是 典型情況),甚至是與一個過濾器鏈相關聯。那麼過濾器究竟做什麼呢? 像 servlet 一 樣,它接受請求並響應對象。然後過濾器會檢查請求對象,並決定將該請求轉發給鏈中的 下一個組件,或者中止該請求並直接向客戶機發回一個響應。如果請求被轉發了,它將被 傳遞給鏈中的下一個資源(另一個過濾器、servlet 或 JSP 頁面)。在這個請求設法通過 過濾器鏈並被服務器處理之後,一個響應將以相反的順序通過該鏈發送回去。這樣就給每 個過濾器都提供了根據需要處理響應對象的機會。
當過濾器在 Servlet 2.3 規范中首次引入時,它們只能過濾 Web 客戶機和客戶機所訪 問的指定 Web 資源之間的內容。如果該資源然後將請求調度給其他 Web 資源,那就不能 向幕後委托的任何請求應用過濾器。2.4 規范消除了這個限制。Servlet 過濾器現在可以 應用於 J2EE Web 環境中存在請求和響應對象的任何地方。因此,Servlet 過濾器可以應 用在客戶機和 servlet 之間、servlet 和 servlet 或 JSP 頁面之間,以及所包括的每個 JSP 頁面之間。這才是我所稱的強大能力和靈活性!
實現一個 Servlet 過濾器
他們說“好事多磨”。我不知道“他們”指的是誰,或 者這句古老的諺語究竟有多真實,但是實現一個 Servlet 過濾器的確要經歷三個步驟。首 先要編寫過濾器實現類的程序,然後要把該過濾器添加到 Web 應用程序中(通過在 Web 部署描述符 /web.xml 中聲明它),最後要把過濾器與應用程序一起打包並部署它。我們 將詳細研究這其中的每個步驟。
1. 編寫實現類的程序
過濾器 API 包含 3 個簡單的接口(又是數字 3!),它們整潔地嵌套在 javax.servlet 包中。那 3 個接口分別是 Filter 、 FilterChain 和 FilterConfig 。 從編程的角度看,過濾器類將實現 Filter 接口,然後使用這個過濾器類中的 FilterChain 和 FilterConfig 接口。該過濾器類的一個引用將傳遞給 FilterChain 對象 ,以允許過濾器把控制權傳遞給鏈中的下一個資源。 FilterConfig 對象將由容器提供給 過濾器,以允許訪問該過濾器的初始化數據。
為了與我們的三步模式保持一致,過濾器必須運用三個方法,以便完全實現 Filter 接 口:
init() :這個方法在容器實例化過濾器時被調用,它主要設計用於使過濾器為處理做 准備。該方法接受一個 FilterConfig 類型的對象作為輸入。
doFilter() :與 servlet 擁有一個 service() 方法(這個方法又調用 doPost() 或 者 doGet() )來處理請求一樣,過濾器擁有單個用於處理請求和響應的方法―― doFilter() 。這個方法接受三個輸入參數:一個 ServletRequest 、 response 和一個 FilterChain 對象。
destroy() :正如您想像的那樣,這個方法執行任何清理操作,這些操作可能需要在自 動垃圾收集之前進行。
清單 1 展示了一個非常簡單的過濾器,它跟蹤滿足一個客戶機的 Web 請求所花的大致 時間。
清單 1. 一個過濾器類實現
import javax.servlet.*;
import java.util.*;
import java.io.*;
public class TimeTrackFilter implements Filter {
private FilterConfig filterConfig = null;
public void init(FilterConfig filterConfig)
throws ServletException {
this.filterConfig = filterConfig;
}
public void destroy() {
this.filterConfig = null;
}
public void doFilter( ServletRequest request,
ServletResponse response, FilterChain chain )
throws IOException, ServletException {
Date startTime, endTime;
double totalTime;
startTime = new Date();
// Forward the request to the next resource in the chain
chain.doFilter(request, wrapper);
// -- Process the response -- \\
// Calculate the difference between the start time and end time
endTime = new Date();
totalTime = endTime.getTime() - startTime.getTime();
totalTime = totalTime / 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: " + totalTime + " seconds." );
writer.println("===============");
// Log the resulting string
writer.flush();
filterConfig.getServletContext().
log(sw.getBuffer().toString());
}
}
這個過濾器的生命周期很簡單,不管怎樣,我們還是研究一下它吧:
初始化
當容器第一次加載該過濾器時, init() 方法將被調用。該類在這個方法中包含了一個 指向 FilterConfig 對象的引用。我們的過濾器實際上並不需要這樣做,因為其中沒有使 用初始化信息,這裡只是出於演示的目的。
過濾
過濾器的大多數時間都消耗在這裡。 doFilter() 方法被容器調用,同時傳入分別指向 這個請求/響應鏈中的 ServletRequest 、 ServletResponse 和 FilterChain 對象的引用 。然後過濾器就有機會處理請求,將處理任務傳遞給鏈中的下一個資源(通過調用 FilterChain 對象引用上的 doFilter() 方法),之後在處理控制權返回該過濾器時處理 響應。
析構
容器緊跟在垃圾收集之前調用 destroy() 方法,以便能夠執行任何必需的清理代碼。
2. 配置 Servlet 過濾器
過濾器通過 web.xml 文件中的兩個 XML 標簽來聲明。 <filter> 標簽定義過濾 器的名稱,並且聲明實現類和 init() 參數。 <filter-mapping> 標簽將過濾器與 servlet 或 URL 模式相關聯。
清單 2 摘自一個 web.xml 文件,它展示了如何聲明過濾器的包含關系:
清單 2. 在 web.xml 中聲明一個過濾器
<filter>
<filter-name>Page Request Timer</filter-name>
<filter-class>TimeTrackFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Page Request Timer</filter-name>
<servlet-name>Main Servlet</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>Main Servlet</servlet-name>
<servlet-class>MainServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Main Servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
上面的代碼示例聲明了一個過濾器("Page Request Timer"),並把它映射 到一個 servlet("Main Servlet")。然後為該 servlet 定義了一個映射,以 便把每個請求(由通配符指定)都發送到該 servlet。這是控制器組件的典型映射聲明。 您應該注意這些聲明的順序,因為千萬不能背離這些元素的順序。
3. 部署 Servlet 過濾器
事實上,與 Web 應用程序一起部署過濾器絕對不涉及任何復雜性。只需把過濾器類和 其他 Web 組件類包括在一起,並像您通常所做的那樣把 web.xml 文件(連同過濾器定義 和過濾器映射聲明)放進 Web 應用程序結構中,servlet 容器將處理之後的其他所有事情 。
過濾器的許多應用
您在 J2EE Web 應用程序中利用過濾器的能力,僅受到您自己的創造性和應用程序設計 本領的限制。在適合使用裝飾過濾器模式或者攔截器模式的任何地方,您都可以使用過濾 器。過濾器的一些最普遍的應用如下:
加載:對於到達系統的所有請求,過濾器收集諸如浏覽器類型、一天中的時間、轉發 URL 等相關信息,並對它們進行日志記錄。
性能:過濾器在內容通過線路傳來並在到達 servlet 和 JSP 頁面之前解壓縮該內容, 然後再取得響應內容,並在將響應內容發送到客戶機機器之前將它轉換為壓縮格式。
安全:過濾器處理身份驗證令牌的管理,並適當地限制安全資源的訪問,提示用戶進行 身份驗證和/或將他們指引到第三方進行身份驗證。過濾器甚至能夠管理訪問控制列表 (Access Control List,ACL),以便除了身份驗證之外還提供授權機制。將安全邏輯放 在過濾器中,而不是放在 servlet 或者 JSP 頁面中,這樣提供了巨大的靈活性。在開發 期間,過濾器可以關閉(在 web.xml 文件中注釋掉)。在生產應用中,過濾器又可以再次 啟用。此外還可以添加多個過濾器,以便根據需要提高安全、加密和不可拒絕的服務的等 級。
會話處理:將 servlet 和 JSP 頁面與會話處理代碼混雜在一起可能會帶來相當大的麻 煩。使用過濾器來管理會話可以讓 Web 頁面集中精力考慮內容顯示和委托處理,而不必擔 心會話管理的細節。
XSLT 轉換:不管是使用移動客戶端還是使用基於 XML 的 Web 服務,無需把邏輯嵌入 應用程序就在 XML 語法之間執行轉換的能力都絕對是無價的。
使過濾器適應 MVC 體系結構
模型-視圖-控制器(Model-View-Controller,MVC)體系結構是一個有效的設計,它現 在已作為最重要的設計方法學,整合到了諸如 Jakarta Struts 和 Turbine 等大多數流行 的 Web 應用框架中。過濾器旨在擴充 MVC 體系結構的請求/響應處理流。不管請求/響應 發生在客戶機和服務器之間,還是發生在服務器上的其他組件之間,過濾器在處理流中的 應用都是相同的。從 MVC 的觀點看,調度器組件(它或者包括在控制器組件中,或者配合 控制器組件工作)把請求轉發給適當的應用程序組件以進行處理。這使得控制器層成為包 括 Servlet 過濾器的最佳位置。通過把過濾器放在控制器組件本身的前面,過濾器可以應 用於所有請求,或者通過將它放在控制器/調度器與模型和控制器之間,它可以應用於單獨 的 Web 組件。
MVC 體系結構廣為傳播,並具有良好的文檔。請通過 參考資料中的鏈接了解關於 MVC 和 MVC 體系結構中的 Servlet 實現的更多信息。
結束語
雖然過濾器才出現幾年時間,但它們本身已作為一個關鍵組件嵌入到了所有敏捷的、面 向對象的 J2EE Web 應用程序中。本文向您介紹了 Servlet 過濾器的使用。 本文討論了 過濾器的高級設計,比較了當前規范(2.4)和以前(2.3)的模型,講述了實現過濾器所 涉及的精確步驟,以及如何在 Web 應用程序中聲明過濾器,然後與應用程序一起部署它。 本文還闡述了 Servlet 過濾器的一些最普遍應用,並提到了過濾器如何適應傳統的 MVC 體系結構。
這是 J2EE 探索者系列的最後一篇文章。我們在年初通過粗略研究 Enterprise JavaBean 組件來開始我們的旅程,並提到了何時使用這些組件才真正有意義,以及何時這 些組件才會變得大材小用的問題。然後我們將目光轉向了 Web 層,繪制了一條通過 Servlet、JSP 頁面、JavaBean 技術以及 Java Servlet API 中的無數選擇和功能的路徑 。在這個系列文章中與您一起艱苦跋涉真是一件快樂的事情。我享受著編寫這個系列文章 的樂趣,並且我從大家的反饋中知道,這對您也是一個很有價值的過程。感謝您對本系列 文章的參與。祝您好運,探索快樂!