在Java中,構(gòu)造異常對(duì)象是"十分"耗時(shí)的,其原因是在默認(rèn)情況下,創(chuàng)建異常對(duì)象時(shí)會(huì)調(diào)用父類Throwable
的fillInStackTrace()
方法生成棧追蹤信息杉辙,JDK中的源碼如下:
public synchronized Throwable fillInStackTrace() {
if (stackTrace != null ||
backtrace != null /* Out of protocol state */ ) {
fillInStackTrace(0); // native方法
stackTrace = UNASSIGNED_STACK;
}
return this;
}
在我自己做的測(cè)試中,new
一個(gè)帶有棧追蹤信息的Exception
對(duì)象要比創(chuàng)建不帶追蹤信息的對(duì)象慢50倍以上捶朵。雖然打印調(diào)用棧能夠精確定位到錯(cuò)誤發(fā)生的代碼所在行蜘矢,但我們可以考慮一下,真的有必要讓所有異常都生成這些信息嗎综看?
我們?cè)陂_發(fā)業(yè)務(wù)系統(tǒng)的過程中一般都會(huì)使用異常機(jī)制來實(shí)現(xiàn)錯(cuò)誤處理邏輯品腹,這些異常通常都可以分成兩大類:
業(yè)務(wù)異常
這些是我們自定義的、可以預(yù)知的異常红碑,拋出這種異常并不表示系統(tǒng)出了問題舞吭,而是正常業(yè)務(wù)邏輯上的需要,例如用戶名密碼錯(cuò)誤析珊、參數(shù)錯(cuò)誤等羡鸥。系統(tǒng)異常
往往是運(yùn)行時(shí)異常,比如數(shù)據(jù)庫連接失敗忠寻、IO失敗惧浴、空指針等,這種異常的產(chǎn)生多數(shù)表示系統(tǒng)存在問題奕剃,需要人工排查定位衷旅。
其實(shí)對(duì)于業(yè)務(wù)異常哑姚,我們只需要簡(jiǎn)單的知道一個(gè)描述問題的字符串即可,棧追蹤信息對(duì)我們的意義并不大芜茵。而對(duì)于系統(tǒng)異常,追蹤信息才是排查錯(cuò)誤不可或缺的參考倡蝙。因此我們可以想辦法控制一下九串,創(chuàng)建業(yè)務(wù)異常時(shí)不生成調(diào)用棧追蹤信息以降低開銷,系統(tǒng)異常則正常生成寺鸥。
其實(shí)方法非常簡(jiǎn)單猪钮,在我們自定義異常時(shí),只需要重寫父類的一個(gè)帶有4個(gè)參數(shù)的構(gòu)造方法即可胆建,此方法在Exception
和RuntimeException
類中都存在:
protected RuntimeException(String message, Throwable cause,
boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
這幾個(gè)參數(shù)的意義如下:
- message
異常的描述信息烤低,也就是在打印棧追蹤信息時(shí)異常類名后面緊跟著的描述字符串 - cause
導(dǎo)致此異常發(fā)生的父異常,即追蹤信息里的caused by
- enableSuppress
關(guān)于異常掛起的參數(shù)笆载,這里我們永遠(yuǎn)設(shè)為false
即可 - writableStackTrace
表示是否生成棧追蹤信息扑馁,只要將此參數(shù)設(shè)為false
, 則在構(gòu)造異常對(duì)象時(shí)就不會(huì)調(diào)用fillInStackTrace()
例如,業(yè)務(wù)異沉棺ぃ可以這樣定義:
public class XXXException extends RuntimeException {
/**
* 僅包含message, 沒有cause, 也不記錄棧異常, 性能最高
* @param msg
*/
public XXXException(String msg) {
this(msg, false);
}
/**
* 包含message, 可指定是否記錄異常
* @param msg
* @param recordStackTrace
*/
public EngineException(String msg, boolean recordStackTrace) {
super(msg, null, false, recordStackTrace);
}
/**
* 包含message和cause, 會(huì)記錄棧異常
* @param msg
* @param cause
*/
public EngineException(String msg, Throwable cause) {
super(msg, cause, false, true);
}
}
即通過使用父類中4參數(shù)的構(gòu)造方法精確控制異常類的行為腻要。當(dāng)我們想要?jiǎng)?chuàng)建"輕量級(jí)"異常時(shí),使用第一個(gè)構(gòu)造方法即可涝登;如果我們想將系統(tǒng)級(jí)異常封裝成一下雄家,并希望在日志中打印棧追蹤時(shí),就使用第三個(gè)構(gòu)造方法胀滚。
PS: 只有在高并發(fā)系統(tǒng)中做上述優(yōu)化才會(huì)有明顯效果趟济。如果拋異常不頻繁的話也不會(huì)有明顯效果,因?yàn)榧幢闶锹?0倍咽笼,實(shí)際也是納秒級(jí)的區(qū)別顷编,對(duì)一個(gè)請(qǐng)求處理來說微不足道。