Filter
Filter
是servlet
規(guī)范中定義的java web組件, 在所有支持java web的容器中都可以使用-
Filter
和Filter Chain
是密不可分的,Filter
可以實(shí)現(xiàn)依次調(diào)用正是因?yàn)橛辛?code>Filter Chain
上圖是
Filter
對(duì)請(qǐng)求進(jìn)行攔截的原理圖, 那么java web容器(以tomcat為例子)是如何實(shí)現(xiàn)這個(gè)功能的呢?下面看下
Filter
和Filter Chain
的源碼// Filter public interface Filter { // 容器創(chuàng)建的時(shí)候調(diào)用, 即啟動(dòng)tomcat的時(shí)候調(diào)用 public void init(FilterConfig filterConfig) throws ServletException; // 由FilterChain調(diào)用, 并且傳入Filter Chain本身 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; // 容器銷毀的時(shí)候調(diào)用, 即關(guān)閉tomcat的時(shí)候調(diào)用 public void destroy(); } // FilterChain public interface FilterChain { // 由Filter.doFilter()中的chain.doFilter調(diào)用 public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException; }
- 正是因?yàn)?code>Filter Chain在調(diào)用每一個(gè)
Filter.doFilter()
時(shí)將自身引用傳遞進(jìn)去, 才實(shí)現(xiàn)了Filter
的依次調(diào)用, 在Filter
全部調(diào)用完之后再調(diào)用真正處理請(qǐng)求的servlet
, 并且再次逆序回調(diào)Filter
. 可能這么看還是不太明白是怎么實(shí)現(xiàn)Filter的順序調(diào)用, 調(diào)用真正的servlet
, 逆序調(diào)用Filter
的, 一起看下Tomcat的源碼就一目了然了.
- 正是因?yàn)?code>Filter Chain在調(diào)用每一個(gè)
-
在tomcat中
Filter Chain
的默認(rèn)實(shí)現(xiàn)是ApplicationFilterChain
, 在ApplicationFilterChain
中最關(guān)鍵的方法就是internalDoFilter
, 整個(gè)Filter
流程的實(shí)現(xiàn)就是由這個(gè)方法完成.// internalDoFilter(只保留關(guān)鍵代碼) private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { // Call the next filter if there is one // pos: 當(dāng)前的filter的索引, n: 調(diào)用鏈中所有的Filter的數(shù)量 // 如果調(diào)用鏈中還有沒有調(diào)用的Filter就繼續(xù)調(diào)用, 否則跳過if語句 if (pos < n) { ApplicationFilterConfig filterConfig = filters[pos++]; try { // 獲取Filter Filter filter = filterConfig.getFilter(); if( Globals.IS_SECURITY_ENABLED ) { ... 其他代碼 ... } else { // 這句話是重點(diǎn), 調(diào)用Filter的doFilter方法并把Filter Chain本身傳進(jìn)去(this) filter.doFilter(request, response, this); } } catch (IOException | ServletException | RuntimeException e) { ... 異常處理代碼 ... } return; } // We fell off the end of the chain -- call the servlet instance try { ... 其他代碼 ... // Use potentially wrapped request from this point if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) && Globals.IS_SECURITY_ENABLED ) { ... 其他代碼 ... } else { // 調(diào)用真正的Filter servlet.service(request, response); } } catch (IOException | ServletException | RuntimeException e) { ... 異常處理代碼 ... } finally { ... 始終要執(zhí)行的代碼 ... } }
- 看了上面我添加的注釋之后應(yīng)該可以知道
Filter
的正序調(diào)用的過程和調(diào)用真正的servlet
的過程了, 但是Filter
的逆序調(diào)用在哪里體現(xiàn)了呢?
- 看了上面我添加的注釋之后應(yīng)該可以知道
假設(shè)下面的
Filter
就是調(diào)用鏈中的最后一個(gè)Filter
public class LogFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
Log.info("before");
chain.doFilter(request, response);
Log.info("after");
}
}
那么在調(diào)用
chain.doFilter
之后就跳過了if
語句從而調(diào)用了真正的servlet
, 然后internalDoFilter
方法就結(jié)束(出棧)了, 緊接著就是調(diào)用Log.info("after")
了, 然后LogFilter的doFilter就結(jié)束了(也出棧了)
, 緊接著就是internalDoFilter
中filter.doFilter(request, response, this)
的結(jié)束然后return
, 然后就是調(diào)用上一個(gè)filter的chain.doFilter()
之后的代碼, 以此類推.因此
Filter
調(diào)用鏈的實(shí)現(xiàn)其實(shí)就是一個(gè)方法調(diào)用鏈的過程. 剛開始,Filter Chain
每調(diào)用一個(gè)Filter.doFilter()
方法就是向方法調(diào)用棧中進(jìn)行壓棧操作(代碼上的體現(xiàn)就是執(zhí)行Filter.doFilter
之前的代碼), 當(dāng)Filter
全部調(diào)用完成之后就調(diào)用真正處理請(qǐng)求的servlet
, 然后由方法調(diào)用鏈自動(dòng)進(jìn)行出棧操作(代碼上的體現(xiàn)就是執(zhí)行Filter.doFilter
之后的代碼), 從而完成整個(gè)Filter
的調(diào)用鏈. 因?yàn)?code>Filter功能實(shí)現(xiàn)實(shí)際上就是利用了方法的壓棧出棧, 所以可以在調(diào)用chain.doFilter
之前將方法返回, 讓容器不在調(diào)用servlet
方法, 從而實(shí)現(xiàn)權(quán)限的控制, 關(guān)鍵詞的過濾等功能.
Interceptor
-
Interceptor
不是servlet
規(guī)范中的java web組件, 而是Spring提供的組件, 功能上和Filter差不多. 但是實(shí)現(xiàn)上和Filter不一樣.
Interceptor
功能的實(shí)現(xiàn)主要是在Spring Mvc的DispatcherServelt.doDispatch
方法中, 讓我們來看看源碼
// Interceptor的源碼
public interface HandlerInterceptor {
// 在調(diào)用真正的處理請(qǐng)求類之前調(diào)用
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
// 在調(diào)用真正的處理請(qǐng)求類之后調(diào)用
void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
// 在完成渲染或者出錯(cuò)之后調(diào)用
void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
}
// doDispatch源碼(只保留關(guān)鍵代碼)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
....
其它的處理代碼
....
// 調(diào)用攔截器的前置處理方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 調(diào)用真正的處理請(qǐng)求的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 找到渲染模版
applyDefaultViewName(processedRequest, mv);
// 調(diào)用攔截器的后置處理方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
....
異常處理代碼
....
}
finally {
....
始終要執(zhí)行的代碼
....
}
其實(shí)看了doDispatch
的關(guān)鍵代碼, Spring Mvc對(duì)整個(gè)請(qǐng)求的處理流程已經(jīng)很清楚了:
調(diào)用攔截器的前置方法 -> 調(diào)用處理請(qǐng)求的方法 -> 渲染模版 -> 調(diào)用攔截器的后置處理方法 -> 調(diào)用攔截器的完成方法
接下來看一看Spring Mvc是如何實(shí)現(xiàn)依此調(diào)用這么多攔截器的前置方法, 后置方法, 完成方法的
進(jìn)入到mapperHandler.applyPreHandle()
方法中(調(diào)用攔截器的前置方法)
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
// 如果攔截器數(shù)組不為空
if (!ObjectUtils.isEmpty(interceptors)) {
// 按順序調(diào)用攔截器數(shù)組中的preHandle方法
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
// 如果攔截器的preHandle方法返回false, 則調(diào)用當(dāng)前攔截器的triggerAfterCompletion方法, 然后返回, 并且不再調(diào)用后續(xù)的攔截器
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
進(jìn)入到mappedHandler.applyPostHandle()
方法中(調(diào)用攔截器的后置方法)
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
// 如果攔截器數(shù)組不為空
if (!ObjectUtils.isEmpty(interceptors)) {
// 倒序調(diào)用攔截器數(shù)組中攔截器的postHandle方法
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
不管是否出異常triggerAfterCompletion
方法始終會(huì)被調(diào)用
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
// 攔截器數(shù)組不為空
if (!ObjectUtils.isEmpty(interceptors)) {
// 從成功執(zhí)行的最后一個(gè)攔截器開始逆序調(diào)用afterCompletion方法
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);
}
}
}
}
看過以上三個(gè)方法之后, Spring Mvc如何處理攔截器的前置, 后置, 完成方法就一目了然了. 其實(shí)Spring Mvc就是將攔截器統(tǒng)一放到了攔截器數(shù)組中, 然后在調(diào)用真正的處理請(qǐng)求方法之前和之后正序或者倒序遍歷攔截器, 同時(shí)調(diào)用攔截器的相應(yīng)的方法. 最后不管是否正常結(jié)束這個(gè)流程還是出異常都會(huì)從成功的最后一個(gè)攔截器開始逆序調(diào)用afterCompletion
方法
總結(jié)
- 從以上分析可以看到過濾器和攔截器實(shí)現(xiàn)的方式的不同.
Filter
是利用了方法的調(diào)用(入棧出棧)完成整個(gè)流程, 而Interceptor
是利用了for
循環(huán)完成了整個(gè)流程. -
Filter
的實(shí)現(xiàn)比較占用棧空間, 在Filter
多的情況下可能會(huì)有棧溢出的風(fēng)險(xiǎn)存在. -
Interceptor
的實(shí)現(xiàn)邏輯更加的清晰簡(jiǎn)單 -
Filter
組件更加的通用, 只要支持java servlet
的容器都可以使用, 而Interceptor
必須依賴于Spring - Filter的優(yōu)先級(jí)是高于
Interceptor
, 即請(qǐng)求是先到Filter
再到Interceptor
的, 因?yàn)?code>Interceptor的實(shí)現(xiàn)主體還是一個(gè)servlet