netty(3)-譯j2ee interceptingfilter
- 2019 年 10 月 11 日
- 筆記
上下文 表示層請求處理機制接收許多不同類型的請求,這需要各種類型的處理。有些請求只是簡單地轉發到適當的處理程序組件,而其他請求必須先進行修改,審核或解壓縮,然後再進一步處理。
問題 客戶端Web請求和響應的預處理和後處理是必需的。
當請求進入Web應用程序時,它通常必須在主處理階段之前通過幾個入口測試。例如,
客戶端已通過身份驗證嗎?
客戶端是否有有效的會話? 客戶端的IP地址是否來自受信任的網絡? 請求路徑是否違反任何約束? 客戶端用於發送數據的編碼是什麼? 我們是否支持客戶端的瀏覽器類型? 其中一些檢查是測試,結果為是或否,以決定是否繼續處理。其他檢查將進入的數據流操縱為適合處理的形式。
經典解決方案包括一系列條件檢查,任何失敗的檢查都會中止請求。嵌套的if / else語句是一種標準策略,但是此解決方案會導致代碼易碎,並且會產生編程的複製粘貼風格,因為過濾的流程和過濾器的操作已編譯到應用程序中。
以靈活,簡便的方式解決此問題的關鍵是要有一種簡單的機制來添加和刪除處理組件,其中每個組件都可以完成特定的過濾動作。
強制 常見處理(例如檢查數據編碼方案或記錄有關每個請求的信息)將針對每個請求完成。
需要集中通用邏輯。 服務應該易於添加或刪除,而且不會影響現有組件,因此可以將它們以多種組合使用,例如 記錄和認證 調試和轉換特定客戶端的輸出 輸入的解壓縮轉換編碼方案 解 創建可插拔過濾器以標準方式處理通用服務,而無需更改核心請求處理代碼。篩選器攔截傳入的請求和傳出的響應,從而允許進行預處理和後處理。我們能夠毫不干擾地添加和刪除這些過濾器,而無需更改現有代碼。
實際上,我們能夠使用各種常用服務(例如安全性,日誌記錄,調試等)來修飾我們的主處理程序。這些過濾器是獨立於主要應用程序代碼的組件,可以聲明性地添加或刪除它們。例如,可以修改部署配置文件以設置過濾器鏈。同一配置文件可能包含特定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 * <[email protected]>. 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