1. 程式人生 > 實用技巧 >Spring Boot 異常處理-轉載

Spring Boot 異常處理-轉載

Spring Boot 異常處理

轉載[於此]: http://www.imooc.com/wiki/springbootlesson/except.html

程式碼可看

1. 前言

程式中出現異常是普遍現象, Java 程式設計師想必早已習慣,根據控制檯輸出的異常資訊,分析異常產生的原因,然後進行鍼對性處理的過程。

Spring Boot 專案中,資料持久層、服務層到控制器層都可能丟擲異常。如果我們在各層都進行異常處理,程式程式碼會顯得支離破碎,難以理解。

實際上,異常可以從內層向外層不斷丟擲,最後在控制器層進行統一處理。 Spring Boot 提供了全域性性的異常處理機制,本節我們就分別演示下,預設情況、控制器返回檢視、控制器返回 JSON 資料三種情況的異常處理方法。

2. Spring Boot 預設異常處理機制

Spring Boot 開發的 Web 專案具備預設的異常處理機制,無須編寫異常處理相關程式碼,即可提供預設異常機制,下面具體演示下。

2.1 使用 Spring Initializr 建立專案

Spring Boot 版本選擇 2.2.5 ,Group 為 com.imooc , Artifact 為 spring-boot-exception-default ,生成專案後匯入 Eclipse 開發環境。

2.2 引入專案依賴

引入 Web 專案依賴即可。

例項:

		<!-- web專案依賴 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

2.3 Spring Boot 預設異常處理

我們在啟動專案, Spring Boot Web 專案預設啟動埠為 8080 ,所以直接訪問 http://127.0.0.1:8080 ,顯示如下:

Spring Boot 預設異常資訊提示頁面

如上圖所示,Spring Boot 預設的異常處理機制生效,當出現異常時會自動轉向 /error 路徑。

3. 控制器返回檢視時的異常處理

在使用模板引擎開發 Spring Boot Web 專案時,控制器會返回檢視頁面。我們使用 Thymeleaf 演示控制器返回檢視時的異常處理方式,其他模板引擎處理方式也是相似的。

3.1 使用 Spring Initializr 建立專案

Spring Boot 版本選擇 2.2.5 ,Group 為 com.imooc , Artifact 為 spring-boot-exception-controller,生成專案後匯入 Eclipse 開發環境。

3.2 引入專案依賴

引入 Web 專案依賴、熱部署依賴。此處使用 Thymeleaf 演示控制器返回檢視時的異常處理方式,所以引入 Thymeleaf 依賴。

例項:

		<!-- web專案依賴 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- 熱部署 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
		</dependency>
		<!-- ThymeLeaf依賴 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
                                     

3.3 定義異常類

在異常處理之前,我們應該根據業務場景具體情況,定義一系列的異常類,習慣性的還會為各種異常分配錯誤碼,如下圖為支付寶開放平臺的公共錯誤碼資訊。

支付寶開放平臺錯誤碼

本節我們為了演示,簡單的定義 2 個異常類,包含錯誤碼及錯誤提示資訊。

例項:

/**
 * 自定義異常
 */
public class BaseException extends Exception {
	/**
	 * 錯誤碼
	 */
	private int code;
	/**
	 * 錯誤提示資訊
	 */
	private String msg;

	public BaseException(int code, String msg) {
		super();
		this.code = code;
		this.msg = msg;
	}
	// 省略get set
}
    

例項:

/**
 * 密碼錯誤異常
 */
public class PasswordException extends BaseException {
	public PasswordException() {
		super(10001, "密碼錯誤");
	}
}
       

例項:

/**
 * 驗證碼錯誤異常
 */
public class VerificationCodeException extends BaseException {
	public VerificationCodeException() {
		super(10002, "驗證碼錯誤");
	}
}
                                                     

3.4 控制器丟擲異常

定義控制器 GoodsController ,然後使用註解 @Controller 標註該類,類中方法的返回值即為檢視檔名。

在 GoodsController 類定義 4 個方法,分別用於正常訪問、丟擲密碼錯誤異常、丟擲驗證碼錯誤異常、丟擲未自定義的異常,程式碼如下。

例項:

/**
 * 商品控制器
 */
@Controller
public class GoodsController {
	/**
	 * 正常方法
	 */
	@RequestMapping("/goods")
	public String goods() {
		return "goods";// 跳轉到resource/templates/goods.html頁面
	}

