springboot系列文章之過濾器 vs 攔截器

pjmike_pj發表於2018-09-20

原文部落格地址: 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方法是這樣,下圖表示了兩個攔截器協同工作時的執行順序:

img

上圖出自慕課網

後臺列印日誌也輸出了相同的執行順序:

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,過濾器能做的,攔截器基本上都能做。

參考資料 & 鳴謝

相關文章