一、什么是異常
Java 語言按照錯(cuò)誤嚴(yán)重性,從 throwale 根類衍生出 Error 和 Exception 兩大派系。
Error(錯(cuò)誤):
程序在執(zhí)行過程中所遇到的硬件或操作系統(tǒng)的錯(cuò)誤。錯(cuò)誤對(duì)程序而言是致命的,將導(dǎo)致程序無法運(yùn)行。常見的錯(cuò)誤有內(nèi)存溢出,jvm 虛擬機(jī)自身的非正常運(yùn)行,calss 文件沒有主方法。程序本生是不能處理錯(cuò)誤的,只能依靠外界干預(yù)。Error 是系統(tǒng)內(nèi)部的錯(cuò)誤,由 jvm 拋出,交給系統(tǒng)來處理。
Exception(異常):
程序正常運(yùn)行中,可以預(yù)料的意外情況。比如數(shù)據(jù)庫連接中斷,空指針,數(shù)組下標(biāo)越界。異常出現(xiàn)可以導(dǎo)致程序非正常終止,也可以預(yù)先檢測(cè),被捕獲處理掉,使程序繼續(xù)運(yùn)行。Exception(異常)按照性質(zhì),又分為編譯異常(受檢異常)和運(yùn)行時(shí)異常(非受檢異常)。
?編譯異常:
又叫可檢查異常,通常時(shí)由語法錯(cuò)和環(huán)境因素(外部資源)造成的異常。比如輸入輸出異常 IOException,數(shù)據(jù)庫操作 SQLException。其特點(diǎn)是,Java 語言強(qiáng)制要求捕獲和處理所有非運(yùn)行時(shí)異常。通過行為規(guī)范,強(qiáng)化程序的健壯性和安全性。
?運(yùn)行時(shí)異常:
又叫不檢查異常 RuntimeException,這些異常一般是由程序邏輯錯(cuò)誤引起的,即語義錯(cuò)。比如算術(shù)異常,空指針異常 NullPointerException,下標(biāo)越界 IndexOutOfBoundsException。運(yùn)行時(shí)異常應(yīng)該在程序測(cè)試期間被暴露出來,由程序員去調(diào)試,而避免捕獲。
二、處理異常方式
代碼中,我們最常見到的處理異常的方式就是:try-catch
try { // 業(yè)務(wù)邏輯 } catch (Exception e) { // 捕獲到異常的邏輯 }
或者是再進(jìn)一步區(qū)分下異常類型:
try { // 業(yè)務(wù)邏輯 } catch (IOException ie) { // 捕獲到IO異常的邏輯 } catch (Exception e) { // 捕獲到其他異常的邏輯 }
三、如何拋出異常
我們通常可以用拋出異常的方式來控制代碼流程,然后在網(wǎng)關(guān)處統(tǒng)一catch異常來返回錯(cuò)誤code。這在一定程度上可以簡化代碼流程控制,如下所示:
@Override public UserVO queryUser(Long id) { UserDO userDO = userMapper.queryUserById(id); if (Objects.isNull(userDO)) { throw new RuntimeException("用戶不存在"); //用戶不存在拋出異常 } return userDO.toVo(); }
上面這種拋出異常的方式,雖然簡化了代碼流程,但是在存在多種錯(cuò)誤場(chǎng)景時(shí),沒有辦法細(xì)分具體的錯(cuò)誤類型。如:用戶不存在的錯(cuò)誤、用戶沒有權(quán)限的錯(cuò)誤;
聰明如你,一定想到了自定義異常,如下:
@Override public UserVO queryUser(Long id) { UserDO userDO = userMapper.queryUserById(id); if (Objects.isNull(userDO)) { throw new UserNotFoundException(); //用戶不存在拋出對(duì)應(yīng)異常 } if(!checkLicence(userDO)) { throw new BadLicenceException(); //用戶無權(quán)限拋出對(duì)應(yīng)異常 } return userDO.toVo(); }
確實(shí),自定義異??梢越鉀Q錯(cuò)誤場(chǎng)景細(xì)分的問題。進(jìn)一步的,我們可以對(duì)系統(tǒng)流程不同階段、不同業(yè)務(wù)類型分別自定義異常,但這需要自定義大量的異常;
四、如何優(yōu)雅的拋出異常
上面的方式,可以區(qū)分出錯(cuò)誤場(chǎng)景了,但是還存在一些缺點(diǎn)。如:可讀性差、需要定義大量的自定義異常;
那我們下面就去優(yōu)化上面的問題;
用斷言增加代碼的可讀性;
@Override public UserVO queryUser(Long id) { UserDO userDO = userMapper.queryUserById(id); Assert.notNull(userDO, "用戶不存在"); //用斷言進(jìn)行參數(shù)的非空校驗(yàn) return userDO.toVo(); }
斷言雖然代碼簡潔、可讀性好,但是缺乏像上述自定義異常一樣可以明確區(qū)分錯(cuò)誤場(chǎng)景,這就引出我們的究極方案:自定義斷言;
自定義斷言;
我們用自定義斷言的方式,綜合上面自定義異常和斷言的優(yōu)點(diǎn),在斷言失敗后,拋出我們制定好的異常。代碼如下:
?自定義異?;绢?/p>
@Getter @Setter public class BaseException extends RuntimeException { // 響應(yīng)碼 private IResponseEnum responseEnum; // 參數(shù)信息 private Object[] objs; public BaseException(String message, IResponseEnum responseEnum, Object[] objs) { super(message); this.responseEnum = responseEnum; this.objs = objs; } public BaseException(String message, Throwable cause, IResponseEnum responseEnum, Object[] objs) { super(message, cause); this.responseEnum = responseEnum; this.objs = objs; } }
?自定義斷言接口
public interface MyAssert { /** * 創(chuàng)建自定義異常 * * @param objs 參數(shù)信息 * @return 自定義異常 */ BaseException newException(Object... objs); /** * 創(chuàng)建自定義異常 * * @param msg 描述信息 * @param objs 參數(shù)信息 * @return 自定義異常 */ BaseException newException(String msg, Object... objs); /** * 創(chuàng)建自定義異常 * * @param t 接收驗(yàn)證異常 * @param msg 描述信息 * @param objs 參數(shù)信息 * @return 自定義異常 */ BaseException newException(Throwable t, String msg, Object... objs); /** * 校驗(yàn)非空 * * @param obj 被驗(yàn)證對(duì)象 */ default void assertNotNull(Object obj, Object... objs) { if (obj == null) { throw newException(objs); } } /** * 校驗(yàn)非空 * * @param obj 被驗(yàn)證對(duì)象 */ default void assertNotNull(Object obj, String msg, Object... objs) { if (obj == null) { throw newException(msg, objs); } } }
上述代碼我們可以看出基本設(shè)計(jì),就是在我們自定義斷言失敗后拋出我們自定義異常。
下面是具體的實(shí)現(xiàn)案例:
?自定義業(yè)務(wù)異常類,繼承自異?;绢?/p>
public class BusinessException extends BaseException { public BusinessException(IResponseEnum responseEnum, Object[] args, String msg) { super(msg, responseEnum, args); } public BusinessException(IResponseEnum responseEnum, Object[] args, String msg, Throwable t) { super(msg, t, responseEnum, args); } }
?響應(yīng)code枚舉接口定義
public interface IResponseEnum { /** * 返回code碼 * * @return code碼 */ String getCode(); /** * 返回描述信息 * * @return 描述信息 */ String getMsg(); }
?自定義業(yè)務(wù)異常類斷言定義,實(shí)現(xiàn)自定義斷言失敗后對(duì)應(yīng)的自定義異常的定義;
public interface BusinessExceptionAssert extends IResponseEnum, MyAssert { @Override default BaseException newException(Object... args) { return new BusinessException(this, args, this.getMsg()); //斷言失敗后,拋出自定義異常 } @Override default BaseException newException(String msg, Object... args) { return new BusinessException(this, args, msg); //斷言失敗后,拋出自定義異常 } @Override default BaseException newException(Throwable t, String msg, Object... args) { return new BusinessException(this, args, msg, t); //斷言失敗后,拋出自定義異常 } }
?用枚舉的方式,代替BadLicenceException、UserNotFoundException自定義異常。
public enum ResponseEnum implements IResponseEnum, BusinessExceptionAssert { BAD_LICENCE("0001", "無權(quán)訪問"), USER_NOT_FOUND("1001", "用戶不存在"), ; private final String code, msg; ResponseEnum(String code, String msg) { this.code = code; this.msg = msg; } @Override public String getCode() { return code; } @Override public String getMsg() { return msg; } }
使用實(shí)例
自定義斷言失敗拋出自定義異常
@Override public UserVO queryUser(Long id) { UserDO userDO = userMapper.queryUserById(id); ResponseEnum.USER_NOT_FOUND.assertNotNull(userDO); //自定義斷言失敗拋出自定義異常 return userDO.toVo(); }
網(wǎng)關(guān)處統(tǒng)一catch異常,識(shí)別異常場(chǎng)景
public static void main(String[] args) { UserService userService = new UserServiceImpl(new UserMapperImpl()); UserController userController = new UserController(userService); try { UserVO vo = userController.queryUser(2L); //執(zhí)行業(yè)務(wù)邏輯 } catch (BusinessException e) { System.out.println(e.getResponseEnum().getCode()); //出現(xiàn)異常,錯(cuò)誤code:1001 System.out.println(e.getMessage()); //出現(xiàn)異常,錯(cuò)誤msg:用戶不存在 } }
五、如何優(yōu)雅的處理異常
網(wǎng)關(guān)處統(tǒng)一處理異常,這屬于常規(guī)操作,這里不再贅述,簡單舉例如下:
@ControllerAdvice public class BusinessExceptionHandler { @ExceptionHandler(value = BusinessException.class) @ResponseBody public Response handBusinessException(BaseException e) { return new Response(e.getResponseEnum().getCode(), e.getResponseEnum().getMsg()); //統(tǒng)一處理異常 } }
綜上,我們采用自定義斷言的方式,結(jié)合了斷言的可讀性高的優(yōu)勢(shì)和自定義異常區(qū)分錯(cuò)誤場(chǎng)景的優(yōu)勢(shì)。并且,有新增的錯(cuò)誤場(chǎng)景,我們只需要在錯(cuò)誤碼枚舉中新增對(duì)應(yīng)枚舉即可。
審核編輯 黃宇
-
JAVA
+關(guān)注
關(guān)注
19文章
2969瀏覽量
104789 -
代碼
+關(guān)注
關(guān)注
30文章
4790瀏覽量
68654 -
異常
+關(guān)注
關(guān)注
0文章
22瀏覽量
9256
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論