经典的 MVC 程序中,如果产生了一个非业务异常,或者非本业务可以处理的其他业务异常,那么我们一般会一直往上层抛(或者适当包装后继续抛出)直到控制层,控制层进行异常日志记录然后响应用户错误页面和信息。如果每个控制器都写一个异常处理未免也太多冗余了,好在强大的 Spring 给我们提供了很多种解决方案,这里介绍其中一种 @ControllerAdvice

看该注解字面上的意思就是控制器通知。Advice 是AOP中的概念,这里简单介绍下。我们使用aop切入某一目标方法,那么该方法在切面的角度看来可以有几个执行阶段:

  • 目标方法调用前(before)
  • 目标方法调用后(after)—目标方法异常也执行
  • 目标方法返回后(after return)—目标方法异常不执行
  • 目标方法调用前后(around)
  • 目标方法抛出异常时(throw)

当执行到某个阶段的时候都会有不同阶段类型 Advice 发出,然后你可以根据不同阶段类型的通知对切入点进行一些增强处理了。

 

新建一个类,使用 @ControllerAdvice 标注:

/**
 * 全局异常处理器
 * @author Pacey
 * @date 2016年4月27日 上午11:27:11
 * 
 */
@ControllerAdvice
public class GlobalExceptionHandler {

}

标注完之后,该类就变成一个控制器通知处理器了,接着我们需要进行一些通知的处理。

 

在该类中的方法上标注 @ExceptionHandler 可以将指定方法变成一个异常通知处理方法,处理的异常类型可使用参数指定。除了 @ExceptionHandler 之外还有其他的通知类型,具体可参阅官方文档,本文只以异常处理为例子,事实上运用最广泛的也就是异常处理而已了。

 

新增以下代码可以处理 HttpRequestMethodNotSupportedException(HTTP方法不支持):

@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
@ResponseBody
public Result<String> httpRequestMethodNotSupportedExceptionHandler(HttpServletRequest req, Exception e) throws Exception{
	if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null)
        throw e;

    // Otherwise setup and send the user to a default error-view.

    // 这里你可以自由发挥,咱在这里给前端返回了一个错误提醒的Json
    Result<String> result = new Result<>(false);
    result.setError(HohistarExceptionReason.BIZ_10070);
        
    return result;
}

新增以下代码可以处理 MethodArgumentNotValidException(Validator字段校验失败异常处理):

@ExceptionHandler(MethodArgumentNotValidException.class) 
@ResponseBody
public Result<String> methodArgumentNotValidExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException e) throws Exception{
	if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null)
        throw e;

	BindingResult bindingResult = e.getBindingResult();
		
    // Otherwise setup and send the user to a default error-view.
    Result<String> result = new Result<>(false);
    FieldError firstError = bindingResult.getFieldErrors().get(0);
        
    result.setError(HohistarExceptionReason.BIZ_10074, firstError.getField(), firstError.getDefaultMessage());
        
    return result;
}

如果要成功捕获 MethodArgumentNotValidException,在控制器方法上就不能定义 BindingResult 参数接收校验结果,不然如果校验失败 Spring 是不会抛出 MethodArgumentNotValidException 异常的,自然而然在我们的 GlobalExceptionHandler 就无法处理了。

最后,如果你要捕获任何漏网之异常,你可以新增以下暴力的代码:

@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result<String> defaultExceptionHandler(HttpServletRequest req, Exception e) throws Exception {
    // If the exception is annotated with @ResponseStatus rethrow it and let
    // the framework handle it - like the OrderNotFoundException example
    // at the start of this post.
    // AnnotationUtils is a Spring Framework utility class.
    if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null)
        throw e;
        
    String throwClassName = e.getStackTrace()[0].getClassName();
    Logger logger = getLogger(throwClassName);
    logger.error("GlobalExceptionHandler catch a Server Exception: ", e);

    // Otherwise setup and send the user to a default error-view.
    Result<String> result = new Result<>(false);
    if(BeanUtils.isNotEmpty(this.defaultExceptionReason)){
    	result.setError(HohistarExceptionReason.valueOf(this.defaultExceptionReason));
    } else {
        result.setError(HohistarExceptionReason.INTL_20001);
    }
        
    return result;
}

GlobalExceptionHandler 中可以有多个 @ExceptionHandler 标注的方法,如果控制器中抛出了一个异常,而且没有匹配任何其他类型的异常处理方法,那么上方的方法将会被通知执行,保证从控制器抛出的异常一定会受到处理。

Spring MVC 字段校验组的使用

在Spring MVC中,我们可以在Java Bean的字段上标注一些校验器进行字段数据合法性校验,但有时候我们需要对这些校验器进行分组操作,本文将介绍分组的作用及使...

阅读全文

Spring中多线程定时计划任务

web应用程序中时常需要定时循环的执行一些状态更新,或者数据清理的相关任务,亦如超时订单的处理,spring中实现计划任务也是非常方便 Java代码 public clas...

阅读全文

欢迎留言