手把手教你如何玩轉統一異常處理

手把手教你如何玩轉統一異常處理

本文系投稿,作者:Cs_hnu_scw主頁:https://blog.csdn.net/Cs_hnu_scw


情景引入

我:呼嚕呼嚕呼嚕呼嚕。。。。。。。。

小白:起床起床起床,,快點起床。。。

我:小白,又遇到什麼事了,這麼火急火燎的,年輕人,做事要穩重

小白:我遇到了一個很嚴重的問題,想讓你指導指導我!

我:哎喲,這次這麼虛心請教啦,那我不生氣了,,你說,怎麼了呢?

小白:就是,我在開發的過程中,因為是團隊開發,所以,有時候邏輯就對不上,然後就會 莫名其妙的出現一些問題,並且顯示的效果非常難堪,而且也不容易發現問題,每次都要查看後臺才能知道問題,可是部署到服務器之後,都只能看Log日誌來定位問題。

我:對呀,這項目開發本來就是一個團隊的事情,這是很正常的事,有什麼大驚小怪呢?

小白:所以,我想著,有沒有什麼辦法,可以針對系統中的異常(未知和已知)能夠友好的進行顯示呢?這樣,我們在交流的時候也相對更加方便呀,否則,總是看著一堆亂七八糟的錯誤,挺心煩的。 我:我好像理解了你的意思。你就是想著,能對系統中的異常能夠友好顯示或者說能方便你們團隊開發嘛。

小白:對的對的,就是這麼個意思。

我:這當然有了,而且你現在遇到的這個問題,其實在每個系統中都應該有進行處理,雖然它比較簡單,但是不容小視,也有很多重要的東西的呢~那就好好聽課吧!

小白:真開心,,,,,迫不及待了

導入

問題

針對Web項目來說,我們都知道,一般都是一個團隊進行開發,而不會是一個人單打獨鬥,並且開發團隊還有前後端的人員,那麼有一定的規範就是必不可少的。

我們可能都遇到過一個問題,就是開發環境和正式上線的環境是有很大的差別的。開發環境是針對我們開發人員,而正式環境是一種以用戶的角度來審視我們的整個系統。想想一個問題,如果遇到了我們在開發中沒有碰到的異常,而用戶卻發現了,用戶體驗是不是會非常不好,而且這是我們的一個大忌。

既然如此,我們也知道,開發過程中,有如此多的異常可能會出現,那麼裡面就包含著我們已經考慮到了的,然而還有一些隱藏的異常卻是我們可能忽視的,所以,為了能夠將那些潛在的異常不被用戶直接發現,而影響用戶體驗,這---------異常統一處理,,,就必不可少!

異常統一處理

定義:簡單點說,就是針對我們系統中的異常,給予一定規範的處理結果。(比如,默認的情況,就是將異常堆棧信息直接打印到頁面,然而這種是極其醜陋的)

出現的情景

開發人員預測得到的自定義異常

在開發中,開發人員對某些可能出現的情形是可以預知的,這時候是一種主動處理的狀態。

開發人員無法預測的系統異常

在開發中,存在著開發人員無法全面思考到的異常,那麼這時候就是一種潛在性的可能異常狀態。

前端和後臺交互異常

由於前後端的分離,而且前後端的開發方向也存在著差異,那麼就有可能導致異常的出現。

開發環境

  • windows 7 + 渣渣筆記本
  • IDEA + SpringBoot + Mybatis +Mysql

開發步驟

創建自定義異常

分析:在系統中,存在著系統異常和我們人為的自定義異常,所以,為了能夠有效的針對不同異常進行處理,那麼擁有我們自定義的異常類是非常有必要的。

package
com
.
hnu
.
csapp
.
exception
;
/**
* @ Author :scw
* @ Date :Created in 下午 3:18 2018/11/29 0029
* @ Description:自定義異常,為了區分系統異常和更方便系統的特定一些處理
* @ 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
;

}
}

創建消息返回的包裝實體

分析:對於後臺返回給前端的數據來說,我們很多情況都是返回的JSON格式的數據(當然,並不是侷限於這一種),那麼JSON是一種格式化的形式,所以,我們應該有效的針對這樣的形式來給予一定的返回規範,這樣也方便前端對於我們返回數據的解析。

比如:很多情況一般是如下的格式:

{

"retCode"
:

200
,

//通過狀態碼可以得到消息是否返回正常,然後再決定是否去解析data域的內容

"data"
:

{

//返回的數據內容

}

"retMes"
:
success
//返回的提示內容
}

所以,我們可以定義如下的類:

package
com
.
hnu
.
csapp
.
exception
;
/**
* @ Author :scw
* @ Date :Created in 下午 3:09 2018/11/29 0029

* @ Description:異常處理實體包裝類,自己用泛型進行寫,擴展性強點
* @ Modified By:
* @Version: 1
*/
public

class

Result
<
T
>

{

//返回碼

private

Integer
code
;

//返回消息

private

String
msg
;

//返回數據

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
;

}
}

定義一系列的枚舉返回信息

分析:在系統中,我們應該有統一的某些編碼對應某些內容,這樣能夠方便開發人員進行及時的處理。

