關(guān)鍵詞: JAVA, Spring, SpringMVC
spring exception handler因使用簡(jiǎn)單怀吻,代碼簡(jiǎn)介,優(yōu)雅榛鼎,廣受開(kāi)發(fā)人員喜愛(ài)鲫惶。
使用出來(lái)的效果如下:
(圖是盜的,如何使用 不是本文討論的重點(diǎn)岗憋。)
此處就來(lái) 揭示下其工作原理肃晚。
ExceptionHandler 的運(yùn)行效果有 以下特點(diǎn)。
- 一次聲明仔戈,全接口生效关串。
- Exception的匹配 符合" 就近原則 "
現(xiàn)在我們就來(lái)依次探尋。
首先是 第一條监徘。
眾所周知的是晋修,springMVC 基于servelet ,Spring mvc 的 總?cè)肟?就是凰盔。 org.springframework.web.servlet.DispatcherServlet墓卦。
異常捕獲的偽代碼為
dispatchException=null;
try {
response = handler(request);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
spring 所有 映射接口 的 入口 從 DispatcherServlet 進(jìn)入,在執(zhí)行的時(shí)候 會(huì) 捕獲所有異常
需要注意的是户敬,僅捕獲 Exception落剪。 所有的Error 將被 NestedServletException 進(jìn)行包裝
包裝為 NestedServletException睁本。所以平時(shí)開(kāi)發(fā)的時(shí)候 應(yīng)當(dāng)注意處理 spring的 NestedServletException。
比較神奇的 是第二條忠怖。
Exception的匹配 符合" 就近原則 "呢堰。
平時(shí)我們開(kāi)發(fā)的時(shí)候,一個(gè)try凡泣,寫(xiě)連續(xù)的多個(gè) catch枉疼。catch處理塊的 編寫(xiě)順序應(yīng)當(dāng)是 先寫(xiě)子類(lèi)后寫(xiě)父類(lèi)。
用來(lái)讓錯(cuò)誤被正確的解析鞋拟。
平時(shí) catch 是否匹配的 邏輯 在于往衷,根據(jù)先后順序,依次向下匹配严卖,當(dāng)拋出的異常 為catch 聲明的 異常的 子類(lèi)或類(lèi)型相同,則認(rèn)為匹配 布轿。
spring 是怎么處理的哮笆?
遍地的 exception,以及 各種 exceptionHandler汰扭,是怎么做到 一處聲明 到處處理稠肘。還能遵從子類(lèi)優(yōu)先原則。
簡(jiǎn)單的想法是萝毛,將所有的 exception 串起來(lái)项阴,寫(xiě)一個(gè)長(zhǎng)長(zhǎng)的 catch。如
try{
//do
}catch(E1){
}
catch(E1){
}
catch(E2){
}
catch(E3){
}
catch(E4){
}
catch(E5){
}
那么問(wèn)題來(lái)了笆包,
1 這個(gè)順序 怎么定环揽。
2 我們必然需要將要處理的所有異常類(lèi)型放在 一個(gè) 集合中,遍歷后 catch庵佣。顯然 java 并沒(méi)有優(yōu)雅的提供這種循環(huán)catch的操作歉胶。而且比較傻。
我們的異常通常會(huì)進(jìn)行分類(lèi) EA EB巴粪。 過(guò)一段時(shí)間 又對(duì) EA 擴(kuò)展為 EA1 EA2 EA3 通今。對(duì)EB 也擴(kuò)展出了EB2.
EA 為 EA1 EA2 EA3 的父類(lèi)。
當(dāng)異常發(fā)生的 時(shí)候(如EA1)肛根,按照上述邏輯辫塌,一個(gè)異常 要 去 EA EA1 EA2 EA3 EB EB2等所有 catch塊都進(jìn)行一次匹配,尋找正確的處理器派哲。
實(shí)際上 EA1 的處理器臼氨,只可能 被 EA1 與 EA 的異常處理器 所解析。其他的遍歷 顯然是 不必要的狮辽。
同時(shí) 處理的 時(shí)候一也,應(yīng)當(dāng)必須 EA1的處理器被先匹配巢寡。
那應(yīng)當(dāng)怎樣操作?
最好的方式 當(dāng)然是椰苟,直接一個(gè) hashMap key為異常類(lèi)型抑月,value 是 異常的處理程序。
EA1發(fā)生時(shí)舆蝴,直接根據(jù)map找到 EA1的異常處理程序谦絮。
現(xiàn)實(shí)生活顯然不是這樣的 因?yàn)?EA1 可能沒(méi)有自己的專屬 異常處理程序。那么就要找到一個(gè)最近的 EA的異常處理程序洁仗。
這個(gè)算法具體出來(lái)就是這個(gè)核心算法层皱;
private final Map<Class<? extends Throwable>, Method> mappedMethods =
new ConcurrentHashMap<Class<? extends Throwable>, Method>(16);
private final Map<Class<? extends Throwable>, Method> exceptionLookupCache =
new ConcurrentHashMap<Class<? extends Throwable>, Method>(16);
/**
* Find a {@link Method} to handle the given exception type. This can be
* useful if an {@link Exception} instance is not available (e.g. for tools).
* @param exceptionType the exception type
* @return a Method to handle the exception, or {@code null} if none found
*/
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
Method method = this.exceptionLookupCache.get(exceptionType);
if (method == null) {
method = getMappedMethod(exceptionType);
this.exceptionLookupCache.put(exceptionType, (method != null ? method : NO_METHOD_FOUND));
}
return (method != NO_METHOD_FOUND ? method : null);
}
/**
* Return the {@link Method} mapped to the given exception type, or {@code null} if none.
*/
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>();
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
if (!matches.isEmpty()) {
Collections.sort(matches, new ExceptionDepthComparator(exceptionType));
return this.mappedMethods.get(matches.get(0));
}
else {
return null;
}
}
首先程序會(huì)在一開(kāi)始 加載的時(shí)候?qū)⑺械漠惓L幚矸椒⊕呙杼砑又? mappedMethods 中。所有的赠潦。
當(dāng)一個(gè)新的 需要處理的異常 來(lái)的 時(shí)候 叫胖。
首先從剛才的 mappedMethods 這個(gè) map 中 全部遍歷一遍 ,要求是 isAssignableFrom她奥。就是把能處理此異常的 異常處理程序篩選出來(lái)瓮增。
將篩選后的結(jié)果 放入list中,然后根據(jù)特定的排序器排序哩俭,取第一個(gè) 異常處理器绷跑。
得到后,將此次結(jié)果 放入exceptionLookupCache凡资。下一次 當(dāng)有一樣的異常 來(lái)了的 時(shí)候砸捏,就直接從這個(gè) map 中找到對(duì)應(yīng)的異常處理程序。
那么排序又是怎么實(shí)現(xiàn)的呢隙赁?想來(lái)垦藏,應(yīng)該是子類(lèi)優(yōu)先的機(jī)制。因?yàn)榍懊?的 list 都是 有父子關(guān)系的 異常鸳谜,所有理論上只要根據(jù)子類(lèi)優(yōu)先的排序方法膝藕,子類(lèi)在前,父類(lèi)在后咐扭,然后取第一個(gè)芭挽,自然是 “最近原則” 所匹配的異常處理程序。
實(shí)際代碼要相對(duì)復(fù)雜一些蝗肪,如下
@Override
public int compare(Class<? extends Throwable> o1, Class<? extends Throwable> o2) {
int depth1 = getDepth(o1, this.targetException, 0);
int depth2 = getDepth(o2, this.targetException, 0);
return (depth1 - depth2);
}
private int getDepth(Class<?> declaredException, Class<?> exceptionToMatch, int depth) {
if (exceptionToMatch.equals(declaredException)) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't found it...
if (exceptionToMatch == Throwable.class) {
return Integer.MAX_VALUE;
}
return getDepth(declaredException, exceptionToMatch.getSuperclass(), depth + 1);
}