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

過濾器與攔截器

過濾器(Filter)

Servlet中的過濾器Filter是實現了javax.servlet.Filter介面的伺服器端程式,主要的用途是設定字符集、控制權限、控制轉向、做一些業務邏輯判斷等。其工作原理是,只要你在web.xml檔案配置好要攔截的客戶端請求,它都會幫你攔截到請求,此時你就可以對請求或響應(Request、Response)統一設定編碼,簡化操作;同時還可進行邏輯判斷,如使用者是否已經登陸、有沒有許可權訪問該頁面等等工作。它是隨你的web應用啟動而啟動的,只初始化一次,以後就可以攔截相關請求,只有當你的web應用停止或重新部署的時候才銷燬。

Filter可以認為是Servlet的一種“加強版”,它主要用於對使用者請求進行預處理,也可以對HttpServletResponse進行後處理,是個典型的處理鏈。Filter也可以對使用者請求生成響應,這一點與Servlet相同,但實際上很少會使用Filter向用戶請求生成響應。使用Filter完整的流程是:Filter對使用者請求進行預處理,接著將請求交給Servlet進行處理並生成響應,最後Filter再對伺服器響應進行後處理。

      Filter有如下幾個用處。

  • 在HttpServletRequest到達Servlet之前,攔截客戶的HttpServletRequest。
  • 根據需要檢查HttpServletRequest,也可以修改HttpServletRequest頭和資料。
  • 在HttpServletResponse到達客戶端之前,攔截HttpServletResponse。
  • 根據需要檢查HttpServletResponse,也可以修改HttpServletResponse頭和資料。

     Filter有如下幾個種類。

  • 使用者授權的Filter:Filter負責檢查使用者請求,根據請求過濾使用者非法請求。
  • 日誌Filter:詳細記錄某些特殊的使用者請求。
  • 負責解碼的Filter:包括對非標準編碼的請求解碼。
  • 能改變XML內容的XSLT Filter等。
  • Filter可以負責攔截多個請求或響應;一個請求或響應也可以被多個Filter攔截。

     建立一個Filter只需兩個步驟

  1. 建立Filter處理類
  2. web.xml檔案中配置Filter

   建立Filter必須實現javax.servlet.Filter介面,在該介面中定義瞭如下三個方法。

  • void init(FilterConfig config):用於完成Filter的初始化。
  • void destory():用於Filter銷燬前,完成某些資源的回收。
  • void doFilter(ServletRequest request,ServletResponse response,FilterChain chain):實現過濾功能,該方法就是對每個請求及響應增加的額外處理。該方法可以實現對使用者請求進行預處理(ServletRequest request),也可實現對伺服器響應進行後處理(ServletResponse response)—它們的分界線為是否呼叫了chain.doFilter(),執行該方法之前,即對使用者請求進行預處理;執行該方法之後,即對伺服器響應進行後處理。

 攔截器(Interceptor)

攔截器是在面向切面程式設計中應用的,就是在你的service或者一個方法前呼叫一個方法,或者在方法後呼叫一個方法。是基於JAVA的反射機制。攔截器不是在web.xml,比如struts在struts.xml中配置。

