1. 当我们在开发一个项目时,往往需要对异常进行捕获处理,以提供友好的信息展示给用户。但随着业务的增长,项目越来越复杂,需要捕获异常的地方就会越来越多,如果每个地方都进行try catch,那代码将会变得非常冗余且不好维护。
  2. 我们知道Spring Boot默认情况下会映射到 /error 进行异常处理,但是提示并不十分友好。那有没有一种统一的处理机制?幸好从Spring 3.2以后新增了@ControllerAdvice 注解,使用AOPController控制器进行增强(前置增强、后置增强、环绕增强),那么我们就可以对控制器的方法进行调用前(前置增强)和调用后(后置增强)的处理。<br /> Spring还提供了@ExceptionHandler异常增强注解,一般需要配合@RequestBody注解使用。程序如果在执行控制器方法前或执行时抛出异常,会被@ExceptionHandler注解了的方法处理。如果全部异常处理返回json格式,那么可以使用@RestControllerAdvice 代替@ControllerAdvice,这样在方法上就可以不需要添加@ResponseBody

自定义异常类

定义一个AgException作为全局的自定义异常。继承RuntimeException(运行时异常)对代码无可侵入性,不需要方法中强制捕获或者抛出。

  1. @Getter
  2. @Slf4j
  3. public class AgException extends RuntimeException {
  4. /**
  5. * 响应状态码枚举
  6. */
  7. private StatusResultEnum statusResult;
  8. private Object[] args;
  9. /**
  10. * 构造指定异常代码与消息参数的业务异常。
  11. *
  12. * @param statusResult 异常代码
  13. * @param args 消息参数,该参数将用于格式化异常代码中的消息字符串
  14. */
  15. public AgException(StatusResultEnum statusResult, Object... args) {
  16. this(statusResult, null, args);
  17. }
  18. /**
  19. * 构造指定异常代码、异常原因与消息参数的业务异常。
  20. *
  21. * @param statusResult 异常代码
  22. * @param cause 异常消息
  23. * @param args 消息参数,该参数将用于格式化异常代码中的消息字符串
  24. */
  25. public AgException(StatusResultEnum statusResult, Throwable cause, Object... args) {
  26. super(statusResult.getCodeMsg(args), cause);
  27. log.error("系统异常:{} ", cause.getMessage(), cause);
  28. this.args = args;
  29. this.statusResult = statusResult;
  30. }
  31. }

添加自定义信息枚举类

  1. public enum StatusResultEnum {
  2. SUCCESS("2000", "success", "请求成功"),
  3. /**
  4. * 可预知异常
  5. */
  6. NOT_LOGIN_IN("4001", "未登录", "未登录"),
  7. /**
  8. * 不可预知异常,但有明确错误码
  9. */
  10. UN_AUTHORIZED("4002", "权限不足", "权限不足"),
  11. /**
  12. * 不可预知异常,默认错误
  13. */
  14. INTERNAL_SERVER_ERROR("5000", "%1s", "内部服务器错误,请联系客服人员。");
  15. /**
  16. * 响应返回码
  17. */
  18. @Getter
  19. private String code;
  20. /**
  21. * 响应描述,面向开发者
  22. */
  23. @Setter
  24. private String codeMsg;
  25. /**
  26. * 响应描述,面向用户
  27. */
  28. @Setter
  29. private String statusMsg;
  30. StatusResultEnum(String code, String codeMsg, String statusMsg) {
  31. this.code = code;
  32. this.codeMsg = codeMsg;
  33. this.statusMsg = statusMsg;
  34. }
  35. /**
  36. * 根据指定的占位符参数格式化异常消息。
  37. *
  38. * @param args 占位符参数
  39. * @return 格式化后的异常消息
  40. */
  41. public String getCodeMsg(Object... args) {
  42. return ErrorCodeUtils.formatMessage(codeMsg, args);
  43. }
  44. /**
  45. * 根据指定的占位符参数格式化异常消息。
  46. *
  47. * @param args 占位符参数
  48. * @return 格式化后的异常消息
  49. */
  50. public String getStatusMsg(Object... args) {
  51. return ErrorCodeUtils.formatMessage(statusMsg, args);
  52. }
  53. }

全局异常处理类

  • 对异常进行归类:可预知异常(即自定义异常AgException);不可预知异常,但需要明确定义错误码;不可预知异常,不需特殊处理错误码。
  • 使用Spring MVC控制器增强,捕获全局异常。
  • 捕获AgException业务异常,取出错误码和信息构造响应。
  • 使用一个线程安全、并且不可更改的map存储不可预知异常自定义的错误信息。
  • 捕获AgException以外的异常(Exception),判断map是否定义了该异常错误信息,若有定义取出错误信息构造响应,否则返回默认错误。
    1. @RestControllerAdvice
    2. @Slf4j
    3. public class AgExceptionHandler {
    4. /**
    5. * 线程安全
    6. */
    7. private static final ImmutableMap<Class<? extends Throwable>, StatusResultEnum> EXCEPTIONS;
    8. static {
    9. final ImmutableMap.Builder<Class<? extends Throwable>, StatusResultEnum> builder = ImmutableMap.builder();
    10. builder.put(LockedAccountException.class, StatusResultEnum.IDENTITY_AUTH_FAIL);
    11. builder.put(UnknownAccountException.class, StatusResultEnum.IDENTITY_AUTH_FAIL);
    12. builder.put(IncorrectCredentialsException.class, StatusResultEnum.IDENTITY_AUTH_FAIL);
    13. builder.put(DisabledAccountException.class, StatusResultEnum.IDENTITY_AUTH_FAIL);
    14. builder.put(UnauthorizedException.class, StatusResultEnum.UN_AUTHORIZED);
    15. builder.put(MissingServletRequestParameterException.class, StatusResultEnum.REQUIRE_ARGUMENT);
    16. // 其他未被发现的异常
    17. builder.put(Exception.class, StatusResultEnum.INTERNAL_SERVER_ERROR);
    18. EXCEPTIONS = builder.build();
    19. }
    20. @ExceptionHandler(AgException.class)
    21. public BaseResponse handleAgException(Throwable e) {
    22. AgException agException = (AgException) e;
    23. return new ResultResponse(agException.getStatusResult(), agException.getArgs());
    24. }
    25. @ExceptionHandler(Exception.class)
    26. public BaseResponse handleException(Exception e) {
    27. log.error("系统异常:{} ", e.getMessage(), e);
    28. StatusResultEnum statusResultEnum = EXCEPTIONS.get(e.getClass());
    29. return new ResultResponse(statusResultEnum, e.getMessage());
    30. }
    31. }

    Spring Boot 全局异常处理 - 图1

总结

异常抛出的顺序为Dao—Service—Controller—AgExceptionHandler,SpringMVC增强的即是在Controller层进行拦截,实现全局异常统捕获,异常在AgExceptionHandler 统一处理后,就无需再代码中单独对每个服务进行try catch,此种实现方式代码不仅重用性高,而易于扩展。