上下文
表示層請求處理機制接收許多不一樣類型的請求,這須要各類類型的處理。有些請求只是簡單地轉發到適當的處理程序組件,而其餘請求必須先進行修改,審覈或解壓縮,而後再進一步處理。html
問題
客戶端Web請求和響應的預處理和後處理是必需的。java
當請求進入Web應用程序時,它一般必須在主處理階段以前經過幾個入口測試。例如,編程
客戶端已經過身份驗證嗎?瀏覽器
客戶端是否有有效的會話?
客戶端的IP地址是否來自受信任的網絡?
請求路徑是否違反任何約束?
客戶端用於發送數據的編碼是什麼?
咱們是否支持客戶端的瀏覽器類型?
其中一些檢查是測試,結果爲是或否,以決定是否繼續處理。其餘檢查將進入的數據流操縱爲適合處理的形式。安全
經典解決方案包括一系列條件檢查,任何失敗的檢查都會停止請求。嵌套的if / else語句是一種標準策略,可是此解決方案會致使代碼易碎,而且會產生編程的複製粘貼風格,由於過濾的流程和過濾器的操做已編譯到應用程序中。網絡
以靈活,簡便的方式解決此問題的關鍵是要有一種簡單的機制來添加和刪除處理組件,其中每一個組件均可以完成特定的過濾動做。oracle
強制
常見處理(例如檢查數據編碼方案或記錄有關每一個請求的信息)將針對每一個請求完成。app
須要集中通用邏輯。
服務應該易於添加或刪除,並且不會影響現有組件,所以能夠將它們以多種組合使用,例如
記錄和認證
調試和轉換特定客戶端的輸出
輸入的解壓縮轉換編碼方案
解
建立可插拔過濾器以標準方式處理通用服務,而無需更改核心請求處理代碼。篩選器攔截傳入的請求和傳出的響應,從而容許進行預處理和後處理。咱們可以絕不干擾地添加和刪除這些過濾器,而無需更改現有代碼。函數
實際上,咱們可以使用各類經常使用服務(例如安全性,日誌記錄,調試等)來修飾咱們的主處理程序。這些過濾器是獨立於主要應用程序代碼的組件,能夠聲明性地添加或刪除它們。例如,能夠修改部署配置文件以設置過濾器鏈。同一配置文件可能包含特定URL到此過濾器鏈的映射。當客戶端請求與該配置的URL映射匹配的資源時,將在調用請求的目標資源以前按順序處理鏈中的篩選器。測試
結構體
圖7.1表示了截取濾波器模式。
Figure 7.1 Intercepting Filter pattern class diagram
參加者和責任
Figure 7.2 represents the Intercepting Filter pattern.
Figure 7.2 Intercepting Filter sequence diagram
FilterManager
FilterManager管理過濾器處理。它以正確的順序建立具備適當過濾器的FilterChain,並啓動處理。
FilterChain
FilterChain是獨立過濾器的有序集合。
過濾器一,過濾器二,過濾器三
這些是映射到目標的單個過濾器。 FilterChain協調其處理。
目標
目標是客戶端請求的資源。
策略
自定義過濾策略
過濾器是經過開發人員定義的自定義策略實現的。與首選的標準過濾器策略相比,它的靈活性和功能不足,後者將在下一部分中介紹,而且僅在支持2.3 Servlet規範的容器中可用。自定義篩選器策略的功能較弱,由於它沒法以標準且可移植的方式提供請求和響應對象的包裝。此外,沒法修改請求對象,若是要使用過濾器控制輸出流,則必須引入某種緩衝機制。爲了實現自定義過濾器策略,開發人員可使用裝飾器模式[GoF]將過濾器包裝在覈心請求處理邏輯周圍。例如,可能有一個調試過濾器,它包裝了身份驗證過濾器。例7.1和例7.2顯示瞭如何以編程方式建立此機制:
Example 7.1 Implementing a Filter - Debugging Filter public class DebuggingFilter implements Processor { private Processor target; public DebuggingFilter(Processor myTarget) { target = myTarget; } public void execute(ServletRequest req, ServletResponse res) throws IOException, ServletException { //Do some filter processing here, such as // displaying request parameters target.execute(req, res); } } Example 7.2 Implementing a Filter - Core Processor public class CoreProcessor implements Processor { private Processor target; public CoreProcessor() { this(null); } public CoreProcessor(Processor myTarget) { target = myTarget; } public void execute(ServletRequest req, ServletResponse res) throws IOException, ServletException { //Do core processing here } }
在Servlet控制器中,咱們委託一個名爲processRequest的方法來處理傳入的請求,如示例7.3所示。
Example 7.3 Handling Requests public void processRequest(ServletRequest req, ServletResponse res) throws IOException, ServletException { Processor processors = new DebuggingFilter( new AuthenticationFilter(new CoreProcessor())); processors.execute(req, res); //Then dispatch to next resource, which is probably // the View to display dispatcher.dispatch(req, res); }
僅出於示例目的,假設每一個處理組件在執行時都寫入標準輸出。 例7.4顯示了可能的執行輸出。
例7.4寫入標準輸出的消息
調試過濾器預處理已完成...
驗證過濾器處理已完成...
核心處理完成...
調試過濾器後處理已完成...
處理器鏈按順序執行。 除鏈中的最後一個處理器外,每一個處理器均被視爲過濾器。 最終處理器組件是咱們封裝要爲每一個請求完成的核心處理的地方。 給定這種設計,當咱們想要修改處理請求的方式時,咱們將須要更改CoreProcessor類以及任何過濾器類中的代碼。
圖7.3是描述使用示例7.1,示例7.2和示例7.3的過濾器代碼時控制流程的順序圖。
圖7.3自定義過濾器策略的序列圖,裝飾器實現
注意,當咱們使用裝飾器實現時,儘管使用通用接口,每一個過濾器都會直接在下一個過濾器上調用。 或者,可使用FilterManager和FilterChain實現此策略。 在這種狀況下,這兩個組件協調並管理過濾器處理,而且各個過濾器不直接相互通訊。 儘管它仍然是一個自定義策略,但該設計近似於Servlet 2.3兼容的實現。 例7.5列出了建立FilterChain的FilterManager類,如例7.6所示。 FilterChain以適當的順序將過濾器添加到鏈中(爲簡潔起見,這是在FilterChain構造函數中完成的,但一般會代替註釋來完成),處理過濾器,最後處理目標資源。 圖7.4是此代碼的序列圖。
Figure 7.4 Sequence diagram for Custom Filter Strategy, nondecorator implementation
Example 7.5 FilterManager - Custom Filter Strategy public class FilterManager { public void processFilter(Filter target, javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException { FilterChain filterChain = new FilterChain(); // The filter manager builds the filter chain here // if necessary // Pipe request through Filter Chain filterChain.processFilter(request, response); //process target resource target.execute(request, response); } } Example 7.6 FilterChain - Custom Filter Strategy public class FilterChain { // filter chain private Vector myFilters = new Vector(); // Creates new FilterChain public FilterChain() { // plug-in default filter services for example // only. This would typically be done in the // FilterManager, but is done here for example // purposes addFilter(new DebugFilter()); addFilter(new LoginFilter()); addFilter(new AuditFilter()); } public void processFilter( javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException { Filter filter; // apply filters Iterator filters = myFilters.iterator(); while (filters.hasNext()) { filter = (Filter)filters.next(); // pass request & response through various // filters filter.execute(request, response); } } public void addFilter(Filter filter) { myFilters.add(filter); } }
此策略不容許咱們建立所需的靈活或強大的過濾器。首先,以編程方式添加和刪除過濾器。儘管咱們能夠編寫一種專有的機制來經過配置文件處理添加和刪除過濾器,可是咱們仍然沒法包裝請求和響應對象。此外,若是沒有複雜的緩衝機制,此策略將沒法提供靈活的後處理。
標準過濾器策略利用2.3 Servlet規範的功能爲這些問題提供瞭解決方案,該規範爲過濾器困境提供了標準解決方案。
注意
撰寫本文時,Servlet 2.3規範處於最終草案形式。
標準過濾器策略
過濾器使用部署描述符以聲明方式進行控制,如Servlet規範版本2.3中所述,截至撰寫本文時,該過濾器處於最終草案形式。 Servlet 2.3規範包括一個標準機制,用於構建過濾器鏈並從這些鏈中絕不干擾地添加和刪除過濾器。篩選器圍繞接口構建,並經過修改Web應用程序的部署描述符以聲明方式添加或刪除。
該策略的示例將是建立一個過濾器,該過濾器可預處理任何編碼類型的請求,以即可以在咱們的核心請求處理代碼中以相似方式處理每一個請求。爲何這有必要?包含文件上載的HTML表單使用的編碼類型不一樣於大多數表單。所以,經過簡單的getParameter()調用沒法得到伴隨上傳的表單數據。所以,咱們建立了兩個過濾器來預處理請求,將全部編碼類型轉換爲統一的格式。咱們選擇的格式是使全部表單數據均可以用做請求屬性。
一個過濾器處理application / x-www-form-urlencoded類型的標準表單編碼,另外一個過濾器處理不太常見的編碼類型multipart / form-data,該編碼類型用於包含文件上傳的表單。篩選器將全部表單數據轉換爲請求屬性,所以核心請求處理機制能夠以相同的方式處理每一個請求,而不是使用特殊的大小寫來表示不一樣的編碼。
例7.8展現了一個過濾器,該過濾器使用通用的應用程序表單編碼方案來轉換請求。例7.9顯示了過濾器,該過濾器處理使用多部分表單編碼方案的請求的翻譯。這些過濾器的代碼基於Servlet規範2.3的最終草案。還使用了基本過濾器,這兩個過濾器都從中繼承(請參見「基本過濾器策略」一節)。如例7.7所示,基本過濾器爲標準過濾器回調方法提供了默認行爲。
Example 7.7 Base Filter - Standard Filter Strategy public class BaseEncodeFilter implements javax.servlet.Filter { private javax.servlet.FilterConfig myFilterConfig; public BaseEncodeFilter() { } public void doFilter( javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse, javax.servlet.FilterChain filterChain) throws java.io.IOException, javax.servlet.ServletException { filterChain.doFilter(servletRequest, servletResponse); } public javax.servlet.FilterConfig getFilterConfig() { return myFilterConfig; } public void setFilterConfig( javax.servlet.FilterConfig filterConfig) { myFilterConfig = filterConfig; } } Example 7.8 StandardEncodeFilter - Standard Filter Strategy public class StandardEncodeFilter extends BaseEncodeFilter { // Creates new StandardEncodeFilter public StandardEncodeFilter() { } public void doFilter(javax.servlet.ServletRequest servletRequest,javax.servlet.ServletResponse servletResponse,javax.servlet.FilterChain filterChain) throws java.io.IOException, javax.servlet.ServletException { String contentType = servletRequest.getContentType(); if ((contentType == null) || contentType.equalsIgnoreCase( "application/x-www-form-urlencoded")) { translateParamsToAttributes(servletRequest, servletResponse); } filterChain.doFilter(servletRequest, servletResponse); } private void translateParamsToAttributes( ServletRequest request, ServletResponse response) { Enumeration paramNames = request.getParameterNames(); while (paramNames.hasMoreElements()) { String paramName = (String) paramNames.nextElement(); String [] values; values = request.getParameterValues(paramName); System.err.println("paramName = " + paramName); if (values.length == 1) request.setAttribute(paramName, values[0]); else request.setAttribute(paramName, values); } } } Example 7.9 MultipartEncodeFilter - Standard Filter Strategy public class MultipartEncodeFilter extends BaseEncodeFilter { public MultipartEncodeFilter() { } public void doFilter(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse,javax.servlet.FilterChain filterChain) throws java.io.IOException, javax.servlet.ServletException { String contentType = servletRequest.getContentType(); // Only filter this request if it is multipart // encoding if (contentType.startsWith( "multipart/form-data")){ try { String uploadFolder = getFilterConfig().getInitParameter( "UploadFolder"); if (uploadFolder == null) uploadFolder = "."; /** The MultipartRequest class is: * Copyright (C) 2001 by Jason Hunter * <jhunter@servlets.com>. All rights reserved. **/ MultipartRequest multi = new MultipartRequest(servletRequest, uploadFolder, 1 * 1024 * 1024 ); Enumeration params = multi.getParameterNames(); while (params.hasMoreElements()) { String name = (String)params.nextElement(); String value = multi.getParameter(name); servletRequest.setAttribute(name, value); } Enumeration files = multi.getFileNames(); while (files.hasMoreElements()) { String name = (String)files.nextElement(); String filename = multi.getFilesystemName(name); String type = multi.getContentType(name); File f = multi.getFile(name); // At this point, do something with the // file, as necessary } } catch (IOException e) { LogManager.logMessage( "error reading or saving file"+ e); } } // end if filterChain.doFilter(servletRequest, servletResponse); } // end method doFilter() }
示例7.10中的如下摘錄來自包含此示例的Web應用程序的部署描述符。 它顯示瞭如何註冊這兩個過濾器,而後將它們映射到資源(在本例中爲簡單的測試servlet)。 另外,該示例的時序圖如圖7.5所示。
Example 7.10 Deployment Descriptor - Standard Filter Strategy . . . <filter> <filter-name>StandardEncodeFilter</filter-name> <display-name>StandardEncodeFilter</display-name> <description></description> <filter-class> corepatterns.filters.encodefilter. StandardEncodeFilter</filter-class> </filter> <filter> <filter-name>MultipartEncodeFilter</filter-name> <display-name>MultipartEncodeFilter</display-name> <description></description> <filter-class>corepatterns.filters.encodefilter. MultipartEncodeFilter</filter-class> <init-param> <param-name>UploadFolder</param-name> <param-value>/home/files</param-value> </init-param> </filter> . . . <filter-mapping> <filter-name>StandardEncodeFilter</filter-name> <url-pattern>/EncodeTestServlet</url-pattern> </filter-mapping> <filter-mapping> <filter-name>MultipartEncodeFilter</filter-name> <url-pattern>/EncodeTestServlet</url-pattern> </filter-mapping> . . .
圖7.5攔截過濾器,標準過濾器策略的序列圖-編碼轉換示例
當客戶端向控制器Servlet發出請求時,StandardEncodeFilter和MultiPartEncodeFilter會攔截控件。容器完成了過濾器管理器的角色,並經過調用它們的doFilter方法對這些過濾器進行矢量控制。完成處理後,每一個過濾器將控制權傳遞到其包含的FilterChain,它指示執行下一個過濾器。一旦兩個過濾器都已接收並隨後放棄了控制,則接收控制的下一個組件是實際的目標資源,在這種狀況下爲控制器servlet。
Servlet規範的版本2.3中支持的過濾器還支持包裝請求和響應對象。與使用「自定義過濾器策略」建議的自定義實現所構建的機制相比,此功能提供了更強大的機制。固然,結合兩種策略的混合方法也能夠定製構建,可是仍然缺乏Servlet規範支持的標準過濾器策略的功能。
基本過濾策略
基本過濾器是全部過濾器的通用超類。通用功能能夠封裝在基本過濾器中,並在全部過濾器之間共享。例如,基本過濾器是在「聲明的過濾器策略」中包括容器回調方法的默認行爲的好地方。例7.11顯示瞭如何作到這一點。
Example 7.11 Base Filter Strategy public class BaseEncodeFilter implements javax.servlet.Filter { private javax.servlet.FilterConfig myFilterConfig; public BaseEncodeFilter() { } public void doFilter(javax.servlet.ServletRequest servletRequest,javax.servlet.ServletResponse servletResponse, javax.servlet.FilterChain filterChain) throws java.io.IOException, javax.servlet.ServletException { filterChain.doFilter(servletRequest, servletResponse); } public javax.servlet.FilterConfig getFilterConfig() { return myFilterConfig; } public void setFilterConfig(javax.servlet.FilterConfig filterConfig) { myFilterConfig = filterConfig; } }
模板過濾策略
使用其餘全部繼承的基本過濾器(請參閱本章中的「基本過濾器策略」)容許基類提供模板方法[Gof]功能。 在這種狀況下,基本過濾器用於指示每一個過濾器必須完成的常規步驟,而將如何完成該步驟的細節留給每一個過濾器子類。 一般,這些方法是粗略定義的基本方法,這些方法只是在每一個模板上強加了有限的結構。 該策略也能夠與任何其餘過濾器策略組合。 例7.12和例7.13中的清單顯示瞭如何將此方法與「聲明的過濾器策略」一塊兒使用。
示例7.12顯示了一個名爲TemplateFilter的基本過濾器,以下所示。
Example 7.12 Using a Template Filter Strategy public abstract class TemplateFilter implements javax.servlet.Filter { private FilterConfig filterConfig; public void setFilterConfig(FilterConfig fc) { filterConfig=fc; } public FilterConfig getFilterConfig() { return filterConfig; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // Common processing for all filters can go here doPreProcessing(request, response, chain); // Common processing for all filters can go here doMainProcessing(request, response, chain); // Common processing for all filters can go here doPostProcessing(request, response, chain); // Common processing for all filters can go here // Pass control to the next filter in the chain or // to the target resource chain.doFilter(request, response); } public void doPreProcessing(ServletRequest request, ServletResponse response, FilterChain chain) { } public void doPostProcessing(ServletRequest request, ServletResponse response, FilterChain chain) { } public abstract void doMainProcessing(ServletRequest request, ServletResponse response, FilterChain chain); }
給定TemplateFilter的此類定義,每一個過濾器均做爲子類實現,該子類僅必須實現doMainProcessing方法。 可是,若是須要,這些子類能夠選擇實現全部三種方法。 Example 7.13是一個過濾器子類的示例,該子類實現一個強制方法(由模板過濾器決定)和可選的預處理方法。 另外,使用該策略的時序圖如圖7.6所示。
Example 7.13 Debugging Filter public class DebuggingFilter extends TemplateFilter { public void doPreProcessing(ServletRequest req, ServletResponse res, FilterChain chain) { //do some preprocessing here } public void doMainProcessing(ServletRequest req, ServletResponse res, FilterChain chain) { //do the main processing; } }
Figure 7.6 Intercepting Filter, Template Filter Strategy sequence diagram
在圖7.6的序列圖中,過濾器子類(例如DebuggingFilter)經過覆蓋抽象的doMainProcessing方法以及可選的doPreProcessing和doPostProcessing來定義特定的處理。所以,模板過濾器爲每一個過濾器的處理施加告終構,並提供了一個封裝每一個過濾器通用的代碼的位置。
後果
經過鬆散耦合的處理程序集中控制
過濾器和控制器同樣,爲處理多個請求的處理提供了一箇中心位置。篩選器更適合按摩請求和響應,以最終由目標資源(例如控制器)進行處理。另外,控制器一般將許多不相關的公共服務(例如身份驗證,日誌記錄,加密等)的管理聯繫在一塊兒,而過濾容許鬆散耦合的處理程序,這些處理程序能夠組合成各類組合。
提升可重用性
篩選器可促進更清潔的應用程序分區並鼓勵重用。這些可插入攔截器是透明地添加到現有代碼中或從現有代碼中刪除的,而且因爲它們的標準接口,它們能夠以任何組合使用,而且可用於各類表示形式。
聲明式和靈活的配置
無需單一從新編譯核心代碼庫,便可將許多服務組合成各類排列。
信息共享效率低下
在過濾器之間共享信息可能效率很低,由於根據定義,每一個過濾器都是鬆散耦合的。若是必須在過濾器之間共享大量信息,則此方法可能證實是昂貴的。
相關模式
前控制器
該控制器解決了一些相似的問題,但更適合處理核心處理。
裝飾器[GoF]
攔截過濾器模式與裝飾器模式有關,該裝飾器模式提供可動態插入的包裝器。
模板方法[GoF]
模板方法模式用於實現模板過濾器策略。
攔截器[POSA2]
攔截過濾器模式與攔截器模式有關,後者容許透明地添加服務並自動觸發。
管道和過濾器[POSA1]
攔截過濾器模式與「管道和過濾器」模式有關。
原文鏈接:https://www.oracle.com/technetwork/java/interceptingfilter-142169.html