例外處理
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 = "https://www.cnblogs.com/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 = "https://www.cnblogs.com/liyuelian/p/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 = https://www.cnblogs.com/liyuelian/p/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 = "https://www.cnblogs.com/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 = "https://www.cnblogs.com/testException03")
public String test03() {
int[] arr = {3, 8, 18, 20};
System.out.println(arr[99]);
return "success";
}
(2)在 spring 容器檔案配置
<!--配置統一例外處理的bean-->
<bean >
<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 = "https://www.cnblogs.com/testException04")
public String test04() {
String str = "hello";
//StringIndexOutOfBoundsException
char c = str.charAt(10);
return "success";
}
(2)容器檔案配置處理例外的bean時,擴大捕獲范圍
<!--配置統一例外處理的bean-->
<bean >
<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 默認機制處理
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/544319.html
標籤:Java
