Spring Filter過濾器和Intercpetor攔截器

過濾器和攔截器的區別

Spring開發中我們會遇到Filter過濾器與Interceptor攔截器的使用,他們都能對一些請求做一下預處理,但他們之間還是有很大的不同的:

  1. 攔截器是基於Java的反射機制的,而過濾器是基於函數回調。
  2. Filter是在Servlet規範中定義的,是Servlet容器支持的。而攔截器是在Spring容器內的,是Spring框架支持的。
  3. Filter在只在Servlet前後起作用。而攔截器能夠深入到方法前後、異常拋出前後等,因此攔截器的使用具有更大的彈性。所以在Spring構架的程序中,要優先使用攔截器)
  4. 攔截器可以訪問action上下文、值棧裏的對象,而過濾器不能訪問。
  5. 在action的生命週期中,攔截器可以多次被調用,而過濾器只能在容器初始化時被調用一次。

應用場景

  • 日誌記錄:記錄請求信息的日誌,以便進行信息監控、信息統計、計算PV(Page View)等。
  • 權限檢查:如登錄檢測,進入處理器檢測檢測是否登錄,如果沒有直接返回到登錄頁面;
  • 攔截器能深入方法的前後執行過程,可以進行統計方法執行時間來判斷性能問題

過濾器

過濾器接口

public interface Filter {
    default void init(FilterConfig filterConfig) throws ServletException {
    }

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    default void destroy() {
    }
}

Filter是javax.servlet中的接口,是有生命週期的,存在初始化,執行以及銷燬的過程。

過濾器的實現

下面來看一下我寫的一個權限檢驗的demo


public class AuthCheckFilter implements Filter {


    //@Resource //不生效
    private UserService userService;
    //Filter的創建和銷燬由WEB服務器負責。 web 應用程序啓動時,
    //web 服務器將創建Filter的實例對象,並調用其init方法進行初始化,
    //容器只有在實例化過濾器時纔會調用該方法一次。
    //容器爲這個方法傳遞一個FilterConfig對象,其中包含與Filter相關的配置信息
    public void init(FilterConfig filterConfig) throws ServletException {
       //獲取容器上下文,可以進行對Bean的操作
        ServletContext context = filterConfig.getServletContext();
        ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(context);
        userService = (UserServiceImpl)ctx.getBean(UserServiceImpl.class);


    }
    //每次filter進行攔截都會執行。需要注意的是過濾器的一個實例可以同時服務於多個請求,
    // 特別需要注意線程同步問題,儘量不用或少用實例變量。
    // 在過濾器的doFilter()方法實現中,任何出現在FilterChain的doFilter方法之前地方,
    //request是可用的;在doFilter()方法之後response是可用的。
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        String user_token = request.getHeader("user_token");
        String methodString=request.getMethod();
        //這個判斷只是簡單的說明,通過init方法我們可以獲取bean對象來進行驗證操作
        if(!userService.findOne(user_token)){
            response.setContentType("application/json; charset=utf-8");
            String jsonString="{\"status\":\"false\",\"info\":\"用戶未登陸或超時\"}";
            OutputStream os = response.getOutputStream();
            os.write(jsonString.getBytes());
            os.close();
        }
        //這裏是個簡單的demo ,具體邏輯需要時再處理
        if(StringUtils.isEmpty(user_token)){
            response.setContentType("application/json; charset=utf-8");
            String jsonString="{\"status\":\"false\",\"info\":\"用戶未登陸或超時\"}";
            OutputStream os = response.getOutputStream();
            os.write(jsonString.getBytes());
            os.close();
        }

        filterChain.doFilter(servletRequest, servletResponse);

    }
    //在Web容器卸載 Filter 對象之前被調用
    //如果過濾器使用了其他資源,需要在這個方法中釋放這些資源。
    public void destroy() {

    }
}


在上面代碼的初始化方法init()中,我添加了獲取Spring上下文以及bean對象的方法,這樣我們就可以有更大的靈活性來做處理。

Filter的配置是在web.xml中添加的

 <filter>
    <filter-name>authFilter</filter-name>
    <filter-class>app.filter.AuthCheckFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>authFilter</filter-name>
    <url-pattern>/user/*</url-pattern>
</filter-mapping>
<!--<filter-mapping>標記是有先後順序的,它的聲明順序說明容器是如何形成過濾器鏈的-->
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

過濾器的demo
https://github.com/lili1990/learning/tree/master/spring-learning/spring-filter

攔截器

攔截器接口

public interface HandlerInterceptor {

    //預處理回調方法,實現處理器的預處理(如登錄檢查)
    //第三個參數爲響應的處理器,攔截的controller對象
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception;

    //後處理回調方法,實現處理器的後處理,但在渲染視圖之前
    void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception;


    //整個請求處理完畢回調方法,即在視圖渲染完畢時回調
    void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception;

}

攔截適配器

public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
        return true;
    }

    @Override
    public void postHandle(
            HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception {
    }

    @Override
    public void afterCompletion(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
    }

    @Override
    public void afterConcurrentHandlingStarted(
            HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
    }

}

這是HandlerInterceptor的適配器,簡單的實現了HandlerInterceptor(此處所以三個回調方法都是空實現,preHandle返回true。),我們這樣就不必要實現HandlerInterceptor的全部方法,我們通過HandlerInterceptorAdapter來實現自己需要的方法。

運行流程:

Interceptor運行流程

注意:異常流程中,比如是HandlerInterceptor2中斷的流程(preHandle返回false),此處僅調用它之前攔截器的preHandle返回true的afterCompletion方法。

攔截器配置


    <mvc:annotation-driven />

    <context:component-scan base-package="app.controller"/>

    <context:component-scan base-package="app.service"/>
 <!--攔截器配置-->
    <mvc:interceptors>
        <!-- 日誌攔截器,全局攔截 -->
        <bean class="app.interceptor.LogInterceptor"/>
        <!--配置攔截器, 多個攔截器,順序執行 -->
        <!--權限校驗,排除部分請求-->
        <mvc:interceptor>
            <!-- 匹配的是url路徑, 如果不配置或/**,將攔截所有的Controller -->
            <mvc:mapping path="/**"/>
            <!--排除部分請求,不被攔截(下面攔截的請求都是swagger的請求)-->
            <mvc:exclude-mapping path="/swagger-ui.html*"/>
            <mvc:exclude-mapping path="/webjars/**"/>
            <mvc:exclude-mapping path="/configuration/ui/**"/>
            <mvc:exclude-mapping path="/swagger-resources/**"/>
            <mvc:exclude-mapping path="/v2/**"/>
            <bean class="app.interceptor.AuthCheckInterceptor" />
        </mvc:interceptor>
        <!-- 當設置多個攔截器時,先按順序調用preHandle方法,然後逆序調用每個攔截器的postHandle和afterCompletion方法 -->
    </mvc:interceptors>

攔截器的demo
https://github.com/lili1990/learning/tree/master/spring-learning/spring-interceptor

攔截器的深入學習可以看Spring DispatcherServlet

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章