減少 try-catch ,這樣做才叫優(yōu)雅!
文章目錄
情景引入 導入 問題 異常統(tǒng)一處理 出現(xiàn)的情景 開發(fā)環(huán)境 開發(fā)步驟 創(chuàng)建自定義異常 創(chuàng)建消息返回的包裝實體 定義一系列的枚舉返回信息 定義消息返回工具類 定義異常統(tǒng)一處理類(重點) 定義異常處理頁面 效果 開發(fā)環(huán)境 正式環(huán)境 總結
情景引入
(我) 呼嚕呼嚕呼嚕呼嚕。。。。。。。。
(小白) 起床起床起床,,快點起床。。。
(我) 小白,又遇到什么事了,這么火急火燎的,年輕人,做事要穩(wěn)重
(小白) 我遇到了一個很嚴重的問題,想讓你指導指導我!
(我) 哎喲,這次這么虛心請教啦,那我不生氣了,,你說,怎么了呢?
(小白) 就是,我在開發(fā)的過程中,因為是團隊開發(fā),所以,有時候邏輯就對不上,然后就會 莫名其妙的出現(xiàn)一些問題,并且顯示的效果非常難堪,而且也不容易發(fā)現(xiàn)問題,每次都要查看后臺才能知道問題,可是部署到服務器之后,都只能看Log日志來定位問題。
(我) 對呀,這項目開發(fā)本來就是一個團隊的事情,這是很正常的事,有什么大驚小怪呢?
(小白) 所以,我想著,有沒有什么辦法,可以針對系統(tǒng)中的異常(未知和已知)能夠友好的進行顯示呢?這樣,我們在交流的時候也相對更加方便呀,否則,總是看著一堆亂七八糟的錯誤,挺心煩的。
(我) 我好像理解了你的意思。你就是想著,能對系統(tǒng)中的異常能夠友好顯示或者說能方便你們團隊開發(fā)嘛。
(小白) 對的對的,就是這么個意思。
(我) 這當然有了,而且你現(xiàn)在遇到的這個問題,其實在每個系統(tǒng)中都應該有進行處理,雖然它比較簡單,但是不容小視,也有很多重要的東西的呢~那就好好聽課吧!
(小白) 真開心,,,,,迫不及待了
導入
問題
針對Web項目來說,我們都知道,一般都是一個團隊進行開發(fā),而不會是一個人單打獨斗,并且開發(fā)團隊還有前后端的人員,那么有一定的規(guī)范就是必不可少的。
我們可能都遇到過一個問題,就是開發(fā)環(huán)境和正式上線的環(huán)境是有很大的差別的。開發(fā)環(huán)境是針對我們開發(fā)人員,而正式環(huán)境是一種以用戶的角度來審視我們的整個系統(tǒng)。想想一個問題,如果遇到了我們在開發(fā)中沒有碰到的異常,而用戶卻發(fā)現(xiàn)了,用戶體驗是不是會非常不好,而且這是我們的一個大忌。。
既然如此,我們也知道,開發(fā)過程中,有如此多的異常可能會出現(xiàn),那么里面就包含著我們已經(jīng)考慮到了的,然而還有一些隱藏的異常卻是我們可能忽視的,所以,為了能夠將那些潛在的異常不被用戶直接發(fā)現(xiàn),而影響用戶體驗,這---------異常統(tǒng)一處理,,,就必不可少!
異常統(tǒng)一處理
定義:
簡單點說,就是針對我們系統(tǒng)中的異常,給予一定規(guī)范的處理結果。(比如,默認的情況,就是將異常堆棧信息直接打印到頁面,然而這種是極其丑陋的)
出現(xiàn)的情景
開發(fā)人員預測得到的自定義異常:在開發(fā)中,開發(fā)人員對某些可能出現(xiàn)的情形是可以預知的,這時候是一種主動處理的狀態(tài)。 開發(fā)人員無法預測的系統(tǒng)異常:在開發(fā)中,存在著開發(fā)人員無法全面思考到的異常,那么這時候就是一種潛在性的可能異常狀態(tài)。 前端和后臺交互異常:由于前后端的分離,而且前后端的開發(fā)方向也存在著差異,那么就有可能導致異常的出現(xiàn)。
開發(fā)環(huán)境
windows 7 + 渣渣筆記本 IDEA + SpringBoot + Mybatis +Mysql
開發(fā)步驟
創(chuàng)建自定義異常
分析:在系統(tǒng)中,存在著系統(tǒng)異常和我們人為的自定義異常,所以,為了能夠有效的針對不同異常進行處理,那么擁有我們自定義的異常類是非常有必要的。
package?com.hnu.csapp.exception;
/**
?*?@ Author ????:scw
?*?@ Description:自定義異常,為了區(qū)分系統(tǒng)異常和更方便系統(tǒng)的特定一些處理
?*?@ Modified By:
?*?@Version:?1
?*/
public?class?MyException?extends?RuntimeException{
????//錯誤碼
????private?Integer?code;
????public?Integer?getCode()?{
????????return?code;
????}
????public?void?setCode(Integer?code)?{
????????this.code?=?code;
????}
????public?MyException(String?message)?{
????????super(message);
????}
????/**
?????*?構造器重載,主要是自己考慮某些異常自定義一些返回碼
?????*?@param?code
?????*?@param?message
?????*/
????public?MyException(Integer?code,String?message)?{
????????super(message);
????????this.code?=?code;
????}
}
創(chuàng)建消息返回的包裝實體
分析:對于后臺返回給前端的數(shù)據(jù)來說,我們很多情況都是返回的JSON格式的數(shù)據(jù)(當然,并不是局限于這一種),那么JSON是一種格式化的形式。
所以,我們應該有效的針對這樣的形式來給予一定的返回規(guī)范,這樣也方便前端對于我們返回數(shù)據(jù)的解析。
比如:很多情況一般是如下的格式:
{
??"retCode":?200,??//通過狀態(tài)碼可以得到消息是否返回正常,然后再決定是否去解析data域的內容
?"data":?{????????//返回的數(shù)據(jù)內容
?}
?"retMes":?success??//返回的提示內容
?
}
所以,我們可以定義如下的類:
package?com.hnu.csapp.exception;
/**
?*?@ Author ????:scw
?*?@ Description:異常處理實體包裝類,自己用泛型進行寫,擴展性強點
?*?@ Modified By:
?*?@Version:?1
?*/
public?class?Result<T>?{
????//返回碼
????private?Integer?code;
????//返回消息
????private?String?msg;
????//返回數(shù)據(jù)
????private?T?data;
????public?Integer?getCode()?{
????????return?code;
????}
????public?void?setCode(Integer?code)?{
????????this.code?=?code;
????}
????public?String?getMsg()?{
????????return?msg;
????}
????public?void?setMsg(String?msg)?{
????????this.msg?=?msg;
????}
????public?T?getData()?{
????????return?data;
????}
????public?void?setData(T?data)?{
????????this.data?=?data;
????}
}
定義一系列的枚舉返回信息
分析:在系統(tǒng)中,我們應該有統(tǒng)一的某些編碼對應某些內容,這樣能夠方便開發(fā)人員進行及時的處理。
package?com.hnu.csapp.exception;
/**
?*?@ Author ????:scw
?*?@ Description:自定義一些返回狀態(tài)碼,便于本系統(tǒng)的使用,自己先定義如下的,有需要就后續(xù)補充
?*?@ Modified By:
?*?@Version:?1
?*/
public?enum?ResultEnum?{
????/**
?????*?成功.:?200?(因為http中的狀態(tài)碼200一般都是表示成功)
?????*/
????SUCCESS(200,"成功"),
????/**
?????*?系統(tǒng)異常.?ErrorCode?:?-1
?????*/
????SystemException(-1,"系統(tǒng)異常"),
????/**
?????*?未知異常.?ErrorCode?:?01
?????*/
????UnknownException(01,"未知異常"),
????/**
?????*?服務異常.?ErrorCode?:?02
?????*/
????ServiceException(02,?"服務異常"),
????/**
?????*?業(yè)務錯誤.?ErrorCode?:?03
?????*/
????MyException(03,"業(yè)務錯誤"),
????/**
?????*?提示級錯誤.?ErrorCode?:?04
?????*/
????InfoException(04,?"提示級錯誤"),
????/**
?????*?數(shù)據(jù)庫操作異常.?ErrorCode?:?05
?????*/
????DBException(05,"數(shù)據(jù)庫操作異常"),
????/**
?????*?參數(shù)驗證錯誤.?ErrorCode?:?06
?????*/
????ParamException(06,"參數(shù)驗證錯誤");
????private?Integer?code;
????private?String?msg;
????ResultEnum(Integer?code,?String?msg)?{
????????this.code?=?code;
????????this.msg?=?msg;
????}
????public?Integer?getCode()?{
????????return?code;
????}
????public?String?getMsg()?{
????????return?msg;
????}
}
定義消息返回工具類
分析:對于消息的返回,這是一個非常普通的工作,所以,我們可以將其封裝一個工具類,能夠進行有效代碼的封裝,減少多余的代碼
package?com.hnu.csapp.exception;
/**
?*?@ Author ????:scw
?*?@ Description:返回消息處理的工具類,主要是處理操作成功和失敗的一些內容
?*?@ Modified By:
?*?@Version:?1
?*/
public?class?ResultUtil?{
????/**
?????*?操作成功的處理流程
?????*?@param?object
?????*?@return
?????*/
????public?static?Result?getSuccess(Object?object){
????????Result?result?=?new?Result();
????????//設置操作成功的返回碼
????????result.setCode(200);
????????//設置操作成功的消息
????????result.setMsg("成功");
????????result.setData(object);
????????return?result;
????}
????/**
?????*?重載返回成功的方法,因為有時候我們不需要任何的消息數(shù)據(jù)被返回
?????*?@return
?????*/
????public?static?Result?getSuccess(){
????????return?getSuccess(null);
????}
????/**
?????*?操作失敗的處理流程
?????*?@param?code?錯誤碼
?????*?@param?msg?錯誤消息
?????*?@param?o???錯誤數(shù)據(jù)(其實這個一般都不需要的,因為都已經(jīng)返回失敗了,數(shù)據(jù)都沒必要返回)
?????*?@return
?????*/
????public?static?Result?getError(Integer?code,?String?msg,?Object?o){
????????Result?result?=?new?Result();
????????result.setCode(code);
????????result.setMsg(msg);
????????result.setData(o);
????????return?result;
????}
????/**
?????*?重載,操作失敗的方法(因為操作失敗一般都不需要返回數(shù)據(jù)內容)
?????*?@param?code
?????*?@param?msg
?????*?@return
?????*/
????public?static?Result?getError(Integer?code,?String?msg){
????????return?getError(code,?msg,?null);
????}
}
定義異常統(tǒng)一處理類(重點)
分析:這是如何實現(xiàn)異常統(tǒng)一處理的關鍵地方,而且我也將不同的處理情形,進行了分開注釋,所以,大家一定可以認真的看代碼,我相信你一定能夠明白。
package?com.hnu.csapp.exception;
import?org.slf4j.Logger;
import?org.slf4j.LoggerFactory;
import?org.springframework.web.bind.annotation.ControllerAdvice;
import?org.springframework.web.bind.annotation.ExceptionHandler;
import?org.springframework.web.bind.annotation.ResponseBody;
import?org.springframework.web.servlet.ModelAndView;
import?javax.servlet.http.HttpServletRequest;
/**
?*?@ Author ????:scw
?*?@ Description:異常統(tǒng)一處理類,方便用戶可以更加友好的看到錯誤信息
?*?@ Modified By:
?*?@Version:?1
?*/
@ControllerAdvice
public?class?ExceptionHandle?{
????//增加異常日志打印
????private?final?static?Logger?logger?=?LoggerFactory.getLogger(ExceptionHandle.class);
????//設置異常錯誤的頁面
????public?static?final?String?DEFAULT_ERROR_VIEW?=?"error";
????/**
?????*?以json的格式進行返回內容(開發(fā)環(huán)境一般個人是用這個比較好)
?????*?@param?e
?????*?@return
?????*/
????@ExceptionHandler(Exception.class)
????@ResponseBody
????public?Object?handle(HttpServletRequest?req,?Exception?e){
????????//如果是自定義的異常
????????if(e?instanceof?MyException){
????????????MyException?myException?=?(MyException)e;
????????????return?ResultUtil.getError(myException.getCode(),myException.getMessage());
????????}else{
????????????//如果是系統(tǒng)的異常,比如空指針這些異常
????????????logger.error("【系統(tǒng)異常】={}",e);
????????????return?ResultUtil.getError(ResultEnum.SystemException.getCode(),ResultEnum.SystemException.getMsg());
????????}
????}
????/**
?????*?判斷是否是Ajax的請求
?????*?@param?request
?????*?@return
?????*/
????public?boolean?isAjax(HttpServletRequest?request){
????????return?(request.getHeader("X-Requested-With")?!=?null
????????????????&&
????????????????"XMLHttpRequest".equals(request.getHeader("X-Requested-With").toString()));
????}
????/*
????//備注:
????//這個是正式項目完成之后的錯誤統(tǒng)一處理(開發(fā)情況先用上面的的)
????//我們在開發(fā)過程中還是用json格式的會好一些,要不然看錯誤麻煩
????@ExceptionHandler(value?=?Exception.class)
????public?ModelAndView?defaultErrorHandler(HttpServletRequest?req,?Exception?e)?throws?Exception?{
????????e.printStackTrace();
????????//判斷是否是Ajax的異常請求(如果是Ajax的那么就是返回json格式)
????????if(isAjax(req)){
????????????//如果是自定義的異常
????????????if(e?instanceof?MyException){
????????????????MyException?myException?=?(MyException)e;
????????????????return?ResultUtil.getError(myException.getCode(),myException.getMessage());
????????????}else{
????????????????//如果是系統(tǒng)的異常,比如空指針這些異常
????????????????logger.error("【系統(tǒng)異常】={}",e);
????????????????return?ResultUtil.getError(ResultEnum.SystemException.getCode(),ResultEnum.SystemException.getMsg());
????????????}
????????}else{
????????????//如果是系統(tǒng)內部發(fā)生異常,那么就返回到錯誤頁面進行友好的提示
????????????ModelAndView?mav?=?new?ModelAndView();
????????????//這些就是要返回到頁面的內容(其實不用都行,反正用戶也不懂,沒必要在頁面顯示都可以,先寫著吧)
????????????mav.addObject("exception",?e);
????????????mav.addObject("url",?req.getRequestURL());
????????????mav.setViewName(DEFAULT_ERROR_VIEW);
????????????return?mav;
????????}
????}
????*/
}
定義異常處理頁面
分析:這個的話,其實主要是在正式環(huán)境才有,因為我們在測試環(huán)境的時候,一般都還是會將錯誤以JSON或者堆棧的格式顯示在頁面,而當上線的時候,那么就一定要有一個統(tǒng)一的錯誤頁面,這樣就能夠讓用戶發(fā)現(xiàn)不了是系統(tǒng)出現(xiàn)了哪些問題。
效果
開發(fā)環(huán)境
正式環(huán)境
分析:當出現(xiàn)異常的時候,則顯示如下的頁面。(該頁面是參考一個博友的,感覺挺有意思,,老司機~)
總結
異常統(tǒng)一處理,或許我們看起來實現(xiàn)非常簡單,然而,其他它包含的思想?yún)s是一種大局思想,這是我們開發(fā)人員在開發(fā)過程中都應該關注的點,我們并不是只需要關注我們每個人開發(fā)的那點任務,而要以一種全局的角度去審視整個項目,這樣也能夠提升我們開問題的高度。
異常統(tǒng)一處理,是每個項目都存在的,只是可能實現(xiàn)的方式不一樣而已,或者顯示的效果不一樣而已,這些都不是關鍵的地方。
異常統(tǒng)一處理這個問題,并不是很難,但是這個可以幫助我們延伸到其他的一些相關的開發(fā)層面的知識,比如:
登錄攔截 權限管理 日志管理 事務處理 數(shù)據(jù)控制和過濾 。。。
所以,我們應該學會從一個問題,發(fā)散的看到相關類似的問題,這樣,我們的系統(tǒng)才會更加健壯,高效和可擴展性強。
