|
1、原理springmvc 通过HandlerExceptionResolver 接口来进行异常处理。任何实现该接口的类通过配置即可进行异常处理。
HandlerExceptionResolver的resolveException方法用来处理异常。接口如下:
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
使用使用方法包括@ExceptionHandler和自己实现HandlerExceptionResolver接口。这里不做详细说明,请自行查阅资料。
那么这2中方法有什么区别?
自己实现HandlerExceptionResolver是自己处理异常(废话)。
@ExceptionHandler使用spring自带的类ExceptionHandlerExceptionResolver来处理异常。(具体如何处理后面详细说)spring mvc 异常处理类介绍到这里,我们先来暂停一下,先来了解spring mvc自身的异常处理类,这样才能继续深入。
1. HandlerExceptionResolver接口
SpringMVC异常处理核心接口。最开始所说的基础类,所有异常处理类都要实现该类。
2. AbstractHandlerExceptionResolver抽象类
实现HandlerExceptionResolver接口的抽象类。
其中属性order默认为最低优先级(int最大值),order代表整个exception handler链处理顺序,值越小,处理顺序越靠前。
private int order = Ordered.LOWEST_PRECEDENCE;
**//异常处理方法**
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
// shouldApplyTo方法是用来判断当前exceptionHandler能否处理handler(handler是抛出异常的类或者方法)
if (shouldApplyTo(request, handler)) {
// Log exception, both at debug log level and at warn level, if desired.
if (logger.isDebugEnabled()) {
logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
}
logException(ex, request);
prepareResponse(ex, response);
** //最终调用doResolveException抽象方法,需要子类实现**
return doResolveException(request, response, handler, ex);
}
else {
return null;
}
} |
3. AbstractHandlerMethodExceptionResolver抽象类
继承AbstractHandlerExceptionResolver抽象类的抽象类。 该类主要就是为HandlerMethod类服务,既handler参数是HandlerMethod类型。
该类重写了shouldApplyTo方法:主要目的是将handlerMethod转换为handler
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
if (handler == null) {
return super.shouldApplyTo(request, handler);
}
else if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
handler = handlerMethod.getBean();
return super.shouldApplyTo(request, handler);
}
else {
return false;
}
} |
4. ExceptionHandlerExceptionResolver类
继承自AbstractHandlerMethodExceptionResolver,该类主要处理Controller中用@ExceptionHandler注解定义的方法。这个类也是上面所说的2中实现方式之一的处理类。
但是我不想过于深入源码,我觉得这样会导致文章过长反而不利于轻松的理解更加重要的东西,只做一些简单介绍。
我希望未来自己忘记整个内容的时候,能在几分钟内对整个exception的处理有个整体理解,并对一些重要的注意事项进行记录。
无论如何还是要看一下最重要的方法doResolveHandlerMethodException:
@Override
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {
//*** getExceptionHandlerMethod方法封装了如何寻找@exception注解的内容。 ***
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}
exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
try {
if (logger.isDebugEnabled()) {
logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod);
}
//该处即是交给异常处理方法进行处理。
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception);
}
catch (Exception invocationEx) {
if (logger.isErrorEnabled()) {
logger.error("Failed to invoke @ExceptionHandler method: " + exceptionHandlerMethod, invocationEx);
}
return null;
}
if (mavContainer.isRequestHandled()) {
return new ModelAndView();
}
else {
ModelAndView mav = new ModelAndView().addAllObjects(mavContainer.getModel());
mav.setViewName(mavContainer.getViewName());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
return mav;
}
} |
该方法第一行 getExceptionHandlerMethod(handlerMethod, exception);封装了获取处理handler的异常处理方法(这里指的是@ExceptionHandler注解的方法)。
该方法中包括很多内容,缓存,验证等信息。
这里的缓存指的handler对应的异常处理resolver的mapping;
验证会验证一个handler中的相同@Exceptionhandler是否出现了多次,如果出现多次会抛出
throw new IllegalStateException(
"Ambiguous @ExceptionHandler method mapped for [" + exceptionType + "]: {" +
oldMethod + ", " + method + "}."); |
简单说就是一个controller中针对一个异常类型,只能使用一个@Exceptionhandler,当然如果处理的异常类型有多个,则可以有多个。
比如下面将不合法,应为@ExceptionHandler(Exception.class)有2个,如果将其中一个@ExceptionHandler(Exception.class)修改为其他则可行。
@ExceptionHandler(Exception.class)
@ResponseBody
public CMCCApiResponse processMethod(Exception ex, HttpServletRequest request, HttpServletResponse response) {
CMCCApiResponse cmccApiResponse = new CMCCApiResponse();
cmccApiResponse.setCode(CMCCBaseResultCodeConstant.FAIL);
logger.error(request.getRequestURL() + "?" + request.getQueryString());
logger.error("ApiBaseController:ExceptionHandler:", ex);
if (!EcovacsValidateException.class.isInstance(ex)) {
//内部异常
cmccApiResponse.setMsg("消息接收异常");
} else {
cmccApiResponse.setMsg(ex.getMessage());
}
return cmccApiResponse;
}
@ExceptionHandler(Exception.class)
@ResponseBody
public CMCCApiResponse processMethod2(Exception ex, HttpServletRequest request, HttpServletResponse response) {
CMCCApiResponse cmccApiResponse = new CMCCApiResponse();
cmccApiResponse.setCode(CMCCBaseResultCodeConstant.FAIL);
logger.error(request.getRequestURL() + "?" + request.getQueryString());
logger.error("ApiBaseController:ExceptionHandler:", ex);
if (!EcovacsValidateException.class.isInstance(ex)) {
//内部异常
cmccApiResponse.setMsg("消息接收异常");
} else {
cmccApiResponse.setMsg(ex.getMessage());
}
return cmccApiResponse;
} |
5. DefaultHandlerExceptionResolver类
继承自AbstractHandlerExceptionResolver抽象类。该类封装了对很多不同异常的处理。列举部分:
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
try {
if (ex instanceof NoSuchRequestHandlingMethodException) {
return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, request, response,
handler);
}
else if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request,
response, handler);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response,
handler);
}
else if (ex instanceof HttpMediaTypeNotAcceptableException) {
return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, request, response,
handler);
} |
6. ResponseStatusExceptionResolver类
继承自AbstractHandlerExceptionResolver抽象类。该类的doResolveException方法主要在异常及异常父类中找到@ResponseStatus注解,然后使用这个注解的属性进行处理。这一点和@ExceptionHandler类似.
7. SimpleMappingExceptionResolver类
继承自AbstractHandlerExceptionResolver抽象类,是一个简单的处理,试图映射的类。很多人会继承该类来实现自己的异常处理handler。
8. @ResponseStatus注解
让1个方法或异常有状态码(status)和理由(reason)返回。这个状态码是http响应的状态码。
异常处理的生命周期(关键之处)上面介绍了一些基础的类,但是依然有疑惑。为何配置了@ExceptionHandler注解就可以进行异常处理?
虽然我们知道了ExceptionHandlerExceptionResolver类对配置了@ExceptionHandler注解的类进行处理,但是这是如何发生的?或者说ExceptionHandlerExceptionResolver在何时初始化(配置),何时调用?
答案是:<annotation-driven/>
我们看下<annotation-driven/>解析类AnnotationDrivenBeanDefinitionParser中部分代码片段:
RootBeanDefinition exceptionHandlerExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
exceptionHandlerExceptionResolver.setSource(source);
exceptionHandlerExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
addResponseBodyAdvice(exceptionHandlerExceptionResolver);
String methodExceptionResolverName =
parserContext.getReaderContext().registerWithGeneratedName(exceptionHandlerExceptionResolver);
RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
responseStatusExceptionResolver.setSource(source);
responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
responseStatusExceptionResolver.getPropertyValues().add("order", 1);
String responseStatusExceptionResolverName =
parserContext.getReaderContext().registerWithGeneratedName(responseStatusExceptionResolver);
RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
defaultExceptionResolver.setSource(source);
defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
defaultExceptionResolver.getPropertyValues().add("order", 2);
String defaultExceptionResolverName = |
上面代码中定义了3个异常处理类及其顺序。所以答案在这里得到解决。
顺序order
如果你的项目中有时候出现抛出异常,但是无法进入自定义的异常处理类的时候,就需要当心这个陷阱。
上面代码中定义了异常处理的默认顺序,所以如果我们自己实现自己的异常处理类的时候需要注意自己的order属性,如果不设置默认将会是排在末尾,配置多个将会依次根据顺序排序。
这里需要注意的是:如果希望自己优先处理所有异常,应该将order设置为-1,并且我也推荐使用。应为如果不讲自己的异常处理类设置为最优先的话,我们将对一些异常无法处理。比如封装spring mvc属性的时候出现类型不匹配,jsp中的异常,或者其他异常(这里记得DefaultHandlerExceptionResolver的处理吗?这个类封装了很多异常处理)。所以要当心当出现异常的时候,自己的异常处理类,无法接到任何异常信息。这一点笔者接触的一些公司经常会犯这样的错误,导致一些异常难以排查(应为线上日志几乎不会出现DEBUG模式,而JSP的一些异常却是需要DEBUG才会打印日志)。
异常处理类的调用
观察spring mvc的核心类 DispatcherServlet
整个流程如下:
doService()调用doDispatch->processDispatchResult->processHandlerException。
dodispatch如下(这类忽略掉了其他代码):
try{
xxxxxxxxx
}catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); |
processHandlerException 通过循环来交给异常处理类处理:
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
} |
异常处理类中如果抛出异常这个问题很有趣,如果处理异常的方法自己抛出异常,将会如何处理?这里不妨脑洞一些,如果抛出的异常依然可以被异常处理链路来处理,那么就会出现死循环!!!那么如果不处理,这里抛出的异常该怎么办呢?
结论是:如果抛出异常,将会沿着调用栈向上抛,最终抛出到servlet进行处理,然后抛出servlet异常。也就是会500。
try {
doService(request, response);
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
} |
2、总结本文讲解了spring mvc的异常处理流程。重要的地方发包括:
1、spring mvc 通过<annotation-driven/>默认加入了3个异常处理器。
2、@ExceptionHandler注解由ExceptionHandlerExceptionResolver类来处理。
3、同一个类中不能使用@ExceptionHandler注解解释相同异常2次。并且@Order注解毫无意义,应为@ExceptionHandler不是一个异常处理器,只是让异常处理器能识别到应该如何处理。
4、通过配置文件可以配置多个异常处理器,处理顺序根据order来设置,如果要自己处理所有异常,需要将order设置成小于0。并且强烈推荐如此做。否则一些异常会被spring mvc自身处理,而达不到想要的效果。
5、异常处理器中抛出的异常,将会一直往上抛,直到servlet捕获并处理,最终抛出ServletException,或者其他Exception。其实只有3中ServletException exception 和NestedServletException。
程序猿的技术大观园:www.javathinker.net
|
网站系统异常
系统异常信息 |
Request URL:
http://www.javathinker.net/WEB-INF/lybbs/jsp/topic.jsp?postID=3509
java.lang.NullPointerException
如果你不知道错误发生的原因,请把上面完整的信息提交给本站管理人员。
|
|