Filtering
過濾器(Filter)是 Java 組件, 允許運行過程中改變進入資源的請求和資源返回的響應(yīng)中的有效負(fù)載和 header
信息。
本章描述了 Java Servlet v3.0 API 類和方法,它提供了一種輕量級的框架用于過濾動態(tài)和靜態(tài)內(nèi)容。還描
述了如何在 Web 應(yīng)用配置 Filter和它們實現(xiàn)的約定和語義。
網(wǎng)上提供servlet過濾器的API文檔在。過濾器的配置語法由第14章“部署描述符”中的部署描述符模式給出。閱讀本章時,讀者應(yīng)使用這些來作為參考。
1. 什么是過濾器
過濾器是一段可重用的代碼,它可以轉(zhuǎn)換HTTP請求、響應(yīng)和頭信息的內(nèi)容。過濾器通常不會像servlet那樣創(chuàng)建響應(yīng)或響應(yīng)請求,而是修改或調(diào)整資源的請求,并修改或調(diào)整資源的響應(yīng)。
過濾器可以作用于動態(tài)或靜態(tài)內(nèi)容。為了本章的目的,動態(tài)和靜態(tài)內(nèi)容被稱為Web資源。
對于需要使用過濾器的開發(fā)人員可用的功能類型如下:
- 資源的訪問請求之前調(diào)用它。
- 一個資源請求的處理之前調(diào)用。
- 通過包裝定制版本的請求對象對請求頭和數(shù)據(jù)的修改
- 通過提供定制版本的響應(yīng)對象對響應(yīng)頭和響應(yīng)數(shù)據(jù)的修改。
- 資源的調(diào)用的后攔截。
- 以指定的順序通過零,一個或多個過濾器操作servlet、servlet、組或靜態(tài)內(nèi)容。
1.1 過濾器組件例子
- Authentication filters
- Logging and auditing filters
- Image conversion filters
- Data compression filters
- Encryption filters
- Tokenizing filters
- Filters that trigger resource access events
- XSL/T filters that transform XML content
- MIME-type chain filters
- Caching filters
2.主要概念
本章描述了過濾器模型的主要概念。
應(yīng)用程序開發(fā)人員通過實現(xiàn)javax.servlet.Filter接口創(chuàng)建一個過濾器,并提供一個不帶參數(shù)的公共構(gòu)造函數(shù)。該類及構(gòu)建Web 應(yīng)用的靜態(tài)資源和 Servlet 打包在 Web 應(yīng)用歸檔文件中。在部署描述符中使用元素聲明過濾器。通過在部署描述符中定義<filter-mapping>元素,可以為過濾器或過濾器集合配置調(diào)用??梢允褂?servlet 的
邏輯名把過濾器映射到一個特定的 servlet,或者使用 URL 模式把過濾器映射到一組 Servlet 和靜態(tài)內(nèi)容資源。
2.1 過濾器生命周期
The doFilter method of a filter will typically be implemented following this or some subset of the following pattern:
在部署Web應(yīng)用程序之后,在請求導(dǎo)致容器訪問Web資源之前,容器必須定位必須應(yīng)用于Web資源的過濾器列表,如下所述。容器必須確保它已經(jīng)為列表中的每個過濾器實例化了一個適當(dāng)類的過濾器,并調(diào)用它的init(FilterConfig config)方法。這個過濾器可能會拋出一個異常來表示它不能正常工作。如果異常類型為UnavailableException,則容器可以檢查異常的isPermanent屬性,并在稍后的時間選擇重試過濾器。
在部署描述符中,每個<filter>聲明只有一個實例被實例化到容器的每個JVM中。容器提供過濾器部署描述符中聲明的過濾器配置,用于Web應(yīng)用程序的ServletContext的引用,以及初始化參數(shù)集。
當(dāng)容器接收到一個傳入請求時,它將在列表中獲取第一個過濾器實例,并調(diào)用它的doFilter方法,傳入ServletRequest和ServletResponse,并引用它將使用的FilterChain對象。
過濾器的doFilter方法通常會按照以下模式的某個子集實現(xiàn):
該方法檢查請求的header。
該方法可以用定制的ServletRequest或HttpServletRequest實現(xiàn)來包裝請求對象,以修改請求頭或數(shù)據(jù)。
該方法可以將響應(yīng)對象封裝到其doFilter方法中,并定制實現(xiàn)ServletResponse或HttpServletResponse來修改響應(yīng)頭或數(shù)據(jù)。
過濾器可以調(diào)用過濾器鏈中的下一個實體。下一個實體可能是另一個過濾器,或者如果調(diào)用的過濾器是這個鏈的部署描述符中配置的最后一個過濾器,那么下一個實體就是目標(biāo)Web資源。下一個實體的調(diào)用是通過調(diào)用FilterChain對象上的doFilter方法來實現(xiàn)的,并在請求和響應(yīng)中傳遞它可能已經(jīng)創(chuàng)建的包裝版本。過濾器鏈的doFilter方法的實現(xiàn),由容器提供,必須在過濾器鏈中定位下一個實體,并調(diào)用它的doFilter方法,傳入適當(dāng)?shù)恼埱蠛晚憫?yīng)對象?;蛘?,過濾器鏈可以阻止請求,而不是調(diào)用調(diào)用下一個實體,讓過濾器負(fù)責(zé)填充響應(yīng)對象。service方法需要在與應(yīng)用于servlet的所有過濾器相同的線程中運行。
在調(diào)用鏈中的下一個過濾器之后,過濾器可以檢查響應(yīng)頭。
或者,過濾器可能會拋出一個異常來指示處理中的錯誤。如果過濾器在其doFilter處理過程中拋出一個無法使用的異常,容器就不能嘗試?yán)^續(xù)沿著過濾器鏈進行處理。如果異常不是永久性的,它可以選擇在以后重新嘗試整個鏈。
當(dāng)調(diào)用鏈中的最后一個過濾器時,將訪問的下一個實體是鏈末端的目標(biāo)servlet或資源。
在過濾器實例可以被容器從服務(wù)中刪除之前,容器必須首先調(diào)用過濾器上的destroy方法,以使過濾器能夠釋放任何資源并執(zhí)行其他清理操作。
2.2包裝請求和響應(yīng)
過濾概念的核心是包裝請求或響應(yīng)的概念,以便它可以覆蓋行為來執(zhí)行過濾任務(wù)。在這個模型中,開發(fā)人員不僅有能力覆蓋請求和響應(yīng)對象上的現(xiàn)有方法,而且還能提供適合于特定篩選任務(wù)的新API,從而使過濾器或目標(biāo)web資源沿著鏈向下。例如,開發(fā)人員可能希望將響應(yīng)對象擴展到輸出流或?qū)懭肫鞯母呒墑e的輸出對象,例如允許將DOM對象寫回客戶機的API。
為了支持這種類型的過濾器,容器必須支持以下要求。當(dāng)一個過濾器調(diào)用容器的過濾器鏈上的doFilter方法實現(xiàn)中,容器必須確保它通過過濾器鏈中的下一個實體,或到目標(biāo)web資源如果過濾器鏈中的最后一個的請求和響應(yīng)對象,是通過調(diào)用過濾器傳遞到doFilter方法的相同的對象。
包裝器對象標(biāo)識的相同要求適用于從servlet或過濾器到RequestDispatcher的調(diào)用?;騌equestDispatcher向前發(fā)展。包括,當(dāng)調(diào)用者包裝請求或響應(yīng)對象時。在這種情況下,被調(diào)用的servlet所看到的請求和響應(yīng)對象必須是通過調(diào)用servlet或過濾器傳入的相同的包裝器對象。
2.3 過濾器環(huán)境
在部署描述符中使用<initparams>元素,可以將一組初始化參數(shù)關(guān)聯(lián)到一個過濾器。這些參數(shù)的名稱和值可以通過getInitParameter和getInitParameterNames方法在運行時通過過濾器的FilterConfig對象進行篩選。此外,F(xiàn)ilterConfig提供了對Web應(yīng)用程序的ServletContext的訪問,用于加載資源、記錄功能,以及在ServletContext的屬性列表中存儲狀態(tài)。過濾器和過濾器鏈末端的目標(biāo)servlet或資源必須在相同的調(diào)用線程中執(zhí)行。
2.4 在一個Web應(yīng)用中filter的配置
過濾器是通過@WebFilter注釋定義的,如在規(guī)范的第8-72頁中定義的“@WebFilter”或使用部署描述符中的 <filter>元素 。在這個元素中,程序員聲明如下:
過濾器名稱:用于過濾器映射到一個servlet或URL
過濾器類:容器使用來識別過濾器類型
初始化:一個過濾器的初始化參數(shù)
可選地,程序員可以指定圖標(biāo)、文本描述和工具操作的顯示名稱。容器必須實例化定義在部署描述符中每個過濾器聲明的過濾器的Java類的一個實例。因此,如果開發(fā)人員為同一個過濾器類生成兩個過濾器聲明,那么容器將實例化同一個過濾器類的兩個實例。
下面是一個過濾器聲明的例子:
<filter>
<filter-name>Image Filter</filter-name>
<filter-class>com.example.ImageServlet</filter-class>
</filter>
在部署描述符中聲明了過濾器之后,匯編器使用filter-mapping>元素來定義Web應(yīng)用程序中的servlet和靜態(tài)資源。過濾器可以使用<servlet-name>元素與servlet關(guān)聯(lián)。例如,下面的代碼示例將圖像過濾器過濾器映射到ImageServlet servlet:
<filter-mapping>
<filter-name>Image Filter</filter-name>
<servlet-name>ImageServlet</servlet-name>
</filter-mapping>
Filters can be associated with groups of servlets and static content using the <urlpattern> style of filter mapping:
<filter-mapping>
<filter-name>Logging Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
這里,日志過濾器應(yīng)用于Web應(yīng)用程序中的所有servlet和靜態(tài)內(nèi)容頁,因為每個請求URI都匹配“/*”URL模式。
當(dāng)使用<url-pattern>樣式處理<filter-mapping>元素時,容器必須確定是否匹配請求URI,使用第12章中定義的路徑映射規(guī)則,“將請求映射到servlet”。
在構(gòu)建用于特定請求URI的篩選器鏈時,容器使用的順序如下:
- 首先,<url-pattern>匹配的過濾器映射與這些元素在部署描述符中出現(xiàn)的順序相同。
- 接下來,<servlet-name>匹配的過濾器映射與這些元素在部署描述符中出現(xiàn)的順序相同。
如果一個篩選映射包含了<servlet-name> 和<url-pattern>,那么容器必須將篩選映射擴展到多個篩選映射(每個<servletname> and <url-pattern>),保存<servlet-name> and <urlpattern>元素的順序。例如,下面的篩選器映射:
<filter-mapping>
<filter-name>Multiple Mappings Filter</filter-name>
<url-pattern>/foo/*</url-pattern>
<servlet-name>Servlet1</servlet-name>
<servlet-name>Servlet2</servlet-name>
<url-pattern>/bar/*</url-pattern>
</filter-mapping>
is equivalent to:
<filter-mapping>
<filter-name>Multipe Mappings Filter</filter-name>
<url-pattern>/foo/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Multipe Mappings Filter</filter-name>
<servlet-name>Servlet1</servlet-name>
</filter-mapping>
<filter-mapping>
<filter-name>Multipe Mappings Filter</filter-name>
<servlet-name>Servlet2</servlet-name>
</filter-mapping>
<filter-mapping>
<filter-name>Multipe Mappings Filter</filter-name>
<url-pattern>/bar/*</url-pattern>
</filter-mapping>
關(guān)于過濾器鏈順序的要求意味著容器在接收到傳入請求時按如下方式處理請求:
■根據(jù)第126頁上的“映射規(guī)范”的規(guī)則標(biāo)識目標(biāo)Web資源。
■如果存在通過servlet名稱匹配的過濾器并且Web資源具有<servletname>,那么容器會按照部署描述符中聲明的順序構(gòu)建匹配的過濾器鏈。該鏈中的最后一個過濾器對應(yīng)于最后一個<servlet-name>匹配過濾器,并且是調(diào)用目標(biāo)Web資源的過濾器。
■如果存在使用<url-pattern>匹配的過濾器并且<url-pattern>根據(jù)第12.2節(jié)“映射規(guī)范”的規(guī)則匹配請求URI,則容器會構(gòu)建匹配過濾器的<url-pattern>鏈與部署描述符中聲明的順序相同。此鏈中的最后一個過濾器是此請求URI的部署描述符中的最后一個<url-pattern>匹配過濾器。該鏈中的最后一個過濾器是調(diào)用<servlet-name>匹配鏈中的第一個過濾器的過濾器,或者調(diào)用目標(biāo)Web資源(如果沒有)。
預(yù)計高性能Web容器將緩存過濾器鏈,以便它們不需要按照每個請求計算它們。
2.5 Filters and the RequestDispatcher
自Java Servlet規(guī)范2.4版以來的新增功能是能夠配置被請求調(diào)度器(request dispatcher)的forward()和include()調(diào)用的過濾器。
通過在部署描述符中使用新的<dispatcher>元素,開發(fā)人員可以指出過濾器映射是否希望在以下情況下將過濾器應(yīng)用于請求:
1.請求直接來自客戶端。這由具有值REQUEST的<dispatcher>元素或沒有任何<dispatcher>元素指示。
2.請求正在被表示匹配<url-pattern>或<servlet-name>的Web組件的請求調(diào)度程序下使用forward()調(diào)用所處理。這由具有值FORWARD的<dispatcher>元素指示。
3.請求正在代表匹配<url-pattern>或<servlet-name>的Web組件的請求分派器下使用include()進行處理。
呼叫。這由一個值為INCLUDE的<dispatcher>元素表示。
4.該請求正在使用第112頁上的“錯誤處理”中指定的錯誤頁機制處理為與<url-pattern>匹配的錯誤資源。這由具有值ERROR的<dispatcher>元素指示。
5.該請求正在使用第10頁上的“異步處理”中指定的異步上下文分派機制,通過調(diào)度呼叫處理到Web組件。這由具有值A(chǔ)SYNC的<dispatcher>元素指示。
6.或上述1,2,3,4或5的任何組合。
例如:
<filter-mapping>
<filter-name>Logging Filter</filter-name>
<url-pattern>/products/*</url-pattern>
</filter-mapping>
會導(dǎo)致日志過濾器被客戶端啟動的/products/..請求調(diào)用,但不在請求分派器調(diào)用之下調(diào)用,其中請求分派器具有路徑開始/ products/...。LoggingFilter將在請求最初的調(diào)度和恢復(fù)的請求時被調(diào)用。 以下代碼:
<filter-mapping>
<filter-name>Logging Filter</filter-name>
<servlet-name>ProductServlet</servlet-name>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
將導(dǎo)致日志過濾器不會被客戶端請求調(diào)用到ProductServlet,也不會在請求dispatcher forward()調(diào)用下調(diào)用ProductServlet,而是會在請求調(diào)度程序include()調(diào)用中調(diào)用,其中請求分派器的名稱以ProductServlet開始。下面的代碼:
<filter-mapping>
<filter-name>Logging Filter</filter-name>
<url-pattern>/products/*</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
將導(dǎo)致Logging Filter被/products/...開頭的請求和以/products/...開頭的請求分發(fā)器forward()調(diào)用下調(diào)用。
最后,以下代碼使用特殊的servlet名“*”
<filter-mapping>
<filter-name>All Dispatch Filter</filter-name>
<servlet-name>*</servlet-name>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
在按名字或按路徑獲取的所有請求分派器 forward()調(diào)用時該代碼將導(dǎo)致 All Dispatch Filter 被調(diào)用。