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中,無法通過Controller
或Interceptor
來解決請求和響應的亂碼問題。
在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個處理異常的方法,分別只處理NullPointerException
和ArrayIndexOutOfBoundsException
和RuntimeException
?
答案:可以!允許使用多個不同的方法來處理異常!
思考:假設處理異常的方法在A控制器中,而B控制器中處理請求時出現異常,會被處理嗎?
答案:不可以!通常,可以建立一個BaseController,作為當前專案的控制器類的基類,然後,把處理異常的程式碼編寫在這個類中即可!
關於@ExceptionHandler
,還可以用於限制其對應的方法處理的異常的種類!例如:
@ExceptionHandler(IndexOutOfBoundsException.class)
以上程式碼表示接下來的方法只處理IndexOutOfBoundsException
及其子孫類異常,而其它的異常並不處理!
3.4 小結
處理異常的本質並不可以“撤消”異常,無法讓已經出現的問題還原到沒有問題!所以,處理異常的本質應該是儘量的補救已經出現的問題,並且,嘗試通過提示資訊等方式告之使用者,儘量執行正確的操作,避免後續再次出現同樣的問題!
並且,對於開發者而言,應該處理每一個可能出現的異常,因為,如果沒有處理,就會繼續丟擲,最終被Tomcat捕獲,而Tomcat處理的方式是把異常的跟蹤資訊顯示在頁面上,是極為不合適的!
在SpringMVC中,處理異常有2種方式,第1種是通過SimpleMappingExceptionResolver
設定異常與轉發目標的對應關係,第2種是使用@ExceptionHandler
為處理異常的方法進行註解,推薦使用第2種方式。
通常,會把處理異常的方法寫在專案的控制器類的基類中,即寫在BaseController
中,在使用@ExceptionHandler
時,可以明確的指定所處理的異常型別,例如:@ExceptionHandler(NullPointerException)
,且,在控制器類中,可以編寫多個處理異常的方法!
關於處理異常的方法,應該是public
方法,返回值型別與處理請求的方式相同,可以是String
或ModelAndView
或其它允許的型別,方法的名稱可以自定義,引數列表中必須包括Exception
型別的引數,還允許存在HttpServletRequest
、HttpServletResponse
,不允許使用ModelMap
。