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