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 處理異常-@ExceptionHandler
可以在控制器類中自定義一個處理異常的方法,在方法之前添加@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。
