聊聊過濾器與攔截器區別
1. 過濾器(Filter)
Servlet 中的過濾器 Filter 實現了 javax.servlet.Filter 接口的服務器端程序,主要用途是設置字符集(CharacterEncodingFilter)、控制權限、控制轉向、用戶是否已經登陸、有沒有權限訪問該頁面等。
其工作原理是,只要你在 web.xml 文件配置好要攔截的客戶端請求,它都會幫你攔截到請求。此時,其實你可以對請求或響應(request、response)統一設置編碼;它隨 web 應用啓動而啓動,只初始化一次,以後就可以攔截相關請求。只有當你的 web 應用停止或重新部署的時候才銷燬。
-
Filter 可以認爲是 Servlet 的一種” 加強版”。它主要用於對用戶請求進行預處理,也可以對 HttpServletResponse 進行後處理,是個典型的處理鏈;
-
Filter 可以對用戶請求生成響應,和 Servlet 相同;
-
處理流程:用戶請求 -> Filter 預處理 -> Servlet 處理請求生成響應 -> Filter 對響應進行後處理。
1.1 Filter 的用處
-
在 HttpServletRequest 到達 Servlet 之前,攔截客戶的 HttpServletRequest;
-
根據需要檢查 HttpServletRequest,也可以修改 HttpServletRequest 頭和數據;
-
在 HttpServletResponse 到達客戶端之前,攔截 HttpServletResponse;
-
根據需要檢查 HttpServletResponse,也可以修改 HttpServletResponse 頭和數據。
Filter 有如下幾個種類。
1.2 Filter 的種類
-
用戶授權的 Filter:Filter 負責檢查用戶請求,根據請求過濾用戶非法請求;
-
日誌 Filter:詳細記錄某些特殊的用戶請求;
-
負責解碼的 Filter:包括對非標準編碼的請求解碼;
-
能改變 XML 內容的 XSLT Filter 等。
Filter 可以負責攔截多個請求或響應,一個請求或響應也可以被多個 Filter 攔截。
1.3 創建 Filter 的步驟
-
創建 Filter 處理類,並實現 javax.servlet.Filter 接口;
-
web.xml 文件中配置 Filter(或者使用 @WebFilter 註解)。
javax.servlet.Filter 接口中定義了三個方法:
-
void init(FilterConfig config) 用於完成 Filter 的初始化;
-
void destory() 用於 Filter 銷燬前,完成某些資源的回收;
-
void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) 實現過濾功能。
doFilter 方法就是對每個請求及響應增加的額外處理。該方法可以實現對用戶請求進行預處理(ServletRequest request),也可實現對服務器響應進行後處理 (ServletResponse response)。它們的分界線爲,是否調用了 chain.doFilter()。執行該方法之前,即對用戶請求進行預處理;執行該方法之後,即對服務器響應進行後處理。
2. 攔截器(Interceptor)
攔截器是在面向切面編程中應用的,就是 service 或一個方法前 / 後調用一個方法。是基礎 Java 的反射機制。攔截不是在 web.xml,而是在 AOP(Aspect-Oriented Programming)中用於某個方法或字段被訪問之前,進行攔截。然後在之前或之後加入某些操作,甚至在拋出異常的時候做業務邏輯的操作。攔擊器是 AOP 的一種實現策略。
2.1 攔截器的實現方式
SpringMVC 中的 Interceptor 攔截請求是通過 HandlerInterceptor 來實現的。在 SpringMVC 中定義 Interceptor 主要有兩種方式:
-
實現 Spring 的 HandlerInterceptor 接口或者繼承了實現 HandlerInterceptor 接口的類(比如 HandlerInterceptorAdapter);
-
實現 Spring 的 WebRequestInterceptor 接口,或者繼承了實現 WebRequestInterceptor 接口的類。
Interceptor 中的方法:
preHandle (HttpServletRequest request, HttpServletResponse response, Object handle) 方法
顧名思義,該方法將在請求處理之前進行調用。
SpringMVC 中的 Interceptor 是鏈式調用。在一個應用中或者說是在一個請求中可以同時存在多個 Interceptor,每個 Interceptor 的調用會依據它的聲明順序依次執行,而且最先執行的都是 Interceptor 中的 preHandle 方法。
所以可以在這個方法中進行一些前置初始化操作或者是對當前請求的一個預處理,也可以在這個方法中進行一些判斷來決定請求是否要繼續進行下去。該方法的返回值是布爾值 Boolean 類型的,當它返回爲 false 時,表示請求結束,後續的 Interceptor 和 Controller 都不會再執行;當返回值爲 true 時就會繼續調用下一個 Interceptor 的 preHandle 方法。如果已經是最後一個 Interceptor 的時候就會是調用當前請求的 Controller 方法。
postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView) 方法
由 preHandle 方法的解釋我們知道,這個方法包括後面要說到的 afterCompletion 方法都只能是在當前所屬的 Interceptor 的 preHandle 方法的返回值爲 true 時才能被調用。
postHandle 方法,顧名思義就是在當前請求進行處理之後,也就是 Controller 方法調用之後執行,但是它會在 DispatcherServlet 進行視圖返回渲染之前被調用。所以,我們可以在這個方法中對 Controller 處理之後的 ModelAndView 對象進行操作。
postHandle 方法被調用的方向跟 preHandle 是相反的。也就是說,先聲明的 Interceptor 的 postHandle 方法反而會後執行。這和 Struts2 裏面的 Interceptor 的執行過程有點類似。Struts2 裏面的 Interceptor 的執行過程也是鏈式的,只是在 Struts2 裏面需要手動調用 ActionInvocation 的 invoke 方法來觸發對下一個 Interceptor 或者是 Action 的調用,然後每一個 Interceptor 中在 invoke 方法調用之前的內容都是按照聲明順序執行的,而 invoke 方法之後的內容就是反向的。
afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 方法
該方法也是需要當前對應的 Interceptor 的 p reHandle 方法的返回值爲 true 時纔會執行。顧名思義,該方法將在整個請求結束之後,也就是在 DispatcherServlet 渲染了對應的視圖之後執行。這個方法的主要作用是用於進行資源清理工作的。
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
public class ExecuteTimeInterceptor extends HandlerInterceptorAdapter{
private static final Logger logger = Logger.getLogger(ExecuteTimeInterceptor.class);
//before the actual handler will be executed
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
return true;
}
//after the handler is executed
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
long startTime = (Long)request.getAttribute("startTime");
long endTime = System.currentTimeMillis();
//統計耗時
long executeTime = endTime - startTime;
//modified the exisitng modelAndView
modelAndView.addObject("executeTime",executeTime);
//log it
if(logger.isDebugEnabled()){
logger.debug("[" + handler + "] executeTime : " + executeTime + "ms");
}
}
}
非 SpringBoot 項目
使用 mvc:interceptors 標籤來聲明需要加入到 SpringMVC 攔截器鏈中的攔截器。
<mvc:interceptors>
<!-- 使用bean定義一個Interceptor,直接定義在mvc:interceptors根下面的Interceptor將攔截所有的請求 -->
<bean/>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/parent/**"/>
<bean />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/parent/**"/>
<bean />
</mvc:interceptor>
</mvc:interceptors>
可以利用 mvc:interceptors 標籤聲明一系列的攔截器,然後它們就可以形成一個攔截器鏈。攔截器的執行順序是按聲明的先後順序執行的,先聲明的攔截器中的 preHandle 方法會先執行,然而它的 postHandle 方法和 afterCompletion 方法卻會後執行。
在 mvc:interceptors 標籤下聲明 interceptor 主要有兩種方式:
-
直接定義一個 Interceptor 實現類的 bean 對象。使用這種方式聲明的 Interceptor 攔截器將會對所有的請求進行攔截;
-
使用 mvc:interceptor 標籤進行聲明。使用這種方式進行聲明的 Interceptor 可以通過 mvc:mapping 子標籤來定義需要進行攔截的請求路徑。
經過上述兩步之後,定義的攔截器就會發生作用對特定的請求進行攔截了。
Spring Boot 項目
配置攔截器
@Configuration
//@Configurationpublic
public class WebAppConfigurer implements WebMvcConfigurer {
/**
* 配置攔截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 多個攔截器組成一個攔截器鏈
registry.addInterceptor(new ExecuteTimeInterceptor()).addPathPatterns("/**");
//API限流攔截
registry.addInterceptor(accessLimitAjaxInterceptor()).addPathPatterns("/**").excludePathPatterns("/static/**","/login.html");
registry.addInterceptor(accessInterceptor()).addPathPatterns("/**").excludePathPatterns("/static/**","/login.html");
}
}
2.2 攔截器(interceptor)使用
-
請求到達 DispatcherServlet;
-
DispatcherServlet 發送至 Interceptor,執行 preHandler;
-
請求到達 Controller;
-
請求結束後,執行 postHandler。
3. 過濾器(Filter)與 攔截器(Interceptor)的區別
Spring 的 Interceptor(攔截器)與 Servlet 的 Filter 有相似之處,比如二者都是 AOP 編程思想的體現,都能實現權限檢查、日誌記錄等。
不同之處總結如下:
3.1 執行順序
用戶請求 -> 過濾前 -> 攔截前 -> Action 處理 -> 攔截後 -> 過濾後 -> 響應
4. 過濾器(Filter)與 攔截器(Interceptor)常見用途
-
Authentication Filters
-
Logging and Auditing Filtersx
-
Image conversion Filters
-
Data compression Filters
-
Encryption Filters
-
Tokenizing Filters
-
Filters that trigger resource access events
-
XSL/T filters
-
Mime-type chain Filter
Request Filters 可以實現:
-
執行安全檢查
-
格式化請求頭和主體
-
審查或者記錄日誌
-
根據請求內容授權或者限制用戶訪問
-
根據請求頻率限制用戶訪問
Response Filters 可以實現
-
壓縮響應內容:比如讓下載的內容更小
-
追加或者修改響應
-
創建或者整體修改響應
-
根據地方不同修改響應內容
5. 總結
過濾器
所謂過濾器顧名思義是用來過濾的。在 Java Web 中,傳入的 request、response 提前過濾掉一些信息,或者提前設置一些參數;然後再傳入 Servlet 或者 Struts 的 action 進行業務邏輯。比如,過濾掉非法 URL(不是 login.do 的地址請求,如果用戶沒有登陸都過濾掉),或者在傳入 Servlet 或者 Struts 的 action 前統一設置字符集,或者去除掉一些非法字符(聊天室經常用到的,一些罵人的話)。
Filter 流程是線性的, URL 傳來之後,檢查之後,可保持原來的流程繼續向下執行,被下一個 filter、servlet 接收等。
Java 的攔截器
主要是用在插件上,擴展件上比如 Hibernate Spring Struts2 等 有點類似面向切片的技術,在用之前先要在配置文件即 XML 文件裏進行對應的聲明。
攔截器(Interceptor)是基於 Java 的反射機制,而過濾器(Filter)是基於函數回調。從靈活性上說攔截器功能更強大些,Filter 能做的事情,攔截器都能做,而且可以在請求前、請求後執行,比較靈活。Filter 主要是針對 URL 地址做一個編碼的事情、過濾掉沒用的參數、安全校驗(比如登錄不登錄之類),太細的話,還是建議用攔截器。
轉自:小余哥,
鏈接:xiaoyuge.work/filter-interceptor/index.html
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/qWQHkFzdBorCYJrMhrz9Mg