首页 > 代码库 > [六字真言]4.叭.SpringMVC异常痛苦

[六字真言]4.叭.SpringMVC异常痛苦

“叭”,除畜生道劳役之苦;

在学过的三阶段的时候,我们对SpringMVC的异常处理,一直可以算是简单中透着暴力,不要不重视异常!真的很重要,不要让它处在尴尬的位置!

在二阶段或者说三阶段中,技术一方面其中我认为最关键的地方是MVC思想的理解,对MVC理解程度决定着你学习过程中的难易程度,虽然有点夸张!

例如,当我们产生了一个 非业务异常 ,或者 非本业务可以处理的其他业务异常 ,那么我们一般会一直往 上层抛 (或者适当包装后继续抛出)直到 控制层 [我们就是这么解决问题,不知道你是否还记得],之后,控制层进行异常日志记录然后响应用户错误页面和信息。

如果每个控制器都写一个异常处理未免也太多冗余了,好在强大的 Spring 给我们提供了很多种解决方案,这里介绍其中一种 @ControllerAdvice [个人认为目前最好的方案之一,XML方式的简单统一异常处理,这里就不说了]。

1. 为什么现在我才告诉你呢?

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

  • 目标方法调用前(before)- 前置通知
  • 目标方法调用后(after)- 后置通知
  • 目标方法返回后(after return)- 返回通知
  • 目标方法调用前后(around)- 环绕通知
  • 目标方法抛出异常时(throw)- 异常通知

是否还记得每种通知的特点呢?呵呵!忘记了吧!
去看笔记或者PPT吧!!!
当执行到某个阶段的时候都会有不同阶段类型 Advice 发出,然后你可以根据不同阶段类型的通知对切入点进行一些 增强处理 了。

在不改动原来代码的基础上,增加新的功能!
看看如何声明@ControllerAdvice

  1. /**
  2. * 全局异常处理器
  3. * @author 胖先生
  4. *
  5. */
  6. @ControllerAdvice
  7. public class GlobalExceptionHandler {
  8. }

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

解除痛苦

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

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

  1. @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
  2. @ResponseBody
  3. public Result<String> httpRequestMethodNotSupportedExceptionHandler(HttpServletRequest req, Exception e) throws Exception{
  4. if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null)
  5. throw e;
  6. // Otherwise setup and send the user to a default error-view.
  7. // 这里你可以自由发挥,咱在这里给前端返回了一个错误提醒的Json
  8. Result<String> result = new Result<>(false);
  9. result.setError(HohistarExceptionReason.BIZ_10070);
  10. return result;
  11. }

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

  1. @ExceptionHandler(MethodArgumentNotValidException.class)
  2. @ResponseBody
  3. public Result<String> methodArgumentNotValidExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException e) throws Exception{
  4. if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null)
  5. throw e;
  6. BindingResult bindingResult = e.getBindingResult();
  7. // Otherwise setup and send the user to a default error-view.
  8. Result<String> result = new Result<>(false);
  9. FieldError firstError = bindingResult.getFieldErrors().get(0);
  10. result.setError(HohistarExceptionReason.BIZ_10074, firstError.getField(), firstError.getDefaultMessage());
  11. return result;
  12. }

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

一个都不放过

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

  1. @ExceptionHandler(value = Exception.class)
  2. @ResponseBody
  3. public Result<String> defaultExceptionHandler(HttpServletRequest req, Exception e) throws Exception {
  4. // If the exception is annotated with @ResponseStatus rethrow it and let
  5. // the framework handle it - like the OrderNotFoundException example
  6. // at the start of this post.
  7. // AnnotationUtils is a Spring Framework utility class.
  8. if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null)
  9. throw e;
  10. String throwClassName = e.getStackTrace()[0].getClassName();
  11. Logger logger = getLogger(throwClassName);
  12. logger.error("GlobalExceptionHandler catch a Server Exception: ", e);
  13. // Otherwise setup and send the user to a default error-view.
  14. Result<String> result = new Result<>(false);
  15. if(BeanUtils.isNotEmpty(this.defaultExceptionReason)){
  16. result.setError(HohistarExceptionReason.valueOf(this.defaultExceptionReason));
  17. } else {
  18. result.setError(HohistarExceptionReason.INTL_20001);
  19. }
  20. return result;
  21. }

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

大部分代码来自官方文档:这里大部分摘抄Spring官方手册



来自为知笔记(Wiz)


[六字真言]4.叭.SpringMVC异常痛苦