本文在個人技術博客【鳥不拉屎】同步發布,詳情可猛戳 亦可掃描文章末尾二維碼關注個人公眾號【鳥不拉屎】
一、前言
過濾器和攔截器兩者都具有AOP的切面思想,關於aop切面,可以看上一篇文章。過濾器filter和攔截器interceptor都屬於面向切面編程的具體實現。
二、過濾器
過濾器工作原理
從上圖可以看出,當瀏覽器發送請求到服務器時,先執行過濾器,然后才訪問Web資源。服務器響應Response,從Web資源抵達瀏覽器之前,也會途徑過濾器。
過濾器是一個實現javax.servlet.Filter接口的Java類。javax.servlet.Filter接口定義了三個方法
方法 | 描述 |
---|---|
public void init(FilterConfig filterConfig) | web 應用程序啟動時,web 服務器將創建Filter 的實例對象,並調用其init方法,讀取web.xml配置,完成對象的初始化功能,從而為后續的用戶請求作好攔截的准備工作(filter對象只會創建一次,init方法也只會執行一次)。開發人員通過init方法的參數,可獲得代表當前filter配置信息的FilterConfig對象。 |
public void doFilter (ServletRequest, ServletResponse, FilterChain) | 該方法完成實際的過濾操作,當客戶端請求方法與過濾器設置匹配的URL時,Servlet容器將先調用過濾器的doFilter方法。FilterChain用戶訪問后續過濾器。 |
public void destroy() | Servlet容器在銷毀過濾器實例前調用該方法,在該方法中釋放Servlet過濾器占用的資源。 |
SpringBoot摒棄了繁瑣的xml配置的同時,提示了幾種注冊組件:ServletRegistrationBean,
FilterRegistrationBean,ServletListenerRegistrationBean,DelegatingFilterProxyRegistrationBean,用於注冊自對應的組件,如過濾器,監聽器等。
代碼實現
1、添加maven依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--devtools熱部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
2、添加攔截器
@Configuration
public class WebConfig {
@Bean
public RemoteIpFilter remoteIpFilter() {
return new RemoteIpFilter();
}
/**
* 注冊第三方過濾器
* 功能與spring mvc中通過配置web.xml相同
* 可以添加過濾器鎖攔截的 URL,攔截更加精准靈活
* @return
*/
@Bean
public FilterRegistrationBean testFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new MyFilter());
// 過濾應用程序中所有資源,當前應用程序根下的所有文件包括多級子目錄下的所有文件,注意這里*前有“/”
registration.addUrlPatterns("/*");
registration.addInitParameter("paramName", "paramValue");
registration.setName("MyFilter");
// 過濾器順序
registration.setOrder(1);
return registration;
}
// 定義過濾器
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
System.out.println("this is MyFilter,url :" + request.getRequestURI());
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("destroy");
}
}
}
3、controller層
@RestController
public class HelloController {
@GetMapping("/filter")
public String testFilter(){
return "filter is ok";
}
}
4、測試
通過發送post請求:127.0.0.1:8081/filter
查看日志可以看到過濾器已經開始工作了。
三、攔截器
攔截器概念
不同於過濾器,具體區別我們下面再將,先講一講攔截器實現的機制。
在AOP(Aspect-Oriented Programming)中用於在某個方法或字段被訪問之前,進行攔截,然后在之前或之后加上某些操作。攔截是AOP的一種實現策略。
攔截器作用
有什么作用呢?AOP面向切面有什么作用,那么攔截器就有什么作用。
- 日志記錄:記錄請求信息的日志,以便進行信息監控、信息統計、計算PV...
- 權限檢查:認證或者授權等檢查
- 性能監控:通過攔截器在進入處理器之前記錄開始時間,處理完成后記錄結束時間,得到請求處理時間。
- 通用行為:讀取cookie得到用戶信息並將用戶對象放入請求頭中,從而方便后續流程使用。
攔截器實現
攔截器集成接口HandlerInterceptor
,實現攔截,接口方法有下面三種:
-
preHandler(HttpServletRequest request, HttpServletResponse response, Object handler)
方法將在請求處理之前進行調用。SpringMVC中的Interceptor
同Filter一樣都是鏈式調用。每個Interceptor的調用會依據它的聲明順序依次執行,而且最先執行的都是Interceptor中的preHandle方法,所以可以在這個方法中進行一些前置初始化操作或者是對當前請求的一個預處理,也可以在這個方法中進行一些判斷來決定請求是否要繼續進行下去。該方法的返回值是布爾值Boolean 類型的,當它返回為false時,表示請求結束,后續的Interceptor和Controller都不會再執行;當返回值為true時就會繼續調用下一個Interceptor 的preHandle 方法,如果已經是最后一個Interceptor 的時候就會是調用當前請求的Controller 方法。 -
postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
在當前請求進行處理之后,也就是Controller 方法調用之后執行,但是它會在DispatcherServlet 進行視圖返回渲染之前被調用,所以我們可以在這個方法中對Controller 處理之后的ModelAndView 對象進行操作。 -
afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex)
該方法也是需要當前對應的Interceptor的preHandle方法的返回值為true時才會執行。顧名思義,該方法將在整個請求結束之后,也就是在DispatcherServlet 渲染了對應的視圖之后執行。這個方法的主要作用是用於進行資源清理工作的。
總結一點就是:
preHandle是請求執行前執行
postHandle是請求結束執行
afterCompletion是視圖渲染完成后執行
代碼實現
1、添加Maven依賴
和過濾器一樣
2、添加攔截器類
其中LogInterceptor
實現HandlerInterceptor
接口的三個方法,同時需要preHandle
返回true,該方法通常用於清理資源等工作。
主方法繼承WebMvcConfigurer
注意不用用WebMvcConfigurerAdapter
,該方法已經被官方標注過時了,在java8是默認實現的。
所以我們需要使用的是WebMvcConfigurer
進行靜態資源的配置。
配置的主要有兩項:一個是制定攔截器,第二個是指定攔截的URL
@Slf4j
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
/**
* 攔截器注冊類
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor()).addPathPatterns("/**");
}
/**
* 定義攔截器
*/
public class LogInterceptor implements HandlerInterceptor {
long start = System.currentTimeMillis();
/**
* 請求執行前執行
*/
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
log.info("preHandle");
start = System.currentTimeMillis();
return true;
}
/**
* 請求結束執行
*/
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
log.info("Interceptor cost="+(System.currentTimeMillis()-start));
}
/**
* 視圖渲染完成后執行
*/
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
log.info("afterCompletion");
}
}
}
3、controller層
@RestController
public class HelloController {
@RequestMapping("/interceptor")
public String home(){
return "interceptor is ok";
}
}
4、測試
可以看到,我們通過攔截器實現了同樣的功能。不過這里還要說明一點的是,其實這個實現是有問題的,因為preHandle和postHandle是兩個方法,所以我們這里不得不設置一個共享變量start來存儲開始值,但是這樣就會存在線程安全問題。當然,我們可以通過其他方法來解決,比如通過ThreadLocal就可以很好的解決這個問題,有興趣的同學可以自己實現。不過通過這一點我們其實可以看到,雖然攔截器在很多場景下優於過濾器,但是在這種場景下,過濾器比攔截器實現起來更簡單。
四、過濾器和攔截器的區別
Spring的攔截器與Servlet的Filter有相似之處,比如二者都是AOP編程思想的體現,都能實現權限檢查、日志記錄等。
不同的是:
- 使用范圍不同:Filter是Servlet規范規定的,只能用於Web程序中。而攔截器既可以用於Web程序,也可以用於Application、Swing程序中。
- 規范不同: Filter是在Servlet規范中定義的,是Servlet容器支持的。而攔截器是在Spring容器內的,是Spring框架支持的。
- 使用的資源不同:同其他的代碼塊一樣,攔截器也是一個Spring的組件,歸Spring管理,配置在Spring文件中,因此能使用Spring里的任何資源、對象,例如Service對象、數據源、事務管理等,通過loC注入到攔截器即可:而Filter則不能。
- 深度不同:Filter在只在Servlet前后起作用。而攔截器能夠深入到方法前后、異常拋出前后等,因此攔截器的使用具有更大的彈性。所以在Spring構架的程序中,要優先使用攔截器。
五、總結
注意:過濾器的觸發時機是容器后,servlet之前,所以過濾器的doFilter(ServletRequest request, ServletResponse response, FilterChain chain)的入參是ServletRequest,而不是HttpServletRequest,因為過濾器是在HttpServlet之前。下面這個圖,可以讓你對Filter和Interceptor的執行時機有更加直觀的認識。
只有經過DispatcherServlet 的請求,才會走攔截器鏈,自定義的Servlet請求是不會被攔截的,比如我們自定義的Servlet地址。
過濾器依賴於Servlet容器,而Interceptor則為SpringMVC的一部分。過濾器能夠攔截所有請求,而Interceptor只能攔截Controller的請求,所以從覆蓋范圍來看,Filter應用更廣一些。但是在Spring逐漸一統Java框架、前后端分離越演越烈,實際上大部分的應用場景,攔截器都可以滿足了。
六、源碼
SpringBoot-過濾器spring-boot-16-filter
SpringBoot-攔截器spring-boot-17-interceptor
七、參考
SpringBoot實現過濾器、攔截器與切片
Spring Boot實戰:攔截器與過濾器
Spring Boot使用過濾器和攔截器分別實現REST接口簡易安全認證