前言 先談談“異常處理”這件事。下麵有 2 份偽代碼,對比下: 可以看出,如果使用異常替代返回錯誤碼,錯誤處理代碼就能從主路徑邏輯中分離出來,得到簡化! ②中,基於異常處理的代碼真的好嗎?其實是醜陋不堪的,它搞亂了代碼結構,把錯誤處理與正常流程混為一談。最好把 try 和 catch 代碼塊的主體部 ...
前言
先談談“異常處理”這件事。下麵有 2 份偽代碼,對比下:
// ① 基於 if/else 判斷
if(deletePage(page) == E_OK){
if(registry.deleteReference(page.name) == E_OK){
if(configKeys.deleteKey(page.name.makeKey()) == E_OK){
logger.log("page deleted");
}else{
logger.log("configKey not deleted");
}
}else{
logger.log("deleteReference from registry failed");
}
}else{
logger.log("delete failed");
return E_RROR;
}
// ② 基於異常處理
try{
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}catch(Exception e){
logError(e);
}
可以看出,如果使用異常替代返回錯誤碼,錯誤處理代碼就能從主路徑邏輯中分離出來,得到簡化!
②中,基於異常處理的代碼真的好嗎?其實是醜陋不堪的,它搞亂了代碼結構,把錯誤處理與正常流程混為一談。最好把 try 和 catch 代碼塊的主體部分抽離出來,形成另外的函數。
// ③ 優雅的異常處理邏輯
public void delete(Page page){
try{
deletePageAndAllReferences(page);
}catch(Exception e){
logError(e);
}
}
private void deletePageAndAllReferences(Page page) throw Exception{
deletePage(page);
registry.deleteReference(page.name);
configKeys.deleteKey(page.name.makeKey());
}
private void logError(Exception e){
logger.log(e.getMessage());
}
③中,函數各司其職,更易於理解和修改了。
總結:使用異常而不是錯誤碼,優雅地使用異常!函數應該只做一件事,處理錯誤就是一件事。因此,處理錯誤的函數不該做其他事!
在 Spring Boot 中處理異常
1、預設的異常處理
例如 401,404,500,5XX 等異常,Spring Boot 預設會跳轉到預配置的頁面,此處以 thymeleaf 模板引擎為例:
+ resources
+ templates
+ error
- 401.html
- 404.html
- 500.html
只需在 resources/templates/error/
路徑下添加對應的html文件即可。
2、局部異常處理
局部異常一般處理業務邏輯出現的異常情況,在 Controller 下使用 @ExceptionHandler
註解來處理異常。舉個小例子:
先定義 ResponseBean 和 ExceptionEnum 兩個對象,輔助完成優雅的代碼。
/**
* 統一響應
* @author anoy
*/
public class ResponseBean<T> {
private int code;
private String message;
private T data;
public ResponseBean(){}
public ResponseBean(ExceptionEnum exceptionEnum){
this.code = exceptionEnum.getCode();
this.message = exceptionEnum.getMessage();
}
// 省略 setter/getter
}
/**
* 異常類型枚舉
* @author anoy
*/
public enum ExceptionEnum {
GIRL_FRIEND_NOT_FOUND(100000, "girl friend not found");
private int code;
private String message;
ExceptionEnum(int code, String message){
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
今天七夕,寫個 GirlFriendNotFoundException
(很有同感,是不是?)
@Controller
public class UserController {
@RequestMapping("/friend/{id}")
public String friend(@PathVariable("id") Long id) throws GirlFriendNotFoundException {
if (id == 1L){
throw new GirlFriendNotFoundException();
}
return "friend";
}
@ExceptionHandler(GirlFriendNotFoundException.class)
@ResponseBody
public ResponseBean handleGirlFriendNotFound(GirlFriendNotFoundException exception){
loggerError(exception);
return new ResponseBean(ExceptionEnum.GIRL_FRIEND_NOT_FOUND);
}
private void logError(Exception e){
logger.error(e.getMessage());
}
}
3、全局異常處理
個人觀點:全局異常應該處理系統故障級別的問題,像參數校驗這種類型的異常,應該作為局部異常來處理,例如 Redis 連接斷開,無法請求數據,這種異常就應該當做全局異常來處理,在異常處理的邏輯中,還應該添加通知到開發人員的功能,方便開發人員及時處理錯誤!
全局異常處理,使用 @ControllerAdvice
和 @ExceptionHandler
來配合。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RedisConnectionFailureException.class)
public void handlerRedisConnectionFailureException(RedisConnectionFailureException exception){
logError(exception);
noticeToDev();
}
private void logError(Exception e){
logger.error(e.getMessage());
}
private void noticeToDev(){
// 通知具體開發人員
}
}
常見問題
1、局部異常和全局異常處理同一種類型的 Exception,會發生什麼結果?
答:只會執行局部異常的處理邏輯!
2、GirlFriendNotFoundException 繼承了 RuntimeException, 使用
@ExceptionHandler(RuntimeException.class) 能處理異常嗎?
答:可以的!所以對於局部比較公用的異常可以定義一個父類,拋出異常時可以拋出具體的子類異常,處理時,處理父類異常即可(即只用寫一個方法處理一系列類似的異常)
© 著作權歸作者所有,轉載或內容合作請聯繫作者
● 史上最輕鬆入門之Spring Batch - 輕量級批處理框架實踐
● APM工具尋找了一圈,發現SkyWalking才是我的真愛
● Spring Boot 註入外部配置到應用內部的靜態變數
● Java 使用 UnixSocket 調用 Docker API
● Service Mesh - gRPC 本地聯調遠程服務
本文由博客一文多發平臺 OpenWrite 發佈!