在咱們的程序中,不少時候會碰到對異常的處理,咱們也許會定義一些本身特殊業務的異常,在發生錯誤的時候會拋出異常,在springmvc的實際應用中,咱們常常須要返回異常的信息以及錯誤代碼,而且對異常進行一些處理而後返回再返回視圖。這就要涉及到咱們這一篇主要講的HandlerExceptionResolver前端
其實springmvc已經默認給咱們注入了3個異常處理的解器:java
AnnotationMethodHandlerExceptionResolver(針對@ExceptionHandler,3.2已廢除,轉而使用ExceptionHandlerExceptionResolver)
ResponseStatusExceptionResolver(針對加了@ResponseStatus的exception)
DefaultHandlerExceptionResolver(默認異常處理器)git
圖小能夠放大!😳github
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 */ ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex); }
HandlerExceptionResolver只有一個核心方法,就是resolveException,方法體中包含處理的方法,異常已經請求和響應參數。web
在咱們本身去實現自定義異常解析器的時候,咱們通常是去繼承AbstractHandlerExceptionResolverspring
AbstractHandlerExceptionResolver實現了HandlerExceptionResolver和Orderedjson
那麼針對異常的處理具體是在哪裏執行的呢?mvc
答案是springmvc核心類DispatcherServletapp
在DispatcherServlet的doDispatch()方法最後會執行異步
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
它將異常給統一處理了!
咱們先來看下DispatcherServlet類中的兩個方法:
源碼2.2.1
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; } } if (exMv != null) { 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()) { exMv.setViewName(getDefaultViewName(request)); } if (logger.isDebugEnabled()) { logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex); } WebUtils.exposeErrorRequestAttributes(request, ex, getServletName()); return exMv; } throw ex; }
在以上源碼可知:
1)異常處理器只有當返回的ModelAndView不是空的時候纔會返回最終的異常視圖,當異常處理返回的ModelAndView若是是空,那麼它將繼續去下一個異常解析器。
2)異常解析器是有執行順序的,咱們在合適的場景能夠定義本身的order來絕對哪一個異常解析器先執行,order越小,越先執行
源碼2.2.2
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { boolean errorView = false; if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); 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"); } } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; } if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, null); } }
當異常返回的視圖ModelAndView不是空的時候,DispatcherServlet最終會重定向到執行View。
咱們接下來要實現2種自定義異常處理器
先上一個rest的response的一個標準實體
/** * <p class="detail"> * 功能:REST接口標準容器 * </p> * @param <T> the type parameter * * @author Kings * @ClassName Rest response. * @Version V1.0. * @date 2016.08.16 09:28:55 */ @Setter @Getter public class RestResponse<T> { /** * The constant VOID_REST_RESPONSE. */ public static final RestResponse<Void> VOID_REST_RESPONSE = new RestResponse<>(null); @ApiModelProperty(value = "狀態碼", required = true) private int code; @ApiModelProperty(value = "服務端消息", required = true) private String message; @ApiModelProperty (value = "數據") private T data = null; /** * Instantiates a new Rest response. * @param code the code * @param message the message * @param data the data */ public RestResponse(int code, String message, T data) { this.code = code; this.message = message; if (data != null && "class com.github.pagehelper.PageInfo".equals(data.getClass().toString())) { Map<String, Object> map = new HashMap<String, Object>(); map.put("pageInfo", data); this.data = (T) map; } else { this.data = data; } } /** * Instantiates a new Rest response. * @param status the status */ public RestResponse(HttpStatus status, T data) { this(status.value(), status.getReasonPhrase(), data); } /** * Instantiates a new Rest response. * @param data the data */ public RestResponse(T data) { this(HttpStatus.OK.value(), "OK", data); } @Override public String toString() { return "{\"code\":" + code + ",\"message\":\"" + message + "\",\"data\":" + data + "}"; } }
先上springmvc validate切面實現錯誤信息綁定,validate是經過切面來實現,省去控制器層一大堆對BindingResult處理代碼。
public class ErrorMessage { /** 字段名 */ private String fieldName; /** 錯誤提示. */ private String message; /** * Instantiates a new Error message. * @param fieldName the field name * @param message the message */ public ErrorMessage(String fieldName, String message) { this.fieldName = fieldName; this.message = message; } /** * Gets field name. * @return the field name */ public String getFieldName() { return fieldName; } /** * Gets message. * @return the message */ public String getMessage() { return message; } @Override public String toString() { return "{\"fieldName\":\""+fieldName+"\",\"message\":\""+message+"\"}"; } }
validate錯誤信息實體
/** * <p class="detail"> * 功能:驗證註解,aop掃描次註解進行錯誤信息的綁定輸出 * </p> * @author Kings * @ClassName Validate method. * @Version V1.0. * @date 2016.08.03 17:51:01 */ @Retention (RetentionPolicy.RUNTIME) @Target (ElementType.METHOD) public @interface ValidateMethod { }
/** * <p class="detail"> * 功能:驗證異常 * </p> * @author Kings * @ClassName Validate exception. * @Version V1.0. * @date 2016.08.09 14:45:58 */ @ResponseStatus (value = HttpStatus.BAD_REQUEST, code = HttpStatus.BAD_REQUEST) public class ValidateException extends RuntimeException { /** * Instantiates a new Validate exception. * @param message the message */ public ValidateException(String message) { super(message); } }
狀態碼定義是400
public class ErrorHelper { private static Logger logger = LoggerFactory.getLogger(ErrorHelper.class); public RestResponse converBindError2AjaxError(BindingResult result, boolean validAllPropeerty) { try { RestResponse res = new RestResponse(HttpStatus.BAD_REQUEST,"validate error!"); List<ErrorMessage> errorMesages = new ArrayList<>(); List<ObjectError> objectErrors = result.getAllErrors(); for (ObjectError objError : objectErrors) { if (objError instanceof FieldError) { FieldError objectError = (FieldError) objError; errorMesages.add(new ErrorMessage(objectError.getField(), objError.getDefaultMessage())); } else { errorMesages.add(new ErrorMessage(objError.getCode(), objError.getDefaultMessage())); } if(!validAllPropeerty){ //noinspection BreakStatement break;//just one error object } } res.setData(errorMesages); return res; } catch (Exception e) { logger.error("com.gttown.common.support.web.validate.ErrorHelper error",e); } return null; } }
/** * <p class="detail"> * 功能:驗證切面 * </p> * @author Kings * @ClassName Validate handel aspect. * @Version V1.0. * @date 2016.08.03 17:51:42 */ @Aspect public class ValidateHandelAspect { /**judge is all property error need to be export*/ private boolean outputAllPropError = false; /** * <p class="detail"> * 功能:驗證輸出結果 * </p> * @param pjp : * * @return object * @throws Throwable the throwable * @author Kings * @date 2016.08.03 17:51:42 */ @Around ("validatePointcut()") public Object validateAround(ProceedingJoinPoint pjp) throws Throwable { Object[] args = pjp.getArgs(); BindingResult bindingResult = null; if (args != null) { for (Object obj : args) { if (obj instanceof BindingResult) { bindingResult = (BindingResult) obj; //noinspection BreakStatement break; } } } if ( bindingResult != null && bindingResult.hasErrors() ){//異常輸出 ErrorHelper errorHelper = new ErrorHelper(); throw new ValidateException(errorHelper.converBindError2AjaxError(bindingResult,outputAllPropError).toString()); //return errorHelper.converBindError2AjaxError(bindingResult,outputAllPropError); } else {//正常輸出 return pjp.proceed(args); } } /** * <p class="detail"> * 功能:切點 * </p> * @author Kings * @date 2016.08.03 17:51:42 */ @Pointcut ("@annotation(com.kings.common.validate.ValidateMethod)") public void validatePointcut() { } public void setOutputAllPropError(boolean outputAllPropError) { this.outputAllPropError = outputAllPropError; } }
關於validate的就涉及到以上幾個類
下面上異常處理器
/** * <p class="detail"> * 功能:針對ResponseStatus和ResponseBody的異常處理器,請在配置文件中將order設置爲-1覆蓋ResponseStatusExceptionResolver * </p> * @author Kings * @ClassName Response status and body exception resolver. * @Version V1.0. * @date 2016.08.09 15:23:54 */ public class ResponseStatusAndBodyExceptionResolver extends AbstractHandlerExceptionResolver { /** Argument error. */ private boolean argumentError = false; @Override protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { 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) { if (judgeInstance(ex)) { argumentError = true; } ex = (Exception) ex.getCause(); return doResolveException(request, response, handler, ex); } //just Intercept the method @ResponseBody and @RestController or else skip ResponseBody rexist = ((HandlerMethod) handler).getMethod().getAnnotation(ResponseBody.class); RestController rcexist = ((HandlerMethod) handler).getBeanType().getAnnotation(RestController.class); if (rexist != null || rcexist != null) { try { HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;//默認500 if (argumentError) {//參數錯誤400 status = HttpStatus.BAD_REQUEST; } response.setStatus(status.value()); Object data; if (ex instanceof ValidateException) {//validateExcepption已經包含了錯誤的信息 data = JSONObject.fromObject(ex.getMessage()); } else { Map<String, Object> errorMap = new HashMap<>(); errorMap.put("error", ex.toString()); data = errorMap;// for json } RestResponse res = new RestResponse(status, data); Map<String, Object> map = new HashMap<>();//put error message map.put("error", res); return new ModelAndView("errorJsonView", map); } catch (Exception e) { logger.warn("error", e); } finally { argumentError = false;//release } } return null; } /** * <p class="detail"> * 功能: * </p> * @param responseStatus :ResponseStatus * @param request :請求 * @param response :響應 * @param handler :methodHandler * @param ex :異常 * * @return model and view * @throws Exception the exception * @author Kings * @date 2016.08.09 15:23:54 */ protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { int statusCode = responseStatus.code().value(); response.setStatus(statusCode); Map<String, Object> map = new HashMap<>(); Object data; if (ex instanceof ValidateException) { data = JSONObject.fromObject(ex.getMessage()); } else { Map<String, Object> errorMap = new HashMap<>(); errorMap.put("error", ex.toString()); data = errorMap;// for json } map.put("error", data); return new ModelAndView("errorJsonView", map);//返回jsonView } private boolean judgeInstance(Exception ex) { return ex instanceof PropertyAccessException || ex instanceof ServletRequestBindingException; } }
springmvc默認使用了ResponseStatusExceptionResolver來處理異常帶有@ResponseStatus的異常類,而且返回對應code的視圖。而rest在發生錯誤的時候,友好的形式是返回一個json視圖,而且說明錯誤的信息,這樣更加有利於在碰到異常的狀況下進行錯誤的定位,提升解決bug的效率。
咱們採用ResponseStatusAndBodyExceptionResolver,是對ResponseStatusExceptionResolver作了進一步處理,並做用在ResponseStatusExceptionResolver以前。ResponseStatusAndBodyExceptionResolver是針對加了@ResponseBody或者控制器加了@RestController的處理程序遇到異常的異常解析器,得到異常結果而且返回json(RestResponse)視圖
ResponseStatusExceptionResolver須要咱們在配置文件中加入配置
請看3.1.8中的配置
/** * <p class="detail"> * 功能:JsonView for error * </p> * @author Kings * @ClassName Error json view. * @Version V1.0. * @date 2016.08.09 15:17:39 */ public class ErrorJsonView extends AbstractView { @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { response.setContentType("text/json; charset=UTF-8"); try (PrintWriter out = response.getWriter()) { Gson jb = new Gson(); out.write(jb.toJson(model.get("error"))); out.flush(); } catch (IOException e) { logger.error("com.gttown.common.support.web.view.ErrorJsonView", e); } } }
<mvc:annotation-driven validator="validator"/> <!--驗證bean--> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/> <!-- 若是不加默認到 使用classpath下的 ValidationMessages.properties --> <property name="validationMessageSource" ref="messageSource"/> </bean> <!-- 國際化的消息資源文件(本系統中主要用於顯示/錯誤消息定製) --> <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basenames"> <list> <!-- 在web環境中必定要定位到classpath 不然默認到當前web應用下找 --> <value>classpath:error</value> </list> </property> <property name="defaultEncoding" value="UTF-8"/> <property name="cacheSeconds" value="60"/> </bean> <!--validate 切面--> <aop:aspectj-autoproxy /> <bean class="com.kings.common.validate.ValidateHandelAspect"> <!--outputAllPropError默認是false,將只輸出一個錯誤字段的信息,若是須要所有字段異常錯誤信息,那麼outputAllPropError設置爲true--> <property name="outputAllPropError" value="true"/> </bean> <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"> <property name="order" value="-1" /><!--這邊的order必需要大於咱們jsp等視圖模板的order--> </bean> <!--錯誤JsonView--> <bean id="errorJsonView" class="com.kings.template.mvc.view.ErrorJsonView"/> <!--responseStatus和responseBody異常處理器--> <bean id="responseStatusAndBodyExceptionResolver" class="com.kings.template.mvc.ResponseStatusAndBodyExceptionResolver"> <property name="order" value="-1"/><!--負1用來覆蓋springmvc自帶的ResponseStatusExceptionResolver--> </bean>
@ValidateMethod @RequestMapping (value = "/errorhandler/2", method = RequestMethod.POST) public Person demo1(@Valid Person p, BindingResult bindingResult) {//BindingResult必須得寫,並且是緊跟在驗證明體以後,驗證的很少說了,就是得在方法體上加註解@ValidateMethod return p; } @RequestMapping (value = "/errorhandler/{id}", method = RequestMethod.GET) public String demo1(@PathVariable Long id) { return id.toString(); }
1.驗證
2.普通400
/** * <p class="detail"> * 功能:自定義異常處理類 * </p> * @author Kings * @ClassName Custom simple mapping exception resolver. * @Version V1.0. * @date 2016.07.18 14:40:16 */ public class CustomSimpleMappingExceptionResolver extends SimpleMappingExceptionResolver { /** Logger. */ private Logger logger = Logger.getLogger(CustomSimpleMappingExceptionResolver.class); @Override protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { super.doResolveException(request, response, handler, ex); logger.error(ex.getMessage(), ex); String viewName = determineViewName(ex, request); if (viewName != null) {// JSP格式返回 if (! (request.getHeader("accept").contains("application/json") || (request.getHeader("X-Requested-With") != null && request.getHeader("X-Requested-With").contains("XMLHttpRequest")))) { // 若是不是異步請求 // Apply HTTP status code for error views, if specified. // Only apply it if we're processing a top-level request. Integer statusCode = determineStatusCode(request, viewName); if (statusCode != null) { applyStatusCodeIfPossible(request, response, statusCode); } return getModelAndView(viewName, ex, request); } else { return null; } } else { return null; } } }
<!-- 統一異常處理 具備集成簡單、有良好的擴展性、對已有代碼沒有入侵性 --> <bean id="exceptionResolver" class="com.kings.common.resolver.CustomSimpleMappingExceptionResolver"> <property name="defaultErrorView" value="/error/500"/> <property name="exceptionAttribute" value="ex"/> <property name="exceptionMappings"> <props> <!-- 自定義業務異常 --> <prop key="com.gttown.common.support.exception.BizException">/error/biz</prop> <!-- 可再添加 --> </props> </property> <!-- 默認HTTP錯誤狀態碼 --> <property name="defaultStatusCode" value="500"/> <!-- 將路徑映射爲錯誤碼,供前端獲取。 --> <property name="statusCodes"> <props> <prop key="/error/500">500</prop> </props> </property> </bean>
statusCodes須要web.xml error-code碼結合使用指向指定頁面
<error-page> <error-code>500</error-code> <location>/WEB-INF/pages/error/500.jsp</location> </error-page>
@ControllerAdvice public class CustomerControllerAdvice { @ExceptionHandler (Exception.class) @ResponseStatus (HttpStatus.INTERNAL_SERVER_ERROR) @ResponseBody public RestResponse handleBadRequestException(Exception ex) { Map<String,Object> map = new HashMap<String,Object>(); map.put("error",ex.toString()); RestResponse response = new RestResponse(map); response.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value()); response.setMessage("error"); return response; } }
經過ExceptionHandler指定哪些類型的錯誤執行具體某個返回錯誤方法
而且可使用@ResponseStatus執行錯誤代碼
注意在配置ControllerAdvice的時候,必須跟controller同樣在springmvc.xml配置掃描初始化
在springmvc中咱們能夠有各類類型的異常解析器來統一處理異常,方便了咱們對異常的處理,經過在配置中加入異常處理的解析器,節約了控制器層的代碼,而且使得前端呈現出不一樣的響應code。