package
com
.
hnu
.
csapp
.
exception
;
/**
* @ Author :scw
* @ Date :Created in 下午 3:23 2018/11/29 0029
* @ Description:自定義一些返回狀態碼,便於本系統的使用,自己先定義如下的,有需要就後續補充
* @ Modified By:
* @Version: 1
*/
public

enum

ResultEnum

{

/**
* 成功.: 200 (因為http中的狀態碼200一般都是表示成功)
*/
SUCCESS
(
200
,
"成功"
),

/**
* 系統異常. ErrorCode : -1
*/

SystemException
(-
1
,
"系統異常"

),

/**
* 未知異常. ErrorCode : 01
*/

UnknownException
(
01
,
"未知異常"
),

/**
* 服務異常. ErrorCode : 02
*/

ServiceException
(
02
,

"服務異常"
),

/**
* 業務錯誤. ErrorCode : 03
*/

MyException
(
03
,
"業務錯誤"
),

/**
* 提示級錯誤. ErrorCode : 04
*/

InfoException
(
04
,

"提示級錯誤"
),


/**
* 數據庫操作異常. ErrorCode : 05
*/

DBException
(
05
,
"數據庫操作異常"
),

/**
* 參數驗證錯誤. ErrorCode : 06
*/

ParamException
(
06
,
"參數驗證錯誤"
);

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
* @ Date :Created in 下午 3:12 2018/11/29 0029
* @ 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
;

}

/**
* 重載返回成功的方法,因為有時候我們不需要任何的消息數據被返回
* @return
*/

public

static

Result
getSuccess

(){

return
getSuccess
(
null
);

}

/**
* 操作失敗的處理流程
* @param code 錯誤碼
* @param msg 錯誤消息
* @param o 錯誤數據(其實這個一般都不需要的,因為都已經返回失敗了,數據都沒必要返回)
* @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
;

}

/**
* 重載,操作失敗的方法(因為操作失敗一般都不需要返回數據內容)
* @param code
* @param msg
* @return
*/

public

static

Result
getError
(
Integer
code
,

String
msg
){

return
getError
(
code
,
msg
,

null
);

}
}

定義異常統一處理類(重點)

分析:這是如何實現異常統一處理的關鍵地方,而且我也將不同的處理情形,進行了分開註釋,所以,大家一定可以認真的看代碼,我相信你一定能夠明白。

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
* @ Date :Created in 下午 3:20 2018/11/29 0029
* @ Description:異常統一處理類,方便用戶可以更加友好的看到錯誤信息
* @ 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的格式進行返回內容(開發環境一般個人是用這個比較好)
* @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
{

//如果是系統的異常,比如空指針這些異常
logger
.
error
(
"【系統異常】={}"
,
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
()));

}

/*
//備註:
//這個是正式項目完成之後的錯誤統一處理(開發情況先用上面的的)
//我們在開發過程中還是用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{
//如果是系統的異常,比如空指針這些異常
logger.error("【系統異常】={}",e);
return ResultUtil.getError(ResultEnum.SystemException.getCode(),ResultEnum.SystemException.getMsg());
}
}else{
//如果是系統內部發生異常,那麼就返回到錯誤頁面進行友好的提示
ModelAndView mav = new ModelAndView();

//這些就是要返回到頁面的內容(其實不用都行,反正用戶也不懂,沒必要在頁面顯示都可以,先寫著吧)
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
}
*/
}

定義異常處理頁面

分析:這個的話,其實主要是在正式環境才有,因為我們在測試環境的時候,一般都還是會將錯誤以JSON或者堆棧的格式顯示在頁面,而當上線的時候,那麼就一定要有一個統一的錯誤頁面,這樣就能夠讓用戶發現不了是系統出現了哪些問題。

這個的話,我將具體的“error”頁面放在了我的另外一篇博文裡面,歡迎大家進行閱讀。

https://blog.csdn.net/Cshnuscw/article/details/84640257

效果

1:開發環境

2:正式環境

分析:當出現異常的時候,則顯示如下的頁面。(該頁面是參考一個博友的,感覺挺有意思,,老司機~)

總結

異常統一處理,或許我們看起來實現非常簡單,然而,其他它包含的思想卻是一種大局思想,這是我們開發人員在開發過程中都應該關注的點,我們並不是只需要關注我們每個人開發的那點任務,而要以一種全局的角度去審視整個項目,這樣也能夠提升我們開問題的高度。

異常統一處理,是每個項目都存在的,只是可能實現的方式不一樣而已,或者顯示的效果不一樣而已,這些都不是關鍵的地方。

異常統一處理這個問題,並不是很難,但是這個可以幫助我們延伸到其他的一些相關的開發層面的知識,比如:

  • 登錄攔截
  • 權限管理
  • 日誌管理
  • 事務處理
  • 數據控制和過濾
  • 。。。

所以,我們應該學會從一個問題,發散的看到相關類似的問題,這樣,我們的系統才會更加健壯,高效和可擴展性強。

感謝你的閱讀

源碼的話,都已經在上面進行了貼出。更多幹貨內容,轉發+關注。私信“資料”即可獲取。


分享到:


相關文章: