统一异常处理 GlobalExceptionHandler

平时经常在代码里面

try {
		...
} catch (Exception e) {
    ...
}

这样子代码繁琐而且也不好看。特别是还有一些需要自己new一个map封装业务的异常信息,是在是难看。
所以就需要引出全局异常。经过一些封装,就可以实现这样的效果了。
统一异常处理 GlobalExceptionHandler_第1张图片

还是看代码吧!
首先是定义了一个GlobalExceptionHandler组件,通过添加@RestControllerAdvice注解,作为默认的Controller全局异常处理增强组件,在这个组件中分别对 系统级别未知系统、客户端异常、服务端异常 都分别做了统一处理。

/**
 * 默认的Controller全局异常处理增强组件
 **/
@Slf4j
@RestControllerAdvice
@Order
public class GlobalExceptionHandler {

    // =========== 系统级别未知异常 =========

    @ExceptionHandler(value = Exception.class)
    public JsonResult<Object> handle(Exception e) {
        log.error("[ 系统未知错误 ]", e);
        return JsonResult.buildError(CommonErrorCodeEnum.SYSTEM_UNKNOWN_ERROR);
    }

    // =========== 客户端异常 =========

    /**
     * 1001 HTTP请求方法类型错误
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
    public JsonResult<Object> handle(HttpRequestMethodNotSupportedException e) {
        log.error("[客户端HTTP请求方法错误]", e);
        return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_HTTP_METHOD_ERROR);
    }

    /**
     * 1002 客户端请求体参数校验不通过
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public JsonResult<Object> handle(MethodArgumentNotValidException e) {
        log.error("[客户端请求体参数校验不通过]", e);
        String errorMsg = this.handle(e.getBindingResult().getFieldErrors());
        return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_BODY_CHECK_ERROR.getErrorCode(), errorMsg);
    }

    private String handle(List<FieldError> fieldErrors) {
        StringBuilder sb = new StringBuilder();
        for (FieldError obj : fieldErrors) {
            sb.append(obj.getField());
            sb.append("=[");
            sb.append(obj.getDefaultMessage());
            sb.append("]  ");
        }
        return sb.toString();
    }

    /**
     * 1003 客户端请求体JSON格式错误或字段类型不匹配
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    public JsonResult<Object> handle(HttpMessageNotReadableException e) {
        log.error("[客户端请求体JSON格式错误或字段类型不匹配]", e);
        return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_BODY_FORMAT_ERROR);
    }

    /**
     * 1004 客户端URL中的参数类型错误
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = BindException.class)
    public JsonResult<Object> handle(BindException e) {
        log.error("[客户端URL中的参数类型错误]", e);
        FieldError fieldError = e.getBindingResult().getFieldError();
        String errorMsg = null;
        if (fieldError != null) {
            errorMsg = fieldError.getDefaultMessage();
            if (errorMsg != null && errorMsg.contains("java.lang.NumberFormatException")) {
                errorMsg = fieldError.getField() + "参数类型错误";
            }
        }
        if (errorMsg != null && !"".equals(errorMsg)) {
            return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_PATH_VARIABLE_ERROR.getErrorCode(), errorMsg);
        }
        return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_PATH_VARIABLE_ERROR);
    }

    /**
     * 1005 客户端请求参数校验不通过
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public JsonResult<Object> handle(ConstraintViolationException e) {
        log.error("[客户端请求参数校验不通过]", e);
        Iterator<ConstraintViolation<?>> it = e.getConstraintViolations().iterator();
        String errorMsg = null;
        if (it.hasNext()) {
            errorMsg = it.next().getMessageTemplate();
        }
        if (errorMsg != null && !"".equals(errorMsg)) {
            return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_PARAM_CHECK_ERROR.getErrorCode(), errorMsg);
        }
        return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_PARAM_CHECK_ERROR);
    }


    /**
     * 1006 客户端请求缺少必填的参数
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public JsonResult<Object> handle(MissingServletRequestParameterException e) {
        log.error("[客户端请求缺少必填的参数]", e);
        String errorMsg = null;
        String parameterName = e.getParameterName();
        if (!"".equals(parameterName)) {
            errorMsg = parameterName + "不能为空";
        }
        if (errorMsg != null) {
            return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_PARAM_REQUIRED_ERROR.getErrorCode(), errorMsg);
        }
        return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_PARAM_REQUIRED_ERROR);
    }

    // =========== 服务端异常 =========

    /**
     * 2001 业务方法参数检查不通过
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = IllegalArgumentException.class)
    public JsonResult<Object> handle(IllegalArgumentException e) {
        log.error("[业务方法参数检查不通过]", e);
        return JsonResult.buildError(CommonErrorCodeEnum.SERVER_ILLEGAL_ARGUMENT_ERROR);
    }

    /**
     * 系统自定义业务异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = BaseBizException.class)
    public JsonResult<Object> handle(BaseBizException e) {
        log.error("[ 业务异常 ]", e);
        return JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());
    }

其次是BaseBizException自定义基础业务异常类

/**
 * 基础自定义业务异常
 */
public class BaseBizException extends RuntimeException {

