1. 程式人生 > >springboot下使用攔截器和過濾器

springboot下使用攔截器和過濾器

1. 攔截器Interceptor

Spring MVC的攔截器(Interceptor)和Filter不同,但是也可以實現對請求進行預處理,後處理。先介紹它的使用,只需要兩步:
1.1 實現攔截器
實現攔截器可以自定義實現HandlerInterceptor介面,也可以通過繼承HandlerInterceptorAdapter類,後者是前者的實現類。如果preHandle方法return true,則繼續後續處理。

public class InterceptorDemo implements HandlerInterceptor {


    @Override
    public
boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { StringBuffer requestURL = httpServletRequest.getRequestURL(); System.out.println("前置攔截器1 preHandle: 請求的uri為:"+requestURL.toString()); return true; } @Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { System.out.println("攔截器1 postHandle: "); } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws
Exception { System.out.println("攔截器1 afterCompletion: "); } }

1.2 註冊攔截器
實現攔截器後還需要將攔截器註冊到spring容器中,可以通過繼承WebMvcConfigurerAdapter類,覆蓋其addInterceptors(InterceptorRegistry registry)方法。記得把Bean註冊到Spring容器中,可以選擇@Component 或者 @Configuration。

@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry){

        InterceptorRegistration registration2 = registry.addInterceptor(new InterceptorDemo2());
        //配置攔截路徑
        registration2.addPathPatterns("/**");
        //配置不攔截的路徑
        registration2.excludePathPatterns("/**.html");

        //註冊其他的攔截器,執行順序和配置順序有關係

        //註冊攔截器
        InterceptorRegistration registration = registry.addInterceptor(new InterceptorDemo());
        //配置攔截路徑
        registration.addPathPatterns("/**");
        //配置不攔截的路徑
        registration.excludePathPatterns("/**.html");

    }

注意這裡註冊了兩個攔截器。這兩個攔截器的執行順序和配置順序有關係,即先配置順序就在前(感覺這樣不太方便,但沒有找到設定類似order的API)。

發起一個請求,在控制檯可以看到攔截器生效:

前置攔截器2 preHandle: 使用者名稱:null
前置攔截器1 preHandle: 請求的uri為:http://localhost:8010/user/353434
攔截器1 postHandle: 
攔截器2 postHandle: 
攔截器1 afterCompletion: 
攔截器2 afterCompletion

1.3 攔截器的總結
1.3.1 工作原理
一個攔截器,只有preHandle方法返回true,postHandle、afterCompletion才有可能被執行;如果preHandle方法返回false,則該攔截器的postHandle、afterCompletion必然不會被執行。攔截器不是Filter,卻實現了Filter的功能,其原理在於:

  • 所有的攔截器(Interceptor)和處理器(Handler)都註冊在HandlerMapping中。
  • Spring MVC中所有的請求都是由DispatcherServlet分發的。
  • 當請求進入DispatcherServlet.doDispatch()時候,首先會得到處理該請求的Handler(即Controller中對應的方法)以及所有攔截該請求的攔截器。攔截器就是在這裡被呼叫開始工作的。

1.3.2 攔截器工作流程

  • 正常流程:

        1.Interceptor2.preHandle
        2.Interceptor1.preHandle
        3.Controller處理請求
        4.Interceptor1.postHandle
        5.Interceptor2.postHandle
        6.渲染檢視view
        2.Interceptor1.afterCompletion
        2.Interceptor2.afterCompletion
    
  • 中斷流程
    如果在Interceptor1.preHandle中報錯或返回false ,那麼接下來的流程就會被中斷,但注意被執行過的攔截器的afterCompletion仍然會執行。下圖為Interceptor1.preHandle返回false的情況:

    前置攔截器2 preHandle: 使用者名稱:null
    前置攔截器1 preHandle: 請求的uri為:http://localhost:8010/user/353434
    攔截器2 afterCompletion:

1.3.3 和Filter共存時的執行順序
攔截器是在DispatcherServlet這個servlet中執行的,因此所有的請求最先進入Filter,最後離開Filter。其順序如下。

Filter->Interceptor.preHandle->Handler->Interceptor.postHandle->Interceptor.afterCompletion->Filter

