Servlet 3.0 之 Filtering

Filters是Java組件,它們在從請求到資源及從資源到響應(yīng)上允許有效負荷與頭部信息的傳遞。

本章描述了Java Servlet v.3.0中提供為行為和靜態(tài)內(nèi)容過濾的輕量級框架的API類和方法。它描述了filter在一個Web應(yīng)用中如何被配置以及它們的實現(xiàn)的約定和機制。

Servlet filters有在線API文檔。filters的配置語法在14章的部署描述符Deployment Descriptor機制中有具體描述。讀者在閱讀本章的時候應(yīng)該使用這些資源作為參考。

什么是filter

filter是一個可重用代碼片段,它能變換HTTP request,response,以及頭部信息的內(nèi)容。Filters通常不會創(chuàng)建一個response,或者如servlet去響應(yīng)一個請求,而是為一個資源修改或者調(diào)整請求,從資源中修改或者調(diào)整響應(yīng)。

Filters能作用于動態(tài)或者靜態(tài)內(nèi)容。為了本章的目的,動態(tài)和靜態(tài)內(nèi)容統(tǒng)稱為Web資源。

對需要使用filters的開發(fā)者,可用的功能如下:

  • 請求調(diào)用之前訪問資源。
  • 被調(diào)用前處理對資源的請求。
  • 通過把請求對象包裝成定制版本來修改請求頭部和數(shù)據(jù)。
  • 通過把響應(yīng)對象包裝成定制版本來修改響應(yīng)頭部和響應(yīng)數(shù)據(jù)。
  • 在它調(diào)用后攔截對一個資源調(diào)用。
  • 按指定的順序作用于一個servlet,一組servlet,或者0個,一個或多個filter上。

攔截器組件的例子

  • 鑒權(quán)filters
  • 日志和審計filters
  • 圖像轉(zhuǎn)換filters
  • 數(shù)據(jù)壓縮filters
  • 加密filters
  • 標記filters
  • 觸發(fā)資源訪問事件
  • 轉(zhuǎn)換XML內(nèi)容的XSL/T filters
  • MIME-type鏈filters
  • 緩存filters

主要概念

應(yīng)用開發(fā)者通過實現(xiàn)javax.servlet.Filter 接口且提供一個無參public類型的構(gòu)造函數(shù)來創(chuàng)建了一個攔截器。這個類與靜態(tài)內(nèi)容和組成Web應(yīng)用的servlets一起打包在Web歸檔文件中。一個filter用<filter>元素在部署描述符中聲明。一個filter或者filter集合能夠通過在部署描述符中定義<filter-mapping>元素來配置來被調(diào)用。這通過按照servlet的邏輯名把filter映射到指定的servlet,或者通過映射一個filter到一個URL pattern來把filter映射到一組servlet和靜態(tài)資源。

Filter 生命周期

在Web應(yīng)用部署以后,并且請求引發(fā)容器訪問Web資源之前,容器必須定位應(yīng)用于上述Web資源的攔截器列表。容器必須確保為列表中每一個filter的類實例化一個攔截器,并且調(diào)用它的init(FilterConfig config) 方法。這個filter可以拋出一個異常來表明它不能正常工作。如果異常是UnavailableException類型,容器可以檢查異常的isPermanent屬性并且可以選擇稍后重試filter。

對于一個容器的每個JVM中,部署描述符中每個<filter>聲明僅有一個實例會被實例化。容器提供一個過濾器config,它指向Web應(yīng)用中ServletContext的引用,以及初始化參數(shù)的集合一樣。

當(dāng)容器接收到一個傳入的請求,它會使用列表中第一個過濾器實例,并且調(diào)用doFilter方法,并傳遞ServletRequest,ServletResponse以及FilterChain對象的一個引用給這個方法。