    /**
     * 默认错误码
     */
    private static final String DEFAULT_ERROR_CODE = "-1";

    private String errorCode;

    private String errorMsg;

    public BaseBizException(String errorMsg) {
        super(errorMsg);
        this.errorCode = DEFAULT_ERROR_CODE;
        this.errorMsg = errorMsg;
    }

    public BaseBizException(String errorCode, String errorMsg) {
        super(errorMsg);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public BaseBizException(BaseErrorCodeEnum baseErrorCodeEnum) {
        super(baseErrorCodeEnum.getErrorMsg());
        this.errorCode = baseErrorCodeEnum.getErrorCode();
        this.errorMsg = baseErrorCodeEnum.getErrorMsg();
    }

    public BaseBizException(String errorCode, String errorMsg, Object... arguments) {
        super(MessageFormat.format(errorMsg, arguments));
        this.errorCode = errorCode;
        this.errorMsg = MessageFormat.format(errorMsg, arguments);
    }

    public BaseBizException(BaseErrorCodeEnum baseErrorCodeEnum, Object... arguments) {
        super(MessageFormat.format(baseErrorCodeEnum.getErrorMsg(), arguments));
        this.errorCode = baseErrorCodeEnum.getErrorCode();
        this.errorMsg = MessageFormat.format(baseErrorCodeEnum.getErrorMsg(), arguments);
    }

    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }
}

异常错误码枚举抽象定义

/**
 * 异常错误码枚举抽象定义
 **/
public interface BaseErrorCodeEnum {

    String getErrorCode();

    String getErrorMsg();
}

系统通用的业务异常错误码枚举

/**
 * 系统通用的业务异常错误码枚举
 * @version 1.0
 */
public enum CommonErrorCodeEnum implements BaseErrorCodeEnum {

    // =========== 系统级别未知异常 =========

    /**
     * 系统未知错误
     */
    SYSTEM_UNKNOWN_ERROR("-1", "系统未知错误"),

    // =========== 客户端异常 =========

    /**
     * 客户端HTTP请求方法错误
     * org.springframework.web.HttpRequestMethodNotSupportedException
     */
    CLIENT_HTTP_METHOD_ERROR("1001", "客户端HTTP请求方法错误"),