1.3.4 應用場景
攔截器本質上是面向切面程式設計(AOP),符合橫切關注點的功能都可以放在攔截器中來實現,主要的應用場景包括:

  • 登入驗證,判斷使用者是否登入。
  • 許可權驗證,判斷使用者是否有許可權訪問資源。
  • 日誌記錄,記錄請求日誌,以便統計請求訪問量。
  • 處理cookie、本地化、國際化、主題等。
  • 效能監控,監控請求處理時長等。

2. 過濾器Filter

springboot下過濾器的使用有兩種形式:
2.1 註解形式
建立一個Filter,並使用WebFilter註解進行修飾,表示該類是一個Filter,以便於啟動類進行掃描的時候確認

@WebFilter(urlPatterns = "/*",filterName = "filter2")
public class FilterAnnotationTest implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("過濾器2開始初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("過濾器2開始工作");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("過濾器2銷燬");
    }
}

然後在啟動類上添加註解@ServletComponentScan,該註解用於自動掃描指定包下(預設是與啟動類同包下)的WebFilter/WebServlet/WebListener等特殊類。
2.2 程式碼註冊方式
同樣編寫Filter,但是不在新增WebFilter註解,

public class FilterPorcess implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("過濾器開始初始化。。。");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        System.out.println("過濾器開始工作。。"+httpServletRequest.getRequestURL());
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("過濾器開始銷燬");
    }
}

然後利用filterRegistrationBean來進行註冊。

@Configuration
public class FilterDemo {
    @Bean
    @Order(2)
    //spring boot 會按照order值的大小,從小到大的順序來依次過濾
    public FilterRegistrationBean configFilter(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new FilterPorcess());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.setName("sessionFilter");
        return filterRegistrationBean;
    }

}

2.3 過濾器filter和攔截器Interceptor的區別
spring的攔截器和servlet的過濾器有相似之處,都是AOP思想的體現,都可以實現許可權檢查,日誌記錄,不同的是
1. 適用範圍不同:Filter是Servlet容器規定的,只能使用在servlet容器中,而攔截器的使用範圍就大得多
2. 使用的資源不同:攔截器是屬於spring的一個元件,因此可以使用spring的所有資源,物件,如service物件,資料來源,事務控制等,而過濾器就不行
3. 深度不同:Filter還在servlet前後起作用。而攔截器能夠深入到方法前後,異常丟擲前後,因此攔截器具有更大的彈性,所有在spring框架中應該優先使用攔截器。
通過除錯可以發現,攔截器的執行過程是在過濾器的doFilter中執行的,過濾器的初始化會在專案啟動時執行。

過濾器開始工作。。http://localhost:8010/user/353434
前置攔截器2 preHandle: 使用者名稱:null
前置攔截器1 preHandle: 請求的uri為:http://localhost:8010/user/353434
攔截器1 postHandle: 
攔截器2 postHandle: 
攔截器1 afterCompletion: 
攔截器2 afterCompletion: 
過濾器開始工作。。http://localhost:8010/favicon.ico

可以通過這個部落格裡的一張圖來說明:
這裡寫圖片描述

3. 監聽器

監聽器的簡單使用如下:先編寫監聽器的實現:

@WebListener
public class WebListenerDemo implements ServletContextListener {


    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("監聽器初始化。。。。。。。。。。。。");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("監聽器銷燬。。。。。。。。。。。");
    }
}

監聽session建立的監聽器(可以用來統計線上人數)

@WebListener
public class SessionListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("。。。建立session成功");
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("。。。銷燬session");
    }
}

然後在啟動類上添加註解@ServletComponentScan即可,當然也可以註冊到spring容器中省卻@ServletComponentScan註解。

@Configuration
public class ListenerConfig {
    @Bean
    public ServletListenerRegistrationBean servletListenerRegistrationBean() {
        ServletListenerRegistrationBean slrBean = new ServletListenerRegistrationBean();
        slrBean.setListener(new WebListenerDemo());
        return slrBean;
    }

    @Bean
    public ServletListenerRegistrationBean sessionListenerRegistrationBean() {
        ServletListenerRegistrationBean slrBean = new ServletListenerRegistrationBean();
        slrBean.setListener(new SessionListener());
        return slrBean;
    }
}