Servlet 3.0 之 Filtering

weixin_33936401發表於2016-02-16

Filters是Java元件,它們在從請求到資源及從資源到響應上允許有效負荷與頭部資訊的傳遞。

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

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

什麼是filter

filter是一個可重用程式碼片段,它能變換HTTP request,response,以及頭部資訊的內容。Filters通常不會建立一個response,或者如servlet去響應一個請求,而是為一個資源修改或者調整請求,從資源中修改或者調整響應。

Filters能作用於動態或者靜態內容。為了本章的目的,動態和靜態內容統稱為Web資源。

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

  • 請求呼叫之前訪問資源。
  • 被呼叫前處理對資源的請求。
  • 通過把請求物件包裝成定製版本來修改請求頭部和資料。
  • 通過把響應物件包裝成定製版本來修改響應頭部和響應資料。
  • 在它呼叫後攔截對一個資源呼叫。
  • 按指定的順序作用於一個servlet,一組servlet,或者0個,一個或多個filter上。

攔截器元件的例子

  • 鑑權filters
  • 日誌和審計filters
  • 影像轉換filters
  • 資料壓縮filters
  • 加密filters
  • 標記filters
  • 觸發資源訪問事件
  • 轉換XML內容的XSL/T filters
  • MIME-type鏈filters
  • 快取filters

主要概念

應用開發者通過實現javax.servlet.Filter 介面且提供一個無參public型別的建構函式來建立了一個攔截器。這個類與靜態內容和組成Web應用的servlets一起打包在Web歸檔檔案中。一個filter用<filter>元素在部署描述符中宣告。一個filter或者filter集合能夠通過在部署描述符中定義<filter-mapping>元素來配置來被呼叫。這通過按照servlet的邏輯名把filter對映到指定的servlet,或者通過對映一個filter到一個URL pattern來把filter對映到一組servlet和靜態資源。

Filter 生命週期

在Web應用部署以後,並且請求引發容器訪問Web資源之前,容器必須定位應用於上述Web資源的攔截器列表。容器必須確保為列表中每一個filter的類例項化一個攔截器,並且呼叫它的init(FilterConfig config) 方法。這個filter可以丟擲一個異常來表明它不能正常工作。如果異常是UnavailableException型別,容器可以檢查異常的isPermanent屬性並且可以選擇稍後重試filter。

對於一個容器的每個JVM中,部署描述符中每個<filter>宣告僅有一個例項會被例項化。容器提供一個過濾器config,它指向Web應用中ServletContext的引用,以及初始化引數的集合一樣。

當容器接收到一個傳入的請求,它會使用列表中第一個過濾器例項,並且呼叫doFilter方法,並傳遞ServletRequest,ServletResponse以及FilterChain物件的一個引用給這個方法。

一個過濾器的doFilter 方法將依據下列模式或者下列模式的子集來實現:

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

包裝Requests和Responses

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

為了支援這種型別的過濾器,容器必須支援下列要求。當一個過濾器呼叫容器的過濾器鏈實現上的doFilter方法,容器必須保證它傳遞給執行鏈中的下一個實體,或者當這個過濾器是當前鏈中最後一個時就傳遞給目標Web資源的request和response物件與呼叫過濾器時傳遞進doFilter方法的物件是同一個物件。

當呼叫者包裝了request或者response物件,包裝物件的相同要求適用於來自一個servlet或者對RequestDispatcher.forward or RequestDispatcher.include的過濾器的呼叫。這種場景下,發起呼叫的servlet的request和response物件必須與呼叫servlet或filter時傳遞進來的是相同的包裝物件。

Filter 環境

一個初始化引數集合可以在部署描述符中使用<init-params>元素與filter關聯起來。這些引數的名字和值能夠在執行時通過過濾器的FilterConfig物件上的getInitParametergetInitParameterNames方法讓filter訪問到。此外,為了載入資源,記錄日誌,以及在ServletContext的屬性列表中儲存狀態,FilterConfig能夠訪問Web應用的ServletContext。一個Filter和一個過濾器鏈尾的目標servlet或者資源必須在一個呼叫執行緒中執行。

在Web應用中配置Filters

一個filter通過@WebFilter註解或者在部署描述符中使用<filter>元素來定義。這個元素中,程式設計師宣告下列元素:

  • filter-name: 把filter對映到一個servlet或URL
  • filter-class: 容器用它來識別filter型別
  • init-params: 給filter配置初始化引數

程式設計師能為工具操作指定icons,文字描述以及顯示名字。容器必須準確地為部署描述符中每一個filter宣告例項化一個定義filter的Java類例項。因此,如果開發者為同一個filter類宣告瞭兩次,容器為同一個filter類例項化兩個例項。

這是一個filter宣告的例子:

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

一旦一個filter在部署描述符中宣告,assembler用<filter-mapping>元素來定義Web應用中filter對應的servlets和靜態資源。Filters能使用<servlet-name>元素與一個servlet關聯起來。比如,下列程式碼把Image Filter filter對映到ImageServletservlet:

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

Filters通過使用<url-pattern>filter對映方式與一組servlet和靜態內容關聯:

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

這是應用於Web應用中所有servlets和靜態內容頁面的日誌過濾器,因為每一個請求URI都匹配'/*'URL模式。
當使用<url-pattern>方式處理一個<filter-mapping>元素,容器必須使用路徑匹配規則來決定<url-pattern>是否匹配請求URI。

在應用於某個特定請求URI的filter鏈中,容器的使用順序如下:

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

如果一個過濾器對映同時包含<servlet-name>和<url-pattern>,容器必須擴充套件過濾器對映為多個過濾器對映(每個<servlet-name><url-pattern>對應一個),且保留<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>

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

  • 根據對映規範的規則,識別目標Web資源。
  • 如果有通過servlet名字匹配的過濾器,以及Web資源有一個<servlet-name>,容器會根據部署描述符中宣告的順序建立過濾器鏈。鏈中最後一個filter對應於過濾器匹配的最後一個<servlet-name>,並且最後一個filter是呼叫目標Web資源的filter。
  • 如果有使用<url-pattern>的過濾器匹配filter,以及<url-pattern>根據對映規範規則匹配請求URI,容器按照部署描述符中宣告的順序構建匹配<url-pattern>的過濾器鏈。鏈中的最後一個過濾器是部署描述符中最後一個匹配<url-pattern>的filter。“鏈中最後一個過濾器是<servlet-name>匹配鏈中呼叫第一個過濾器的過濾器,或者如果沒有<servet-name>,哪兒呼叫目標Web資源。”

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

Filters 和 RequestDispatcher

自Java Servlet 2.4 規範起,可以在請求分發forward()include()方法中來配置被呼叫的filter。

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

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

比如:

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

這會讓Logging Filter被/products/...開頭的客戶端請求呼叫,但是不會在一個有以/products/...開頭路徑的請求分發器呼叫下被呼叫。LoggingFilter 將會在請求排程開始時以及恢復的請求上被呼叫。下列程式碼:

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

這會讓Logging Filter不能被請求ProductServlet的客戶端呼叫,也不會被請求分發器forward()呼叫到,但是會在一個請求分發器include()方法中被呼叫。下列程式碼:

<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開頭的請求分發器的請求分發器forward()方法下呼叫。下列程式碼:

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

這會讓Logging Filter被以/products/..開頭的客戶端請求以及在一個請求分發器有/products/..開頭的路徑的請求分發forward()方法下呼叫。

最後,下列程式碼使用特殊的servlet名字'*':

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

這段程式碼會讓所有的分發過濾器在請求分發forward()方法上被呼叫。

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

相關文章