1. 程式人生 > >SpringMVC中的攔截器、過濾器的區別、處理異常

SpringMVC中的攔截器、過濾器的區別、處理異常

1. SpringMVC中的攔截器(Interceptor)

1.1. 作用

攔截器是執行在DispatcherServlet之後,在每個Controller之前的,且執行結果可以選擇放行或攔截!

除此以外,攔截器還會執行在Controller之後,關於攔截器,在處理某一個請求時,最多有3次執行!只不過,通常關注最多的是第1次執行,即在Controller之前的那次!

1.2. 基本使用

需要自定義類,例如名為LoginInterceptor,實現HandlerInterceptor介面,重寫3個抽象方法:

public class LoginInterceptor implements HandlerInterceptor {

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("LoginInterceptor.preHandle()");
        return false;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("LoginInterceptor.postHandle()");
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("LoginInterceptor.afterCompletion()");
    }

}

攔截器需要在Spring的配置檔案中進行配置後才可以生效!所以,需要新增配置:

<!-- 配置攔截器鏈 -->
<mvc:interceptors>
    <!-- 配置第1個攔截器 -->
    <mvc:interceptor>
        <!-- 指定攔截路徑,不在攔截路徑之內的將不予處理,即攔截器根本就不執行 -->
        <mvc:mapping path="/user/info.do"/>
        <mvc:mapping path="/user/password.do"/>
        <!-- 指定攔截器類 -->
        <bean class="cn.tedu.spring.interceptor.LoginInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

在攔截器類中,執行在Controller之前的是preHandle()方法,該方法返回true表示放行,返回false表示攔截!

對於登入攔截器而言,可以判斷Session中的使用者資料,如果資料存在,視為已登入,可直接放行,如果沒有資料,則視為沒登入,需要重定向,並攔截:

public boolean preHandle(
        HttpServletRequest request, 
        HttpServletResponse response, Object handler)
        throws Exception {
    System.out.println("LoginInterceptor.preHandle()");
    // 判斷Session中的資料,得知是否登入
    HttpSession session = request.getSession();
    if (session.getAttribute("username") == null) {
        // Session中沒有使用者名稱,即:沒有登入,則:重定向,並攔截
        response.sendRedirect("../user/login.do");
        // 返回false表示攔截
        return false;
    }
    // 返回true表示放行
    return true;
}

在配置攔截器時,根節點是<mvc:interceptors>,用於配置攔截器鏈,即任何一個SpringMVC專案,都可以有若干個攔截器,從而形成攔截器鏈,如果某個請求符合多個攔截器的攔截配置,則會依次被各攔截器進行處理,任何一個攔截,都會導致後續不再將請求交給Controller去處理!

<mvc:interceptors>(有s)節點子級,可以配置多個<mvc:interceptor>(沒有s)子級節點,表示多個攔截器,攔截器鏈的執行順序取決於這裡各節點的配置先後順序!

<mvc:interceptor>中,<bean>節點用於指定攔截器類;<mvc:mapping>節點,用於配置攔截的請求路徑,每個攔截器可以配置多個該節點,並且,在配置時,支援使用星號*作為萬用字元,例如:<mvc:mapping path="/user/*"/>,為了避免攔截範圍過大,可以通過<mvc:exclude-mapping />配置排除在外的請求路徑,可以理解為白名單,該節點也可以配置多個:

<!-- 配置攔截器鏈 -->
<mvc:interceptors>
    <!-- 配置第1個攔截器 -->
    <mvc:interceptor>
        <!-- 指定攔截路徑,不在攔截路徑之內的將不予處理,即攔截器根本就不執行 -->
        <mvc:mapping path="/user/*" />
        <!-- 指定白名單,列舉的請求路徑將不予處理,即攔截器根本就不執行 -->
        <mvc:exclude-mapping path="/user/reg.do" />
        <mvc:exclude-mapping path="/user/login.do" />
        <mvc:exclude-mapping path="/user/handle_reg.do" />
        <mvc:exclude-mapping path="/user/handle_login.do" />
        <!-- 指定攔截器類 -->
        <bean class="cn.tedu.spring.interceptor.LoginInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

