springMVC之HandlerExceptionResolver使用
請(qǐng)求異常的處理
Handler查找以及執(zhí)行期間可能會(huì)出現(xiàn)異常,需要對(duì)其進(jìn)行處理,HandlerExceptionResolver就被設(shè)計(jì)出來(lái)了,
大致邏輯如下:
// 此段邏輯可以在dispatcherServlet中找到相似部分 ModelAndView mv = null; try{ ?? ?mv = hanlder.handle(); }catch(Exception e){ ?? ?mv = handlerExceptionResolver.handle(); }
springMVC也是這么設(shè)計(jì)的,當(dāng)然比這要復(fù)雜一點(diǎn),我們先來(lái)看一下HandlerExceptionResolver這個(gè)接口設(shè)計(jì)。
public interface HandlerExceptionResolver { ? ? // 處理異常,返回視圖信息 ?? ?@Nullable ?? ?ModelAndView resolveException( ?? ??? ??? ?HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex); }
可用的HandlerExceptionResolver
AbstractHandlerExceptionResolver這個(gè)抽象類的設(shè)計(jì)可以幫助我們針對(duì)不同的handler配置不同的HandlerExceptionResolver。
// AbstractHandlerExceptionResolver中解析異常的實(shí)現(xiàn) public ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { // 檢查這個(gè)解析器是否適用于這個(gè)處理器 if (shouldApplyTo(request, handler)) { // 添加響應(yīng)頭,阻止響應(yīng)緩存 prepareResponse(ex, response); // 解析異常 交給子類覆蓋實(shí)現(xiàn) ModelAndView result = doResolveException(request, response, handler, ex); // 日志記錄,這里我將代碼省略了 return result; }else { return null; } } protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) { if (handler != null) { // 可以通過(guò)設(shè)置mappedHandlers或mappedHandlerClasses來(lái)指定只為某個(gè)handler解析 if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) { return true; } if (this.mappedHandlerClasses != null) { for (Class<?> handlerClass : this.mappedHandlerClasses) { if (handlerClass.isInstance(handler)) { return true; } } } } // 這里就是判斷mappedHandlers或mappedHandlerClasses是否為空,都為空返回false // 意味著異常解析器可以適用于任何handler return !hasHandlerMappings(); }
接下來(lái)我們來(lái)看看不同子類對(duì)于doResolveException方法的實(shí)現(xiàn)。
1. SimpleMappingExceptionResolver
首先我們來(lái)看一下該類提供了哪些屬性供我們進(jìn)行設(shè)置。
?? ?// 1 配置的異常映射 key為異常名稱 value為視圖名稱 ?? ?private Properties exceptionMappings; ?? ?// 2 排除的異常類型數(shù)組 ?? ?private Class<?>[] excludedExceptions; ?? ?// 3 默認(rèn)的錯(cuò)誤視圖名稱 ?? ?private String defaultErrorView; ?? ?// 4 默認(rèn)的響應(yīng)狀態(tài)碼 ?? ?private Integer defaultStatusCode; ?? ?// 5 key為異常名,value為響應(yīng)狀態(tài)碼 ?? ?private Map<String, Integer> statusCodes = new HashMap<>();
protected ModelAndView doResolveException( ?? ??? ??? ?HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { ?? ??? ?// 確定視圖名稱。 ?? ??? ?String viewName = determineViewName(ex, request); ?? ??? ?if (viewName != null) { ?? ??? ??? ?// 根據(jù)視圖名稱去statusCodes中確定響應(yīng)狀態(tài)碼 ?? ??? ??? ?Integer statusCode = determineStatusCode(request, viewName); ?? ??? ??? ?if (statusCode != null) { ?? ??? ??? ??? ?applyStatusCodeIfPossible(request, response, statusCode); ?? ??? ??? ?} ?? ??? ??? ?return getModelAndView(viewName, ex, request); ?? ??? ?}else { ?? ??? ??? ?return null; ?? ??? ?} ?? ?}
protected String determineViewName(Exception ex, HttpServletRequest request) { ?? ??? ?String viewName = null; ?? ??? ?// 如果排除的異常數(shù)組中包含發(fā)生的異常,則返回null ?? ??? ?if (this.excludedExceptions != null) { ?? ??? ??? ?for (Class<?> excludedEx : this.excludedExceptions) { ?? ??? ??? ??? ?if (excludedEx.equals(ex.getClass())) { ?? ??? ??? ??? ??? ?return null; ?? ??? ??? ??? ?} ?? ??? ??? ?} ?? ??? ?} ?? ??? ?// 檢查特定的異常映射。 ?? ??? ?if (this.exceptionMappings != null) { ?? ??? ??? ?viewName = findMatchingViewName(this.exceptionMappings, ex); ?? ??? ?} ?? ??? ?// 定義了默認(rèn)錯(cuò)誤視圖 ?? ??? ?if (viewName == null && this.defaultErrorView != null) { ?? ??? ??? ?viewName = this.defaultErrorView; ?? ??? ?} ?? ??? ?return viewName; ?? ?}
protected String findMatchingViewName(Properties exceptionMappings, Exception ex) { ?? ??? ?String viewName = null; ?? ??? ?String dominantMapping = null; ?? ??? ?int deepest = Integer.MAX_VALUE; ?? ??? ?for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements();) { ?? ??? ??? ?String exceptionMapping = (String) names.nextElement(); ?? ??? ??? ?// depth =0 表示剛好找到;depth =-1 表示沒(méi)找到,這是一個(gè)遞歸方法, ?? ??? ??? ?// 會(huì)一直沿著異常的繼承結(jié)構(gòu)向上找,每向上一層,depth+1 ?? ??? ??? ?int depth = getDepth(exceptionMapping, ex); ?? ??? ??? ?if (depth >= 0 && (depth < deepest || (depth == deepest && ?? ??? ??? ??? ??? ?dominantMapping != null && exceptionMapping.length() > dominantMapping.length()))) { ?? ??? ??? ??? ?// 這里將深度賦值了,意味著一旦有匹配的異常結(jié)果時(shí),即使下一次更匹配,但是 ?? ??? ??? ??? ?// depth < deepest這個(gè)條件也無(wú)法滿足 ?? ??? ??? ??? ?deepest = depth; ?? ??? ??? ??? ?dominantMapping = exceptionMapping; ?? ??? ??? ??? ?viewName = exceptionMappings.getProperty(exceptionMapping); ?? ??? ??? ?} ?? ??? ?} ?? ??? ?return viewName; ?? ?}
?? ?private int getDepth(String exceptionMapping, Class<?> exceptionClass, int depth) { ?? ??? ?// 需要注意,這里使用的是contains,如果異常名稱不適用全限定名 ?? ??? ?// 一旦出現(xiàn)多個(gè)異常映射項(xiàng)匹配的情況,將直接選擇第一個(gè)匹配的結(jié)果 ?? ??? ?if (exceptionClass.getName().contains(exceptionMapping)) { ?? ??? ??? ?return depth; ?? ??? ?} ?? ??? ?if (exceptionClass == Throwable.class) { ?? ??? ??? ?return -1; ?? ??? ?} ?? ??? ?return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1); ?? ?}
demo
/** ?* 通過(guò)SimpleMappingExceptionResolver做全局異常處理 ?*/ @Configuration public class ExceptionConfig { ?? ?@Bean ?? ?public SimpleMappingExceptionResolver simpleMappingExceptionResolver() { ?? ??? ?SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();?? ??? ? ?? ??? ?Properties exceptionMappings= new Properties();?? ??? ? ?? ??? ?/** ?? ??? ? * 這里是不建議的使用方式,如果出現(xiàn)異常java.lang.ArithmeticException,最終得到的視圖卻為 ?? ??? ? * error,這是我們不希望的,所以請(qǐng)使用全限定名 ?? ??? ? */ ?? ??? ?mappers.put("Exception", "error"); ?? ??? ?mappers.put("ArithmeticException", "error1"); ?? ??? ?mappers.put("java.lang.ArithmeticException", "error2"); ?? ??? ?resolver.setExceptionMappings(exceptionMappings); ?? ??? ?resolver.setDefaultErrorView("error3"); ?? ??? ?return resolver; ?? ?} }
2. DefaultHandlerExceptionResolver
(解決標(biāo)準(zhǔn)的Spring MVC異常并將其轉(zhuǎn)換為相應(yīng)的HTTP狀態(tài)碼),使用時(shí)我們不用設(shè)置order,默認(rèn)的最小的優(yōu)先級(jí)。
protected ModelAndView doResolveException( ?? ??? ??? ?HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { ?? ??? ?try { ?? ??? ??? ?if (ex instanceof HttpRequestMethodNotSupportedException) { ?? ??? ??? ??? ?return handleHttpRequestMethodNotSupported( ?? ??? ??? ??? ??? ??? ?(HttpRequestMethodNotSupportedException) ex, request, response, handler); ?? ??? ??? ?} ?? ??? ??? ?// 這里有很多if語(yǔ)句,就是針對(duì)標(biāo)準(zhǔn)的Spring MVC異常,返回對(duì)應(yīng)的狀態(tài)碼進(jìn)行處理 ?? ??? ?}catch (Exception handlerEx) { ?? ??? ??? ?// 日志打印... ?? ??? ?} ?? ??? ?return null; ?? ?}
3. ResponseStatusExceptionResolver
protected ModelAndView doResolveException( ?? ??? ??? ?HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { ?? ??? ?try { ?? ??? ??? ?if (ex instanceof ResponseStatusException) { ?? ??? ??? ??? ?return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler); ?? ??? ??? ?} ?? ??? ??? ?// 找到異常上使用的ResponseStatus注解 ?? ??? ??? ?ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class); ?? ??? ??? ?if (status != null) { ?? ??? ??? ??? ?return resolveResponseStatus(status, request, response, handler, ex); ?? ??? ??? ?} ?? ??? ??? ?// ResponseStatus為空,異常鏈仍未結(jié)束,遞歸調(diào)用 ?? ??? ??? ?if (ex.getCause() instanceof Exception) { ?? ??? ??? ??? ?return doResolveException(request, response, handler, (Exception) ex.getCause()); ?? ??? ??? ?} ?? ??? ?}catch (Exception resolveEx) { ?? ??? ??? ?... ?? ??? ?} ?? ??? ?return null; ?? ?}
ResponseStatus注解使用方式挺多的,這里是其中一種,就是在自定義異常的類上添加此注解。還有其他用法大家可以去看看這篇文章,@ResponseStatus注解的更多用法。
4. ExceptionHandlerExceptionResolver
在看這個(gè)類解析異常的方法之前,我們先認(rèn)識(shí)一下兩個(gè)緩存,分別為局部和全局異常方法解析器映射緩存,源碼中也就是這兩個(gè)變量。
?? ?// 局部異常方法解析器緩存,下面統(tǒng)稱為局部緩存 ?? ?private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = ?? ??? ??? ?new ConcurrentHashMap<>(64); ?? ?// 全局異常方法解析器緩存,下面統(tǒng)稱為全局緩存 ?? ?private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = ?? ??? ??? ?new LinkedHashMap<>();
全局緩存在bean初始化的時(shí)候就會(huì)加載,將會(huì)把容器中含有注解ControllerAdvice的bean收集起來(lái)。
?? ?// bean 初始化的時(shí)候會(huì)加載這個(gè)方法 ?? ?public void afterPropertiesSet() { ?? ??? ?// 初始化全局緩存 ?? ??? ?initExceptionHandlerAdviceCache(); ?? ??? ?if (this.argumentResolvers == null) { ?? ??? ??? ?// 設(shè)置請(qǐng)求參數(shù)解析器 ?? ??? ?} ?? ??? ?if (this.returnValueHandlers == null) { ?? ??? ??? ?// 設(shè)置返回值解析器 ?? ??? ?} ?? ?} ?? ?private void initExceptionHandlerAdviceCache() { ?? ??? ?if (getApplicationContext() == null) { ?? ??? ??? ?return; ?? ??? ?} ?? ??? ?// 獲取含有注解ControllerAdvice的bean ?? ??? ?List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); ?? ??? ?for (ControllerAdviceBean adviceBean : adviceBeans) { ?? ??? ??? ?Class<?> beanType = adviceBean.getBeanType(); ?? ??? ??? ?if (beanType == null) { ?? ??? ??? ??? ?throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean); ?? ??? ??? ?} ?? ??? ??? ?ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); ?? ??? ??? ?if (resolver.hasExceptionMappings()) { ?? ??? ??? ??? ?// 加入全局緩存 ?? ??? ??? ??? ?this.exceptionHandlerAdviceCache.put(adviceBean, resolver); ?? ??? ??? ?} ?? ??? ??? ?// MARK:如果該類實(shí)現(xiàn)了ResponseBodyAdvice接口 ?? ??? ??? ?if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) { ?? ??? ??? ??? ?this.responseBodyAdvice.add(adviceBean); ?? ??? ??? ?} ?? ??? ?} ?? ??? ?// ... ?? ?}
有關(guān)MARK部分ResponseBodyAdvice接口的用處,這里不展開(kāi)了。
而局部緩存是在解析異常的方法中動(dòng)態(tài)加載的。
?? ?protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, ?? ??? ??? ?HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) { ?? ??? ?// 根據(jù)異常類型,處理器找到處理異常的方法 ?? ??? ?ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); ?? ??? ?// 異常方法調(diào)用... ?? ??? ?// 返回ModelAndView... ?? ?}
protected ServletInvocableHandlerMethod getExceptionHandlerMethod( ?? ??? ??? ?@Nullable HandlerMethod handlerMethod, Exception exception) { ?? ?if (handlerMethod != null) { ?? ??? ??? ?handlerType = handlerMethod.getBeanType(); ?? ??? ??? ?// 從局部緩存中查找,找不到,則構(gòu)建一個(gè) ?? ??? ??? ?ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType); ?? ??? ??? ?if (resolver == null) { ?? ??? ??? ??? ?resolver = new ExceptionHandlerMethodResolver(handlerType); ?? ??? ??? ??? ?this.exceptionHandlerCache.put(handlerType, resolver); ?? ??? ??? ?} ?? ??? ??? ?// 解析異常獲取對(duì)應(yīng)方法 ?? ??? ??? ?Method method = resolver.resolveMethod(exception); ?? ??? ??? ?if (method != null) { ?? ??? ??? ??? ?return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method); ?? ??? ??? ?} ?? ??? ??? ?// 如果是代理類,則要獲取到原目標(biāo)類型 ?? ??? ??? ?if (Proxy.isProxyClass(handlerType)) { ?? ??? ??? ??? ?handlerType = AopUtils.getTargetClass(handlerMethod.getBean()); ?? ??? ??? ?} ?? ??? ?} ?? ??? ?// 局部緩存沒(méi)有找到,則去全局緩存中找 ?? ??? ?for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) { ?? ??? ??? ?ControllerAdviceBean advice = entry.getKey(); ?? ??? ??? ?// MARK:檢查是否應(yīng)該通過(guò)給定的bean類型? ?? ??? ??? ?if (advice.isApplicableToBeanType(handlerType)) { ?? ??? ??? ??? ?ExceptionHandlerMethodResolver resolver = entry.getValue(); ?? ??? ??? ??? ?Method method = resolver.resolveMethod(exception); ?? ??? ??? ??? ?if (method != null) { ?? ??? ??? ??? ??? ?return new ServletInvocableHandlerMethod(advice.resolveBean(), method); ?? ??? ??? ??? ?} ?? ??? ??? ?} ?? ??? ?} ?? ?return null; }?? ??? ?
上述代碼MARK部分涉及到另一知識(shí)點(diǎn),有關(guān)注解ControllerAdvice的設(shè)置部分,這里就不展開(kāi)了。
同前面一樣,在看解析異常獲取對(duì)應(yīng)方法前,我先介紹另一個(gè)緩存-異常映射方法緩存。這個(gè)緩存在ExceptionHandlerMethodResolver實(shí)例化的時(shí)候被加載。
?? ?// 用于選擇@ExceptionHandler方法的過(guò)濾器。 ?? ?public static final MethodFilter EXCEPTION_HANDLER_METHODS = method -> ?? ??? ??? ?AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class); ?? ?// 異常映射方法緩存 key為異常類型 value為方法 ?? ??? ? ?? ?private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16); ?? ? ?? ?public ExceptionHandlerMethodResolver(Class<?> handlerType) { ?? ??? ?for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) { ?? ??? ??? ?// 檢測(cè)方法映射的異常 ?? ??? ??? ?for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) { ?? ??? ??? ??? ?// 加入緩存 ?? ??? ??? ??? ?addExceptionMapping(exceptionType, method); ?? ??? ??? ?} ?? ??? ?} ?? ?} ?? ? ?? ?private List<Class<? extends Throwable>> detectExceptionMappings(Method method) { ?? ??? ?List<Class<? extends Throwable>> result = new ArrayList<>(); ?? ??? ?// 獲取注解ExceptionHandler的value屬性 ?? ??? ?detectAnnotationExceptionMappings(method, result); ?? ??? ?// 如果注解value沒(méi)有設(shè)置 ?? ??? ?if (result.isEmpty()) { ?? ??? ??? ?for (Class<?> paramType : method.getParameterTypes()) { ?? ??? ??? ??? ?// 方法參數(shù)中需要設(shè)置異常字段 ?? ??? ??? ??? ?if (Throwable.class.isAssignableFrom(paramType)) { ?? ??? ??? ??? ??? ?result.add((Class<? extends Throwable>) paramType); ?? ??? ??? ??? ?} ?? ??? ??? ?} ?? ??? ?} ?? ??? ?if (result.isEmpty()) { ?? ??? ??? ?throw new IllegalStateException("No exception types mapped to " + method); ?? ??? ?} ?? ??? ?return result; ?? ?}
了解這個(gè)緩存后,再回過(guò)頭來(lái)看解析異常獲取對(duì)應(yīng)方法,其實(shí)就是從緩存中找而已。
?? ?private Method getMappedMethod(Class<? extends Throwable> exceptionType) { ?? ??? ?List<Class<? extends Throwable>> matches = new ArrayList<>(); ?? ??? ?for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) { ?? ??? ??? ?// 只要出現(xiàn)的異常是指定異常的子類,就算作匹配 ?? ??? ??? ?if (mappedException.isAssignableFrom(exceptionType)) { ?? ??? ??? ??? ?matches.add(mappedException); ?? ??? ??? ?} ?? ??? ?} ?? ??? ?if (!matches.isEmpty()) { ?? ??? ??? ?// 有可能出現(xiàn)同一個(gè)異常匹配到多個(gè)映射的情況,這里按異常層級(jí)關(guān)系,從小大大排序 ?? ??? ??? ?matches.sort(new ExceptionDepthComparator(exceptionType)); ?? ??? ??? ?// 取最小層級(jí)的 ?? ??? ??? ?return this.mappedMethods.get(matches.get(0)); ?? ??? ?}else { ?? ??? ??? ?return null; ?? ??? ?} ?? ?}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java swagger ui 添加header請(qǐng)求頭參數(shù)的方法
今天小編就為大家分享一篇java swagger ui 添加header請(qǐng)求頭參數(shù)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-08-08SpringBoot整合Mongodb實(shí)現(xiàn)增刪查改的方法
這篇文章主要介紹了SpringBoot整合Mongodb實(shí)現(xiàn)簡(jiǎn)單的增刪查改,MongoDB是一個(gè)以分布式數(shù)據(jù)庫(kù)為核心的數(shù)據(jù)庫(kù),因此高可用性、橫向擴(kuò)展和地理分布是內(nèi)置的,并且易于使用。況且,MongoDB是免費(fèi)的,開(kāi)源的,感興趣的朋友跟隨小編一起看看吧2022-05-05Java創(chuàng)建可執(zhí)行的Jar文件的方法實(shí)踐
創(chuàng)建的可執(zhí)行Jar文件實(shí)際就是在原始Jar的清單文件中添加了Main-Class的配置,本文主要介紹了Java創(chuàng)建可執(zhí)行的Jar文件的方法實(shí)踐,感興趣的可以了解一下2023-12-12Java使用icepdf將pdf文件按頁(yè)轉(zhuǎn)成圖片
這篇文章主要為大家詳細(xì)介紹了Java使用icepdf將pdf文件按頁(yè)轉(zhuǎn)成圖片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12RabbitMQ的ACK確認(rèn)機(jī)制保障消費(fèi)端消息的可靠性詳解
這篇文章主要介紹了RabbitMQ的ACK確認(rèn)機(jī)制保障消費(fèi)端消息的可靠性詳解,簡(jiǎn)單來(lái)說(shuō),就是你必須關(guān)閉 RabbitMQ 的自動(dòng)ack ,可以通過(guò)一個(gè) api 來(lái)調(diào)用就行,然后每次你自己代碼里確保處理完的時(shí)候,再在程序里 ack 一把,需要的朋友可以參考下2023-12-12關(guān)于Redis鍵值出現(xiàn)\xac\xed\x00\x05t\x00&錯(cuò)誤的解決方法
這篇文章主要介紹了關(guān)于Redis鍵值出現(xiàn)\xac\xed\x00\x05t\x00&的解決方法,出現(xiàn)該問(wèn)題的原因是, redis template向redis存放使用java對(duì)象序列化的值,序列化方式和string的一般方式不同,需要的朋友可以參考下2023-08-08談?wù)凧ava利用原始HttpURLConnection發(fā)送POST數(shù)據(jù)
這篇文章主要給大家介紹java利用原始httpUrlConnection發(fā)送post數(shù)據(jù),設(shè)計(jì)到httpUrlConnection類的相關(guān)知識(shí),感興趣的朋友跟著小編一起學(xué)習(xí)吧2015-10-10