1. 程式人生 > >Spring過濾器和攔截器

Spring過濾器和攔截器

什麼是攔截器

​ 攔截器(Interceptor): 用於在某個方法被訪問之前進行攔截,然後在方法執行之前或之後加入某些操作,其實就是AOP的一種實現策略。它通過動態攔截Action呼叫的物件,允許開發者定義在一個action執行的前後執行的程式碼,也可以在一個action執行前阻止其執行。同時也是提供了一種可以提取action中可重用的部分的方式。

攔截器作用

​ 攔截使用者的請求並進行相應的處理,比如:判斷使用者是否登陸,是否在可購買時間內,記錄日誌資訊等。

Spring中兩種實現方式

實現HandlerInterceptor介面

​ 通過實現HandlerInterceptor介面, 一般通過繼承HandlerInterceptorAdapter抽象類實現。

handlerInterceptor介面實現

DispatcherServlet處理流程:DispatcherServlet處理請求時會構造一個Excecution Chain,即(可能多個)攔截器和真正處理請求的Handler
即Interceptor是鏈式呼叫的。

preHandle: 在執行Handler之前進行,即Controller方法呼叫之前執行,主要進行初始化操作。

postHandle: 在執行Handler之後進行,即Controller 方法呼叫之後執行,主要對ModelAndView物件進行操作。

afterCompletion: 在整個請求結束之後,即渲染對應的檢視之後執行, 主要進行資源清理工作。

注意事項: 每個Interceptor的呼叫會依據它在xml檔案中宣告順序依次執行。

DispatcherServlet中攔截器相關

DispatcherServlet中doDispatch

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler =
null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { //檢查是否是請求multipart,如檔案上傳 processedRequest = checkMultipart(request); multipartRequestParsed = processedRequest != request; // Determine handler for the current request. //請求到處理器(頁面控制器)的對映,通過HanMapping進行對映 mappedHandler = getHandler(processedRequest, false); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. //處理適配,將處理器包裝成相應的介面卡 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { String requestUri = urlPathHelper.getRequestUri(request); logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } //這裡是關鍵,執行處理器相關的攔截器的預處理 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } try { // Actually invoke the handler. //由介面卡執行處理器(呼叫處理器相應功能處理方法) mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } applyDefaultViewName(request, mv); //執行處理器相關的攔截器的後處理 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } //該方法中會呼叫triggerAfterCompletion,執行處理器相關的攔截器的完成後處理 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Error err) { triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); return; } // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } }

HandlerExecutionChain中applyPreHandle

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (getInterceptors() != null) {
            //順序執行攔截器的preHandle方法,如果返回false,則呼叫triggerAfterCompletion方法
            for (int i = 0; i < getInterceptors().length; i++) {
                HandlerInterceptor interceptor = getInterceptors()[i];
                if (!interceptor.preHandle(request, response, this.handler)) {
                    triggerAfterCompletion(request, response, null);
                    return false;
                }
                this.interceptorIndex = i;
            }
        }
        return true;
    }

applyPostHandle

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
    if (getInterceptors() == null) {
        return;
    }
    //逆序執行攔截器的postHandle方法
    for (int i = getInterceptors().length - 1; i >= 0; i--) {
        HandlerInterceptor interceptor = getInterceptors()[i];
        interceptor.postHandle(request, response, this.handler, mv);
    }
}

triggerAfterCompletion

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
        throws Exception {
 
    if (getInterceptors() == null) {
        return;
    }
    //逆序執行攔截器的afterCompletion方法
    for (int i = this.interceptorIndex; i >= 0; i--) {
        HandlerInterceptor interceptor = getInterceptors()[i];
        try {
            interceptor.afterCompletion(request, response, this.handler, ex);
        }
        catch (Throwable ex2) {
            logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
        }
    }
}

實現WebRequestInterceptor介面

WebRequestInterceptor介面定義 摺疊原碼

//與HandlerInterceptor的區別在於無法終止訪問請求
public interface WebRequestInterceptor {
 
    //返回類行為void,與HandlerInterceptor區別就體現在這裡
    void preHandle(WebRequest request) throws Exception;
 
    void postHandle(WebRequest request, ModelMap model) throws Exception;
 
    void afterCompletion(WebRequest request, Exception ex) throws Exception;
}

------------------------------------------------

SpringMVC的攔截器Interceptor和過濾器Filter功能非常相似,使用場景也差不多,看起來難以區分。比如兩者都能在程式碼前後插入執行片段,都可以用來實現一些公共元件的功能複用(許可權檢查、日誌記錄等),其實它們並不一樣,首先了解一下Interceptor和Filter。

一.Interceptor

Interceptor是Spring攔截器,要實現一個攔截器功能可以繼承Spring的HandlerInterceptor介面:

package com.hpx.xiyou.wuKong.aop;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@Component
public class sanZangInterceptor implements HandlerInterceptor{
    static public final Logger logger = LoggerFactory.getLogger(sanZangInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        //System.out.println("interceptortest pre");
        logger.info("interceptortest pre");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        //System.out.println("interceptortest post");
        logger.info("interceptortest post");
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        //System.out.println("interceptortest after");
        logger.info("interceptortest after");
    }
}

HandlerInterceptor介面有三個需要實現的方法:preHandle(),postHandle()和afterCompletion()。

preHandle方法將在請求處理之前呼叫,SpringMVC中的Interceptor是鏈式呼叫的,每個Interceptor的呼叫都根據它的宣告順序依次執行,且最先執行其preHandle方法,所以可以在該方法中進行一些前置初始化操作或是預處理。該方法的返回值是布林型別,如果返回false,表示請求結束,後續的Interceptor和Controller都不會再執行了,如果返回true就執行下一個攔截器的preHandle方法,一直到最後一個攔截器preHandle方法執行完成後呼叫當前請求的Controller方法。

postHandle方法是在當前請求進行處理之後,也就是Controller方法呼叫結束之後執行,但是它會在DispatcherServlet進行檢視渲染之前被呼叫,所以可以在這個方法中可以對Controller處理之後的ModelAndView物件進行操作。postHandle方法被呼叫的方向跟preHandle是相反的,也就是說先宣告的Interceptor的postHandle方法反而後執行。

afterCompletion方法需要當前對應的Interceptor的preHandle方法的返回值為true時才會執行。該方法會在整個請求結束之後,也就是在DispatcherServlet渲染了對應的檢視之後執行,這個方法的主要作用是用於資源清理工作。

實現一個interceptor攔截器類後,需要在配置中配置使它生效:實現 WebMvcConfigurerAdapter並重寫 addInterceptors,同時在這個方法裡設定要過濾的URL。

package com.hpx.xiyou.wuKong.Adapter;

import com.hpx.xiyou.wuKong.aop.sanZangInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;


@Configuration
public class WebConfigurerAdapter extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new sanZangInterceptor()).addPathPatterns("/wukong/**");

    }
}

以上配置生效後,當訪問/wukong/**型別url時,控制檯輸出如下,其中controller為controller方法中的列印資訊:

interceptortest pre
controller
interceptortest post
interceptortest after

二.Filter

Filter是Spring過濾器,要定義一個Filter類有以下步驟:

首先定義一個Filter類,繼承javax.servlet.Filter類,重寫其init、doFilter、destroy方法。init()方法會在Filter初始化後進行呼叫,在init()方法裡面我們可以通過FilterConfig訪問到初始化引數( getInitParameter()或getInitParameters() )、ServletContext (getServletContext)和當前Filter部署的名稱( getFilterName() )等資訊。destroy()方法將在Filter被銷燬之前呼叫。而doFilter()方法則是真正進行過濾處理的方法,在doFilter()方法內部,我們可以過濾請求的request和返回的response,同時我們還可以利用FilterChain把當前的request和response傳遞給下一個過濾器或Servlet進行處理。

public class FilterTest implements Filter {
    @Autowired
    private PointService pointService;
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init yes");
    }
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("filter test");
        filterChain.doFilter(servletRequest, servletResponse);  // 傳遞給下一個Filter進行處理
        return;
    }
 
    @Override
    public void destroy() {
        System.out.println("destroy yes");
    }
}

然後在配置中使該Filter生效:

<filter>
    <filter-name>filtertest</filter-name>
    <filter-class>FilterTest</filter-class>
</filter>
<filter-mapping>
    <filter-name>filtertest</filter-name>
    <url-pattern>/point/*</url-pattern>
</filter-mapping>

這樣,當我們訪問/point/*型別的url,控制檯輸出如下:

init yes

filter test

controller

三.比較

同時配置過濾器和攔截器然後請求,結果如下:

init yes

filter test

interceptortest pre

controller

interceptortest post

interceptortest after

可以看到filter優先於interceptor被呼叫。

過濾器和攔截器主要區別如下:

1.二者適用範圍不同。Filter是Servlet規範規定的,只能用於Web程式中,而攔截器既可以用於Web程式,也可以用於Application、Swing程式中。

2.規範不同。Filter是在Servlet規範定義的,是Servlet容器支援的,而攔截器是在Spring容器內的,是Spring框架支援的。

3.使用的資源不同。同其他程式碼塊一樣,攔截器也是一個Spring的元件,歸Spring管理,配置在Spring檔案中,因此能使用Spring裡的任何資源、物件(各種bean),而Filter不行。

4.深度不同。Filter只在Servlet前後起作用,而攔截器能夠深入到方法前後、異常跑出前後等,攔截器的使用有更大的彈性。