springboot異常處理
時間:2024-01-19 來源:華清遠見
springboot異常處理
我們知道 Spring Boot 已經提供了一套默認的異常處理機制,但是 Spring Boot 提供的默認異常處理機制卻
并不一定適合我們實際的業務場景,因此,我們通常會根據自身的需要對 Spring Boot 全局異常進行統一定制,
例如定制錯誤頁面,定制錯誤數據等。
Spring Boot 提供了一套默認的異常處理機制,其主要流程如下:
1. 發生異常時,將請求轉發到 “/error”,交由 BasicErrorController(Spring Boot 默認的 Error 控制器) 進
行處理;
2. BasicErrorController 根據客戶端的不同,自動適配返回的響應形式,瀏覽器客戶端返回錯誤頁面,機器客
戶端返回 JSON 數據。
3. BasicErrorController 處理異常時,會調用 DefaultErrorAttributes(默認的錯誤屬性處理工具) 的
getErrorAttributes() 方法獲取錯誤數據。
自定義動態錯誤頁面
如果 Sprng Boot 項目使用了模板引擎,當程序發生異常時,Spring Boot 的默認錯誤視圖解析器
(DefaultErrorViewResolver)就會解析模板引擎文件夾(resources/templates/)下 error 目錄中的錯誤視圖
頁面。
我們可以根據錯誤狀態碼(例如 404、500、400 等等)的不同,分別創建不同的動態錯誤頁面(例如
404.html、500.html、400.html 等等),并將它們存放在模板引擎文件夾下的 error 目錄中。當發生異常時,
Spring Boot 會根據其錯誤狀態碼精確匹配到對應的錯誤頁面上
1 在模板引擎templates文件夾下 error 目錄
2 在error目錄下分別創建404.html,400.html,500.html,代碼如下:
404.html
400.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
錯誤狀態碼:<span th:text="${status}"></span><br>
請求地址有錯誤!
</body>
</html>
500.html
3 構建成功,測試一下幾個錯誤吧
自定義靜態錯誤頁面
若 Sprng Boot 項目沒有使用模板引擎,當程序發生異常時,Spring Boot 的默認錯誤視圖解析器
(DefaultErrorViewResolver)則會解析靜態資源文件夾下 error 目錄中的靜態錯誤頁面。
我們可以根據錯誤狀態碼(例如 404、500、400 等等)的不同,分別創建不同的靜態錯誤頁面(例如
404.html、500.html、400.html 等等),并將它們存放在靜態資源文件夾下的 error 目錄中。當發生異常時,
Spring Boot 會根據錯誤狀態碼精確匹配到對應的錯誤頁面上。
1. 在靜態資源文件夾 src/recources/static 下創建error 目錄,其他步驟和上述一樣,注意因為沒有使用模板
引擎,所以不支持theamleft表達式th:text="${status}"
原理解析
當發出請求的客戶端為瀏覽器時,Spring Boot 會獲取容器中所有的 ErrorViewResolver 對象(錯誤視圖解析
器),并分別調用它們的 resolveErrorView() 方法對異常信息進行解析,其中自然也包括
DefaultErrorViewResolver(默認錯誤信息解析器)
DefaultErrorViewResolver 的部分代碼如下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
錯誤狀態碼:<span th:text="${status}"></span><br>
springmvc 封裝前端傳來參數有問題
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
錯誤狀態碼:<span th:text="${status}"></span><br>
服務端報錯,請檢查服務端控制臺打印錯誤
</body>
</html>
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
private static final Map<HttpStatus.Series, String> SERIES_VIEWS;
static {
Map<HttpStatus.Series, String> views = new EnumMap<>(HttpStatus.Series.class);
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx");
SERIES_VIEWS = Collections.unmodifiableMap(views);
}
......
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
//嘗試以錯誤狀態碼作為錯誤頁面名進行解析
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
//嘗試以 4xx 或 5xx 作為錯誤頁面頁面進行解析
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//錯誤模板頁面,例如 error/404、error/4xx、error/500、error/5xx
String errorViewName = "error/" + viewName;
//當模板引擎可以解析這些模板頁面時,就用模板引擎解析
TemplateAvailabilityProvider provider =
this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if (provider != null) {
//在模板能夠解析到模板頁面的情況下,返回 errorViewName 指定的視圖
return new ModelAndView(errorViewName, model);
}
//若模板引擎不能解析,則去靜態資源文件夾下查找 errorViewName 對應的頁面
return resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
//遍歷所有靜態資源文件夾
for (String location : this.resources.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
//靜態資源文件夾下的錯誤頁面,例如error/404.html、error/4xx.html、
error/500.html、error/5xx.html
resource = resource.createRelative(viewName + ".html");
//若靜態資源文件夾下存在以上錯誤頁面,則直接返回
if (resource.exists()) {
return new ModelAndView(new
DefaultErrorViewResolver.HtmlResourceView(resource), model);
}
} catch (Exception ex) {
}
}
return null;
}
......
}
DefaultErrorViewResolver 解析異常信息的步驟如下:
1. 根據錯誤狀態碼(例如 404、500、400 等),生成一個錯誤視圖 error/status,例如 error/404、
error/500、error/400。
2. 嘗試使用模板引擎解析 error/status 視圖,即嘗試從 classpath 類路徑下的 templates 目錄下,查找
error/status.html,例如 error/404.html、error/500.html、error/400.html。
3. 若模板引擎能夠解析到 error/status 視圖,則將視圖和數據封裝成 ModelAndView 返回并結束整個解析流
程,否則跳轉到第 4 步。
4. 依次從各個靜態資源文件夾中查找 error/status.html,若在靜態文件夾中找到了該錯誤頁面,則返回并結
束整個解析流程,否則跳轉到第 5 步。
5. 將錯誤狀態碼(例如 404、500、400 等)轉換為 4xx 或 5xx,然后重復前 4 個步驟,若解析成功則返回并
結束整個解析流程,否則跳轉第 6 步。
6. 處理默認的 “/error ”請求,使用 Spring Boot 默認的錯誤頁面(Whitelabel Error Page)
自定義異常處理
在實際的應用開發中,很多時候往往因為一些不可控的因素導致程序出現一些錯誤,這個時候就要及時把異
常信息反饋給客戶端,便于客戶端能夠及時地進行處理,而針對代碼導致的異常,我們一般有兩種處理方式,一
種是throws直接拋出,一種是使用try..catch捕獲,一般的話,如果邏輯的異常,需要知道異常信息,我們往往
選擇將異常拋出,如果只是要保證程序在出錯的情況下 依然可以繼續運行,則使用try..catch來捕獲。
但是try..catch會導致代碼量的增加,讓后期我們的代碼變得臃腫且難以維護。當然,springboot作為一個如此優
秀的框架,肯定不會坐視不管的,通過springboot自帶的注解,我們可以方便的自定義我們的全局異常處理器,
并且以json格式返回給我們的客戶端。
在spring框架開發中,相信很多初學者都遇到過ioc注入失敗,注入對象為空指針異常,我們就以這個為例
1 創建ObjectNullException 類 ,繼承 exception異常
2 定義一個處理自定義異常的通知類
@ControllerAdvice 通過AOP的方式配合@ExceptionHandler()注解捕獲在Controller層面發生的異常
@ExceptionHandler 是指向處理那個自定義異常類
/**
* @Description
* @Autor 伍軍
* @Date 2021/11/18 9:09
* @Version 1.0
**/
public class ObjectNullException extends Exception {
}
/**
* @Description 自定義異常
* @Autor 伍軍
* @Date 2021/11/18 8:34
* @Version 1.0
**/
//@ControllerAdvice 通過AOP的方式配合@ExceptionHandler()注解捕獲在Controller層面發生的異常
@ControllerAdvice(basePackages ="com.hqyj.controller")
public class MyControllerException {
/**
* 處理對象空指針異常
* @param
* @return
*/
@ResponseBody
@ExceptionHandler(value = ObjectNullException.class)
public Map<String,Object> exceptionHandler(){
Map<String,Object> map = new HashMap<String,Object>();
map.put("code",0);
map.put("msg","IOC注入失敗,對象為空");
return map;
}
}
3 在 查詢數據庫之前,寫一個判斷拋出異常代碼
@Autowired(required = false)
AdminMapper adminMapper;
@Override
public HashMap<String, Object> select(Admin a) throws ObjectNullException {
if(adminMapper == null){
throw new ObjectNullException();
}
....省略用adminMapper查詢的代碼
//查詢數據庫
4 測試 ,取消 @Autowired 注入模擬 注入失敗異常,前端用ajax發送請求,在瀏覽器控制臺,可以看到自定義
的錯誤信息,如圖所示
我們在看看自定義運行異常
1 自定義一個IdNotExistExcetipn 異常列
/**
* @Description
* @Autor 伍軍
* @Date 2021/11/18 8:54
* @Version 1.0
**/
public class IdNotExistExcetipn extends RuntimeException {
}
2 在自定義異常的通知類里添加 處理id傳值為空的異常的方法
/**
* 處理id傳值為空的異常
* @param
* @return
*/
@ResponseBody
@ExceptionHandler(value = IdNotExistExcetipn.class)
public Map<String,Object> IdNotExistExcetipnHandler(){
Map<String,Object> map = new HashMap<String,Object>();
map.put("code",1);
map.put("msg","id值不存在");
return map;
}
3 我們在數據庫刪除之前判斷id 是否為空,拋出異常
@Override
public HashMap<String, Object> del(Integer a) {
HashMap<String, Object> map = new HashMap<String, Object>();
//拋出自定義異常
if(a==null){
throw new IdNotExistExcetipn();
}
//刪除
int num = adminMapper.deleteById(a);
if(num>0){
map.put("info","保存成功");
}else{
map.put("info","保存錯誤");
}
return map;
}
4 測試,模擬給刪除id傳一個空值,來測試

