行爲型-職責鏈模式(下)

職責鏈模式最常用來開發框架的過濾器和攔截器。通過 Servlet Filter、Spring Interceptor 這兩個 Java開發中常用的組件,來具體講講它在框架開發中的應用。

Servlet FilterServlet Filter 是 Java Servlet 規範中定義的組件,翻譯成中文就是過濾器,它可以實現對 HTTP 請求的過濾功能,比如鑑權、限流、記錄日誌、驗證參數等等。因爲它是 Servlet 規範的一部分,所以,只要是支持 Servlet 的 Web 容器(比如,Tomcat、Jetty 等),都支持過濾器功能。

原理圖

在實際項目中,該如何使用 Servlet Filter 呢?以下是一個簡單的示例代碼,如下所示。添加一個過濾器,只需要定義一個實現 javax.servlet.Filter 接口的過濾器類,並且將它配置在 web.xml 配置文件中。Web 容器啓動的時候,會讀取 web.xml中的配置,創建過濾器對象。當有請求到來的時候,會先經過過濾器,然後才由 Servlet 來處理。


public class LogFilter implements Filter {
  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    // 在創建Filter時自動調用,
    // 其中filterConfig包含這個Filter的配置參數,比如name之類的(從配置文件中讀取的)
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    System.out.println("攔截客戶端發送來的請求.");
    chain.doFilter(request, response);
    System.out.println("攔截髮送給客戶端的響應.");
  }

  @Override
  public void destroy() {
    // 在銷燬Filter時自動調用
  }
}

// 在web.xml配置文件中如下配置:
<filter>
  <filter-name>logFilter</filter-name>
  <filter-class>com.xzg.cd.LogFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>logFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

從剛剛的示例代碼中,我們發現,添加過濾器非常方便,不需要修改任何代碼,定義一個實現 javax.servlet.Filter 的類,再改改配置就搞定了,完全符合開閉原則。那 Servlet Filter 是如何做到如此好的擴展性的呢?它利用的就是職責鏈模式。現在,我們通過剖析它的源碼,詳細地看看它底層是如何實現的。

職責鏈模式的實現包含處理器接口(IHandler)或抽象類(Handler),以及處理器鏈(HandlerChain)。對應到 Servlet Filter,javax.servlet.Filter 就是處理器接口,FilterChain 就是處理器鏈。重點來看 FilterChain 是如何實現的。不過,Servlet 只是一個規範,並不包含具體的實現,所以,Servlet 中的 FilterChain 只是一個接口定義。具體的實現類由遵從 Servlet 規範的 Web 容器來提供,比如,ApplicationFilterChain 類就是 Tomcat 提供的 FilterChain 的實現類,源碼如下所示。

但是隻保留了跟設計思路相關的代碼片段。完整的代碼你可以自行去 Tomcat 中查看。


public final class ApplicationFilterChain implements FilterChain {
  private int pos = 0; //當前執行到了哪個filter
  private int n; //filter的個數
  private ApplicationFilterConfig[] filters;
  private Servlet servlet;
  
  @Override
  public void doFilter(ServletRequest request, ServletResponse response) {
    if (pos < n) {
      ApplicationFilterConfig filterConfig = filters[pos++];
      Filter filter = filterConfig.getFilter();
      filter.doFilter(request, response, this);
    } else {
      // filter都處理完畢後,執行servlet
      servlet.service(request, response);
    }
  }
  
  public void addFilter(ApplicationFilterConfig filterConfig) {
    for (ApplicationFilterConfig filter:filters)
      if (filter==filterConfig)
         return;

    if (n == filters.length) {//擴容
      ApplicationFilterConfig[] newFilters = new ApplicationFilterConfig[n + INCREMENT];
      System.arraycopy(filters, 0, newFilters, 0, n);
      filters = newFilters;
    }
    filters[n++] = filterConfig;
  }
}

