天天看點

攔截器和過濾器詳解

作者:小彭思考

過濾器Filter

定義

對Servlet容器調用Servlet的過程進行攔截,基于函數回調實作

常見使用場景

  • 統一設定編碼
  • 過濾敏感字元
  • 登入校驗
  • URL級别的通路權限控制
  • 資料壓縮

使用方式

這裡展示的是SpringBoot整合過濾器的方式

使用配置類的方式(bean注入)

  • 編寫filter類并實作Filter接口
  • 實作Filter接口中的init,doFilter方法,與destory方法
  • doFilter方法中使用chain.doFilter(request, response);來放行servlet
  • 編寫Filter配置類,注解加上@ConFiguration注解
  • 編寫方法,傳回值類型為FilterRegistrationBean
  • 方法内建立FilterRegistrationBean對象,構造器内傳入過濾器類的對象,直接new一個就可以,或者讓Spring容器自動注入後再使用
  • 調用FilterRegistrationBean 對象的addUrlPatterns方法類設定過濾的請求路徑

過濾器類

@Slf4j//友善使用下面的log.info()方法輸出日志
public class LoginCheckFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        log.info("過濾器{}正在生效",LoginCheckFilter.class.getName());
        log.info("過濾器{}攔截到了請求{}",LoginCheckFilter.class.getName(),request.getRequestURI());
        filterChain.doFilter(request,response);
        log.info("請求{}已經被放行",request.getRequestURI());
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("過濾器{}被初始化",LoginCheckFilter.class.getName());
        Filter.super.init(filterConfig);
    }

    @Override
    public void destroy() {
        log.info("過濾器{}被銷毀",LoginCheckFilter.class.getName());
        Filter.super.destroy();
    }
}           

配置類

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean<LoginCheckFilter> filterRegistrationBean() {
        FilterRegistrationBean<LoginCheckFilter> loginCheckFilter = new FilterRegistrationBean<>();
        loginCheckFilter.setFilter(new LoginCheckFilter());//注冊自定義過濾器
        loginCheckFilter.setName("LoginCheckFilter");//過濾器名稱
        loginCheckFilter.addUrlPatterns("/*");//過濾所有路徑
        loginCheckFilter.setOrder(1);//優先級,最頂級按照資料從小到大來執行過濾器
        return loginCheckFilter;
    }
}           

使用注解

  • 建立過濾器類,方法與上面建立過濾器類的步驟一樣
  • 過濾器類上面添加注解 @WebFilter(urlPatterns = {“攔截路徑”}, filterName = “過濾器名稱”)
  • 在啟動類上添加@ServeltComponentScan注解來掃描servlet元件(包括:@WebFilter和@WebListener)

注意:使用注解方式無法控制多個過濾器的順序,()預設執行順序是按照類名字母的排序,

使用@Order注解控制Bean的執行順序時,必須配合注解一起使用,才會起作用

但是@Component與@WebFilter注解都會把過濾器類加載一遍,并且@component會把@WebFilter加載的Bean覆寫掉,進而使得@WebFilter配置的攔截路徑與過濾器名稱失效,

是以如果指定了優先級則配置的攔截路徑失效,預設為任意路徑,如果不使用@Order注解則無法控制多個過濾器的執行順序,

綜上筆者這裡推薦使用Bean注入的方式來使用過濾器

總結

  • Filter是依賴于Servlet的,需要導入Servlet的依賴
  • 過濾器會在容器啟動時被初始化,并且隻會初始化一次
  • doFilter()方法在目标請求被攔截前執行,放行調用filterChain.doFilter(servletRequest,servletResponse);具體變量名根據方法體而變
  • destroy()方法在容器銷毀時執行,隻執行一次
  • Filter可以攔截所有請求,包括靜态資源
  • 過濾器基于函數回調實作

