我們之前學習了職責鍊模式的原理與實作,并且通過一個敏感詞過濾架構的例子,展示了職責鍊模式的設計意圖。本質上來說,它跟大部分設計模式一樣,都是為了解耦代碼,應對代碼的複雜性,讓代碼滿足開閉原則,提高代碼的可擴充性。
除此之外,我們還提到,職責鍊模式常用在架構的開發中,為架構提供擴充點,讓架構的使用者在不修改架構源碼的情況下,基于擴充點添加新的功能。實際上,更具體點來說,職責鍊模式最常用來開發架構的過濾器和攔截器。今天,我們就通過 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 是如何做到如此好的擴充性的呢?我想你應該已經猜到了,它利用的就是職責鍊模式。現在,我們通過剖析它的源碼,詳細地看看它底層是如何實作的。
在上一節課中,我們講到,職責鍊模式的實作包含處理器接口(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 行代碼,一眼就能看出是遞歸調用了。我替換了一下,如下所示。
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
//filter.doFilter(request, response, this);
//把filter.doFilter的代碼實作展開替換到這裡
System.out.println("攔截用戶端發送來的請求.");
chain.doFilter(request, response); // chain就是this
System.out.println("攔截發送給用戶端的響應.")
} else {
// filter都處理完畢後,執行servlet
servlet.service(request, response);
}
}
這樣實作主要是為了在一個 doFilter() 方法中,支援雙向攔截,既能攔截用戶端發送來的請求,也能攔截發送給用戶端的響應,你可以結合着 LogFilter 那個例子,以及對比待會要講到的 Spring Interceptor,來自己了解一下。而我們上一節課給出的兩種實作方式,都沒法做到在業務邏輯執行的前後,同時添加處理代碼。
Spring Interceptor
剛剛講了 Servlet Filter,現在我們來講一個功能上跟它非常類似的東西,Spring Interceptor,翻譯成中文就是攔截器。盡管英文單詞和中文翻譯都不同,但這兩者基本上可以看作一個概念,都用來實作對 HTTP 請求進行攔截處理。
它們不同之處在于,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() 函數,用來實作攔截的功能。具體的代碼實作很簡單,你自己應該能腦補出來,這裡就不羅列了。感興趣的話,你可以自行去檢視。