Spring Boot 全局异常处理

开发项目时,异常捕获处理若每个地方都用try catch会使代码冗余且难维护。Spring Boot默认异常处理提示不友好,从Spring 3.2后新增@ControllerAdvice注解,结合@ExceptionHandler可实现全局异常处理。通过自定义异常类、信息枚举类,对异常归类处理,实现代码重用和扩展。

       当我们在开发一个项目时,往往需要对异常进行捕获处理,以提供友好的信息展示给用户。但随着业务的增长,项目越来越复杂,需要捕获异常的地方就会越来越多,如果每个地方都进行try catch,那代码将会变得非常冗余且不好维护。

       我们知道Spring Boot默认情况下会映射到 /error 进行异常处理,但是提示并不十分友好。那有没有一种统一的处理机制?幸好从Spring 3.2以后新增了@ControllerAdvice 注解,使用AOP对Controller控制器进行增强(前置增强、后置增强、环绕增强),那么我们就可以对控制器的方法进行调用前(前置增强)和调用后(后置增强)的处理。

       Spring还提供了@ExceptionHandler异常增强注解,一般需要配合@RequestBody注解使用。程序如果在执行控制器方法前或执行时抛出异常,会被@ExceptionHandler注解了的方法处理。如果全部异常处理返回json格式,那么可以使用@RestControllerAdvice 代替@ControllerAdvice,这样在方法上就可以不需要添加@ResponseBody。

自定义异常类

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

@Getter
@Slf4j
public class AgException extends RuntimeException {

    /**
     * 响应状态码枚举
     */
    private StatusResultEnum statusResult;

    private Object[] args;

    /**
     * 构造指定异常代码与消息参数的业务异常。
     *
     * @param statusResult 异常代码
     * @param args 消息参数,该参数将用于格式化异常代码中的消息字符串
     */
    public AgException(StatusResultEnum statusResult, Object... args) {
        this(statusResult, null, args);
    }

    /**
     * 构造指定异常代码、异常原因与消息参数的业务异常。
     *
     * @param statusResult 异常代码
     * @param cause 异常消息
     * @param args 消息参数,该参数将用于格式化异常代码中的消息字符串
     */
    public AgException(StatusResultEnum statusResult, Throwable cause, Object... args) {
        super(statusResult.getCodeMsg(args), cause);
        log.error("系统异常:{} ", cause.getMessage(), cause);
        this.args = args;
        this.statusResult = statusResult;
    }

}

添加自定义信息枚举类

public enum StatusResultEnum {

    SUCCESS("2000", "success", "请求成功"),

    /**
     * 可预知异常
     */
    NOT_LOGIN_IN("4001", "未登录", "未登录"),
    /**
     * 不可预知异常,但有明确错误码
     */
    UN_AUTHORIZED("4002", "权限不足", "权限不足"),
    /**
     * 不可预知异常,默认错误
     */
    INTERNAL_SERVER_ERROR("5000", "%1s", "内部服务器错误,请联系客服人员。");

    /**
     * 响应返回码
     */
    @Getter
    private String code;
    /**
     * 响应描述,面向开发者
     */
    @Setter
    private String codeMsg;
    /**
     * 响应描述,面向用户
     */
    @Setter
    private String statusMsg;

    StatusResultEnum(String code, String codeMsg, String statusMsg) {
        this.code = code;
        this.codeMsg = codeMsg;
        this.statusMsg = statusMsg;
    }

    /**
     * 根据指定的占位符参数格式化异常消息。
     *
     * @param args 占位符参数
     * @return 格式化后的异常消息
     */
    public String getCodeMsg(Object... args) {
        return ErrorCodeUtils.formatMessage(codeMsg, args);
    }

    /**
     * 根据指定的占位符参数格式化异常消息。
     *
     * @param args 占位符参数
     * @return 格式化后的异常消息
     */
    public String getStatusMsg(Object... args) {
        return ErrorCodeUtils.formatMessage(statusMsg, args);
    }

}

全局异常处理类

  • 对异常进行归类:可预知异常(即自定义异常AgException);不可预知异常,但需要明确定义错误码;不可预知异常,不需特殊处理错误码。
  • 使用Spring MVC控制器增强,捕获全局异常。
  • 捕获AgException业务异常,取出错误码和信息构造响应。
  • 使用一个线程安全、并且不可更改的map存储不可预知异常自定义的错误信息。
  • 捕获AgException以外的异常(Exception),判断map是否定义了该异常错误信息,若有定义取出错误信息构造响应,否则返回默认错误。
@RestControllerAdvice
@Slf4j
public class AgExceptionHandler {

    /**
     * 线程安全
     */
    private static final ImmutableMap<Class<? extends Throwable>, StatusResultEnum> EXCEPTIONS;

    static {
        final ImmutableMap.Builder<Class<? extends Throwable>, StatusResultEnum> builder = ImmutableMap.builder();

        builder.put(LockedAccountException.class, StatusResultEnum.IDENTITY_AUTH_FAIL);
        builder.put(UnknownAccountException.class, StatusResultEnum.IDENTITY_AUTH_FAIL);
        builder.put(IncorrectCredentialsException.class, StatusResultEnum.IDENTITY_AUTH_FAIL);
        builder.put(DisabledAccountException.class, StatusResultEnum.IDENTITY_AUTH_FAIL);

        builder.put(UnauthorizedException.class, StatusResultEnum.UN_AUTHORIZED);
        builder.put(MissingServletRequestParameterException.class, StatusResultEnum.REQUIRE_ARGUMENT);

        // 其他未被发现的异常
        builder.put(Exception.class, StatusResultEnum.INTERNAL_SERVER_ERROR);

        EXCEPTIONS = builder.build();
    }

    @ExceptionHandler(AgException.class)
    public BaseResponse handleAgException(Throwable e) {
        AgException agException = (AgException) e;
        return new ResultResponse(agException.getStatusResult(), agException.getArgs());
    }

    @ExceptionHandler(Exception.class)
    public BaseResponse handleException(Exception e) {
        log.error("系统异常:{} ", e.getMessage(), e);
        StatusResultEnum statusResultEnum = EXCEPTIONS.get(e.getClass());
        return new ResultResponse(statusResultEnum, e.getMessage());
    }

}

 

 c454eb6d88900b351ae00015e38f0ae0c41.jpg

总结

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

参考

Java架构师笔记-如何优雅处理项目中的Exception

转载于:https://my.oschina.net/lienson/blog/3054971

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值