詳解SpringMVC中的異常處理機(jī)制
開(kāi)頭
試想一下我們一般怎么統(tǒng)一處理異常呢,答:切面。但拋開(kāi)切面不講,如果對(duì)每一個(gè)controller方法拋出的異常做專門(mén)處理,那么著實(shí)太費(fèi)勁了,有沒(méi)有更好的方法呢?當(dāng)然有,就是本篇文章接下來(lái)要介紹的springmvc的異常處理機(jī)制,用到了ControllerAdvice和ExceptionHandler注解,有點(diǎn)切面的感覺(jué)哈哈。
1.ExceptionHandlerExceptionResolver
首先從springmvc的異常處理解析器開(kāi)始講,當(dāng)執(zhí)行完controller方法后,不管有沒(méi)有異常產(chǎn)生都會(huì)調(diào)用DispatcherServlet#doDispatch()方法中的processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 方法,接著會(huì)判斷是否有異常,若無(wú)異常則走正常流程,若有異常則需要進(jìn)行處理 mv = processHandlerException(request, response, handler, exception); 再接著就是遍歷spring已經(jīng)注冊(cè)的異常處理解析器直到有處理器返回mav
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// 執(zhí)行處理器產(chǎn)生的異常處理
mv = processHandlerException(request, response, handler, exception);
// 是否有異常視圖返回
errorView = (mv != null);
}
}
// Did the handler return a view to render? 處理程序是否返回要渲染的視圖
if (mv != null && !mv.wasCleared()) {
// 渲染視圖
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
} @Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
// 無(wú)視圖view
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}其中最重要也是最常使用的一個(gè)處理器就是ExceptionHandlerExceptionResolver,下面將著重介紹它,先來(lái)看看這個(gè)類的繼承結(jié)構(gòu)圖,實(shí)現(xiàn)了InitializingBean接口,在這個(gè)bean創(chuàng)建完成之前會(huì)調(diào)用生命周期初始化方法afterPropertiesSet(),這里面包含了對(duì)@ControllerAdvice注解的解析,初始化完后的信息供后續(xù)解析異常使用。

