一、引言
在面向對象編程(OOP)的過程中,很容易通過繼承、多態來解決縱向擴展。但對于橫向的功能,如登記所有的客戶端請求耗時、統一開啟事務等功能,OOP 無能為力。面向切面編程(AOP)的編程思想是對 OOP 的一個補充,本篇所討論的過濾器和攔截器都屬于 AOP 的具體實現。
二、過濾器 Filter
過濾器(Filter)的預處理發生在請求進入容器后,未進入 Servlet 之前。相應的,其后處理發生在 Servlet 處理完成后,返回給前端之前。所以過濾器的 doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
的入參是ServletRequest
,而不是httpservletrequest
。因為過濾器是在httpservlet
之前。
@Override
public void init(FilterConfig arg0) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
log.info("before...");
chain.doFilter(request, response);
log.info("after...");
}
@Override
public void destroy() {
}
Filter
跟Servlet
都是由容器負責創建和銷毀的。在一個應用程序中,一個Filter
只會被創建和銷毀一次。web 應用程序啟動時,容器調用public void init(FilterConfig filterConfig) throws ServletException
方法初始化Filter
;web 應用程序被移除或者是容器關閉時,調用public void destroy()
銷毀Filter
。
Filter
中聲明的doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
方法,用于實現具體的過濾邏輯。其中FilterChain chain
參數是一個過濾鏈對象,它包含了用戶定義的一系列過濾器,這些過濾器根據其定義順序依次被執行。通過執行chain.doFilter(request, response)
方法可以跳過當前過濾器處理邏輯,執行過濾鏈中的下一個過濾器。事實上調用Servlet
的doService()
方法是在chain.doFilter(request, response)
這個方法中進行的。
三、攔截器 Interceptor
通過繼承HandlerInterceptorAdapter
類并重寫下列三個方法可以快速的實現自定義攔截器:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
上述方法分別實現了攔截器的預處理preHanlde
、后處理postHandle
(調用了Service并返回ModelAndView,但未進行頁面渲染)、返回處理afterCompletion
(已經渲染了頁面)。
preHandle()
方法實現了攔截器的預處理。如果存在多個攔截器,則會依次調用攔截器鏈中每一個攔截器的preHandle()
方法:
- 當
preHandle
方法返回false
時,DispatcherServlet
處理器認為攔截器已經處理完了請求,不再繼續執行鏈中的其它攔截器和處理器,而是從當前攔截器往回執行所有攔截器的afterCompletion
方法,退出攔截器鏈 - 當
preHandle
方法全為true
時,執行下一個攔截器,直到所有攔截器執行完。再運行被攔截的 Controller。然后返回攔截器鏈,運行所有攔截器的postHandle
方法,然后從最后一個攔截器往回執行所有攔截器的afterCompletion
方法 - 當有攔截器拋出異常時,會從當前攔截器往回執行所有攔截器的
afterCompletion
方法
四、過濾器和攔截器的對比
二者在功能上很相似,其主要區別在于:
- 適用范圍:Filter 依賴于 Servlet 容器,屬于 Servlet 規范的一部分,只能用于 Web 程序;而攔截器是獨立存在的,由 Spring 框架支持,可以用于 Web 程序、Application、Swing 程序中
- 作用范圍:Filter 的執行由 Servlet 容器回調完成,過濾邏輯只能發生在 Servlet 調用前后;而攔截器基于 Java 反射機制,通常通過動態代理的方式來執行,能夠深入到方法的前后、異常拋出的前后等,使用起來更加靈活
- 可支配的資源:攔截器屬于Spring 組件,是通過 IoC 容器來管理,它能通過依賴注入的方式調用 Spring 里的任何資源、對象,例如 Service 對象、數據源、事務管理等;而 Filter 做不到,Filter 的生命周期由 Servlet 容器管理
五、運用場景
攔截器的主要應用場景有:
- 日志記錄:記錄請求信息的日志,以便進行信息監控、信息統計、計算PV(Page View)等。
- 權限檢查:如登錄檢測,進入處理器檢測用戶是否登錄,如沒有則跳轉到登錄頁面;
- 性能監控:有時候系統在某段時間莫名其妙的慢,可以通過攔截器在進入處理器之前記錄開始時間,在處理完后記錄結束時間,從而得到該請求的處理時間(如果有反向代理,如apache可以自動記錄);
- 通用行為:讀取 cookie 得到用戶信息并將用戶對象放入請求,從而方便后續流程使用,還有如提取 Locale、Theme 信息等,只要是多個處理器都需要的即可使用攔截器實現。
- OpenSessionInView:如 hibernate,在進入處理器打開Session,在完成后關閉Session。
過濾器通常用于:
- 在過濾器中修改字符編碼(CharacterEncodingFilter)、在過濾器中修改 HttpServletRequest 的一些參數(XSSFilter(自定義過濾器)),如:過濾低俗文字、危險字符等。
六、過濾器和攔截器的執行順序
綜上所述可知,Filter
的執行順序在Interceptor
之前。一圖勝千言:假設自定義了2個過濾器TestFilter1
和TestFilter2
,2個攔截器TestInterceptor
,BaseInterceptor
,其執行流程可能如下: