- 輕量級Java EE企業應用開發實戰
- 柳偉衛編著
- 2939字
- 2022-07-29 14:31:19
3.3 過濾器
過濾器是Java組件,它允許動態改變進入資源的請求和從資源返回的響應的有效載荷(Payload)和頭(Header)信息。
Java Servlet API類和方法提供了用于過濾動態和靜態內容的輕量級框架。它描述了如何在Web應用程序中配置過濾器,以及實現過濾器的約定和語義。
3.3.1 什么是過濾器
過濾器是一組可重用的代碼,能轉換HTTP請求、響應和頭信息的內容。過濾器通常不產生響應或者像Servlet那樣對請求做出響應,而是修改或者調整對資源的請求,修改和調整來自資源的響應。
過濾器可以作用在動態或靜態內容上。動態和靜態內容指的是Web資源。
過濾器可以用于以下場景:
- 對資源的請求執行之前訪問資源。
- 對資源的請求之前對請求進行處理。
- 通過對請求對象進行自定義版本包裝來對請求頭和數據進行修改。
- 提供自定義版本的響應對象來修改響應頭和響應數據。
- 調用資源后對其進行偵聽。
- 在一個Servlet、一組Servlet或靜態內容上按照指定的順序執行0個、1個或多個過濾器。
在現實項目中,經常使用過濾器來實現以下功能:
- 驗證過濾器。
- 日志和審計過濾器。
- 圖片轉換過濾器。
- 數據壓縮過濾器。
- 加密過濾器。
- 標記化過濾器。
- 觸發資源訪問事件過濾器。
- 轉換XML內容的XSL/T過濾器。
- MIME-type鏈過濾器。
- 緩存過濾器。
應用開發者通過實現javax.servlet.Filter接口并且提供一個無參公用構造器創建過濾器。此類跟構建Web應用的靜態內容和Servlet一起打包進Web歸檔中。過濾器在部署描述符中使用<filter>元素聲明。一個過濾器或過濾器集合可以通過在部署描述符里定義<filter-mapping>元素來配置調用。通過映射Servlet的邏輯名稱把過濾器映射到一個特別的Servlet,通過映射一個URL模式將過濾器映射到一組Servlet和靜態內容資源來完成配置。
3.3.2 過濾器生命周期
Web應用部署之后,在請求導致容器訪問Web資源之前,容器必須定位應用到Web資源的過濾器列表。容器必須保證列表中的每一個過濾器(元素)都實例化了一個適當類的過濾器(對象),然后調用它的init(FilterConfig config)方法。過濾器可能會拋出一個異常,表明它不能正常運轉。如果異常是UnavailableException類型的,容器就可以檢查異常的isPermanent屬性,并選擇將來某個時候重試該過濾器。
在部署描述符中聲明的每個<filter>在容器的每個JVM中只實例化一個實例。容器提供在過濾器部署描述符中聲明的過濾器配置、Web應用的ServletContext的引用和一組初始化參數。
當容器接收到一個進入的請求時,容器獲取列表中的第一個過濾器的實例并且調用它的doFilter,傳入ServletRequest和ServletResponse,以及一個它將使用的FilterChain的引用。過濾器的doFilter方法通常按照下面模式的子集來實現:
- 該方法檢查請求的頭。
- 為了修改請求頭或數據,該方法可能使用ServletRequest或HttpServletRequest的自定義實現來包裝請求對象。
- 該方法可能用ServletResponse或HttpServletResponse的自定義實現來包裝響應對象,傳入doFilter方法來修改響應頭或數據。
- 該過濾器可能會調用過濾器鏈中的下一個實體。下一個實體可能是另一個過濾器,如果執行調用的過濾器是此過濾器鏈中在部署描述符中為其配置的最后一個過濾器,下一個實體就是目標Web資源。FilterChain對象調用doFilter方法將影響下一個實體的調用并傳入它被調用時的request和response或者傳入它可能已創建的包裝版本。過濾器鏈的doFilter方法的實現由容器提供,必須定位過濾器鏈中的下一個實體并且調用它的doFilter方法,傳入恰當的request和response對象。或者,過濾器鏈可以通過不調用下一個實體阻塞請求,由離開的過濾器負責填充響應對象。service方法必須與應用到Servlet的所有過濾器運行在相同的線程中。
- 鏈中的下一個過濾器調用之后,該過濾器可能檢查響應的頭。
- 過濾器可以拋出一個異常表明一個錯誤正在處理。如果過濾器在其doFilter處理中拋出了一個UnavailableException,容器不應該嘗試繼續處理剩下的過濾器鏈,如果異常沒有標識為永久的,它就可能選擇晚些時候重試整個過濾器鏈。
- 當鏈中的最后一個過濾器被調用時,下一個訪問的實體是目標Servlet或者位于鏈尾的資源。
- 在一個過濾器實例可以被容器從服務中移除之前,容器必須首先調用過濾器的destroy方法使過濾器釋放所有資源和執行其他清除操作。
3.3.3 包裝請求和響應
過濾的核心概念是包裝請求和響應以便它能覆蓋行為執行過濾任務。在此模型中,開發者不僅有能力覆蓋請求和響應對象已存在的方法,也能提供新的API對鏈中剩余的過濾器或目標Web資源執行特別過濾任務。例如,開發者可能希望使用更高級輸出對象(output stream或writer)來擴展響應對象,比如允許DOM對象被寫回客戶端的API。
為了支持這種風格的過濾器,容器必須滿足下面的要求。當過濾器調用容器的過濾器鏈實現的doFilter方法時,容器必須確保傳遞給過濾器鏈的下一個實體或目標Web資源(如果此過濾器是鏈中的最后一個)的request和response對象與傳入調用過濾器的doFilter方法的(request和response)對象相同。
當調用者包裝request或response對象時,對包裝對象標識的要求同樣適用于從Servlet或Filter到RequestDispatcher.forward或RequestDispatcher.include的調用。
3.3.4 過濾器環境
通過在部署描述符中使用<init-params>元素可以將一組初始化參數與過濾器關聯。通過過濾器的FilterConfig對象的getInitParameter和getInitParameterNames方法,過濾器可以在運行時使用這些參數的名稱和值。除此之外,FilterConfig提供對Web應用的ServletContext的訪問來加載資源、記錄日志和在ServletContext的屬性列表中存儲狀態。
3.3.5 Web應用中過濾器的配置
過濾器既可以通過@WebFilter注解來定義,又可以通過在部署描述符中使用<filter>來定義。在此元素中,開發人員可以進行以下聲明:
- filter-name:用來映射過濾器到Servlet或URL。
- filter-class:容器用來識別過濾器類型。
- init-params:過濾器的初始化參數。
容器必須為在部署描述中聲明的每一個過濾器準確實例化一個定義該過濾器的Java類的實例。因此,如果開發者為同一個過濾器類做了兩個過濾器聲明,容器就會實例化兩個相同過濾器類的實例。
這里有一個過濾器聲明的例子:

一旦過濾器在部署描述中聲明,組裝者使用<filter-mapping>元素定義此過濾器將應用在此Web應用中的哪些Servlet和靜態資源上。使用<servlet-name>元素,過濾器可以關聯一個Servlet。例如,下面的代碼示例將映射Image Filter過濾器到ImageServlet上:

使用<url-pattern>風格的過濾器映射,過濾器可以關聯一組Servlet和靜態內容:

在此,Logging過濾器被應用到Web應用中所有的Servlet和靜態內容頁面,因為每個請求URI都匹配“/*”URL模式。
容器為特定請求URI創建應用到它的過濾器鏈的順序如下:
- 首先,按照這些元素在部署描述符中出現的順序,<url-pattern>匹配過濾器映射。
- 接下來,按照這些元素在部署描述符中出現的順序,<servlet-name>匹配過濾器映射。
如果一個過濾器映射同時包含<servlet-name>和<url-pattern>,容器就必須展開此過濾器映射為多個過濾器映射(每個<servlet- name>和<url-pattern>一個映射),保留<servlet-name>和<url-pattern>元素的順序。例如下面的過濾器映射:

3.3.6 過濾器和請求分派器
從Java Servlet規范新版本2.4起可以配置請求分派器forward()和include()調用時被調用的過濾器。在部署描述符中使用新的<dispatcher>元素,開發者可以為filter-mapping指明希望此攔截器應用到的請求:
- 直接來自客戶端的請求。可以由帶有REQUEST值的<dispatcher>元素或者沒有任何<dispatcher>元素表示。
- 該請求正在請求分配器下處理,該分配器代表使用forward()調用與<url-pattern>或<servlet-name>匹配的Web組件。這由具有值FORWARD的<dispatcher>元素指示。
- 該請求正在請求分配器下處理,該分配器代表使用include()調用與<url-pattern>或<servlet-name>匹配的Web組件。這由具有值INCLUDE的<dispatcher>元素指示。
- 對匹配<url-pattern>的錯誤資源,使用錯誤頁面機制來處理請求。用一個帶有值ERROR的<dispatcher>元素表示。
- 正在使用異步上下文分派機制時,使用分派調用將請求處理到Web組件。這由帶有值ASYNC的<dispatcher>元素指示。
例如:

以/products/...開始的客戶端請求將導致Logging Filter被調用,但在以路徑開頭為/products/...的請求分派器的請求分派調用時不會。Logging Filter在請求的初始分派和恢復請求時都會被調用。
下面的代碼:

客戶端對ProductServlet的請求和請求分派器forward()調用到ProductServlet時不會導致Logging Filter被調用,但是以ProductServlet開始的名字的請求分派器include()調用時會調用它。
下面的代碼:

以/products/...開始的客戶端請求和路徑開頭為/products/...的請求分派器在forward()調用時會導致Logging Filter被調用。
最后,下面的代碼使用了特殊的Servlet名字“*”:

按照名字或路徑獲得的所有請求分派器forward()調用時,這些代碼會導致All Dispatch Filter被調用。