實(shí)現(xiàn)HandlerExceptionResolver接口,實(shí)現(xiàn)解析方法resolveException()
public interface HandlerExceptionResolver {
/**
* Try to resolve the given exception that got thrown during handler execution,
* returning a {@link ModelAndView} that represents a specific error page if appropriate.
* <p>The returned {@code ModelAndView} may be {@linkplain ModelAndView#isEmpty() empty}
* to indicate that the exception has been resolved successfully but that no view
* should be rendered, for instance by setting a status code.
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or {@code null} if none chosen at the
* time of the exception (for example, if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding {@code ModelAndView} to forward to,
* or {@code null} for default processing in the resolution chain
*/
@Nullable
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBodyAdvice beans
// 初始化異常注解 @ControllerAdvice
initExceptionHandlerAdviceCache();
}
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Looking for exception mappings: " + getApplicationContext());
}
// 解析有@ControllerAdvice注解的bean,并將這個(gè)bean構(gòu)建成ControllerAdviceBean對(duì)象
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
// 將ControllerAdviceBean根據(jù)order排序
AnnotationAwareOrderComparator.sort(adviceBeans);
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
// mappedMethods 映射不為空
if (resolver.hasExceptionMappings()) {
// 添加到緩存中
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
if (logger.isInfoEnabled()) {
logger.info("Detected @ExceptionHandler methods in " + adviceBean);
}
}
// 若實(shí)現(xiàn)了ResponseBodyAdvice接口(暫不介紹)
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
if (logger.isInfoEnabled()) {
logger.info("Detected ResponseBodyAdvice implementation in " + adviceBean);
}
}
}
} ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); 這行代碼會(huì)解析擁有@ControllerAdvice 注解的class,并且會(huì)遍歷class中帶有 @ExceptionHandler注解的方法,獲取方法注解帶有的異常類型,將異常類型和方法放入到mappedMethods中供后面獲取,獲取的時(shí)候若對(duì)應(yīng)處理此異常類型的method有多個(gè),則需要進(jìn)行排序,選取一個(gè)異常類型與method ExceptionHandler注解異常類型最近的一個(gè)(深度最小的那個(gè)也即是繼承關(guān)系最少的那個(gè))具體代碼如下:
ExceptionHandlerMethodResolver
@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
// exception為controller方法拋出的異常
// 根據(jù)異常及其類型從上述的mappedMethods中獲取對(duì)應(yīng)的方法,再獲取方法所在的對(duì)象 封裝成ServletInvocableHandlerMethod
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}
// 設(shè)置參數(shù)解析器,主要用來(lái)獲取方法的參數(shù)值的,供后續(xù)反射調(diào)用方法
if (this.argumentResolvers != null) {
exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
// 設(shè)置返回值解析器,當(dāng)執(zhí)行完方法后獲取返回值,對(duì)返回值進(jìn)行處理 或返回視圖或?qū)⒔Y(jié)果寫(xiě)入到response
if (this.returnValueHandlers != null) {
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
try {
if (logger.isDebugEnabled()) {
logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod);
}
Throwable cause = exception.getCause();
if (cause != null) {
// Expose cause as provided argument as well
// 執(zhí)行異常處理方法,也就是我們的自定義的異常處理方法
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
}
else {
// Otherwise, just the given exception as-is
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
}
}
catch (Throwable invocationEx) {
// Any other than the original exception is unintended here,
// probably an accident (e.g. failed assertion or the like).
if (invocationEx != exception && logger.isWarnEnabled()) {
logger.warn("Failed to invoke @ExceptionHandler method: " + exceptionHandlerMethod, invocationEx);
}
// Continue with default processing of the original exception...
return null;
}
// 根據(jù)后續(xù)的返回值解析器設(shè)置的,將返回值寫(xiě)入到response中了直接返回空的mav
if (mavContainer.isRequestHandled()) {
return new ModelAndView();
}
else {
ModelMap model = mavContainer.getModel();
HttpStatus status = mavContainer.getStatus();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
mav.setViewName(mavContainer.getViewName());
// (this.view instanceof String)
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}
}exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod); 此方法執(zhí)行完成后已經(jīng)完成了異常處理方法的調(diào)用,若方法返回值為視圖ModelAndView或其他視圖類型,則還需要借助視圖解析器如InternalResourceViewResolver對(duì)視圖進(jìn)行解析渲染,若為其他類型的值則將值寫(xiě)入到response響應(yīng)中。
2. demo
Controller類方法:
@Controller
@RequestMapping(value = "test")
public class HelloWorldController{
@Data
public static class User {
private String username;
private Integer age;
private String address;
}
@RequestMapping(value = "user/get", method = RequestMethod.POST)
@ResponseBody
public Object testObject(@RequestBody @Valid User user, @RequestParam String address) {
user.setAddress(address);
// 這里特意拋出RuntimeException異常
throw new RuntimeException("this is a exception");
}
}ExceptionHandlerController異常處理類
@ControllerAdvice
@ResponseBody
public class ExceptionHandlerController {
@ExceptionHandler(value = Exception.class)
public Object handleException(Exception e) {
return CommonResult.fail("Exception:" + e.getMessage());
}
@ExceptionHandler(value = RuntimeException.class)
public Object handlerRuntimeException(Exception e) {
return CommonResult.fail("handlerRuntimeException:" + e.getMessage());
}
}ExceptionHandlerController類中定義了兩個(gè)異常處理方法,一個(gè)處理Exception異常,一個(gè)處理RuntimeException異常,那個(gè)根據(jù)controller方法拋出的異常RuntimeException再結(jié)合上面的分析(RuntimeException到RuntimeException深度為0,RuntimeException到Exception中間繼承了一次深度為1)可以得出拋出異常類型的處理方法為handlerRuntimeException 方法。 運(yùn)行程序結(jié)果如下:

到此這篇關(guān)于詳解SpringMVC中的異常處理機(jī)制的文章就介紹到這了,更多相關(guān)SpringMVC異常處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決Spring boot 嵌入的tomcat不啟動(dòng)問(wèn)題
這篇文章主要介紹了解決Spring boot 嵌入的tomcat不啟動(dòng)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10
Spring MVC的項(xiàng)目準(zhǔn)備和連接建立方法
SpringWebMVC是基于Servlet API的Web框架,屬于Spring框架的一部分,主要用于簡(jiǎn)化Web應(yīng)用程序的開(kāi)發(fā),SpringMVC通過(guò)控制器接收請(qǐng)求,使用模型處理數(shù)據(jù),并通過(guò)視圖展示結(jié)果,感興趣的朋友跟隨小編一起看看吧2024-10-10
Java編程通過(guò)匹配合并數(shù)據(jù)實(shí)例解析(數(shù)據(jù)預(yù)處理)
這篇文章主要介紹了Java編程通過(guò)匹配合并數(shù)據(jù)實(shí)例解析(數(shù)據(jù)預(yù)處理),分享了相關(guān)代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01
基于Java手寫(xiě)一個(gè)好用的FTP操作工具類
網(wǎng)上百度了很多FTP的java?工具類,發(fā)現(xiàn)文章代碼都比較久遠(yuǎn),且代碼臃腫,即使搜到了代碼寫(xiě)的還可以的,封裝的常用操作方法不全面。所以本文將手寫(xiě)一個(gè)好用的Java?FTP操作工具類,需要的可以參考一下2022-04-04
java環(huán)境配好后jar文件打開(kāi)命令框閃退(無(wú)打開(kāi)方式,無(wú)反應(yīng))解決辦法
在Java開(kāi)發(fā)中我們經(jīng)常會(huì)遇到運(yùn)行Jar包時(shí)閃退的問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于java環(huán)境配好后jar文件打開(kāi)命令框閃退(無(wú)打開(kāi)方式,無(wú)反應(yīng))的解決辦法,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04
Java正則表達(dá)式詳解及實(shí)用案例(附詳細(xì)代碼)
正則表達(dá)式是一種強(qiáng)大的字符串處理工具,用于匹配和解析文本,這篇文章主要給大家介紹了關(guān)于Java正則表達(dá)式詳解及實(shí)用案例的相關(guān)資料,本文通過(guò)代碼示例講解了正則表達(dá)式的基本語(yǔ)法規(guī)則,包括字符類、預(yù)定義字符類和數(shù)量詞,需要的朋友可以參考下2024-11-11
使用Springboot注入帶參數(shù)的構(gòu)造函數(shù)實(shí)例
這篇文章主要介紹了使用Springboot注入帶參數(shù)的構(gòu)造函數(shù)實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-04-04
Java將GeoHash轉(zhuǎn)化為對(duì)應(yīng)的經(jīng)緯度坐標(biāo)實(shí)例代碼
這篇文章主要介紹了Java實(shí)現(xiàn)將GeoHash轉(zhuǎn)化為對(duì)應(yīng)的經(jīng)緯度坐標(biāo)的相關(guān)資料,需要的朋友可以參考下2016-01-01
Java使用設(shè)計(jì)模式中的代理模式構(gòu)建項(xiàng)目的實(shí)例展示
這篇文章主要介紹了Java使用設(shè)計(jì)模式中的代理模式構(gòu)建項(xiàng)目的實(shí)例展示,代理模式中的代理對(duì)象可以在客戶端和目標(biāo)對(duì)象之間起到中介的作用,需要的朋友可以參考下2016-05-05
JAVA中while循環(huán)的使用與注意事項(xiàng)
這篇文章主要介紹了while循環(huán)在編程中的應(yīng)用,包括其基本結(jié)構(gòu)、語(yǔ)句示例、適用場(chǎng)景以及注意事項(xiàng),文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-01-01