    /**
     * 客户端request body参数错误
     * 主要是未能通过Hibernate Validator校验的异常处理
     * 

* org.springframework.web.bind.MethodArgumentNotValidException */ CLIENT_REQUEST_BODY_CHECK_ERROR("1002", "客户端请求体参数校验不通过"), /** * 客户端@RequestBody请求体JSON格式错误或字段类型错误 * org.springframework.http.converter.HttpMessageNotReadableException *

* eg: * 1、参数类型不对:{"test":"abc"},本身类型是Long * 2、{"test":} test属性没有给值 */ CLIENT_REQUEST_BODY_FORMAT_ERROR("1003", "客户端请求体JSON格式错误或字段类型不匹配"), /** * 客户端@PathVariable参数错误 * 一般是类型不匹配,比如本来是Long类型,客户端却给了一个无法转换成Long字符串 * org.springframework.validation.BindException */ CLIENT_PATH_VARIABLE_ERROR("1004", "客户端URL中的参数类型错误"), /** * 客户端@RequestParam参数校验不通过 * 主要是未能通过Hibernate Validator校验的异常处理 * javax.validation.ConstraintViolationException */ CLIENT_REQUEST_PARAM_CHECK_ERROR("1005", "客户端请求参数校验不通过"), /** * 客户端@RequestParam参数必填 * 入参中的@RequestParam注解设置了必填,但是客户端没有给值 * javax.validation.ConstraintViolationException */ CLIENT_REQUEST_PARAM_REQUIRED_ERROR("1006", "客户端请求缺少必填的参数"), // =========== 服务端异常 ========= /** * 通用的业务方法入参检查错误 * java.lang.IllegalArgumentException */ SERVER_ILLEGAL_ARGUMENT_ERROR("2001", "业务方法参数检查不通过"), ; private String errorCode; private String errorMsg; CommonErrorCodeEnum(String errorCode, String errorMsg) { this.errorCode = errorCode; this.errorMsg = errorMsg; } @Override public String getErrorCode() { return errorCode; } public void setErrorCode(String errorCode) { this.errorCode = errorCode; } @Override public String getErrorMsg() { return errorMsg; } public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } }

在业务代码的开发中,可以直接抛出这个异常类,也可以在具体的业务代码工程新建一个类继承BaseBizException,然后抛出自定义的异常类。比如如在客户模块中,新建一个CustomerBizException自定义类,然后继承BaseBizException,


public class CustomerBizException extends BaseBizException {

    public CustomerBizException(String errorMsg) {
        super(errorMsg);
    }

    public CustomerBizException(String errorCode, String errorMsg) {
        super(errorCode, errorMsg);
    }

    public CustomerBizException(BaseErrorCodeEnum baseErrorCodeEnum) {
        super(baseErrorCodeEnum);
    }

    public CustomerBizException(String errorCode, String errorMsg, Object... arguments) {
        super(errorCode, errorMsg, arguments);
    }

    public CustomerBizException(BaseErrorCodeEnum baseErrorCodeEnum, Object... arguments) {
        super(baseErrorCodeEnum, arguments);
    }
}
/**
 * 异常错误码枚举值
 * 前三位代表服务,后三位代表功能错误码
 */
public enum CustomerErrorCodeEnum implements BaseErrorCodeEnum {

    CUSTOMER_AUDIT_REPEAT("200000", "客服审核重复"),
    ;

    private String errorCode;

    private String errorMsg;

    CustomerErrorCodeEnum(String errorCode, String errorMsg) {
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    @Override
    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    @Override
    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }

}

最后在业务代码中抛出 CustomerBizException即可。如下:

@RestController
@RequestMapping("/exception")
public class ExceptionController {

    @GetMapping("/test")
    public JsonResult<String> exceptionTest(String num) {
        if (num.equals("22")) {
            throw new CustomerBizException(CustomerErrorCodeEnum.CUSTOMER_AUDIT_REPEAT);
        }
    }

image.png

spring:
  # 出现错误时, 直接抛出异常(便于异常统一处理,否则捕获不到404)
  mvc:
    throw-exception-if-no-handler-found: true
  # 不要为工程中的资源文件建立映射
  web:
    resources:
      add-mappings: false

如果你是Spring Boot 2.4.0 以下的版本,配置需要这样写

spring:
  # 出现错误时, 直接抛出异常(便于异常统一处理,否则捕获不到404)
  mvc:
    throw-exception-if-no-handler-found: true
  # 不要为工程中的资源文件建立映射
  resources:
    add-mappings: false

https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.4.0-Configuration-Changelog

有了这个全局异常处理,就有多种玩法了。可以写一个参数的校验的工具类。比如这样子的,不用自己写if…return;
统一异常处理 GlobalExceptionHandler_第2张图片

欢迎评论私信交流!!

也可以看看sprainkle大佬的这两篇文章
https://www.jianshu.com/p/3f3d9e8d1efa
https://www.jianshu.com/p/179daa24ef52

你可能感兴趣的:(java,spring,spring,boot)