攔截過濾器模式

jdon發表於2019-06-05

表示層請求處理機制接收許多不同型別的請求,這些請求需要不同型別的處理。有些請求只是轉發到適當的處理程式元件,而其他請求必須在進一步處理之前進行修改,稽核或解壓縮。
攔截過濾器模式的最好例子之一是Spring Security 的 DelegatingFilterProxy,它將攔截HTTP請求並進行身份驗證檢查。Spring安全性構建在過濾器鏈上。
讓我們看看攔截過濾器模式如何透過示例解決問題。為簡單起見,這種模式分為若干部分,如問題,動因,解決方案,實施等。


問題
(問題部分描述了開發人員面臨的設計問題)
需要對客戶端Web請求和響應進行預處理和後處理。當請求進入Web應用程式時,它通常必須在主處理階段之前透過多個入口測試。例如,

  • 客戶端是否已透過身份驗證?
  • 客戶端是否有有效的會話?
  • 客戶端的IP地址是否來自可信網路?
  • 請求路徑是否違反任何約束?
  • 客戶端使用什麼編碼來傳送資料?
  • 我們是否支援客戶端的瀏覽器型別?其中一些檢查是測試,導致是或否答案,確定是否繼續處理。其他檢查將輸入資料流操作為適合於處理的形式。您希望對客戶端Web請求和響應進行預處理和後處理。


動因
(本節描述了列出影響問題和解決方案的原因和動機。動因列表顯示了人們可能選擇並使用模式的理由)

  • 您希望跨請求進行集中、通用的處理,例如檢查每個請求的資料編碼方案,記錄有關每個請求的資訊或壓縮傳出響應。
  • 您希望預處理和後處理元件與核心請求處理服務鬆散耦合,以方便不引人注目的新增和刪除。
  • 您希望預處理和後處理元件彼此獨立且自包含以便於重用。


解決方案
(此處解決方案部分簡要介紹瞭解決方案的方法,並詳細介紹瞭解決方案)
使用攔截過濾器作為可插拔過濾器來預處理和後處理請求和響應。過濾器管理器將鬆散耦合的過濾器組合在一個鏈中,將控制權委託給適當的過濾器。透過這種方式,您可以以各種方式新增、刪除和組合這些過濾器,而無需更改現有程式碼。

結構
類圖

攔截過濾器模式

序列圖

攔截過濾器模式


參與者

過濾器 (Filter)-  在請求處理程式執行請求之前或之後執行特定任務的過濾器。 

過濾鏈(Filter Chain) -  過濾鏈帶有多個過濾器,有助於在目標上按照定義的順序執行它們。

目標 (Target )-  目標物件是請求處理程式  

過濾器管理器(Filter Manager) -  過濾器管理器管理過濾器和過濾器鏈。  

客戶端(Client) -  客戶端是向Target物件傳送請求的物件。

執行

第1步:  建立過濾器(Filter)介面。

public  interface  Filter {
    public  void  execute(String  request);
}


第2步:  建立具體過濾器 - AuthenticationFilter,DebugFilter。

public class AuthenticationFilter implements Filter {
   public void execute(String request){
      System.out.println("Authenticating request: " + request);
   }
}


public class DebugFilter implements Filter {
   public void execute(String request){
      System.out.println("request log: " + request);
   }
}


第3步:  建立目標(Target )

public class Target {
   public void execute(String request){
      System.out.println("Executing request: " + request);
   }
}


第4步:  建立過濾鏈(Filter Chain)

import java.util.ArrayList;
import java.util.List;

public class FilterChain {
   private List<Filter> filters = new ArrayList<Filter>();
   private Target target;

   public void addFilter(Filter filter){
      filters.add(filter);
   }

   public void execute(String request){
      for (Filter filter : filters) {
         filter.execute(request);
      }
      target.execute(request);
   }

   public void setTarget(Target target){
      this.target = target;
   }
}


第5步:  建立過濾器管理器(FilterManager)

public class FilterManager {
   FilterChain filterChain;

   public FilterManager(Target target){
      filterChain = new FilterChain();
      filterChain.setTarget(target);
   }
   public void setFilter(Filter filter){
      filterChain.addFilter(filter);
   }

   public void filterRequest(String request){
      filterChain.execute(request);
   }
}


第6步:  建立客戶端(Client)

public class Client {
   FilterManager filterManager;

   public void setFilterManager(FilterManager filterManager){
      this.filterManager = filterManager;
   }

   public void sendRequest(String request){
      filterManager.filterRequest(request);
   }
}


步驟7:  使用客戶端演示攔截過濾器設計模式

public class InterceptingFilterDemo {
   public static void main(String args) {
      FilterManager filterManager = new FilterManager(new Target());
      filterManager.setFilter(new AuthenticationFilter());
      filterManager.setFilter(new DebugFilter());

      Client client = new Client();
      client.setFilterManager(filterManager);
      client.sendRequest("HOME");
   }
}


第8步:  驗證輸出。

Authenticating request: HOME
request log: HOME
Executing request: HOME


標準過濾策略
servlet 2.3規範包括一個標準機制,用於構建過濾器鏈,並從這些鏈中不顯眼地新增和刪除過濾器。 
過濾器圍繞介面構建,並透過修改Web應用程式的部署描述符以宣告方式新增或刪除。

javax.servlet.Filter(介面)
過濾器是對資源請求(servlet或靜態內容)或資源響應(或兩者)執行過濾任務的物件。過濾器在doFilter方法中執行過濾。 
每個Filter都可以訪問FilterConfig物件,從中可以獲取其初始化引數,例如,可以使用ServletContext的引用,以載入過濾任務所需的資源。

過濾器在Web應用程式的部署描述符中配置。
本設計中已確定的示例如下:

  1. 驗證過濾器
  2. 記錄和稽核過濾器
  3. 影像轉換過濾器
  4. 資料壓縮過濾器
  5. 加密過濾器
  6. 標記過濾器
  7. 觸發資源訪問事件的過濾器
  8. XSL / T過濾器
  9. Mime型鏈式過濾器

javax.servlet.FilterChain(介面)
FilterChain是servlet容器向開發人員提供的物件,它提供對資源的篩選請求的呼叫鏈的檢視。過濾器使用FilterChain呼叫鏈中的下一個過濾器,或者如果呼叫過濾器是鏈中的最後一個過濾器,則呼叫鏈末尾的資源。

示例
這個策略的示例是建立一個過濾器,它對任何編碼型別的請求進行預處理,以便在核心請求處理程式碼中對每個請求進行類似的處理。為什麼有必要這樣做?包含檔案上載的HTML表單使用與大多數表單不同的編碼型別。
因此,透過simplegetParameter()呼叫無法獲得伴隨上載的表單資料  。所以,我們建立了兩個預處理請求的過濾器,將所有編碼型別轉換為單一的一致格式。我們選擇的格式是將所有表單資料作為請求屬性提供。

示例1 - 基本過濾器 - 標準過濾策略

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;
  }
}


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);
    }
 }
}


示例2- MultipartEncodeFilter - 標準過濾策略

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()
}


部署描述符 - 標準過濾策略

<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>


後果

  • 使用鬆散耦合的處理程式集中控制
  • 提高可重用性
  • 宣告性和靈活的配置
  • 資訊共享效率低下

適用性
使用擷取過濾器模式時

  • 系統使用預處理或後處理請求
  • 系統應該執行身份驗證/授權/記錄或跟蹤請求,然後將請求傳遞給相應的處理程式
  • 您需要一種模組化方法來配置預處理和後處理方案


相關文章