前言
先談?wù)劇爱惓L幚怼边@件事。下面有 2 份偽代碼奠骄,對(duì)比下:
// ① 基于 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);
}
可以看出,如果使用異常替代返回錯(cuò)誤碼番刊,錯(cuò)誤處理代碼就能從主路徑邏輯中分離出來(lái)含鳞,得到簡(jiǎn)化!
②中撵枢,基于異常處理的代碼真的好嗎民晒?其實(shí)是丑陋不堪的,它搞亂了代碼結(jié)構(gòu)锄禽,把錯(cuò)誤處理與正常流程混為一談潜必。最好把 try 和 catch 代碼塊的主體部分抽離出來(lái),形成另外的函數(shù)沃但。
// ③ 優(yōu)雅的異常處理邏輯
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());
}
③中磁滚,函數(shù)各司其職,更易于理解和修改了宵晚。
總結(jié):使用異常而不是錯(cuò)誤碼垂攘,優(yōu)雅地使用異常!函數(shù)應(yīng)該只做一件事淤刃,處理錯(cuò)誤就是一件事晒他。因此,處理錯(cuò)誤的函數(shù)不該做其他事逸贾!
在 Spring Boot 中處理異常
1陨仅、默認(rèn)的異常處理
例如 401,404铝侵,500灼伤,5XX 等異常,Spring Boot 默認(rèn)會(huì)跳轉(zhuǎn)到預(yù)配置的頁(yè)面咪鲜,此處以 thymeleaf 模板引擎為例:
+ resources
+ templates
+ error
- 401.html
- 404.html
- 500.html
只需在 resources/templates/error/
路徑下添加對(duì)應(yīng)的html文件即可狐赡。
2、局部異常處理
局部異常一般處理業(yè)務(wù)邏輯出現(xiàn)的異常情況疟丙,在 Controller 下使用 @ExceptionHandler
注解來(lái)處理異常颖侄。舉個(gè)小例子:
先定義 ResponseBean 和 ExceptionEnum 兩個(gè)對(duì)象,輔助完成優(yōu)雅的代碼隆敢。
/**
* 統(tǒng)一響應(yīng)
* @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;
}
}
今天七夕发皿,寫個(gè) 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穴墅、全局異常處理
個(gè)人觀點(diǎn):全局異常應(yīng)該處理系統(tǒng)故障級(jí)別的問題,像參數(shù)校驗(yàn)這種類型的異常温自,應(yīng)該作為局部異常來(lái)處理玄货,例如 Redis 連接斷開,無(wú)法請(qǐng)求數(shù)據(jù)悼泌,這種異常就應(yīng)該當(dāng)做全局異常來(lái)處理松捉,在異常處理的邏輯中,還應(yīng)該添加通知到開發(fā)人員的功能馆里,方便開發(fā)人員及時(shí)處理錯(cuò)誤隘世!
全局異常處理可柿,使用 @ControllerAdvice
和 @ExceptionHandler
來(lái)配合。
@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(){
// 通知具體開發(fā)人員
}
}
常見問題
1丙者、局部異常和全局異常處理同一種類型的 Exception复斥,會(huì)發(fā)生什么結(jié)果?
答:只會(huì)執(zhí)行局部異常的處理邏輯械媒!
2目锭、GirlFriendNotFoundException 繼承了 RuntimeException, 使用
@ExceptionHandler(RuntimeException.class) 能處理異常嗎纷捞?
答:可以的痢虹!所以對(duì)于局部比較公用的異常可以定義一個(gè)父類主儡,拋出異常時(shí)可以拋出具體的子類異常奖唯,處理時(shí),處理父類異常即可(即只用寫一個(gè)方法處理一系列類似的異常)