注意:以上配置黑名單或白名單時,都可以使用星號*作為萬用字元,但是,它只能匹配1層路徑(或者說只能通配任意資源)!例如配置的是/user/*,可以匹配/user/reg.do/user/login.do,卻無法匹配到/user/a/reg.do/user/a/b/login.do這樣的路徑!如果需要匹配多層路徑,可以使用2個星期,即**,例如配置/user/**則可以匹配以上任意路徑!

2. 亂碼

2.1. 關於亂碼

在處理中文或非ASCII字元(需要使用輸入法才可以輸入的)時,如果存、取時,使用的字元編碼不統一,就會出現亂碼!

所以,出現亂碼原因就是因為字元編碼不統一,而解決問題的方案就是使用統一的編碼

需要統一編碼的位置有:專案原始碼、資料庫、處理資料的服務端元件、資料傳輸過程、#顯示介面

2.2. 解決控制器中接收請求引數的亂碼

通常,在Java EE專案中,解決問題的方式是:

request.setCharacterEncoding("utf-8");

由於Controller是執行在DispatcherServlet之後的,在Controller內部再執行更改編碼格式已經晚了,事實上SpringMVC框架在DispatcherServlet之前就存在CharacterEncodingFilter可以確定請求與響應的編碼格式,所以,在SpringMVC中,無法通過ControllerInterceptor來解決請求和響應的亂碼問題。

在SpringMVC框架的CharacterEncodingFilter中,把使用的字元編碼設計為變數,可以在web.xml中新增配置加以應用,來統一設定編碼:

<!-- 配置字元編碼過濾器 -->
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

2.3. 攔截器與過濾器有什麼區別

攔截器是Interceptor,過濾器是Filter;

攔截器是SpringMVC中的元件,過濾器是Java EE中的元件;

攔截器是配置在Spring的配置檔案中的,過濾器是配置在web.xml中的;

攔截器是執行在DispatcherServlet之後、Controller之前的,且在Controller執行完後還會呼叫2個方法,而過濾器是執行在所有的Servlet之前的;

攔截器的配置非常靈活,可以配置多項黑名單,也可以配置多項白名單,過濾器的配置非常單一,只能配置1項過濾路徑;

攔截器與過濾器也有很多相似之處,例如:都可拒絕掉某些訪問,也可以選擇放行;都可以形成鏈。

相比之下,在一個使用SpringMVC框架的專案中,攔截器會比過濾器要好用一些,但是,由於執行時間節點的原因,它並不能完全取代過濾器!

3. 異常

3.1. 基本概念

在Java中,異常的體系結構是:

Throwable
    Error
        OutOfMemoryError
    Exception
        IOException
            FileNotFoundException
        SQLException
        UnsupportedEncodingException
        RuntimeException
            NullPointerException
            ClassCastException
            ArithmeticException
            IllegalArgumentException
            IndexOutOfBoundsException
                ArrayIndexOutOfBoundsException

在這些異常中,RuntimeException及其子孫類異常,在Java語法中並不要求必須處理!主要的原因有:這些異常出現的頻率可能非常高,如果一定要處理,例如try...catch,則幾乎所有的程式碼都需要放在try程式碼塊中!並且,這些異常是可以杜絕的異常,通過嚴謹的程式設計,可以保證這些異常絕對不會出現!

處理異常有2種方式:使用try...catch處理異常,或者使用throw丟擲異常物件,並且在方法的宣告中使用throws語法宣告丟擲!

通常,異常都是必須處理的,如果沒有處理異常,會導致異常不斷向上丟擲,最終,Java EE中的異常會由Tomcat來處理,會把跟蹤日誌顯示在頁面中!而這些跟蹤日誌是普通使用者看不懂的,對於專業人士而言,還可能分析出專案中的一些內容,對於後續解決問題也沒有任何幫助,所以,從開發原則上來說,必須處理異常!

3.2 處理異常-SimpleMappingExceptionResolver

RuntimeException是從語法上強制要求處理的,所以,每次呼叫了丟擲異常的方法,就必須try...catch或繼續宣告丟擲!

RuntimeException及其子孫類異常並不從語法上強制要求處理,但是,一旦出現,會專案的安全、使用者體驗等各方面都會產生負面影響!

SpringMVC提供了統一處理異常的方式:使用SimpleMappingExceptionResolver類,該類通過private Properties exceptionMappings;屬性來配置異常種類與轉發到的頁面的對應關係,即每一種異常都可以有一個與之對應的頁面,一旦專案中出現配置過的異常,就會自動轉發到對應的頁面來提示錯誤資訊!

<!-- 配置統一處理異常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <prop key="java.lang.RuntimeException">runtime</prop>
            <prop key="java.lang.NullPointerException">null</prop>
            <prop key="java.lang.ArrayIndexOutOfBoundsException">index</prop>
        </props>
    </property>
</bean>

這種方式處理異常非常的簡單,但是,也有一個比較大的問題:無法提示更詳細的資訊!例如出現ArrayIndexOutOfBoundsException時,其實,異常物件中還封裝了詳細的錯誤資訊,即:越界值是多少。

3.3 處理異常[email protected]

可以在控制器類中自定義一個處理異常的方法,在方法之前新增@ExceptionHandler,然後,凡是約定範圍內的異常,都可以由該方法來決定如何處理:

@ExceptionHandler
public String handleException(Exception e) {
    System.out.println(
        "ExceptionController.handleException()");

    if (e instanceof NullPointerException) {
        return "null";
    } else if (e instanceof ArrayIndexOutOfBoundsException) {
        return "index";
    }

    return "runtime";
}

在處理異常時,如果需要轉發資料,可以將返回值修改為ModelAndView,或者,在處理異常的方法引數列表中新增HttpServletRequest,但是,卻不可以使用ModelMap來封裝轉發的資料:

@ExceptionHandler
public String handleException(Exception e,
        HttpServletRequest request) {
    System.out.println(
        "ExceptionController.handleException()");

    if (e instanceof NullPointerException) {
        return "null";
    } else if (e instanceof ArrayIndexOutOfBoundsException) {
        request.setAttribute("msg", e.getMessage());
        return "index";
    }

    return "runtime";
}

思考:是否可以在類中新增3個處理異常的方法,分別只處理NullPointerExceptionArrayIndexOutOfBoundsExceptionRuntimeException

答案:可以!允許使用多個不同的方法來處理異常!

思考:假設處理異常的方法在A控制器中,而B控制器中處理請求時出現異常,會被處理嗎?

答案:不可以!通常,可以建立一個BaseController,作為當前專案的控制器類的基類,然後,把處理異常的程式碼編寫在這個類中即可!

關於@ExceptionHandler,還可以用於限制其對應的方法處理的異常的種類!例如:

@ExceptionHandler(IndexOutOfBoundsException.class)

以上程式碼表示接下來的方法只處理IndexOutOfBoundsException及其子孫類異常,而其它的異常並不處理!

3.4 小結

處理異常的本質並不可以“撤消”異常,無法讓已經出現的問題還原到沒有問題!所以,處理異常的本質應該是儘量的補救已經出現的問題,並且,嘗試通過提示資訊等方式告之使用者,儘量執行正確的操作,避免後續再次出現同樣的問題!

並且,對於開發者而言,應該處理每一個可能出現的異常,因為,如果沒有處理,就會繼續丟擲,最終被Tomcat捕獲,而Tomcat處理的方式是把異常的跟蹤資訊顯示在頁面上,是極為不合適的!

在SpringMVC中,處理異常有2種方式,第1種是通過SimpleMappingExceptionResolver設定異常與轉發目標的對應關係,第2種是使用@ExceptionHandler為處理異常的方法進行註解,推薦使用第2種方式。

通常,會把處理異常的方法寫在專案的控制器類的基類中,即寫在BaseController中,在使用@ExceptionHandler時,可以明確的指定所處理的異常型別,例如:@ExceptionHandler(NullPointerException),且,在控制器類中,可以編寫多個處理異常的方法!

關於處理異常的方法,應該是public方法,返回值型別與處理請求的方式相同,可以是StringModelAndView或其它允許的型別,方法的名稱可以自定義,引數列表中必須包括Exception型別的引數,還允許存在HttpServletRequestHttpServletResponse,不允許使用ModelMap