用Java Servlets 2.4實(shí)現(xiàn)過濾
Posted on 2006-10-24 10:03 daniel-shen 閱讀(10) 評(píng)論(0) 編輯 收藏 引用 網(wǎng)摘 所屬分類: javaServlet 過濾器是可插入的 Web 組件,它允許我們實(shí)現(xiàn) Web 應(yīng)用程序中的預(yù)處理和后期處理邏輯。過濾器支持 servlet 和 JSP 頁面的基本請(qǐng)求處理功能,比如日志記錄、性能、安全、會(huì)話處理、XSLT 轉(zhuǎn)換,等等。 過濾器最初是隨 Java Servlet 2.3 規(guī)范發(fā)布的,最近定稿的 2.4 規(guī)范對(duì)它進(jìn)行了重大升級(jí)。在此我將向您介紹 Servlet 過濾器的基礎(chǔ)知識(shí) ―― 比如總體的體系結(jié)構(gòu)設(shè)計(jì)、實(shí)現(xiàn)細(xì)節(jié),以及在 J2EE Web 應(yīng)用程序中的典型應(yīng)用,還會(huì)涉及一些預(yù)計(jì)最新的 Servlet 規(guī)范將會(huì)提供的擴(kuò)展功能。
Servlet 過濾器是什么?
Servlet 過濾器是小型的 Web 組件,它們攔截請(qǐng)求和響應(yīng),以便查看、提取或以某種方式操作正在客戶機(jī)和服務(wù)器之間交換的數(shù)據(jù)。過濾器是通常封裝了一些功能的 Web 組件,這些功能雖然很重要,但是對(duì)于處理客戶機(jī)請(qǐng)求或發(fā)送響應(yīng)來說不是決定性的。典型的例子包括記錄關(guān)于請(qǐng)求和響應(yīng)的數(shù)據(jù)、處理安全協(xié)議、管理會(huì)話屬性,等等。過濾器提供一種面向?qū)ο蟮哪K化機(jī)制,用以將公共任務(wù)封裝到可插入的組件中,這些組件通過一個(gè)配置文件來聲明,并動(dòng)態(tài)地處理。
Servlet 過濾器中結(jié)合了許多元素,從而使得過濾器成為獨(dú)特、強(qiáng)大和模塊化的 Web 組件。也就是說,Servlet 過濾器是:
* 聲明式的:過濾器通過 Web 部署描述符(web.xml)中的 XML 標(biāo)簽來聲明。這樣允許添加和刪除過濾器,而無需改動(dòng)任何應(yīng)用程序代碼或 JSP 頁面。
* 動(dòng)態(tài)的:過濾器在運(yùn)行時(shí)由 Servlet 容器調(diào)用來攔截和處理請(qǐng)求和響應(yīng)。
* 靈活的:過濾器在 Web 處理環(huán)境中的應(yīng)用很廣泛,涵蓋諸如日志記錄和安全等許多最公共的輔助任務(wù)。過濾器還是靈活的,因?yàn)樗鼈兛捎糜趯?duì)來自客戶機(jī)的直接調(diào)用執(zhí)行預(yù)處理和后期處理,以及處理在防火墻之后的 Web 組件之間調(diào)度的請(qǐng)求。最后,可以將過濾器鏈接起來以提供必需的功能。
* 模塊化的:通過把應(yīng)用程序處理邏輯封裝到單個(gè)類文件中,過濾器從而定義了可容易地從請(qǐng)求/響應(yīng)鏈中添加或刪除的模塊化單元。
* 可移植的:與 Java 平臺(tái)的其他許多方面一樣,Servlet 過濾器是跨平臺(tái)和跨容器可移植的,從而進(jìn)一步支持了 Servler 過濾器的模塊化和可重用本質(zhì)。
* 可重用的:歸功于過濾器實(shí)現(xiàn)類的模塊化設(shè)計(jì),以及聲明式的過濾器配置方式,過濾器可以容易地跨越不同的項(xiàng)目和應(yīng)用程序使用。
* 透明的:在請(qǐng)求/響應(yīng)鏈中包括過濾器,這種設(shè)計(jì)是為了補(bǔ)充(而不是以任何方式替代)servlet 或 JSP 頁面提供的核心處理。因而,過濾器可以根據(jù)需要添加或刪除,而不會(huì)破壞 servlet 或 JSP 頁面。
所以 Servlet 過濾器是通過一個(gè)配置文件來靈活聲明的模塊化可重用組件。過濾器動(dòng)態(tài)地處理傳入的請(qǐng)求和傳出的響應(yīng),并且無需修改應(yīng)用程序代碼就可以透明地添加或刪除它們。最后,過濾器獨(dú)立于任何平臺(tái)或者 Servlet 容器,從而允許將它們?nèi)菀椎夭渴鸬饺魏蜗嗳莸?J2EE 環(huán)境中。
在接下來的幾小節(jié)中,我們將進(jìn)一步考察 Servlet 過濾器機(jī)制的總體設(shè)計(jì),以及實(shí)現(xiàn)、配置和部署過濾器所涉及的步驟。我們還將探討 Servlet 過濾器的一些實(shí)際應(yīng)用,最后簡要考察一下模型-視圖-控制器(MVC)體系結(jié)構(gòu)中包含的 Servlet 過濾器,從而結(jié)束本文的討論。
Servlet 過濾器體系結(jié)構(gòu)
正如其名稱所暗示的, Servlet 過濾器用于攔截傳入的請(qǐng)求和/或傳出的響應(yīng),并監(jiān)視、修改或以某種方式處理正在通過的數(shù)據(jù)流。過濾器是自包含、模塊化的組件,可以將它們添加到請(qǐng)求/響應(yīng)鏈中,或者在無需影響應(yīng)用程序中其他 Web 組件的情況下刪除它們。過濾器僅只是改動(dòng)請(qǐng)求和響應(yīng)的運(yùn)行時(shí)處理,因而不應(yīng)該將它們直接嵌入 Web 應(yīng)用程序框架,除非是通過 Servlet API 中良好定義的標(biāo)準(zhǔn)接口來實(shí)現(xiàn)。
Web 資源可以配置為沒有過濾器與之關(guān)聯(lián)(這是默認(rèn)情況)、與單個(gè)過濾器關(guān)聯(lián)(這是典型情況),甚至是與一個(gè)過濾器鏈相關(guān)聯(lián)。那么過濾器究竟做什么呢? 像 servlet 一樣,它接受請(qǐng)求并響應(yīng)對(duì)象。然后過濾器會(huì)檢查請(qǐng)求對(duì)象,并決定將該請(qǐng)求轉(zhuǎn)發(fā)給鏈中的下一個(gè)組件,或者中止該請(qǐng)求并直接向客戶機(jī)發(fā)回一個(gè)響應(yīng)。如果請(qǐng)求被轉(zhuǎn)發(fā)了,它將被傳遞給鏈中的下一個(gè)資源(另一個(gè)過濾器、servlet 或 JSP 頁面)。在這個(gè)請(qǐng)求設(shè)法通過過濾器鏈并被服務(wù)器處理之后,一個(gè)響應(yīng)將以相反的順序通過該鏈發(fā)送回去。這樣就給每個(gè)過濾器都提供了根據(jù)需要處理響應(yīng)對(duì)象的機(jī)會(huì)。
當(dāng)過濾器在 Servlet 2.3 規(guī)范中首次引入時(shí),它們只能過濾 Web 客戶機(jī)和客戶機(jī)所訪問的指定 Web 資源之間的內(nèi)容。如果該資源然后將請(qǐng)求調(diào)度給其他 Web 資源,那就不能向幕后委托的任何請(qǐng)求應(yīng)用過濾器。2.4 規(guī)范消除了這個(gè)限制。Servlet 過濾器現(xiàn)在可以應(yīng)用于 J2EE Web 環(huán)境中存在請(qǐng)求和響應(yīng)對(duì)象的任何地方。因此,Servlet 過濾器可以應(yīng)用在客戶機(jī)和 servlet 之間、servlet 和 servlet 或 JSP 頁面之間,以及所包括的每個(gè) JSP 頁面之間。這才是我所稱的強(qiáng)大能力和靈活性!
實(shí)現(xiàn)一個(gè) Servlet 過濾器
他們說“好事多磨& rdquo;。我不知道“他們”指的是誰,或者這句古老的諺語究竟有多真實(shí),但是實(shí)現(xiàn)一個(gè) Servlet 過濾器的確要經(jīng)歷三個(gè)步驟。首先要編寫過濾器實(shí)現(xiàn)類的程序,然后要把該過濾器添加到 Web 應(yīng)用程序中(通過在 Web 部署描述符 /web.xml 中聲明它),最后要把過濾器與應(yīng)用程序一起打包并部署它。我們將詳細(xì)研究這其中的每個(gè)步驟。
1. 編寫實(shí)現(xiàn)類的程序
過濾器 API 包含 3 個(gè)簡單的接口(又是數(shù)字 3?。鼈冋麧嵉厍短自?javax.servlet 包中。那 3 個(gè)接口分別是 Filter 、 FilterChain 和 FilterConfig 。從編程的角度看,過濾器類將實(shí)現(xiàn) Filter 接口,然后使用這個(gè)過濾器類中的 FilterChain 和 FilterConfig 接口。該過濾器類的一個(gè)引用將傳遞給 FilterChain 對(duì)象,以允許過濾器把控制權(quán)傳遞給鏈中的下一個(gè)資源。 FilterConfig 對(duì)象將由容器提供給過濾器,以允許訪問該過濾器的初始化數(shù)據(jù)。
為了與我們的三步模式保持一致,過濾器必須運(yùn)用三個(gè)方法,以便完全實(shí)現(xiàn) Filter 接口:
* init() :這個(gè)方法在容器實(shí)例化過濾器時(shí)被調(diào)用,它主要設(shè)計(jì)用于使過濾器為處理做準(zhǔn)備。該方法接受一個(gè) FilterConfig 類型的對(duì)象作為輸入。
* doFilter() :與 servlet 擁有一個(gè) service() 方法(這個(gè)方法又調(diào)用 doPost() 或者 doGet() )來處理請(qǐng)求一樣,過濾器擁有單個(gè)用于處理請(qǐng)求和響應(yīng)的方法―― doFilter() 。這個(gè)方法接受三個(gè)輸入?yún)?shù):一個(gè) ServletRequest 、 response 和一個(gè) FilterChain 對(duì)象。
* destroy() :正如您想像的那樣,這個(gè)方法執(zhí)行任何清理操作,這些操作可能需要在自動(dòng)垃圾收集之前進(jìn)行。
清單 1 展示了一個(gè)非常簡單的過濾器,它跟蹤滿足一個(gè)客戶機(jī)的 Web 請(qǐng)求所花的大致時(shí)間。
清單 1. 一個(gè)過濾器類實(shí)現(xiàn)
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());
}
}
這個(gè)過濾器的生命周期很簡單,不管怎樣,我們還是研究一下它吧:
初始化
當(dāng)容器第一次加載該過濾器時(shí), init() 方法將被調(diào)用。該類在這個(gè)方法中包含了一個(gè)指向 FilterConfig 對(duì)象的引用。我們的過濾器實(shí)際上并不需要這樣做,因?yàn)槠渲袥]有使用初始化信息,這里只是出于演示的目的。
過濾
過濾器的大多數(shù)時(shí)間都消耗在這里。 doFilter() 方法被容器調(diào)用,同時(shí)傳入分別指向這個(gè)請(qǐng)求/響應(yīng)鏈中的 ServletRequest 、 ServletResponse 和 FilterChain 對(duì)象的引用。然后過濾器就有機(jī)會(huì)處理請(qǐng)求,將處理任務(wù)傳遞給鏈中的下一個(gè)資源(通過調(diào)用 FilterChain 對(duì)象引用上的 doFilter() 方法),之后在處理控制權(quán)返回該過濾器時(shí)處理響應(yīng)。
析構(gòu)
容器緊跟在垃圾收集之前調(diào)用 destroy() 方法,以便能夠執(zhí)行任何必需的清理代碼。
2. 配置 Servlet 過濾器
過濾器通過 web.xml 文件中的兩個(gè) XML 標(biāo)簽來聲明。 <filter> 標(biāo)簽定義過濾器的名稱,并且聲明實(shí)現(xiàn)類和 init() 參數(shù)。 <filter-mapping> 標(biāo)簽將過濾器與 servlet 或 URL 模式相關(guān)聯(lián)。
清單 2 摘自一個(gè) web.xml 文件,它展示了如何聲明過濾器的包含關(guān)系:
清單 2. 在 web.xml 中聲明一個(gè)過濾器
<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>
上面的代碼示例聲明了一個(gè)過濾器("Page Request Timer"),并把它映射到一個(gè) servlet("Main Servlet")。然后為該 servlet 定義了一個(gè)映射,以便把每個(gè)請(qǐng)求(由通配符指定)都發(fā)送到該 servlet。這是控制器組件的典型映射聲明。您應(yīng)該注意這些聲明的順序,因?yàn)榍f不能背離這些元素的順序。
3. 部署 Servlet 過濾器
事實(shí)上,與 Web 應(yīng)用程序一起部署過濾器絕對(duì)不涉及任何復(fù)雜性。只需把過濾器類和其他 Web 組件類包括在一起,并像您通常所做的那樣把 web.xml 文件(連同過濾器定義和過濾器映射聲明)放進(jìn) Web 應(yīng)用程序結(jié)構(gòu)中,servlet 容器將處理之后的其他所有事情。
過濾器的許多應(yīng)用
您在 J2EE Web 應(yīng)用程序中利用過濾器的能力,僅受到您自己的創(chuàng)造性和應(yīng)用程序設(shè)計(jì)本領(lǐng)的限制。在適合使用裝飾過濾器模式或者攔截器模式的任何地方,您都可以使用過濾器。過濾器的一些最普遍的應(yīng)用如下:
# 加載:對(duì)于到達(dá)系統(tǒng)的所有請(qǐng)求,過濾器收集諸如瀏覽器類型、一天中的時(shí)間、轉(zhuǎn)發(fā) URL 等相關(guān)信息,并對(duì)它們進(jìn)行日志記錄。
# 性能:過濾器在內(nèi)容通過線路傳來并在到達(dá) servlet 和 JSP 頁面之前解壓縮該內(nèi)容,然后再取得響應(yīng)內(nèi)容,并在將響應(yīng)內(nèi)容發(fā)送到客戶機(jī)機(jī)器之前將它轉(zhuǎn)換為壓縮格式。
# 安全:過濾器處理身份驗(yàn)證令牌的管理,并適當(dāng)?shù)叵拗瓢踩Y源的訪問,提示用戶進(jìn)行身份驗(yàn)證和/或?qū)⑺麄冎敢降谌竭M(jìn)行身份驗(yàn)證。過濾器甚至能夠管理訪問控制列表(Access Control List,ACL),以便除了身份驗(yàn)證之外還提供授權(quán)機(jī)制。將安全邏輯放在過濾器中,而不是放在 servlet 或者 JSP 頁面中,這樣提供了巨大的靈活性。在開發(fā)期間,過濾器可以關(guān)閉(在 web.xml 文件中注釋掉)。在生產(chǎn)應(yīng)用中,過濾器又可以再次啟用。此外還可以添加多個(gè)過濾器,以便根據(jù)需要提高安全、加密和不可拒絕的服務(wù)的等級(jí)。
# 會(huì)話處理:將 servlet 和 JSP 頁面與會(huì)話處理代碼混雜在一起可能會(huì)帶來相當(dāng)大的麻煩。使用過濾器來管理會(huì)話可以讓 Web 頁面集中精力考慮內(nèi)容顯示和委托處理,而不必?fù)?dān)心會(huì)話管理的細(xì)節(jié)。
# XSLT 轉(zhuǎn)換:不管是使用移動(dòng)客戶端還是使用基于 XML 的 Web 服務(wù),無需把邏輯嵌入應(yīng)用程序就在 XML 語法之間執(zhí)行轉(zhuǎn)換的能力都絕對(duì)是無價(jià)的。
使過濾器適應(yīng) MVC 體系結(jié)構(gòu)
模型-視圖-控制器(Model-View- Controller,MVC)體系結(jié)構(gòu)是一個(gè)有效的設(shè)計(jì),它現(xiàn)在已作為最重要的設(shè)計(jì)方法學(xué),整合到了諸如 Jakarta Struts 和 Turbine 等大多數(shù)流行的 Web 應(yīng)用框架中。過濾器旨在擴(kuò)充 MVC 體系結(jié)構(gòu)的請(qǐng)求/響應(yīng)處理流。不管請(qǐng)求/響應(yīng)發(fā)生在客戶機(jī)和服務(wù)器之間,還是發(fā)生在服務(wù)器上的其他組件之間,過濾器在處理流中的應(yīng)用都是相同的。從 MVC 的觀點(diǎn)看,調(diào)度器組件(它或者包括在控制器組件中,或者配合控制器組件工作)把請(qǐng)求轉(zhuǎn)發(fā)給適當(dāng)?shù)膽?yīng)用程序組件以進(jìn)行處理。這使得控制器層成為包括 Servlet 過濾器的最佳位置。通過把過濾器放在控制器組件本身的前面,過濾器可以應(yīng)用于所有請(qǐng)求,或者通過將它放在控制器/調(diào)度器與模型和控制器之間,它可以應(yīng)用于單獨(dú)的 Web 組件。
MVC 體系結(jié)構(gòu)廣為傳播,并具有良好的文檔,詳細(xì)討論MVC不是本文的內(nèi)容,有興趣的讀者可自行查找相關(guān)內(nèi)容。
結(jié)束語
雖然過濾器才出現(xiàn)幾年時(shí)間,但它們本身已作為一個(gè)關(guān)鍵組件嵌入到了所有敏捷的、面向?qū)ο蟮?J2EE Web 應(yīng)用程序中。本文向您介紹了 Servlet 過濾器的使用。本文討論了過濾器的高級(jí)設(shè)計(jì),比較了當(dāng)前規(guī)范(2.4)和以前(2.3)的模型,講述了實(shí)現(xiàn)過濾器所涉及的精確步驟,以及如何在 Web 應(yīng)用程序中聲明過濾器,然后與應(yīng)用程序一起部署它。本文還闡述了 Servlet 過濾器的一些最普遍應(yīng)用,并提到了過濾器如何適應(yīng)傳統(tǒng)的 MVC 體系結(jié)構(gòu)。