一個過濾器的doFilter 方法將依據(jù)下列模式或者下列模式的子集來實現(xiàn):

  1. 方法檢查請求的頭部。
  2. 為了修改請求的頭部或者數(shù)據(jù),方法可以用ServletRequest或者HttpServletRequest的定制實現(xiàn)來包裝請求對象。
  3. 方法可以用ServletResponse或者HttpServletResponse的一個實現(xiàn)來包裝響應(yīng)對象,傳遞給它的doFilter方法來修改響應(yīng)頭或者數(shù)據(jù)。
  4. 過濾器可以調(diào)用過濾器鏈中下一個entity。下一個實體可以是另一個過濾器,或者如果發(fā)起調(diào)用的過濾器是部署描述符中配置的最后一個過濾器,下一個實體就是目標Web資源。下一個實體的調(diào)用通過調(diào)用FilterChain對象上的doFilter方法生效,同時把request和response對象或者它們的包裝對象傳遞給doFilter方法。
    另外,過濾器鏈可以通過不調(diào)用下一個實體來阻塞request,讓filter來填充響應(yīng)對象。
  5. 在調(diào)用鏈中下一個過濾器之后,過濾器可以檢查響應(yīng)頭。
  6. 另外,過濾器可以拋出一個異常來表明處理過程中的一個錯誤。如果在它的doFilter處理過程中拋出UnavailableException異常,容器一定不能嘗試繼續(xù)執(zhí)行過濾器鏈。如果異常沒有被標識永久,容器可以選擇稍后重試整個鏈。
  7. 當(dāng)鏈中的最后一個過濾器被調(diào)用,訪問的下一個實體將是鏈尾處的目標servlet或者資源。
  8. 在一個過濾器實例能被容器從service中移除之前,容器必須首先調(diào)用過濾器上的destroy方法來使filter釋放任何資源以及執(zhí)行一些其它清理動作。

包裝Requests和Responses

過濾器概念的核心是包裝一個request或者response,目的是重寫行為來執(zhí)行一個過濾任務(wù)。在這個模型中,開發(fā)者不僅能夠重寫request和response中已存在的方法,而且為執(zhí)行鏈中的過濾器或者目標web資源提供用于特定過濾任務(wù)的新API。比如,開發(fā)者可能希望用更高級的輸出對象(i.e. 輸出流或者writer)來擴展響應(yīng)對象,這些API能夠讓DOM對象被寫回客戶端。

為了支持這種類型的過濾器,容器必須支持下列要求。當(dāng)一個過濾器調(diào)用容器的過濾器鏈實現(xiàn)上的doFilter方法,容器必須保證它傳遞給執(zhí)行鏈中的下一個實體,或者當(dāng)這個過濾器是當(dāng)前鏈中最后一個時就傳遞給目標Web資源的request和response對象與調(diào)用過濾器時傳遞進doFilter方法的對象是同一個對象。

當(dāng)調(diào)用者包裝了request或者response對象,包裝對象的相同要求適用于來自一個servlet或者對RequestDispatcher.forward or RequestDispatcher.include的過濾器的調(diào)用。這種場景下,發(fā)起調(diào)用的servlet的request和response對象必須與調(diào)用servlet或filter時傳遞進來的是相同的包裝對象。

Filter 環(huán)境

一個初始化參數(shù)集合可以在部署描述符中使用<init-params>元素與filter關(guān)聯(lián)起來。這些參數(shù)的名字和值能夠在運行時通過過濾器的FilterConfig對象上的getInitParametergetInitParameterNames方法讓filter訪問到。此外,為了加載資源,記錄日志,以及在ServletContext的屬性列表中保存狀態(tài),FilterConfig能夠訪問Web應(yīng)用的ServletContext。一個Filter和一個過濾器鏈尾的目標servlet或者資源必須在一個調(diào)用線程中執(zhí)行。

在Web應(yīng)用中配置Filters

一個filter通過@WebFilter注解或者在部署描述符中使用<filter>元素來定義。這個元素中,程序員聲明下列元素:

  • filter-name: 把filter映射到一個servlet或URL
  • filter-class: 容器用它來識別filter類型
  • init-params: 給filter配置初始化參數(shù)

程序員能為工具操作指定icons,文本描述以及顯示名字。容器必須準確地為部署描述符中每一個filter聲明實例化一個定義filter的Java類實例。因此,如果開發(fā)者為同一個filter類聲明了兩次,容器為同一個filter類實例化兩個實例。

這是一個filter聲明的例子:

<filter>
  <filter-name>Image Filter</filter-name>
  <filter-class>com.acme.ImageServlet</filter-class>
</filtler>

一旦一個filter在部署描述符中聲明,assembler用<filter-mapping>元素來定義Web應(yīng)用中filter對應(yīng)的servlets和靜態(tài)資源。Filters能使用<servlet-name>元素與一個servlet關(guān)聯(lián)起來。比如,下列代碼把Image Filter filter映射到ImageServletservlet:

<fillter-mapping>
  <filter-name>Image Filter</filter-name>
  <servlet-name>ImageServlet</servlet-name>
</fillter-mapping>

Filters通過使用<url-pattern>filter映射方式與一組servlet和靜態(tài)內(nèi)容關(guān)聯(lián):

<filter-mapping>
  <filter-name>Logging Filter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

這是應(yīng)用于Web應(yīng)用中所有servlets和靜態(tài)內(nèi)容頁面的日志過濾器,因為每一個請求URI都匹配'/*'URL模式。
當(dāng)使用<url-pattern>方式處理一個<filter-mapping>元素,容器必須使用路徑匹配規(guī)則來決定<url-pattern>是否匹配請求URI。

在應(yīng)用于某個特定請求URI的filter鏈中,容器的使用順序如下:

  1. 首先,<url-pattern>按這些元素在部署描述符中出現(xiàn)的順序匹配過濾器映射。
  2. 接著,<servlet-name>按這些元素在部署描述符中出現(xiàn)的順序匹配過濾器映射。

如果一個過濾器映射同時包含<servlet-name>和<url-pattern>,容器必須擴展過濾器映射為多個過濾器映射(每個<servlet-name><url-pattern>對應(yīng)一個),且保留<servlet-name>和<url-pattern>元素的順序。比如,下列過濾器映射:

<filter-mapping>
  <filter-name>Multipe 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>

等價于:

<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>

當(dāng)接受到一個進來的請求,filter鏈的順序的要求容器按如下步驟處理請求:

  • 根據(jù)映射規(guī)范的規(guī)則,識別目標Web資源。
  • 如果有通過servlet名字匹配的過濾器,以及Web資源有一個<servlet-name>,容器會根據(jù)部署描述符中聲明的順序建立過濾器鏈。鏈中最后一個filter對應(yīng)于過濾器匹配的最后一個<servlet-name>,并且最后一個filter是調(diào)用目標Web資源的filter。
  • 如果有使用<url-pattern>的過濾器匹配filter,以及<url-pattern>根據(jù)映射規(guī)范規(guī)則匹配請求URI,容器按照部署描述符中聲明的順序構(gòu)建匹配<url-pattern>的過濾器鏈。鏈中的最后一個過濾器是部署描述符中最后一個匹配<url-pattern>的filter?!版溨凶詈笠粋€過濾器是<servlet-name>匹配鏈中調(diào)用第一個過濾器的過濾器,或者如果沒有<servet-name>,哪兒調(diào)用目標Web資源?!?/li>

高性能Web容器將緩存過濾器鏈,這樣它們就不需要對每一個請求推斷過濾器鏈。

Filters 和 RequestDispatcher

自Java Servlet 2.4 規(guī)范起,可以在請求分發(fā)forward()include()方法中來配置被調(diào)用的filter。

