“叭”,除畜生道劳役之苦;
在学过的三阶段的时候,我们对SpringMVC的异常处理,一直可以算是简单中透着暴力,不要不重视异常!真的很重要,不要让它处在尴尬的位置!
在二阶段或者说三阶段中,技术一方面其中我认为最关键的地方是MVC思想的理解,对MVC理解程度决定着你学习过程中的难易程度,虽然有点夸张!
例如,当我们产生了一个 非业务异常
,或者 非本业务可以处理的其他业务异常
,那么我们一般会一直往 上层抛
(或者适当包装后继续抛出)直到 控制层
[我们就是这么解决问题,不知道你是否还记得],之后,控制层进行异常日志记录然后响应用户错误页面和信息。
如果每个控制器都写一个异常处理未免也太多冗余了,好在强大的 Spring 给我们提供了很多种解决方案,这里介绍其中一种 @ControllerAdvice [个人认为目前最好的方案之一,XML方式的简单统一异常处理,这里就不说了]。
1. 为什么现在我才告诉你呢?
看该注解字面上的意思就是 控制器通知
。
Advice 是AOP中的概念,这里简单介绍下。
我们使用AOP切入某一目标方法,那么该方法在切面的角度看来可以有几个执行阶段:
- 目标方法调用前(before)- 前置通知
- 目标方法调用后(after)- 后置通知
- 目标方法返回后(after return)- 返回通知
- 目标方法调用前后(around)- 环绕通知
- 目标方法抛出异常时(throw)- 异常通知
是否还记得每种通知的特点呢?呵呵!忘记了吧!
去看笔记或者PPT吧!!!
当执行到某个阶段的时候都会有不同阶段类型 Advice 发出,然后你可以根据不同阶段类型的通知对切入点进行一些 增强处理
了。
在不改动原来代码的基础上,增加新的功能!
看看如何声明@ControllerAdvice
/**
* 全局异常处理器
* @author 胖先生
*
*/
@ControllerAdvice
public class GlobalExceptionHandler {
}
So Easy!标注完之后,该类就变成一个控制器通知处理器了,接着我们需要进行一些通知的处理。
解除痛苦
在该类中的方法上标注 @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官方手册