擴充

  • 過濾器,監聽器和Servlet是JavaWeb三大元件之一,它們的在SpringBoot中的使用方式都差不多差別如下
  • 使用注解方式使用時都需要在啟動類加上@ServletComponentScan注解
  • 三個注解為@WebSrvlet,@WebFilter和@Weblistener
  • Servlet類繼承的是HttpServlet,Filter實作的是Filter類,Listener實作ServletContextListener類
  • 使用Bean注入方式使用時,Filter元件方法傳回類型是FilterRegistrationBean,Listener元件方法傳回類型為ServletListenerRefistrationBean,Servlet元件傳回類型為ServletRefistrationBean

攔截器Interceptor

定義

類似于Servlet中的過濾器,主要用于攔截使用者請求并做相應的處理,基于java反射機制(動态代理)實作

常見使用場景

  • 日志記錄
  • 權限校驗
  • 登入校驗
  • 性能檢測【檢測方法的執行時間】

其實攔截器和過濾器很像,有些使用場景。無論選用誰都能實作。需要注意的使他們彼此的使用範圍,觸發機制。

使用方式

  • 編寫攔截器類并實作HandlerInterceptor 接口
  • 實作HandlerInterceptor 接口的preHandle,postHandle和afterHandle方法
  • 編寫配置類,注意加上@configuration注解,并繼承WebMvcConfigurer接口

總結

  • 攔截器依賴于SpringMvc的,需要導入Mvc的依賴
  • preHandle() 在目标請求完成之前執行。有傳回值Boolean類型,true:表示放行
  • postHandle() 在目标請求之完成後執行。
  • afterCompletion() 在整個請求完成之後【modelAndView已被渲染執行】。
  • 攔截器隻能攔截action請求,不包括靜态資源(有待驗證)
  • 基于java反射機制實作

攔截器不生效原因分析

  • 配置類沒有加@configuration注解
  • 攔截路徑出錯,/*攔截目前目錄,/**攔截所有目錄
  • WebMvcConfigurationSupport,WebMvcConfigurer,WebMvcConfigurerAdapter項目中不能有兩個以上的類繼承或實作自這三個類 因為這三個類都是WebMVC的配置類,如果同時出現隻會有一個類生效,一般實作WebMvcConfilgurer就可以了

過濾器與攔截器的差別

  • 觸發順序不一樣:
  1. 過濾器的觸發順序在攔截器前,也就是先執行過濾器,在執行攔截器, 過濾前 - 攔截前 - Action處理 - 攔截後 - 過濾後。
  2. 過濾器之間的執行順序嵌套的。
  3. 但是攔截器執行順序也是嵌套執行的但是,因為有三個方法,會先執行pre方法,再一起執行post方法,最後一起執行after方法。
  4. 實作方式不同,過濾器基于函數回調實作,攔截器基于動态代理(反射)實作。
  • 攔截器是基于java的反射機制的,而過濾器是基于函數回調。
  • 攔截器不依賴與servlet容器,過濾器依賴與servlet容器。
  • 攔截器隻能對action請求起作用,而過濾器則可以對幾乎所有的請求起作用。
  • 攔截器可以通路action上下文、值棧裡的對象,而過濾器不能通路。
  • 在action的生命周期中,攔截器可以多次被調用,而過濾器隻能在容器初始化時被調用一次。
  • 攔截器可以擷取IOC容器中的各個bean,而過濾器就不行,這點很重要,在攔截器裡注入一個service,可以調用業務邏輯。

aop與過濾器,攔截器的差別

  • 過濾器,攔截器攔截的是URL。AOP攔截的是類的中繼資料(包、類、方法名、參數等)。
  • 過濾器并沒有定義業務用于執行邏輯前、後等,僅僅是請求到達就執行。
  • 攔截器有三個方法,相對于過濾器更加細緻,有被攔截邏輯執行前、後等。
  • AOP針對具體的代碼,能夠實作更加複雜的業務邏輯。
  • 三者功能類似,但各有優勢,從過濾器 -> 攔截器 -> 切面,攔截規則越來越細緻。
  • 執行順序依次是過濾器、攔截器、切面。