ApplicationFilterChain 中的 doFilter() 函數的代碼實現比較有技巧,實際上是一個遞歸調用。用每個 Filter(比如 LogFilter)的 doFilter() 的代碼實現,直接替換 ApplicationFilterChain 的第 12 行代碼,一眼就能看出是遞歸調用了。

Spring Interceptor

攔截器,用來實現對 HTTP 請求進行攔截處理。Servlet Filter 是 Servlet 規範的一部分,實現依賴於 Web 容器。Spring Interceptor 是 Spring MVC 框架的一部分,由 Spring MVC 框架來提供實現。客戶端發送的請求,會先經過 Servlet Filter,然後再經過 Spring Interceptor,最後到達具體的業務代碼中。

Spring Interceptor原理圖
在項目中,我們該如何使用 Spring Interceptor 呢?示例代碼如下所示。LogInterceptor 實現的功能跟剛纔的 LogFilter 完全相同,只是實現方式上稍有區別。LogFilter 對請求和響應的攔截是在 doFilter() 一個函數中實現的,而 LogInterceptor 對請求的攔截在 preHandle() 中實現,對響應的攔截在 postHandle() 中實現。


public class LogInterceptor implements HandlerInterceptor {

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    System.out.println("攔截客戶端發送來的請求.");
    return true; // 繼續後續的處理
  }

  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    System.out.println("攔截髮送給客戶端的響應.");
  }

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    System.out.println("這裏總是被執行.");
  }
}

//在Spring MVC配置文件中配置interceptors
<mvc:interceptors>
   <mvc:interceptor>
       <mvc:mapping path="/*"/>
       <bean class="com.xzg.cd.LogInterceptor" />
   </mvc:interceptor>
</mvc:interceptors>

底層是如何實現的呢?

它也是基於職責鏈模式實現的。其中,HandlerExecutionChain 類是職責鏈模式中的處理器鏈。它的實現相較於 Tomcat 中的 ApplicationFilterChain 來說,邏輯更加清晰,不需要使用遞歸來實現,主要是因爲它將請求和響應的攔截工作,拆分到了兩個函數中實現。HandlerExecutionChain 的源碼如下所示(進行了一些簡化,只保留了關鍵代碼)


public class HandlerExecutionChain {
 private final Object handler;
 private HandlerInterceptor[] interceptors;
 
 public void addInterceptor(HandlerInterceptor interceptor) {
  initInterceptorList().add(interceptor);
 }

 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
  HandlerInterceptor[] interceptors = getInterceptors();
  if (!ObjectUtils.isEmpty(interceptors)) {
   for (int i = 0; i < interceptors.length; i++) {
    HandlerInterceptor interceptor = interceptors[i];
    if (!interceptor.preHandle(request, response, this.handler)) {
     triggerAfterCompletion(request, response, null);
     return false;
    }
   }
  }
  return true;
 }

 void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
  HandlerInterceptor[] interceptors = getInterceptors();
  if (!ObjectUtils.isEmpty(interceptors)) {
   for (int i = interceptors.length - 1; i >= 0; i--) {
    HandlerInterceptor interceptor = interceptors[i];
    interceptor.postHandle(request, response, this.handler, mv);
   }
  }
 }

 void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
   throws Exception {
  HandlerInterceptor[] interceptors = getInterceptors();
  if (!ObjectUtils.isEmpty(interceptors)) {
   for (int i = this.interceptorIndex; i >= 0; i--) {
    HandlerInterceptor interceptor = interceptors[i];
    try {
     interceptor.afterCompletion(request, response, this.handler, ex);
    } catch (Throwable ex2) {
     logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
    }
   }
  }
 }
}

在 Spring 框架中,DispatcherServlet 的 doDispatch() 方法來分發請求,它在真正的業務邏輯執行前後,執行 HandlerExecutionChain 中的 applyPreHandle() 和 applyPostHandle() 函數,用來實現攔截的功能。具體的代碼實現很簡單,這裏就不羅列了。感興趣的話,可以自行去查看。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章