springmvc支持服务端在处理业务逻辑过程当中出现异常的时候能够配置相应的ModelAndView对象返回给客户端,本文介绍springmvc默认的几种HandlerExceptionResolver类html
springmvc的xml配置化-Exception配置java
<bean id="exceptionHandler" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!--设置默认返回viewName,一般与freemarker引擎搭配使用--> <property name="defaultErrorView" value="error/defaultError" /> <!--设置默认返回response status--> <property name="defaultStatusCode" value="500" /> <!--配置相应的异常类与viewName的映射--> <property name="exceptionMappings"> <props> <prop key="SessionTimeoutException">redirect:../login.html</prop> <prop key="AuthenticationException">error/403</prop> </props> </property> </bean>
以上的配置会对出SessionTimeoutException异常则跳转至login页面,对AuthenticationException异常则跳转至403页面,对其余的异常则默认跳转至defaultError页面呈现并返回500的错误码web
接口内只有一个方法resolveException()
,经过解析异常查询配置以获得符合条件的ModelAndView对象spring
/** * 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 */ ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
全部的spring内置异常解析类都继承于此,直奔主题看resolveException()方法json
@Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { //判断是否须要解析 if (shouldApplyTo(request, handler)) { //此处通常是判断内部属性preventResponseCaching是否为true,是则设置响应包头cache-control:no-store prepareResponse(ex, response); //使用模板方法doResolveException()方法供子类实现 ModelAndView result = doResolveException(request, response, handler, ex); if (result != null) { //日志打印一发 logException(ex, request); } return result; } else { return null; } }
附带着分析下shouldApplyTo()方法mvc
/** **能够配置mappedHandlers和mappedHandlerClasses属性来特定匹配 **默认状况下二者都为空则直接返回true,代表对全部的handler都进行异常解析 */ protected boolean shouldApplyTo(HttpServletRequest request, Object handler) { //此处的handler通常为bean对象 if (handler != null) { 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; } } } } // Else only apply if there are no explicit handler mappings. return (this.mappedHandlers == null && this.mappedHandlerClasses == null); }
比较简单的实现类,能够配绑定viewName和exception以完成简单的异常映射视图页面app
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // Expose ModelAndView for chosen error view. String viewName = determineViewName(ex, request); if (viewName != null) { //若是配置了statusCodes属性,则对此异常的状态码进行设置 Integer statusCode = determineStatusCode(request, viewName); if (statusCode != null) { applyStatusCodeIfPossible(request, response, statusCode); } //建立ModelAndView对象 return getModelAndView(viewName, ex, request); } else { return null; } }
针对以上的源码咱们分两步去简单分析下异步
protected String determineViewName(Exception ex, HttpServletRequest request) { String viewName = null; //判断异常是否属于excludeExceptions集合内,是则直接返回null if (this.excludedExceptions != null) { for (Class<?> excludedEx : this.excludedExceptions) { if (excludedEx.equals(ex.getClass())) { return null; } } } // Check for specific exception mappings. // 从exceptionMappings集合内根据exception获取到相应的viewName if (this.exceptionMappings != null) { viewName = findMatchingViewName(this.exceptionMappings, ex); } //当exceptionMappings集合内不存在指定的exception可是默认视图指定则直接返回默认视图 if (viewName == null && this.defaultErrorView != null) { viewName = this.defaultErrorView; } return viewName; }
excludedExceptions集合能够过滤指定的exception,对其不进行解析直接返回null。可配置ide
exceptionMappings绑定了exception与viewName的关系,若是在其集合内没找到相应的viewName,可是defaultErrorView属性指定,则会直接返回defaultErrorView对应的视图this
protected ModelAndView getModelAndView(String viewName, Exception ex) { ModelAndView mv = new ModelAndView(viewName); //exceptionAttribute默认为exception if (this.exceptionAttribute != null) { //将exception信息添加到model中 mv.addObject(this.exceptionAttribute, ex); } return mv; }
主要就是将exceptionAttribute对应的参数值默认为
exception
属性添加到视图对象的model中
主要是解析带有@ResponseStatus
的异常类,将其中的异常信息描述直接返回给客户端
@Override protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { //获取相应类上的注解@ResponseStatus ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class); if (responseStatus != null) { try { return resolveResponseStatus(responseStatus, request, response, handler, ex); } catch (Exception resolveEx) { logger.warn("Handling of @ResponseStatus resulted in Exception", resolveEx); } } else if (ex.getCause() instanceof Exception) { ex = (Exception) ex.getCause(); //递归 return doResolveException(request, response, handler, ex); } return null; }
读取@ResponseStatus
注解信息,返回异常内容给客户端
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //状态码 int statusCode = responseStatus.code().value(); //异常缘由描述 String reason = responseStatus.reason(); if (this.messageSource != null) { reason = this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()); } //经过response对象直接返回错误信息给客户端 if (!StringUtils.hasLength(reason)) { response.sendError(statusCode); } else { //经过response对象直接返回错误信息给客户端 response.sendError(statusCode, reason); } return new ModelAndView(); }
源码就不公布了,读者可自行去查询,基本都是调用response的sendError()方法返回错误信息给客户端。本文对其中的异常归下类
- 请求方式异常
- HttpRequestMethodNotSupportedException-服务端不支持相应的请求方法
- HttpMediaTypeNotSupportedException/HttpMediaTypeNotAcceptableException-服务端/客户端不支持相应的mediaType,好比
application/json
- MissingPathVariableException-
@PathVaribale
指定参数请求中不包含- MissingServletRequestParameterException/ServletRequestBindingException-请求参数绑定错误
- MethodArgumentNotValidException-
@Valid
注解指定的参数校验失败- AsyncRequestTimeoutException-异步请求超时
- 消息内容异常
- ConversionNotSupportedException-服务端找寻不到相应的Convert对象来解析javabean
- TypeMismatchException-设置javabean属性类型出错
- HttpMessageNotReadableException/HttpMessageNotWritableException-消息内容不可读/不可写
- MissingServletRequestPartException-文件上传类错误,可能请求没有
multipart/form-data
或者服务不支持文件上传- NoHandlerFoundException-handler处理器没有找到,便可能没有对应的请求处理供响应
具体的逻辑本文则不展开了,简述下其中的逻辑:
当处理handlerMethod业务逻辑过程当中出现了异常,则此解析器
尝试从handlerMethod所在的class类去找寻是否含有
@ExceptionHandler
注解的方法判断
@ExceptionHandler
指定的exception类与产生的异常一致,一致则执行相应的方法,当有多个@ExceptionHandler(value)
,则默认采用第一个当上述在class类找寻不到则尝试判断class类是否含有
@ControllerAdvice
注解,有则按照上述第一二步的步骤再次找寻@ControllerAdvice
指定的类
springmvc开放了对异常也能够包装成页面显示的功能,经过本文的简单分析能够帮助博主和读者更好的理解springmvc对异常的处理