一口氣說(shuō)出 過(guò)濾器 和 攔截器 6個(gè)區(qū)別,別再傻傻分不清了
周末有個(gè)小伙伴加我微信,向我請(qǐng)教了一個(gè)問(wèn)題:老哥,「過(guò)濾器 (Filter
) 和 攔截器 (Interceptor
) 有啥區(qū)別???」 聽(tīng)到題目我的第一感覺(jué)就是:「簡(jiǎn)單」!
畢竟這兩種工具開(kāi)發(fā)中用到的頻率都相當(dāng)高,應(yīng)用起來(lái)也是比較簡(jiǎn)單的,可當(dāng)我準(zhǔn)備回復(fù)他的時(shí)候,竟然不知道從哪說(shuō)起,支支吾吾了半天,場(chǎng)面炒雞尷尬有木有,工作這么久一個(gè)基礎(chǔ)問(wèn)題答成這樣,丟了大人了。平時(shí)覺(jué)得簡(jiǎn)單的知識(shí)點(diǎn),但通常都不會(huì)太關(guān)注細(xì)節(jié),一旦被別人問(wèn)起來(lái),反倒說(shuō)不出個(gè)所以然來(lái)。
歸根結(jié)底,還是對(duì)這些知識(shí)了解的不夠,一直停留在會(huì)用的階段,以至于現(xiàn)在「一看就會(huì)一說(shuō)就廢」!這是典型基礎(chǔ)不扎實(shí)的表現(xiàn),哎·~,其實(shí)我也就是個(gè)虛胖!
知恥而后勇,下邊結(jié)合實(shí)踐,更直觀的來(lái)感受一下兩者到底有什么不同?
準(zhǔn)備環(huán)境
我們?cè)陧?xiàng)目中同時(shí)配置 攔截器
和 過(guò)濾器
。
1、過(guò)濾器 (Filter)
過(guò)濾器的配置比較簡(jiǎn)單,直接實(shí)現(xiàn)Filter
接口即可,也可以通過(guò)@WebFilter
注解實(shí)現(xiàn)對(duì)特定URL
攔截,看到Filter
接口中定義了三個(gè)方法。
-
init()
:該方法在容器啟動(dòng)初始化過(guò)濾器時(shí)被調(diào)用,它在Filter
的整個(gè)生命周期只會(huì)被調(diào)用一次。「注意」:這個(gè)方法必須執(zhí)行成功,否則過(guò)濾器會(huì)不起作用。 -
doFilter()
:容器中的每一次請(qǐng)求都會(huì)調(diào)用該方法,FilterChain
用來(lái)調(diào)用下一個(gè)過(guò)濾器Filter
。 -
destroy()
:當(dāng)容器銷毀 過(guò)濾器實(shí)例時(shí)調(diào)用該方法,一般在方法中銷毀或關(guān)閉資源,在過(guò)濾器Filter
的整個(gè)生命周期也只會(huì)被調(diào)用一次
@Component
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter 前置");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter 處理中");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("Filter 后置");
}
}
2、攔截器 (Interceptor)
攔截器它是鏈?zhǔn)秸{(diào)用,一個(gè)應(yīng)用中可以同時(shí)存在多個(gè)攔截器Interceptor
, 一個(gè)請(qǐng)求也可以觸發(fā)多個(gè)攔截器 ,而每個(gè)攔截器的調(diào)用會(huì)依據(jù)它的聲明順序依次執(zhí)行。
首先編寫一個(gè)簡(jiǎn)單的攔截器處理類,請(qǐng)求的攔截是通過(guò)HandlerInterceptor
來(lái)實(shí)現(xiàn),看到HandlerInterceptor
接口中也定義了三個(gè)方法。
-
preHandle()
:這個(gè)方法將在請(qǐng)求處理之前進(jìn)行調(diào)用。「注意」:如果該方法的返回值為false
,將視為當(dāng)前請(qǐng)求結(jié)束,不僅自身的攔截器會(huì)失效,還會(huì)導(dǎo)致其他的攔截器也不再執(zhí)行。 -
postHandle()
:只有在preHandle()
方法返回值為true
時(shí)才會(huì)執(zhí)行。會(huì)在Controller 中的方法調(diào)用之后,DispatcherServlet 返回渲染視圖之前被調(diào)用。「有意思的是」:postHandle()
方法被調(diào)用的順序跟preHandle()
是相反的,先聲明的攔截器preHandle()
方法先執(zhí)行,而postHandle()
方法反而會(huì)后執(zhí)行。 -
afterCompletion()
:只有在preHandle()
方法返回值為true
時(shí)才會(huì)執(zhí)行。在整個(gè)請(qǐng)求結(jié)束之后, DispatcherServlet 渲染了對(duì)應(yīng)的視圖之后執(zhí)行。
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Interceptor 前置");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Interceptor 處理中");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Interceptor 后置");
}
}
將自定義好的攔截器處理類進(jìn)行注冊(cè),并通過(guò)addPathPatterns
、excludePathPatterns
等屬性設(shè)置需要攔截或需要排除的 URL
。
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
}
}
我們不一樣
過(guò)濾器 和 攔截器 均體現(xiàn)了AOP
的編程思想,都可以實(shí)現(xiàn)諸如日志記錄、登錄鑒權(quán)等功能,但二者的不同點(diǎn)也是比較多的,接下來(lái)一一說(shuō)明。
1、實(shí)現(xiàn)原理不同
過(guò)濾器和攔截器 底層實(shí)現(xiàn)方式大不相同,過(guò)濾器
是基于函數(shù)回調(diào)的,攔截器
則是基于Java的反射機(jī)制(動(dòng)態(tài)代理)實(shí)現(xiàn)的。
這里重點(diǎn)說(shuō)下過(guò)濾器!
在我們自定義的過(guò)濾器中都會(huì)實(shí)現(xiàn)一個(gè) doFilter()
方法,這個(gè)方法有一個(gè)FilterChain
參數(shù),而實(shí)際上它是一個(gè)回調(diào)接口。ApplicationFilterChain
是它的實(shí)現(xiàn)類, 這個(gè)實(shí)現(xiàn)類內(nèi)部也有一個(gè) doFilter()
方法就是回調(diào)方法。
public interface FilterChain {
void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}
ApplicationFilterChain
里面能拿到我們自定義的xxxFilter
類,在其內(nèi)部回調(diào)方法doFilter()
里調(diào)用各個(gè)自定義xxxFilter
過(guò)濾器,并執(zhí)行 doFilter()
方法。
public final class ApplicationFilterChain implements FilterChain {
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
...//省略
internalDoFilter(request,response);
}
private void internalDoFilter(ServletRequest request, ServletResponse response){
if (pos < n) {
//獲取第pos個(gè)filter
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
...
filter.doFilter(request, response, this);
}
}
}
而每個(gè)xxxFilter
會(huì)先執(zhí)行自身的 doFilter()
過(guò)濾邏輯,最后在執(zhí)行結(jié)束前會(huì)執(zhí)行filterChain.doFilter(servletRequest, servletResponse)
,也就是回調(diào)ApplicationFilterChain
的doFilter()
方法,以此循環(huán)執(zhí)行實(shí)現(xiàn)函數(shù)回調(diào)。
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(servletRequest, servletResponse);
}
2、使用范圍不同
我們看到過(guò)濾器 實(shí)現(xiàn)的是 javax.servlet.Filter
接口,而這個(gè)接口是在Servlet
規(guī)范中定義的,也就是說(shuō)過(guò)濾器Filter
的使用要依賴于Tomcat
等容器,導(dǎo)致它只能在web
程序中使用。而攔截器(Interceptor
) 它是一個(gè)Spring
組件,并由Spring
容器管理,并不依賴Tomcat
等容器,是可以單獨(dú)使用的。不僅能應(yīng)用在web
程序中,也可以用于Application
、Swing
等程序中。
3、觸發(fā)時(shí)機(jī)不同
過(guò)濾器
和 攔截器
的觸發(fā)時(shí)機(jī)也不同,我們看下邊這張圖。
過(guò)濾器Filter
是在請(qǐng)求進(jìn)入容器后,但在進(jìn)入servlet
之前進(jìn)行預(yù)處理,請(qǐng)求結(jié)束是在servlet
處理完以后。
攔截器 Interceptor
是在請(qǐng)求進(jìn)入servlet
后,在進(jìn)入Controller
之前進(jìn)行預(yù)處理的,Controller
中渲染了對(duì)應(yīng)的視圖之后請(qǐng)求結(jié)束。
4、攔截的請(qǐng)求范圍不同
在上邊我們已經(jīng)同時(shí)配置了過(guò)濾器和攔截器,再建一個(gè)Controller
接收請(qǐng)求測(cè)試一下。
@Controller
@RequestMapping()
public class Test {
@RequestMapping("/test1")
@ResponseBody
public String test1(String a) {
System.out.println("我是controller");
return null;
}
}
項(xiàng)目啟動(dòng)過(guò)程中發(fā)現(xiàn),過(guò)濾器的init()
方法,隨著容器的啟動(dòng)進(jìn)行了初始化。此時(shí)瀏覽器發(fā)送請(qǐng)求,F(xiàn)12 看到居然有兩個(gè)請(qǐng)求,一個(gè)是我們自定義的 Controller
請(qǐng)求,另一個(gè)是訪問(wèn)靜態(tài)圖標(biāo)資源的請(qǐng)求。看到控制臺(tái)的打印日志如下:
執(zhí)行順序 :Filter 處理中
-> Interceptor 前置
-> 我是controller
-> Interceptor 處理中
-> Interceptor 處理后
Filter 處理中
Interceptor 前置
Interceptor 處理中
Interceptor 后置
Filter 處理中
過(guò)濾器Filter
執(zhí)行了兩次,攔截器Interceptor
只執(zhí)行了一次。這是因?yàn)檫^(guò)濾器幾乎可以對(duì)所有進(jìn)入容器的請(qǐng)求起作用,而攔截器只會(huì)對(duì)Controller
中請(qǐng)求或訪問(wèn)static
目錄下的資源請(qǐng)求起作用。
5、注入Bean情況不同
在實(shí)際的業(yè)務(wù)場(chǎng)景中,應(yīng)用到過(guò)濾器或攔截器,為處理業(yè)務(wù)邏輯難免會(huì)引入一些service
服務(wù)。
下邊我們分別在過(guò)濾器和攔截器中都注入service
,看看有什么不同?
@Component
public class TestServiceImpl implements TestService {
@Override
public void a() {
System.out.println("我是方法A");
}
}
過(guò)濾器中注入service
,發(fā)起請(qǐng)求測(cè)試一下 ,日志正常打印出“我是方法A”
。
@Autowired
private TestService testService;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter 處理中");
testService.a();
filterChain.doFilter(servletRequest, servletResponse);
}
Filter 處理中
我是方法A
Interceptor 前置
我是controller
Interceptor 處理中
Interceptor 后置
在攔截器中注入service
,發(fā)起請(qǐng)求測(cè)試一下 ,竟然TM的報(bào)錯(cuò)了,debug
跟一下發(fā)現(xiàn)注入的service
怎么是Null
???這是因?yàn)榧虞d順序?qū)е碌膯?wèn)題,攔截器
加載的時(shí)間點(diǎn)在springcontext
之前,而Bean
又是由spring
進(jìn)行管理。
攔截器:老子今天要進(jìn)洞房;Spring:兄弟別鬧,你媳婦我還沒(méi)生出來(lái)呢!
解決方案也很簡(jiǎn)單,我們?cè)谧?cè)攔截器之前,先將Interceptor
手動(dòng)進(jìn)行注入。「注意」:在registry.addInterceptor()
注冊(cè)的是getMyInterceptor()
實(shí)例。
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Bean
public MyInterceptor getMyInterceptor(){
System.out.println("注入了MyInterceptor");
return new MyInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
}
}
6、控制執(zhí)行順序不同
實(shí)際開(kāi)發(fā)過(guò)程中,會(huì)出現(xiàn)多個(gè)過(guò)濾器或攔截器同時(shí)存在的情況,不過(guò),有時(shí)我們希望某個(gè)過(guò)濾器或攔截器能優(yōu)先執(zhí)行,就涉及到它們的執(zhí)行順序。
過(guò)濾器用@Order
注解控制執(zhí)行順序,通過(guò)@Order
控制過(guò)濾器的級(jí)別,值越小級(jí)別越高越先執(zhí)行。
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {
攔截器默認(rèn)的執(zhí)行順序,就是它的注冊(cè)順序,也可以通過(guò)Order
手動(dòng)設(shè)置控制,值越小越先執(zhí)行。
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1);
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3);
}
看到輸出結(jié)果發(fā)現(xiàn),先聲明的攔截器 preHandle()
方法先執(zhí)行,而postHandle()
方法反而會(huì)后執(zhí)行。
postHandle()
方法被調(diào)用的順序跟 preHandle()
居然是相反的!如果實(shí)際開(kāi)發(fā)中嚴(yán)格要求執(zhí)行順序,那就需要特別注意這一點(diǎn)。
Interceptor1 前置
Interceptor2 前置
Interceptor 前置
我是controller
Interceptor 處理中
Interceptor2 處理中
Interceptor1 處理中
Interceptor 后置
Interceptor2 處理后
Interceptor1 處理后
「那為什么會(huì)這樣呢?」 得到答案就只能看源碼了,我們要知道controller
中所有的請(qǐng)求都要經(jīng)過(guò)核心組件DispatcherServlet
路由,都會(huì)執(zhí)行它的 doDispatch()
方法,而攔截器postHandle()
、preHandle()
方法便是在其中調(diào)用的。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
...........
try {
// 獲取可以執(zhí)行當(dāng)前Handler的適配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 注意: 執(zhí)行Interceptor中PreHandle()方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 注意:執(zhí)行Handle【包括我們的業(yè)務(wù)邏輯,當(dāng)拋出異常時(shí)會(huì)被Try、catch到】
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 注意:執(zhí)行Interceptor中PostHandle 方法【拋出異常時(shí)無(wú)法執(zhí)行】
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
}
...........
}
看看兩個(gè)方法applyPreHandle()
、applyPostHandle()
具體是如何被調(diào)用的,就明白為什么postHandle()
、preHandle()
執(zhí)行順序是相反的了。
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if(!ObjectUtils.isEmpty(interceptors)) {
for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
HandlerInterceptor interceptor = interceptors[i];
if(!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
}
return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = this.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);
}
}
}
發(fā)現(xiàn)兩個(gè)方法中在調(diào)用攔截器數(shù)組 HandlerInterceptor[]
時(shí),循環(huán)的順序竟然是相反的。。。,導(dǎo)致postHandle()
、preHandle()
方法執(zhí)行的順序相反。
總結(jié)
我相信大部分人都能熟練使用濾器和攔截器,但兩者的差別還是需要多了解下,不然開(kāi)發(fā)中使用不當(dāng),時(shí)不時(shí)就會(huì)出現(xiàn)奇奇怪怪的問(wèn)題,以上內(nèi)容比較簡(jiǎn)單,新手學(xué)習(xí)老鳥(niǎo)復(fù)習(xí),有遺漏的地方還望大家積極補(bǔ)充,如有理解錯(cuò)誤之處,還望不吝賜教。
特別推薦一個(gè)分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒(méi)關(guān)注的小伙伴,可以長(zhǎng)按關(guān)注一下:
長(zhǎng)按訂閱更多精彩▼
如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!