Servlet API 很久以前就已成為公司專屬應用程式開發的基石,而 Servlet 過濾器則是對 J2EE 家族的相對較新的補充。在 J2EE 探索者 系列文章的最後一篇中,作者 Kyle Gabhart 將向您介紹 Servlet 過濾器體繫結構,定義過濾器的許多應用,並指導您完成典型過濾器實現的三個步驟。他還會透露 bean 的一些激動人心的變化,預計剛發布的 Java Servlet 2.4 規範會引入這些變化。
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 中的無數選擇和功能的路徑。在這個系列文章中與您一起艱苦跋涉真是一件快樂的事情。我享受著編寫這個系列文章的樂趣,並且我從大家的反饋中知道,這對您也是一個很有價值的過程。感謝您對本系列文章的參與。祝您好運,探索快樂!
參考資料
- 參與關於本文的 討論論壇。(您還可以單擊文章頂部或底部的討論來訪問該論壇。)
- Sun 的 J2EE 教程始終是獲得關於核心 J2EE 技術的好地方。要瞭解 Servlet 過濾器,請參閱 過濾請求和響應 一節。
- Sing Li 的“Taming your Tomcat: Filtering tricks for Tomcat 5”一文(developerWorks,2003 年 3 月)是關於在 Tomcat Web 環境中定義 Servlet 過濾器的優秀文章。
- 要學習 Servlet 2.3 過濾器的基礎知識,請閱讀 java.sun.com 上的“The Essentials of Filters”。
- Jason Hunter 的“Servlet 2.4: What's in store2.4”一文(JavaWorld,2003 年 3 月)是對 Servlet 2.4 規範預計將要做出的變化的全面預覽。
- 當然,您總是可以到源頭去閱讀 Java Servlet 2.4 Specification。
- 訪問 JCP 的 Java Servlet 2.4 Final Release 頁面,下載 Java Servlet 2.4 規範的最終版。
- 您能在 www.Encode.com 找到關於模型-視圖-控制器模式的詳細介紹。
- 要更深入地瞭解 MVC 並獲得特定於 J2EE 的全景視圖,請分析從 Sun Microsystems 的“Designing Enterprise Applications with the J2EE Platform”摘錄的指導方針。
- 要對 MVC 設計採用以 servlet 為中心的方法,使用 Struts 再好不過了。可以通過 Malcolm Davis 的“Struts, an open-source MVC implementation”(developerWorks,2001 年 2 月)瞭解有關 Struts 的方方面面。
- 不要錯過了J2EE 探索者系列文章中的任何一期。請參閱 Kyle Gabhart 的 J2EE 探索者 專欄的完整列表。
- 在 IBM developerWorks Java 技術專區 可以找到有關 Java 編程各個方面的數百篇文章。
- 請訪問 Developer Bookstore 獲得技術書籍的詳盡列表,其中包括了數百本 Java 相關的圖書。