通過在部署描述符中使用新的<dispatcher>元素,開發(fā)者能夠為一個filter-mapping表明,他是否想把這個filter應(yīng)用于請求:

  1. 請求直接來自客戶端。
    這通過一個帶REQUEST值的<dispatcher>元素標識符,或者缺少任何<dispatcher>元素來標識。
  2. 請求在一個使用forward()方法匹配<url-pattern>或<servlet-name>,代表Web組件的請求分發(fā)器下被處理。
    這通過帶值FORWARD的<dispatcher>元素。
  3. 請求在一個使用include()方法匹配<url-pattern>或<servlet-name>,代表Web組件的請求分發(fā)器下被處理。
    這通過帶值INCLUDE的<dispatcher>元素。
  4. 請求正在用錯誤頁面機制跳轉(zhuǎn)到匹配<url-pattern>的錯誤資源。
  5. 請求正在用異步上下文分發(fā)機制跳轉(zhuǎn)到使用一個使用dispatch方法的Web組件。
    這通過帶ASYNC值的<dispatcher>元素標識。
  6. 或者上述條件的任意組合。

比如:

<filter-mapping>
  <filter-name>Logging Filter</filter-name>
  <url-pattern>/products/*</url-pattern>
</filter-mapping>

這會讓Logging Filter被/products/...開頭的客戶端請求調(diào)用,但是不會在一個有以/products/...開頭路徑的請求分發(fā)器調(diào)用下被調(diào)用。LoggingFilter 將會在請求調(diào)度開始時以及恢復(fù)的請求上被調(diào)用。下列代碼:

<filter-mapping>
  <filter-name>Logging Filter</filter-name>
  <servlet-name>ProductServlet</servlet-name>
  <dispatcher>INCLUDE</dispatcher>
</filter-mapping>

這會讓Logging Filter不能被請求ProductServlet的客戶端調(diào)用,也不會被請求分發(fā)器forward()調(diào)用到,但是會在一個請求分發(fā)器include()方法中被調(diào)用。下列代碼:

<filter-mapping>
  <filter-name>Logging Filter</filter-name>
  <url-pattern>/products/*</url-pattern>
  <dispatcher>FORWARD</dispatcher>
  <dispatcher>REQUEST</dispatcher>
</filter-mapping>

這會讓Logging Filter被以/products/..開頭的客戶端請求,以及在一個以ProductServlet開頭的請求分發(fā)器的請求分發(fā)器forward()方法下調(diào)用。下列代碼:

<filter-mapping>
  <filter-name>Logging Filter</filter-name>
  <url-pattern>/products/*</url-pattern>
  <dispatcher>FORWARD</dispatcher>
  <dispatcher>REQUEST</dispatcher>
</filter-mapping>

這會讓Logging Filter被以/products/..開頭的客戶端請求以及在一個請求分發(fā)器有/products/..開頭的路徑的請求分發(fā)forward()方法下調(diào)用。

最后,下列代碼使用特殊的servlet名字'*':

<filter-mapping>
  <filter-name>All Dispatch Filter</filter-name>
  <servlet-name>*</servlet-name>
  <dispatcher>FORWARD</dispatcher>
</filter-mapping>

這段代碼會讓所有的分發(fā)過濾器在請求分發(fā)forward()方法上被調(diào)用。

翻譯自 Java Servlet Specification
Version 3.0 Rev a
Author:Rajiv Mordani
Date: December 2010

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Servlet Interface 是Java Servlet API的核心抽象。所有的servlets都直接或者...
    Lucky_Micky閱讀 1,877評論 2 2
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,551評論 19 139
  • 本章聊一聊ServletContext 3.0規(guī)范中定義的注解以及在web應(yīng)用中使用的框架和庫的可插拔性的提升。 ...
    Lucky_Micky閱讀 6,180評論 0 3
  • 一個Web應(yīng)用是一個Web服務(wù)器上眾多資源的集合,它包括了servlets,HTML頁面,類,和其它組成一個完整應(yīng)...
    Lucky_Micky閱讀 1,847評論 0 2
  • 1. ServletContext接口介紹 ServletContext接口定義了servlet運行時Web應(yīng)用的...
    Lucky_Micky閱讀 1,787評論 0 3

友情鏈接更多精彩內(nèi)容