Spring的攔截器與Servlet的Filter有相似之處,比如二者都是AOP編程思想的體現(xiàn),都能實(shí)現(xiàn)權(quán)限檢查、日志記錄等。但它們之間又有不少區(qū)別,很多朋友工作多年,可能還沒有深刻的了解它們的具體使用以及它們之間的區(qū)別。本文帶大家全面了解一下它們的使用、實(shí)現(xiàn)機(jī)制以及區(qū)別。
過濾器(Filter)的詳解及使用
過濾器(Filter)屬于Servlet的范疇,可以認(rèn)為是Servlet的一種“加強(qiáng)版”,通過實(shí)現(xiàn)JAVAx.servlet.Filter接口來實(shí)現(xiàn)功能。主要用于對用戶請求進(jìn)行預(yù)處理,是個(gè)典型的處理鏈。通常使用場景:檢查用戶授權(quán)、記錄日志信息、解碼、過濾字符編碼等。
基本工作原理:配置完過濾器及需要攔截的請求,當(dāng)請求到來時(shí),通過過濾器提供的方法可以對請求或響應(yīng)(Request、Response)統(tǒng)一處理。比如,可判斷用戶是否登錄,是否擁有請求的訪問權(quán)限等。在Web應(yīng)用啟動時(shí),過濾器僅會被初始化一次,便可處理后續(xù)請求,只有Web應(yīng)用停止或重新部署時(shí)才能銷毀。
使用Filter完整的流程是:Filter對用戶請求進(jìn)行“預(yù)處理”,接著將請求交給Servlet進(jìn)處理并生成響應(yīng),最后Filter再對服務(wù)器響應(yīng)進(jìn)行“后處理”。
上述流程具體到類的處理就是:
1、Filter在ServletRequest到達(dá)Servlet之前,攔截客戶的ServletRequest;
2、根據(jù)需要檢查ServletRequest,也可修改ServletRequest頭和數(shù)據(jù);
3、在ServletResponse到達(dá)客戶端之前,攔截ServletResponse;
4、根據(jù)需要檢查HttpServletResponse,也可修改HttpServletResponse頭和數(shù)據(jù)。
創(chuàng)建Filter必須實(shí)現(xiàn)javax.servlet.Filter接口,該接口定義了3個(gè)方法:
- void init(FilterConfig filterConfig):容器啟動初始化Filter時(shí)會被調(diào)用,整個(gè)生命周期只會被調(diào)用一次。可用于完成Filter的初始化。
- void doFilter(ServletRequest request, ServletResponse response,FilterChain chain):實(shí)現(xiàn)過濾功能,就是通過該方法對每個(gè)請求增加額外的處理。通過其參數(shù)FilterChain調(diào)用下一個(gè)過濾器。
- void destroy():用于Filter銷毀前,完成某些資源的回收。
其中,doFilter方法便是實(shí)現(xiàn)對用戶請求進(jìn)行預(yù)處理(ServletRequest request)和對服務(wù)器響應(yīng)進(jìn)行后處理(ServletResponse response)的方法。預(yù)處理和后處理的分界線為是否調(diào)用了chain.doFilter()。在執(zhí)行該方法之前,是對用戶請求進(jìn)行預(yù)處理,在執(zhí)行該方法之后,是對服務(wù)器響應(yīng)進(jìn)行后處理。
下面以具體的實(shí)現(xiàn)代碼來展示一下:
public class LogFilter 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 預(yù)處理");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("Filter 后處理");
}
@Override
public void destroy() {
System.out.println("容器銷毀");
}
}
關(guān)于Filter的使用在普通的Web項(xiàng)目中可在web.xml中配置:
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>com.secbro2.learn.filter.LogFilter</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mApping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
如果是SpringBoot項(xiàng)目,首先使用@Component將LogFilter實(shí)例化,然后通過如下配置文件,進(jìn)行具體的配置:
@Configuration
public class FilterConfig {
@Resource
private LogFilter logFilter;
@Bean
public FilterRegistrationBean<Filter> registerAuthFilter() {
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
registration.setFilter(logFilter);
registration.addUrlPatterns("/*");
registration.setName("authFilter");
// 值越小,F(xiàn)ilter越靠前
registration.setOrder(1);
return registration;
}
}
定義一個(gè)Contoller,然后依次執(zhí)行啟動項(xiàng)目、訪問Controller、關(guān)閉項(xiàng)目,打印的日志信息依次為:
Filter 初始化
---以上為啟動項(xiàng)目時(shí)打印---
Filter 預(yù)處理
Controller中處理業(yè)務(wù)邏輯
Filter 后處理
---以上為訪問Controller時(shí)打印---
容器銷毀
---以上為關(guān)閉服務(wù)時(shí)打印---
攔截器(Interceptor)的詳解及使用
攔截器,在AOP(Aspect-Oriented Programming)中用于某個(gè)方法或字段被訪問之前進(jìn)行攔截,然后在其之前或之后加入某些操作。攔截器作為動態(tài)攔截Action調(diào)用的對象,它提供了一種機(jī)制使開發(fā)者可以在Action執(zhí)行前后定義可執(zhí)行的代碼,也可以在Action執(zhí)行前阻止其執(zhí)行。
攔截器將Action共用的行為獨(dú)立出來,在Action執(zhí)行前后執(zhí)行。常見的應(yīng)用場景比如權(quán)限管理、日志服務(wù)等。
在Spring MVC當(dāng)中要使用攔截器需要實(shí)現(xiàn)org.springframework.web.servlet.HandlerInterceptor接口,該接口定義了如下三個(gè)方法:
(1)preHandle (HttpServletRequest request, HttpServletResponse response, Object handle) 方法,會在請求處理之前被調(diào)用。SpringMVC中的Interceptor是鏈?zhǔn)秸{(diào)用的,可以存在多個(gè)Interceptor。Interceptor的調(diào)用會依據(jù)聲明順序依次執(zhí)行,最先執(zhí)行的都是preHandle方法,可在該方法中進(jìn)行一些前置(預(yù))處理,也可進(jìn)行判斷來決定是否要繼續(xù)執(zhí)行。當(dāng)返回為false 時(shí),表示請求結(jié)束,后續(xù)的Interceptor和Controller都不會再執(zhí)行;當(dāng)返回值為true時(shí),會繼續(xù)調(diào)用下一個(gè)Interceptor的preHandle方法,執(zhí)行完最后一個(gè)Interceptor后會調(diào)用當(dāng)前請求的Controller方法。
(2 )postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView) 方法,會在Controller方法調(diào)用之后,DispatcherServlet進(jìn)行渲染視圖之前被調(diào)用,所以可以對Controller處理之后的ModelAndView對象進(jìn)行操作。postHandle方法被調(diào)用的方向跟preHandle是相反的,先聲明的Interceptor的postHandle方法反而會后執(zhí)行。
(3 )afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 方法,會在整個(gè)請求結(jié)束之后被調(diào)用,也就是在DispatcherServlet渲染了對應(yīng)的視圖之后執(zhí)行。這個(gè)方法的主要是用于進(jìn)行資源清理。
來看一個(gè)具體的示例:
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("Interceptor preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
System.out.println("Interceptor postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("Interceptor afterCompletion");
}
}
對應(yīng)LoginInterceptor需添加到Spring MVC當(dāng)中:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor).addPathPatterns("/**");
}
}
這里攔截所有的請求,執(zhí)行對應(yīng)的Controller之后,會看到打印如下信息:
Interceptor preHandle
Controller中處理業(yè)務(wù)邏輯
Interceptor postHandle
Interceptor afterCompletion
很明顯可以看到,當(dāng)一個(gè)請求過來之后,會先后執(zhí)行preHandle方法、Controller中的業(yè)務(wù)、postHandle方法和afterCompletion方法。
過濾器與攔截器的區(qū)別
經(jīng)過上面的學(xué)習(xí),我們已經(jīng)大概了解了過濾器和攔截器的基本使用和功能,想必已經(jīng)感覺到它們之間的一些區(qū)別了。先看一張圖,可以更加明顯的看出過濾器和攔截器在使用過程中所處的位置和使用的時(shí)機(jī)。

