優(yōu)雅地處理你的 Java 異常吧
點(diǎn)擊下方“IT牧場”,選擇“設(shè)為星標(biāo)”

本文僅按照業(yè)務(wù)系統(tǒng)開發(fā)角度描述異常的一些處理看法.不涉及java的異?;A(chǔ)知識(shí),可以自行查閱
《Java核心技術(shù) 卷I》和 《java編程思想》 可以得到更多的基礎(chǔ)信息.
系統(tǒng)運(yùn)行出錯(cuò),但是完全不知道錯(cuò)誤發(fā)生的位置. 我們找到了錯(cuò)誤的位置,但是完全不知道是因?yàn)槭裁? 系統(tǒng)明明出了錯(cuò)誤,但是就是看不到錯(cuò)誤堆棧信息.
什么情況需要自定義異常
浪費(fèi)log日志存儲(chǔ)空間,并且棧頂并不是最接近發(fā)生異常的代碼位置. 只有一種異常類,無法精準(zhǔn)區(qū)分開異常類型 異常類后期難以修改以增加其攜帶的信息.
什么情況需要手動(dòng)處理異常
你有能力處理異常,并且你知道如何處理 你有責(zé)任處理異常
自定義業(yè)務(wù)異常
/**
* 業(yè)務(wù)受理失敗異常
*/
public class ServiceException extends RuntimeException {
//接收reason參數(shù)用來描述業(yè)務(wù)失敗原因.
public ServiceException(String reason) { super(reason); }
}
接下來看下Controller層.
// UserController.java
/**
* 修改用戶信息
* @param userID 用戶ID
* @param user 修改用戶信息表單數(shù)據(jù)
*/
@PutMapping("{userID}")
public JSONResult updateUser(@PathVariable("userID") Integer userID, @RequestBody UpdateUserForm userForm) {
User user = new User(); //準(zhǔn)備業(yè)務(wù)邏輯層使用的領(lǐng)域模型
BeanUtils.copyProperties(userForm, user); //拷貝要修改的值
user.setUserId(userID); //設(shè)置主鍵到用戶數(shù)據(jù)中
userService.updateUser(user); //調(diào)用更新業(yè)務(wù)邏輯
JSONResult json = new JSONResult(); //準(zhǔn)備要響應(yīng)的數(shù)據(jù)
json.put("user", user); //把修改后的用戶數(shù)據(jù)還給頁面
return json; // --
}
有效性: 比如用戶所在崗位,是否屬于數(shù)據(jù)庫有記錄的崗位ID,如果不存在,無效. 合法性: 比如用戶名只允許輸入最多12個(gè)字符,用戶提交了20個(gè)字符,不合法.
要修改的用戶ID不存在. 用戶被鎖定,不允許修改. 樂觀鎖機(jī)制發(fā)現(xiàn)用戶已經(jīng)被被人修改過. 由于某種原因,我們的程序無法保存到數(shù)據(jù)庫. 一些程序員錯(cuò)誤的開發(fā)了代碼,導(dǎo)致保存過程中出現(xiàn)異常,比如NPE.
在ccontroller 調(diào)用userService的checkUserExist()方法. 在controller直接書寫業(yè)務(wù)邏輯. 在service響應(yīng)一個(gè)狀態(tài)碼機(jī)制,比如1 2 3表示錯(cuò)誤信息,0 表示沒有任何錯(cuò)誤.
/**
* 修改用戶信息
* @param user 要修改的用戶數(shù)據(jù)
*/
public void updateUser(User user) {
User userOrig = userDao.getUserById(user.getUserID());
if (null == userOrig) {
throw new ServiceException("用戶不存在");
}
if (userOrig.isLocked()) {
throw new ServiceException("用戶被鎖定,不允許修改");
}
if (!user.getVersion().equals(userOrig.getVersion())) {
throw new ServiceException("用戶已經(jīng)被別人修改過,請(qǐng)刷新重試");
}
// TODO 保存用戶數(shù)據(jù) ...
}
在controller 使用try-catch進(jìn)行處理. 直接把異常拋給上層框架統(tǒng)一處理.
@ControllerAdvice(basePackages = { "com.xxx.xxx.bussiness.xxx" })
public class ModuleControllerAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(ModuleControllerAdvice.class);
private static final Logger SERVICE_LOGGER = LoggerFactory.getLogger(ServiceException.class);
/**
* 業(yè)務(wù)受理失敗
*/
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(ServiceException.class)
private JSONResult handleServiceException(ServiceException exception) {
String message = "業(yè)務(wù)受理失敗,原因:" + exception.getLocalizedMessage();
SERVICE_LOGGER.info(message);
JSONResult json = new JSONResult();
json.serCode(500001); // 500000表示系統(tǒng)異常,500001表示業(yè)務(wù)邏輯異常
json.setMessage(message);
return json;
}
}
首先,ServiceException一定要和其他的代碼錯(cuò)誤分離,不應(yīng)該混為一談. 其次,ServiceException并不一定要記錄日志,我們應(yīng)該提供獨(dú)立的log對(duì)象,方便開關(guān).
{
code: 200001,
message: "業(yè)務(wù)受理失敗,原因:用戶名稱不存在!"
}
邏輯異常,這類異常用于描述業(yè)務(wù)無法按照預(yù)期的情況處理下去,屬于用戶制造的意外. 代碼錯(cuò)誤,這類異常用于描述開發(fā)的代碼錯(cuò)誤,例如NPE,ILLARG,都屬于程序員制造的BUG. 專有異常,多用于特定業(yè)務(wù)場景,用于描述指定作業(yè)出現(xiàn)意外情況無法預(yù)先處理.
異常設(shè)計(jì)的初衷是解決程序運(yùn)行中的各種意外情況,且異常的處理效率比條件判斷方式要低很多.
系統(tǒng)有千萬并發(fā),不可能還去考慮這些中規(guī)中矩的按部就班的方式,別忘了MVC本來就浪費(fèi)很多資源,代碼量增加很多. 業(yè)務(wù)系統(tǒng)也存在很多巨量任務(wù)處理的情況.但是那些任務(wù)都是原子性的,現(xiàn)在MVC中的controller和service可不是原子性的,不然為什么要區(qū)分這么多層呢. 如果那么在乎效率,考慮下重寫Throwable的fillStackTrace方法.你要知道異常的開銷大到底大在什么地方,fillStackTrace是一個(gè)native方法,會(huì)填充異常類內(nèi)部的運(yùn)行軌跡.
不要用異常進(jìn)行業(yè)務(wù)邏輯處理
//這是一個(gè)非常典型的反例,也是一個(gè)誤區(qū).
/**
* 處理業(yè)務(wù)消息
* @param message 要處理的消息
*/
public void processMessage(Message<String> message) {
try{
// 處理消息驗(yàn)證
// 處理消息解析
// 處理消息入庫
}catch(ValidateException e ){
// 驗(yàn)證失敗
}catch(ParseException e ){
// 解析失敗
}catch(PersistException e ){
// 入庫失敗
}
}
我們提倡在 業(yè)務(wù)處理 的時(shí)候,如果發(fā)現(xiàn)無法處理直接拋出異常即可. 而并不是在 邏輯處理 的時(shí)候,用異常來判斷邏輯進(jìn)行的狀況.
/**
* 處理業(yè)務(wù)消息
* @param message 要處理的消息
*/
public void processMessage(Message<String> message) {
// 處理消息驗(yàn)證
if(!message.isValud()){
MessageLogService.log("消息校驗(yàn)失敗"+message.errors())
return ;
}
// 處理消息解析
if(!message.parse()){
MessageLogService.log("消息解析失敗"+message.errors())
return ;
}
// TODO ....
}
來源:my.oschina.net/c5ms/blog/1827907
干貨分享
最近將個(gè)人學(xué)習(xí)筆記整理成冊,使用PDF分享。關(guān)注我,回復(fù)如下代碼,即可獲得百度盤地址,無套路領(lǐng)取!
?001:《Java并發(fā)與高并發(fā)解決方案》學(xué)習(xí)筆記;?002:《深入JVM內(nèi)核——原理、診斷與優(yōu)化》學(xué)習(xí)筆記;?003:《Java面試寶典》?004:《Docker開源書》?005:《Kubernetes開源書》?006:《DDD速成(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)速成)》?007:全部?008:加技術(shù)群討論
加個(gè)關(guān)注不迷路
喜歡就點(diǎn)個(gè)"在看"唄^_^
評(píng)論
圖片
表情
