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):
- 方法檢查請求的頭部。
- 為了修改請求的頭部或者數(shù)據(jù),方法可以用ServletRequest或者HttpServletRequest的定制實現(xiàn)來包裝請求對象。
- 方法可以用ServletResponse或者HttpServletResponse的一個實現(xiàn)來包裝響應(yīng)對象,傳遞給它的doFilter方法來修改響應(yīng)頭或者數(shù)據(jù)。
- 過濾器可以調(diào)用過濾器鏈中下一個entity。下一個實體可以是另一個過濾器,或者如果發(fā)起調(diào)用的過濾器是部署描述符中配置的最后一個過濾器,下一個實體就是目標Web資源。下一個實體的調(diào)用通過調(diào)用FilterChain對象上的doFilter方法生效,同時把request和response對象或者它們的包裝對象傳遞給doFilter方法。
另外,過濾器鏈可以通過不調(diào)用下一個實體來阻塞request,讓filter來填充響應(yīng)對象。 - 在調(diào)用鏈中下一個過濾器之后,過濾器可以檢查響應(yīng)頭。
- 另外,過濾器可以拋出一個異常來表明處理過程中的一個錯誤。如果在它的doFilter處理過程中拋出UnavailableException異常,容器一定不能嘗試繼續(xù)執(zhí)行過濾器鏈。如果異常沒有被標識永久,容器可以選擇稍后重試整個鏈。
- 當(dāng)鏈中的最后一個過濾器被調(diào)用,訪問的下一個實體將是鏈尾處的目標servlet或者資源。
- 在一個過濾器實例能被容器從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對象上的getInitParameter和getInitParameterNames方法讓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鏈中,容器的使用順序如下:
- 首先,<url-pattern>按這些元素在部署描述符中出現(xiàn)的順序匹配過濾器映射。
- 接著,<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)用于請求:
- 請求直接來自客戶端。
這通過一個帶REQUEST值的<dispatcher>元素標識符,或者缺少任何<dispatcher>元素來標識。 - 請求在一個使用forward()方法匹配<url-pattern>或<servlet-name>,代表Web組件的請求分發(fā)器下被處理。
這通過帶值FORWARD的<dispatcher>元素。 - 請求在一個使用include()方法匹配<url-pattern>或<servlet-name>,代表Web組件的請求分發(fā)器下被處理。
這通過帶值INCLUDE的<dispatcher>元素。 - 請求正在用錯誤頁面機制跳轉(zhuǎn)到匹配<url-pattern>的錯誤資源。
- 請求正在用異步上下文分發(fā)機制跳轉(zhuǎn)到使用一個使用dispatch方法的Web組件。
這通過帶ASYNC值的<dispatcher>元素標識。 - 或者上述條件的任意組合。
比如:
<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