匯總一下就是:
1、使用范圍與規(guī)范不同:Filter是Servlet規(guī)范中定義的,只能用于Web程序中,依賴于Servlet容器。攔截器是Spring的組件,可用于Web程序、Application、Swing等程序,不依賴Servlet容器。
2、使用資源不同:攔截器可以使用Spring里的任何資源、對象,例如Service對象、數(shù)據(jù)源、事務(wù)管理等,通過IOC注入到攔截器即可;而Filter則不能。
3、作用范圍不同:Filter在只在Servlet前后起作用。而攔截器能夠深入到方法前后、異常拋出前后,對Action請求其作用,可以訪問Action上下文、值棧里的對象等,具有更大的彈性。因此,在Spring框架的過程中,要優(yōu)先使用攔截器。而濾器則可以對幾乎所有的請求起作用。
4、實(shí)現(xiàn)機(jī)制不同:攔截器是基于java的反射機(jī)制的,而過濾器是基于函數(shù)回調(diào)。
上面介紹了過濾器和攔截器的基本不同之處,這里再對上面的圖進(jìn)一步細(xì)化,可得到下圖:

通過上圖,我們可以進(jìn)一步看到攔截器和過濾器的方法在整個(gè)請求過程中所處的位置。
小結(jié)
通過上面的學(xué)習(xí),想必大家已經(jīng)掌握了過濾器和攔截器的基本使用。最后補(bǔ)充一下,什么時(shí)候適合使用過濾器,什么時(shí)候又適合使用攔截器呢?當(dāng)需要過濾掉其中的部分信息,只留一部分時(shí),就用過濾器;當(dāng)需要對其流程進(jìn)行更改,做相關(guān)的記錄時(shí)用攔截器。