異常處理 1.基本介紹 SpringMVC 通過 HandlerExceptionResolver 處理程式的異常,包括 Handler映射、數據綁定以及目標方法執行時發生的異常 有兩種方案來進行異常處理: a.在本類編寫處理異常的方法,將拋出的異常視為局部異常處理 b.額外編寫處理異常的類,將拋出 ...
異常處理
1.基本介紹
-
SpringMVC 通過 HandlerExceptionResolver 處理程式的異常,包括 Handler映射、數據綁定以及目標方法執行時發生的異常
-
有兩種方案來進行異常處理:
a.在本類編寫處理異常的方法,將拋出的異常視為局部異常處理
b.額外編寫處理異常的類,將拋出的異常視為全局異常處理
-
主要處理的是 Handler 中使用了 @ExceptionHandler 註解修飾的方法(局部異常處理)
-
ExceptionHandlerMethodResolver 內部若找不到上述 @ExceptionHandler 註解修飾的方法,就會去找有 @ControllerAdvice 註解修飾的類中含有 @ExceptionHandler 註解的方法,被 @ControllerAdvice 修飾的類稱為全局處理器(全局異常處理)
-
異常處理時,局部異常優先順序高於全局異常
2.局部異常
2.1應用實例
(1)創建 MyExceptionHandler.java,在這個 Handler 中模擬各種出現的異常,併在本類添加處理可能出現的異常的方法(局部異常)
局部異常的處理方法只能處理本類中出現的異常。
package com.li.web.exception;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
/**
* @author 李
* @version 1.0
*/
@Controller
public class MyExceptionHandler {
/**
* 1.localException()方法處理局部異常
* 2.指定處理 算術異常、空指針異常
* 3. Exception ex 本類生成的異常對象,會傳遞給ex,通過ex可以拿到相關信息,
* 在這裡可以加入業務邏輯
* @param ex
* @param request
* @return
*/
@ExceptionHandler({ArithmeticException.class, NullPointerException.class})
public String localException(Exception ex, HttpServletRequest request) {
System.out.println("局部異常信息是=" + ex.getMessage());
//如何將異常的信息帶到下一個頁面(根據你的業務邏輯)
request.setAttribute("reason", ex.getMessage());
return "exception_mes";
}
/**
* 1.編寫方法,模擬異常-算術異常
* 2.如果我們不做異常處理,是由tomcat預設頁面處理
* @param num
* @return
*/
@RequestMapping(value = "/testException01")
public String test01(Integer num) {
int i = 9 / num;
return "success";
}
}
(2)exception_mes.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>異常信息</title>
</head>
<body>
<h1>異常信息提示</h1>
<h4>${requestScope.reason}</h4>
</body>
</html>
(3)測試,直接訪問 http://localhost:8080/springmvc/testException01?num=0
,顯示結果如下:
捕獲到了異常,SpringMVC 底層反射調用了 localException 方法進行異常處理,跳轉到了 exception_mes.jsp 頁面提示異常信息。
如果我們沒有處理異常,預設是由 tomcat 進行異常處理
2.2執行流程
從發生異常到捕獲異常並調用處理方法,局部異常處理的整個執行流程是怎樣的呢?
第一步:瀏覽器訪問目標方法,如果出現異常,會到 ExceptionHandlerMethodResolver 類中執行 resolveMethodByExceptionType() 方法
第二步:執行resolveMethodByExceptionType() 方法,得到處理異常的方法。
@Nullable
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
//獲取目標方法出現的異常類型 exceptionType
//然後通過map去找有沒有一個預設的方法去處理這個異常
Method method = this.exceptionLookupCache.get(exceptionType);
if (method == null) {//如果沒有預設方法
//getMappedMethod 得到該異常映射的處理方法,如例子中的 localException() 方法
method = getMappedMethod(exceptionType);
//將找到的這個方法作為 該異常的處理方法,放入到map中
this.exceptionLookupCache.put(exceptionType, method);
}
//返回這個方法
return (method != NO_MATCHING_EXCEPTION_HANDLER_METHOD ? method : null);
}
第三步:將得到的異常數據放入到該方法的參數中,底層反射調用處理異常的該方法
第四步:根據處理異常方法,執行自定義的業務邏輯。如例子中跳到某個頁面並提示異常信息。
3.全局異常
3.1應用實例
演示全局異常處理機制。
ExceptionHandlerMethodResolver 內部若找不到 @ExceptionHandler 註解的方法,就會找 @ControllerAdvice 類的 @ExceptionHandler 註解方法,這樣就相當於一個全局處理器,全局處理器可以處理不同的 Handler 出現的異常。
(1)自定義一個全局處理器 MyGlobalException.java
package com.li.web.exception;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest;
/**
* @author 李
* @version 1.0
* 如果一個類上標註了 @ControllerAdvice,那麼這個類就是一個全局異常處理器
*/
@ControllerAdvice
public class MyGlobalException {
/**
* 1.不管是哪個 Handler拋出的異常,全局異常都可以捕獲
* 2.@ExceptionHandler({這裡是可以捕獲的異常類型})
* @param ex 接收拋出的異常對象
* @return
*/
@ExceptionHandler({NumberFormatException.class, ClassCastException.class})
public String globalException(Exception ex, HttpServletRequest request) {
System.out.println("全局異常處理=" + ex.getMessage());
//業務處理-將異常信息帶到下一個頁面並顯示
request.setAttribute("reason", ex.getMessage());
return "exception_mes";
}
}
(2)在 Handler 的目標方法中模擬異常
package com.li.web.exception;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
/**
* @author 李
* @version 1.0
*/
@Controller
public class MyExceptionHandler {
//局部異常處理方法
@ExceptionHandler({ArithmeticException.class, NullPointerException.class})
public String localException(Exception ex, HttpServletRequest request) {
System.out.println("局部異常信息是=" + ex.getMessage());
//如何將異常的信息帶到下一個頁面(根據你的業務邏輯)
request.setAttribute("reason", ex.getMessage());
return "exception_mes";
}
//目標方法
@RequestMapping(value = "testGlobalException")
public String global() {
//模擬一個 NumberFormatException異常,若該異常不能在局部異常方法處理,
//就會被交到全局異常處理器中處理
int num = Integer.parseInt("hello");
return "success";
}
}
(3)exception_mes.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>異常信息</title>
</head>
<body>
<h1>異常信息提示</h1>
<h4>${requestScope.reason}</h4>
</body>
</html>
(3)在瀏覽器中訪問目標方法 http://localhost:8080/springmvc/testGlobalException
,可以看到頁面提示了異常信息,同時後臺輸出為 全局異常處理=For input string: "hello"
,這說明是全局異常處理器捕獲到的異常,局部異常方法沒有捕獲到。
3.2執行流程
從發生異常到捕獲異常並調用處理方法,全局異常處理的流程如下:
(1)瀏覽器訪問目標方法,若出現異常,會到 ExceptionHandlerExceptionResolver 類的 ServletInvocableHandlerMethod() 方法進行處理:
(2)上述方法首先會到 ExceptionHandlerMethodResolver 類中執行 resolveMethod 方法
(3)resolveMethod 方法又調用本類的 resolveMethodByThrowable 方法
(4)resolveMethodByThrowable 繼續調用本類的 resolveMethodByExceptionType() 方法:
@Nullable
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
//獲取目標方法的異常類型 exceptionType
//通過map去找處理該異常的預設的方法
Method method = this.exceptionLookupCache.get(exceptionType);
if (method == null) {//若沒有預設方法
//得到該異常映射的處理方法,這一步如果沒有在Handler類中找到處理方法,
//如局部異常方法不匹配該異常,就會返回一個 noMatchingExceptionHandler() 的方法
method = getMappedMethod(exceptionType);
//將找到的這個方法作為 該異常的處理方法,放入到map中
this.exceptionLookupCache.put(exceptionType, method);
}
//若 method為 noMatchingExceptionHandler(),則返回一個 null
return (method != NO_MATCHING_EXCEPTION_HANDLER_METHOD ? method : null);
}
(5)如果 resolveMethodByExceptionType() 方法沒有在本類 Handler 中匹配到可以解決出現的異常的方法,就返回 null
@Nullable
public Method resolveMethodByThrowable(Throwable exception) {
//如果 mothod 返回 null
Method method = resolveMethodByExceptionType(exception.getClass());
if (method == null) {
//獲取出現異常的原因
Throwable cause = exception.getCause();
if (cause != null) {
//根據異常找匹配的方法
method = resolveMethodByThrowable(cause);
}
}
//如果返回null
return method;
}
(6)一路返回到 ExceptionHandlerExceptionResolver 類的 ServletInvocableHandlerMethod() 方法,如果獲取到的 method 仍為 null,就會去遍歷帶有 @ControllerAdvice 註解修飾的類(全局處理器),然後全局處理器中根據 @ExceptionHandler 註解,獲取可以處理異常的方法。
這裡的 entry.getKey() 就是 @ControllerAdvice 註解修飾的類對象。
(7)最後反射調用找到的全局處理器中的方法。
4.自定義異常
可以通過 @ResponseStatus 註解,來自定義異常的說明
4.1應用實例
(1)自定義異常
package com.li.web.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* @author 李
* @version 1.0
*/
@ResponseStatus(reason = "年齡需要在1-120之間",value = HttpStatus.BAD_REQUEST)
public class AgeException extends RuntimeException{
}
(2)修改 MyExceptionHandler.java,增加方法進行測試
package com.li.web.exception;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author 李
* @version 1.0
*/
@Controller
public class MyExceptionHandler {
@RequestMapping(value = "/testException02")
public String test02() {
throw new AgeException();
}
}
(3)瀏覽器訪問 http://localhost:8080/springmvc/testException02
,測試顯示如下:
訪問目標方法,拋出異常,異常的信息就是你指定的信息,狀態碼也是你指定的數據
4.2拓展
自定義異常可以被局部異常處理和全局異常處理接管,只需要在 @ExceptionHandler 註解中添加自定義異常的.class即可
1.當然,如果想要拿到 ex.getMessage() 信息,需要在自定義的異常類創建帶有 message 參數的構造器
自定義異常的 reason,value 屬性是給 tomcat 的預設頁面顯示的,而構造器的 messgae 屬性是傳入對象的
2.然後在創建異常類對象的時候放入提示信息:
自定義異常本質就是異常,它的處理流程由 它被局部異常還是全局異常接管 而定。如果兩者都沒有接管,就會被 tomcat 來處理,然後 tomcat 顯示一個預設的頁面。
5.SimpleMappingExceptionResolver
5.1基本說明
- 如果希望對所有異常進行統一處理,可以使用 SimpleMappingExceptionResolver
- 它將異常類名映射為視圖名,即發生異常時使用對應的視圖報告異常
- 需要在 spring 的容器文件中配置
5.2應用實例
需求:使用 SimpleMappingExceptionResolver 對數據越界異常進行統一處理
(1)修改 MyExceptionHandler.java,增加方法 test03
//模擬數據下標越界異常
@RequestMapping(value = "/testException03")
public String test03() {
int[] arr = {3, 8, 18, 20};
System.out.println(arr[99]);
return "success";
}
(2)在 spring 容器文件配置
<!--配置統一異常處理的bean-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--key為處理的異常的全路徑,
arrEx為出現異常後要跳轉的頁面(頁面所在的目錄要和你的視圖解析器的前尾碼匹配)-->
<prop key="java.lang.ArrayIndexOutOfBoundsException">arrEx</prop>
</props>
</property>
</bean>
(3)arrEx.jsp,該頁面顯示異常信息
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>數組下標越界異常</title>
</head>
<body>
異常信息:數組下標越界異常
</body>
</html>
(4)瀏覽器訪問目標方法,返回的頁面顯示:跳轉到了 arrEx.jsp 頁面。
目標方法發生異常--->先去局部異常方法處理--->如果不行,到全局異常處理器處理--->如果不行,到容器文件配置統一異常處理的 bean 處理--->都不行,最後由 tomcat 處理
5.3對未知異常進行統一處理
在實際開發中,異常的種類非常多,我們的異常處理方法可能不能捕獲到所有的異常。
如何處理沒有歸類(未知的)的異常?
仍然可以使用 SimpleMappingExceptionResolver 進行處理。只需要在配置的時候,將捕獲的異常範圍擴大,如Exception。
例子
(1)修改 MyExceptionHandler.java,增加方法 test04
//如果發生了沒有歸類的異常,可以給出統一的提示頁面
@RequestMapping(value = "/testException04")
public String test04() {
String str = "hello";
//StringIndexOutOfBoundsException
char c = str.charAt(10);
return "success";
}
(2)容器文件配置處理異常的bean時,擴大捕獲範圍
<!--配置統一異常處理的bean-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">arrEx</prop>
<!--捕獲未知異常-->
<prop key="java.lang.Exception">allEx</prop>
</props>
</property>
</bean>
(3)allEx.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>異常通知</title>
</head>
<body>
<h1>系統發生了未知異常..</h1>
</body>
</html>
(4)瀏覽器訪問目標方法,訪問結果如下:
5.4異常處理的優先順序
局部異常處理 > 全局異常處理 > SimpleMappingExceptionResolver 處理 > tomcat 預設機制處理