攔截器,在AOP(Aspect-Oriented Programming)中用於在某個方法或欄位被訪問之前,進行攔截,然後在之前或之後加入某些操作。攔截是AOP的一種實現策略。

     在WebWork的中文文件的解釋為—攔截器是動態攔截Action呼叫的物件。它提供了一種機制使開發者可以定義在一個Action執行的前後執行的程式碼,也可以在一個Action執行前阻止其執行。同時也提供了一種可以提取Action中可重用的部分的方式。

     攔截器將Action共用的行為獨立出來,在Action執行前後執行。這也就是我們所說的AOP,它是分散關注的程式設計方法,它將通用需求功能從不相關類之中分離出來;同時,能夠共享一個行為,一旦行為發生變化,不必修改很多類,只要修改這個行為就可以。

     攔截器將很多功能從我們的Action中獨立出來,大量減少了我們Action的程式碼,獨立出來的行為就有很好的重用性。

     當你提交對Action(預設是.action結尾的url)的請求時,ServletDispatcher會根據你的請求,去排程並執行相應的Action。在Action執行之前,呼叫被Interceptor擷取,Interceptor在Action執行前後執行。

     SpringMVC 中的Interceptor 攔截請求是通過HandlerInterceptor 來實現的。在SpringMVC 中定義一個Interceptor 非常簡單,主要有兩種方式,第一種方式是要定義的Interceptor類要實現了Spring 的HandlerInterceptor 介面,或者是這個類繼承實現了HandlerInterceptor 介面的類,比如Spring 已經提供的實現了HandlerInterceptor 介面的抽象類HandlerInterceptorAdapter ;第二種方式是實現Spring的WebRequestInterceptor介面,或者是繼承實現了WebRequestInterceptor的類。

   (1 )preHandle (HttpServletRequest request, HttpServletResponse response, Object handle) 方法,顧名思義,該方法將在請求處理之前進行呼叫。SpringMVC 中的Interceptor 是鏈式的呼叫的,在一個應用中或者說是在一個請求中可以同時存在多個Interceptor 。每個Interceptor 的呼叫會依據它的宣告順序依次執行,而且最先執行的都是Interceptor 中的preHandle 方法,所以可以在這個方法中進行一些前置初始化操作或者是對當前請求的一個預處理,也可以在這個方法中進行一些判斷來決定請求是否要繼續進行下去。該方法的返回值是布林值Boolean型別的,當它返回為false 時,表示請求結束,後續的Interceptor 和Controller 都不會再執行;當返回值為true 時就會繼續呼叫下一個Interceptor 的preHandle 方法,如果已經是最後一個Interceptor 的時候就會是呼叫當前請求的Controller 方法。

   (2 )postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView) 方法,由preHandle 方法的解釋我們知道這個方法包括後面要說到的afterCompletion 方法都只能是在當前所屬的Interceptor 的preHandle 方法的返回值為true 時才能被呼叫。postHandle 方法,顧名思義就是在當前請求進行處理之後,也就是Controller 方法呼叫之後執行,但是它會在DispatcherServlet 進行檢視返回渲染之前被呼叫,所以我們可以在這個方法中對Controller 處理之後的ModelAndView 物件進行操作。postHandle 方法被呼叫的方向跟preHandle 是相反的,也就是說先宣告的Interceptor 的postHandle 方法反而會後執行,這和Struts2 裡面的Interceptor 的執行過程有點型別。Struts2 裡面的Interceptor 的執行過程也是鏈式的,只是在Struts2 裡面需要手動呼叫ActionInvocation 的invoke 方法來觸發對下一個Interceptor 或者是Action 的呼叫,然後每一個Interceptor 中在invoke 方法呼叫之前的內容都是按照宣告順序執行的,而invoke 方法之後的內容就是反向的。

   (3 )afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) 方法,該方法也是需要當前對應的Interceptor 的preHandle 方法的返回值為true 時才會執行。顧名思義,該方法將在整個請求結束之後,也就是在DispatcherServlet 渲染了對應的檢視之後執行。這個方法的主要作用是用於進行資源清理工作的。

 攔截器(Interceptor)和過濾器(Filter)的區別

Spring的Interceptor(攔截器)與Servlet的Filter有相似之處,比如二者都是AOP程式設計思想的體現,都能實現許可權檢查、日誌記錄等。不同的是:

Filter Interceptor Summary
Filter 介面定義在 javax.servlet 包中 介面 HandlerInterceptor 定義在org.springframework.web.servlet 包中
Filter 定義在 web.xml 中
Filter在只在 Servlet 前後起作用。Filters 通常將 請求和響應(request/response) 當做黑盒子,Filter 通常不考慮servlet 的實現。 攔截器能夠深入到方法前後、異常丟擲前後等,因此攔截器的使用具有更大的彈性。允許使用者介入(hook into)請求的生命週期,在請求過程中獲取資訊,Interceptor 通常和請求更加耦合。 在Spring構架的程式中,要優先使用攔截器。幾乎所有 Filter 能夠做的事情, interceptor 都能夠輕鬆的實現
Filter 是 Servlet 規範規定的。 而攔截器既可以用於Web程式,也可以用於Application、Swing程式中。 使用範圍不同
Filter 是在 Servlet 規範中定義的,是 Servlet 容器支援的。 而攔截器是在 Spring容器內的,是Spring框架支援的。 規範不同
Filter 不能夠使用 Spring 容器資源 攔截器是一個Spring的元件,歸Spring管理,配置在Spring檔案中,因此能使用Spring裡的任何資源、物件,例如 Service物件、資料來源、事務管理等,通過IoC注入到攔截器即可 Spring 中使用 interceptor 更容易
Filter 是被 Server(like Tomcat) 呼叫 Interceptor 是被 Spring 呼叫 因此 Filter 總是優先於 Interceptor 執行

攔截器(Interceptor)和過濾器(Filter)的執行順序

過濾前-攔截前-Action處理-攔截後-過濾後

攔截器(Interceptor)使用

interceptor 的執行順序大致為:

  1. 請求到達 DispatcherServlet
  2. DispatcherServlet 傳送至 Interceptor ,執行 preHandle
  3. 請求達到 Controller
  4. 請求結束後,postHandle 執行

Spring 中主要通過 HandlerInterceptor 介面來實現請求的攔截,實現 HandlerInterceptor 介面需要實現下面三個方法:

  • preHandle() – 在handler執行之前,返回 boolean 值,true 表示繼續執行,false 為停止執行並返回。
  • postHandle() – 在handler執行之後, 可以在返回之前對返回的結果進行修改
  • afterCompletion() – 在請求完全結束後呼叫,可以用來統計請求耗時等等

統計請求耗時

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

import org.apache.log4j.Logger;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class ExecuteTimeInterceptor extends HandlerInterceptorAdapter{

    private static final Logger logger = Logger.getLogger(ExecuteTimeInterceptor.class);

    //before the actual handler will be executed
    public boolean preHandle(HttpServletRequest request,
        HttpServletResponse response, Object handler)
        throws Exception {

        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);

        return true;
    }

    //after the handler is executed
    public void postHandle(
        HttpServletRequest request, HttpServletResponse response,
        Object handler, ModelAndView modelAndView)
        throws Exception {

        long startTime = (Long)request.getAttribute("startTime");

        long endTime = System.currentTimeMillis();

        long executeTime = endTime - startTime;

        //modified the exisitng modelAndView
        modelAndView.addObject("executeTime",executeTime);

        //log it
        if(logger.isDebugEnabled()){
           logger.debug("[" + handler + "] executeTime : " + executeTime + "ms");
        }
    }
}

使用mvc:interceptors標籤來宣告需要加入到SpringMVC攔截器鏈中的攔截器

<mvc:interceptors>  
<!-- 使用bean定義一個Interceptor,直接定義在mvc:interceptors根下面的Interceptor將攔截所有的請求 -->  
<bean class="com.company.app.web.interceptor.AllInterceptor"/>  
    <mvc:interceptor>  
         <mvc:mapping path="/**"/>  
         <mvc:exclude-mapping path="/parent/**"/>  
         <bean class="com.company.authorization.interceptor.SecurityInterceptor" />  
    </mvc:interceptor>  
    <mvc:interceptor>  
         <mvc:mapping path="/parent/**"/>  
         <bean class="com.company.authorization.interceptor.SecuritySystemInterceptor" />  
    </mvc:interceptor>  
</mvc:interceptors>

可以利用mvc:interceptors標籤宣告一系列的攔截器,然後它們就可以形成一個攔截器鏈,攔截器的執行順序是按宣告的先後順序執行的,先宣告的攔截器中的preHandle方法會先執行,然而它的postHandle方法和afterCompletion方法卻會後執行。

在mvc:interceptors標籤下宣告interceptor主要有兩種方式:

  • 直接定義一個Interceptor實現類的bean物件。使用這種方式宣告的Interceptor攔截器將會對所有的請求進行攔截。
  • 使用mvc:interceptor標籤進行宣告。使用這種方式進行宣告的Interceptor可以通過mvc:mapping子標籤來定義需要進行攔截的請求路徑。

經過上述兩步之後,定義的攔截器就會發生作用對特定的請求進行攔截了。

過濾器(Filter)使用

Servlet 的 Filter 介面需要實現如下方法:

  • void init(FilterConfig paramFilterConfig) – 當容器初始化 Filter 時呼叫,該方法在 Filter 的生命週期只會被呼叫一次,一般在該方法中初始化一些資源,FilterConfig 是容器提供給 Filter 的初始化引數,在該方法中可以丟擲 ServletException 。init 方法必須執行成功,否則 Filter 可能不起作用,出現以下兩種情況時,web 容器中 Filter 可能無效: 1)丟擲 ServletException 2)超過 web 容器定義的執行時間。
  • doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse, FilterChain paramFilterChain) – Web 容器每一次請求都會呼叫該方法。該方法將容器的請求和響應作為引數傳遞進來, FilterChain 用來呼叫下一個 Filter。
  • void destroy() – 當容器銷燬 Filter 例項時呼叫該方法,可以在方法中銷燬資源,該方法在 Filter 的生命週期只會被呼叫一次。

    FrequencyLimitFilter com.company.filter.FrequencyLimitFilter FrequencyLimitFilter /login/*

demo

過濾器(Filter):

    <filter>
        <description>字符集過濾器</description>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <description>字符集編碼</description>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

 總結

1.過濾器:所謂過濾器顧名思義是用來過濾的,在java web中,你傳入的request,response提前過濾掉一些資訊,或者提前設定一些引數,然後再傳入servlet或者struts的action進行業務邏輯,比如過濾掉非法url(不是login.do的地址請求,如果使用者沒有登陸都過濾掉),或者在傳入servlet或者struts的action前統一設定字符集,或者去除掉一些非法字元(聊天室經常用到的,一些罵人的話)。filter 流程是線性的, url傳來之後,檢查之後,可保持原來的流程繼續向下執行,被下一個filter, servlet接收等.

2.java的攔截器 主要是用在外掛上,擴充套件件上比如 hibernate spring struts2等 有點類似面向切片的技術,在用之前先要在配置檔案即xml檔案裡宣告一段的那個東西。