	/**
	 * 丟擲密碼錯誤異常的方法
	 */
	@RequestMapping("/checkPassword")
	public String checkPassword() throws PasswordException {
		if (true) {
			throw new PasswordException();// 模擬丟擲異常,便於測試
		}
		return "goods";
	}

	/**
	 * 丟擲驗證碼錯誤異常的方法
	 */
	@RequestMapping("/checkVerification")
	public String checkVerification() throws VerificationCodeException {
		if (true) {
			throw new VerificationCodeException();// 模擬丟擲異常,便於測試
		}
		return "goods";
	}

	/**
	 * 丟擲未自定義的異常
	 */
	@RequestMapping("/other")
	public String other() throws Exception {
		int a = 1 / 0;// 模擬異常
		return "goods";
	}
}

                                                     

3.5 開發基於 @ControllerAdvice 的全域性異常類

@ControllerAdvice 註解標註的類可以處理 @Controller 標註的控制器類丟擲的異常,然後進行統一處理。

例項:

/**
 * 控制器異常處理類
 */
@ControllerAdvice(annotations = Controller.class) // 全域性異常處理
public class ControllerExceptionHandler {
	@ExceptionHandler({ BaseException.class }) // 當發生BaseException類(及其子類)的異常時,進入該方法
	public ModelAndView baseExceptionHandler(BaseException e) {
		ModelAndView mv = new ModelAndView();
		mv.addObject("code", e.getCode());
		mv.addObject("message", e.getMessage());
		mv.setViewName("myerror");// 跳轉到resource/templates/myerror.html頁面
		return mv;
	}

	@ExceptionHandler({ Exception.class }) // 當發生Exception類的異常時,進入該方法
	public ModelAndView exceptionHandler(Exception e) {
		ModelAndView mv = new ModelAndView();
		mv.addObject("code", 99999);// 其他異常統一編碼為99999
		mv.addObject("message", e.getMessage());
		mv.setViewName("myerror");// 跳轉到resource/templates/myerror.html頁面
		return mv;
	}
}

                                                     

按照 ControllerExceptionHandler 類的處理邏輯,當發生 BaseException 型別的異常時,會跳轉到 myerror.html 頁面,並顯示相應的錯誤碼和錯誤資訊;當發生其他型別的異常時,錯誤碼為 99999 ,錯誤資訊為相關的異常資訊。

3.6 開發前端頁面

在 resource/templates 下分別新建 goods.html 和 myerror.html 頁面,作為正常訪問及發生異常時跳轉的檢視頁面。

例項:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>goods.html頁面</title>
</head>
<body>
	<div>商品資訊頁面</div>
</body>
</html>
                                                        

例項:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>myerror.html頁面</title>
</head>
<body>
	錯誤碼:
	<span th:text="${code}"></span> 
	錯誤資訊:
	<span th:text="${message}"></span>
</body>
</html>
       

3.7 測試

啟動專案,分別訪問控制器中的 4 個方法,結果如下:

訪問正常方法 /goods

訪問丟擲自定義異常的方法 /checkPassword

訪問丟擲自定義異常的方法 /checkVerification

訪問丟擲未自定義異常的方法 /other

可見,當控制器方法丟擲異常時,會按照全域性異常類設定的邏輯統一處理。

4. 控制器返回 JSON 資料時的異常處理

在控制器類上新增 @RestController 註解,控制器方法處理完畢後會返回 JSON 格式的資料。

此時,可以使用 @RestControllerAdvice 註解標註的類 ,來捕獲 @RestController 標註的控制器丟擲的異常。

4.1 使用 Spring Initializr 建立專案

Spring Boot 版本選擇 2.2.5 ,Group 為 com.imooc , Artifact 為 spring-boot-exception-restcontroller,生成專案後匯入 Eclipse 開發環境。

4.2 引入專案依賴

引入 Web 專案依賴、熱部署依賴即可。

例項:

		<!-- web專案依賴 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- 熱部署 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
		</dependency>
                                                     

4.3 定義異常類

還是使用上文中定義的異常類即可。

4.4 統一控制器返回資料格式

這時候,我們就需要思考一個問題了。前端請求後端控制器介面後,怎麼區分後端介面是正常返回結果,還是發生了異常?

不論後端介面是正常執行,還是中間發生了異常,最好給前端返回統一的資料格式,便於前端統一分析處理。

OK,此時我們就可以封裝後端介面返回的業務邏輯物件 ResultBo ,程式碼如下:

例項:

/**
 * 後端介面返回的統一業務邏輯物件
 */
public class ResultBo<T> {

	/**
	 * 錯誤碼 0表示沒有錯誤(異常) 其他數字代表具體錯誤碼
	 */
	private int code;
	/**
	 * 後端返回訊息
	 */
	private String msg;
	/**
	 * 後端返回的資料
	 */
	private T data;

	/**
	 * 無引數建構函式
	 */
	public ResultBo() {
		this.code = 0;
		this.msg = "操作成功";
	}

	/**
	 * 帶資料data建構函式
	 */
	public ResultBo(T data) {
		this();
		this.data = data;
	}

	/**
	 * 存在異常的建構函式
	 */
	public ResultBo(Exception ex) {
		if (ex instanceof BaseException) {
			this.code = ((BaseException) ex).getCode();
			this.msg = ex.getMessage();
		} else {
			this.code = 99999;// 其他未定義異常
			this.msg = ex.getMessage();
		}
	}
	// 省略 get set
}
   

4.5 控制器丟擲異常

定義控制器 RestGoodsController ,並使用 @RestController 註解標註。在其中定義 4 個方法,然後分別用於正常訪問、丟擲密碼錯誤異常、丟擲驗證碼錯誤異常,以及丟擲不屬於自定義異常類的異常。

例項:

/**
 * Rest商品控制器
 */
@RestController
public class RestGoodsController {
	/**
	 * 正常方法
	 */
	@RequestMapping("/goods")
	public ResultBo goods() {
		return new ResultBo<>(new ArrayList());// 正常情況下應該返回商品列表
	}

	/**
	 * 丟擲密碼錯誤異常的方法
	 */
	@RequestMapping("/checkPassword")
	public ResultBo checkPassword() throws PasswordException {
		if (true) {
			throw new PasswordException();// 模擬丟擲異常,便於測試
		}
		return new ResultBo<>(true);// 正常情況下應該返回檢查密碼的結果true或false
	}

	/**
	 * 丟擲驗證碼錯誤異常的方法
	 */
	@RequestMapping("/checkVerification")
	public ResultBo checkVerification() throws VerificationCodeException {
		if (true) {
			throw new VerificationCodeException();// 模擬丟擲異常,便於測試
		}
		return new ResultBo<>(true);// 正常情況下應該返回檢查驗證碼的結果true或false
	}

	/**
	 * 丟擲未自定義的異常
	 */
	@RequestMapping("/other")
	public ResultBo other() throws Exception {
		int a = 1 / 0;// 模擬異常
		return new ResultBo<>(true);
	}
}
                                                                                         

4.6 開發基於 @RestControllerAdvice 的全域性異常類

@RestControllerAdvice 註解標註的類可以處理 RestController 控制器類丟擲的異常,然後進行統一處理。

例項:

/**
 * Rest控制器異常處理類
 */
@RestControllerAdvice(annotations = RestController.class) // 全域性異常處理
public class RestControllerExceptionHandler {
	/**
	 * 處理BaseException類(及其子類)的異常
	 */
	@ExceptionHandler({ BaseException.class })
	public ResultBo baseExceptionHandler(BaseException e) {
		return new ResultBo(e);
	}

	/**
	 * 處理Exception類的異常
	 */
	@ExceptionHandler({ Exception.class })
	public ResultBo exceptionHandler(Exception e) {
		return new ResultBo(e);
	}
}

4.7 測試

啟動專案,分別嘗試訪問控制器中的 4 個介面,結果如下。

訪問正常方法 /goods

訪問丟擲異常的方法 /checkPassword

訪問丟擲異常的方法 /checkVerification

訪問丟擲異常的方法 /other

5. 小結

Spring Boot 的預設異常處理機制,實際上只能做到提醒開發者 “這個後端介面不存在” 的作用,作用非常有限。

所以我們在開發 Spring Boot 專案時,需要根據專案的實際情況,定義各類異常,並站在全域性的角度統一處理異常。

不管專案有多少層次,所有異常都可以向外丟擲,直到控制器層進行集中處理。

  • 對於返回檢視的控制器,如果沒發生異常就跳轉正常頁面,如果發生異常可以自定義錯誤資訊頁面。
  • 對於返回 JSON 資料的控制器,最好是定義統一的資料返回格式,便於前端根據返回資訊進行正常或者異常情況的處理