1. 程式人生 > 其它 >行為型設計模式:職責鏈模式

行為型設計模式:職責鏈模式

職責鏈模式的原理和實現

將請求的傳送和接收解耦,讓多個接收物件都有機會處理這個請求。將這些接收物件串成一條鏈,並沿著這條鏈傳遞這個請求,直到鏈上的某個接收物件能夠處理它為止。

在職責鏈模式中,多個處理器一次處理同一個請求,形成一個處理鏈條,鏈條上的每個處理器各司其職,所以叫職責鏈模式。

職責鏈模式有多種實現方式,我們講解其中一種:用陣列儲存處理器的方式,

public interface IHandler {
  boolean handle();
}

public class HandlerA implements IHandler {
  @Override
  public boolean
handle() {
  //這塊定義這個是標記某個處理器處理完了下一個處理器就不用處理的
boolean handled = false; //... return handled; } } public class HandlerB implements IHandler { @Override public boolean handle() { boolean handled = false; //... return handled; } }
//處理器鏈
public class HandlerChain { private List<IHandler> handlers = new
ArrayList<>();   
  //將處理器新增到執行鏈中
public void addHandler(IHandler handler) { this.handlers.add(handler); } public void handle() {
  //挨個執行
for (IHandler handler : handlers) { boolean handled = handler.handle();
    //其中有一個處理器處理後則跳出不繼續執行
if (handled) { break; } } } }
// 使用舉例 public class Application { public static void main(String[] args) { HandlerChain chain = new HandlerChain(); chain.addHandler(new HandlerA()); chain.addHandler(new HandlerB()); chain.handle(); } }

上面是挨個執行,某個處理完其他就不處理了,也可以去掉其中的判斷,那就是挨個處理,比如進行過濾這種需求就要挨個執行。

職責鏈模式的應用場景舉例

處理敏感詞這個應用場景中,有兩種處理結果,1是不進行釋出 2是進行***處理。第一種方式適合用某個處理器進行處理後其他處理器不進行處理,第二種方式就是順序執行,所有處理器都執行完畢。

下面實現一個第一種情況,第二種情況把其中判斷去掉,挨個執行就行。

public interface SensitiveWordFilter {
  boolean doFilter(Content content);
}

public class SexyWordFilter implements SensitiveWordFilter {
  @Override
  public boolean doFilter(Content content) {
    boolean legal = true;
    //...
    return legal;
  }
}

// PoliticalWordFilter、AdsWordFilter類程式碼結構與SexyWordFilter類似

public class SensitiveWordFilterChain {
  private List<SensitiveWordFilter> filters = new ArrayList<>();

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

  // return true if content doesn't contain sensitive words.
  public boolean filter(Content content) {
    for (SensitiveWordFilter filter : filters) {
      if (!filter.doFilter(content)) {
        return false;
      }
    }
    return true;
  }
}

public class ApplicationDemo {
  public static void main(String[] args) {
    SensitiveWordFilterChain filterChain = new SensitiveWordFilterChain();
    filterChain.addFilter(new AdsWordFilter());
    filterChain.addFilter(new SexyWordFilter());
    filterChain.addFilter(new PoliticalWordFilter());

    boolean legal = filterChain.filter(new Content());
    if (!legal) {
      // 不發表
    } else {
      // 發表
    }
  }
}

為什麼要用這種複雜的方式實現?

我們把各個敏感詞過濾函式繼續拆分出來,設計成獨立的類,進一步簡化了 SensitiveWordFilter 類,讓 SensitiveWordFilter 類的程式碼不會過多,過複雜。

如果要增加新的過濾器,只需要新新增一個 Filter 類,並且通過 addFilter() 函式將它新增到 FilterChain 中即可,其他程式碼完全不需要修改。

框架中職責鏈的應用

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

Servlet Filter

Servlet 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 是如何做到如此好的擴充套件性的呢? 它利用的就是職責鏈模式。現在,我們通過剖析它的原始碼,詳細地看看它底層是如何實現的,以下為簡化後的程式碼。
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;
  }
}

這裡面可以看到一個遞迴:filter.doFilter,這樣設計的目的是,我們可以在過濾器執行的前後增加實現內容,達到雙向攔截,既能攔截客戶端傳送來的請求,也能攔截髮送給客戶端的響應的效果。

Spring Interceptor

攔截器,與Servlet Filter不同之處在於,Servlet Filter 是 Servlet 規範的一部分,實現依賴於 Web 容器。

Spring Interceptor 是 Spring MVC 框架的一部分,由 Spring MVC 框架來提供實現。

客戶端傳送的請求,會先經過 Servlet Filter,然後再經過 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>

同樣,我們還是來剖析一下,Spring Interceptor 底層是如何實現的。

其中,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() 函式,用來實現攔截的功能。