原文部落格地址: pjmike的部落格
前言
之前實際開發專案的時候,雖然有用過濾器和攔截器,但是理解上還是有點懵懵懂懂的,沒有徹底明白,這篇文章就來仔細剖析下這二者的區別與聯絡。
過濾器
過濾器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.292 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor2 : preHandle2....
2018-09-13 12:13:31.388 INFO 9736 --- [nio-9999-exec-2] c.p.filter.controller.HelloController : username:pjmike,password:123456
2018-09-13 12:13:31.418 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor2 : postHandle2...
2018-09-13 12:13:31.418 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor : postHandle...
2018-09-13 12:13:31.418 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor2 : afterCompletion2...
2018-09-13 12:13:31.418 INFO 9736 --- [nio-9999-exec-2] c.p.filter.interceptor.MyInterceptor : afterCompletion...
複製程式碼
攔截器與過濾器之間的區別
從上面對攔截器與過濾器的描述來看,它倆是非常相似的,都能對客戶端發來的請求進行處理,它們的區別如下:
- 作用域不同
- 過濾器依賴於servlet容器,只能在 servlet容器,web環境下使用
- 攔截器依賴於spring容器,可以在spring容器中呼叫,不管此時Spring處於什麼環境
- 細粒度的不同
- 過濾器的控制比較粗,只能在請求進來時進行處理,對請求和響應進行包裝
- 攔截器提供更精細的控制,可以在controller對請求處理之前或之後被呼叫,也可以在渲染檢視呈現給使用者之後呼叫
- 中斷鏈執行的難易程度不同
- 攔截器可以
preHandle
方法內返回 false 進行中斷 - 過濾器就比較複雜,需要處理請求和響應物件來引發中斷,需要額外的動作,比如將使用者重定向到錯誤頁面
- 攔截器可以
小結
簡單總結一下,攔截器相比過濾器有更細粒度的控制,依賴於Spring容器,可以在請求之前或之後啟動,過濾器主要依賴於servlet,過濾器能做的,攔截器基本上都能做。