springboot系列文章之過濾器 vs 攔截器
之前實際開發專案的時候,雖然有用過濾器和攔截器,但是理解上還是有點懵懵懂懂的,沒有徹底明白,這篇文章就來仔細剖析下這二者的區別與聯絡。
過濾器
過濾器Filter,是在Servlet規範中定義的,是Servlet容器支援的,該介面定義在 javax.servlet
包下,主要是在客戶端請求(HttpServletRequest)進行預處理,以及對伺服器響應(HttpServletResponse)進行後處理。介面程式碼如下:
package javax.servlet; import java.io.IOException; public interface Filter { void init(FilterConfig var1) throws ServletException; void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; void destroy(); } 複製程式碼
對上面三個介面方法進行分析:
- init(FilterConfig) : 初始化介面,在使用者自定義的Filter初始化時被呼叫,它與Servlet的 init方法的作用是一樣的。
- doFilter(ServletRequest,ServletResponse,FilterChain) : 在每個使用者的請求進來時這個方法都會被呼叫,並在Servlet的service方法之前呼叫(如果我們是開發Servlet專案),而FilterChain就代表當前的整個請求鏈,通過呼叫
FilterChain.doFilter
可以將請求繼續傳遞下去,如果想攔截這個請求,可以不呼叫FilterChain.doFilter,那麼這個請求就直接返回了, 所以Filter是一種責任鏈設計模式 ,在spring security
就大量使用了過濾器,有一條過濾器鏈。 - destroy : 當Filter物件被銷燬時,這個方法被呼叫,注意,當Web容器呼叫這個方法之後,容器會再呼叫一次doFilter方法。
自定義Filter過濾器
在springboot自定義Filter類如下:
@Component public class MyFilter implements Filter { private Logger logger = LoggerFactory.getLogger(MyFilter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { logger.info("filter init"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { logger.info("doFilter"); //對request,response進行預處理 //TODO 進行業務邏輯 filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { logger.info("filter destroy"); } } 複製程式碼
FilterRegistrationBean方式
在springboot中提供了 FilterRegistrationBean
方式,此類提供setOrder方法,可以為多個filter設定排序值。程式碼如下:
@Configuration public class FilterConfig { /** * 配置一個Filter註冊器 * * @return */ @Bean public FilterRegistrationBean filterRegistrationBean1() { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(filter1()); registrationBean.setName("filter1"); //設定順序 registrationBean.setOrder(10); return registrationBean; } @Bean public FilterRegistrationBean filterRegistrationBean2() { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(filter2()); registrationBean.setName("filter2"); //設定順序 registrationBean.setOrder(3); return registrationBean; } @Bean public Filter filter1() { return new MyFilter(); } @Bean public Filter filter2() { return new MyFilter2(); } } 複製程式碼
攔截器
攔截器是Spring提出的概念,它的作用於過濾器類似,可以攔截使用者請求並進行相應的處理,它可以進行更加精細的控制。
在SpringMVC中,DispatcherServlet捕獲每個請求,在到達對應的Controller之前,請求可以被攔截器處理,在攔截器中進行前置處理後,請求最終才到達Controller。
攔截器的介面是 org.springframework.web.servlet.HandlerInterceptor
介面,介面程式碼如下:
public interface HandlerInterceptor { default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } } 複製程式碼
介面方法解讀:
- preHandle方法 :對客戶端發過來的請求進行前置處理,如果方法返回true,繼續執行後續操作,如果返回false,執行中斷請求處理,請求不會發送到Controller
- postHandler方法 :在請求進行處理後執行,也就是在Controller方法呼叫之後處理,當然前提是之前的
preHandle
方法返回 true。具體來說,postHandler
方法會在DispatcherServlet進行檢視返回渲染前被呼叫,也就是說我們可以在這個方法中對 Controller 處理之後的ModelAndView
物件進行操作 - afterCompletion方法 : 該方法在整個請求結束之後執行,當然前提依然是
preHandle
方法的返回值為 true才行。該方法一般用於資源清理工作
自定義攔截器
public class MyInterceptor implements HandlerInterceptor { private Logger logger = LoggerFactory.getLogger(MyInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { logger.info("preHandle...."); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { logger.info("postHandle..."); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { logger.info("afterCompletion..."); } } 複製程式碼
註冊攔截器同時配置攔截器規則
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(handlerInterceptor()) //配置攔截規則 .addPathPatterns("/**"); } @Bean public HandlerInterceptor handlerInterceptor() { return new MyInterceptor(); } } 複製程式碼
多個攔截器協同工作
在springMVC中我們可以實現多個攔截器,並依次將他們註冊進去,如下:
public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(handlerInterceptor()) .addPathPatterns("/**"); registry.addInterceptor(handlerInterceptor2()) .addPathPatterns("/**"); } 複製程式碼
攔截器的順序也跟他們註冊時的順序有關,至少 preHandle
方法是這樣,下圖表示了兩個攔截器協同工作時的執行順序:

上圖出自慕課網
後臺列印日誌也輸出了相同的執行順序:
io-9999-exec-2] c.p.filter.interceptor.MyInterceptor: preHandle.... 2018-09-13 12:13:31.292INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor2: preHandle2.... 2018-09-13 12:13:31.388INFO 9736 --- [nio-9999-exec-2] c.p.filter.controller.HelloController: username:pjmike,password:123456 2018-09-13 12:13:31.418INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor2: postHandle2... 2018-09-13 12:13:31.418INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor: postHandle... 2018-09-13 12:13:31.418INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor2: afterCompletion2... 2018-09-13 12:13:31.418INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor: afterCompletion... 複製程式碼
攔截器與過濾器之間的區別
從上面對攔截器與過濾器的描述來看,它倆是非常相似的,都能對客戶端發來的請求進行處理,它們的區別如下:
- 作用域不同
- 過濾器依賴於servlet容器,只能在 servlet容器,web環境下使用
- 攔截器依賴於spring容器,可以在spring容器中呼叫,不管此時Spring處於什麼環境
- 細粒度的不同
- 過濾器的控制比較粗,只能在請求進來時進行處理,對請求和響應進行包裝
- 攔截器提供更精細的控制,可以在controller對請求處理之前或之後被呼叫,也可以在渲染檢視呈現給使用者之後呼叫
- 中斷鏈執行的難易程度不同
preHandle
小結
簡單總結一下,攔截器相比過濾器有更細粒度的控制,依賴於Spring容器,可以在請求之前或之後啟動,過濾器主要依賴於servlet,過濾器能做的,攔截器基本上都能做。