全局异常捕获

全局异常,冲!

到了睡觉的时候,还是没有太急着下班,把全局异常讲完!

在程序中我们常常会主动或者被动的向外抛出异常,但是如果程序一直向外抛出异常,而不去对其做处理,那用户体验感将会很低,所以我们常常会在代码中使用 try、catch 来对异常进行捕获,我们会大概猜测此处可能会抛出的异常类型,然后使用catch块 对其进行捕获。

但是,还有很多异常我们无法预判到呀!总不能事无巨细的都考虑到。另一方面,当我们在代码中写满 try、catch............那是多么丑陋!为了程序代码的可读性,我们需要将抛出的异常进行一个统一的处理——全局异常处理

一、异常处理注解

/**
* @RestControllerAdvice 捕获@controller 标识的类抛出的异常
* @RestControllerAdvice = @ResponseBody + @ControllerAdvice
* 定义在类名头
*/
@RestControllerAdvice

@ControllerAdvice是处理响应为View或者String的控制器的异常,通常用于后端页面渲染的场景。而@RestControllerAdvice则是处理响应为Json或者Xml等格式的控制器的异常,通常用于前后端分离的场景。

此外,@RestControllerAdvice注解还有一个特殊的作用:当使用@ExceptionHandler注解处理异常时,@RestControllerAdvice注解可以使其返回值直接转换成Json格式的数据,而不需要通过ViewResolver转换成视图。

因此,当使用Spring Boot开发Restful API时,通常会使用@RestControllerAdvice注解进行全局异常处理。

@ExceptionHandler
/**
如果方法上标注了 @ExceptionHandler 注解并指定了对应的异常类型,则 Spring 会自动捕获该异常,并调用标注了 @ExceptionHandler 注解的方法来处理该异常。
*/
​
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public JSONObject handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
                                                          HttpServletRequest request)
    {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
        return ResponseResult.failure("请求不支持!");
    }
​

以上的两个注解都是在全局异常类中使用。

二、为什么需要全局异常类

全局异常处理类是为了处理整个系统中的异常情况,而不是仅仅在每个请求或方法内部处理异常。通过全局异常处理类,可以将相同的异常处理方式应用于整个系统,减少代码重复,提高代码的可维护性和可读性。

另外,全局异常处理类还可以帮助我们优雅地处理异常信息,并且可以提供友好的提示信息避免向用户抛出过于具体的错误信息。同时,全局异常处理类也有助于记录异常信息,方便开发人员进行排查和调试

三、怎么优雅的抛出异常

既然都有了全局异常处理,那我程序就直接抛出异常就好了,不需要写那么多行的try

1、Assert断言

使用assert可以将不符合条件的问题都进行抛出,异常类型会随着使用的方法不同而变化。

明确一点,assert断言一般用于在开发环境中使用,在生产环境中应该尽量使用Preconditions来进行抛出异常。

jdk1.8之后assert在默认情况下是开启的。

Java自带的工具类,可以直接使用。

Preconditions 更适合用于公共方法中对参数的检查,而 assert 更适合用于程序内部的调试和测试。

Assert.isTrue(boolean condition):判断条件是否为true。
​
Assert.isFalse(boolean condition):判断条件是否为false。
​
Assert.isNull():判断对象是否为null。
​
Assert.notNull():判断对象是否不为null。
​
Assert.notEmpty():判断是否为null,长度是否0
​

2、Preconditions

// 检查 boolean 类型的表达式是否为 true,如果不是,则抛出 IllegalArgumentException 异常,并将指定的错误消息添加到异常信息中。
checkArgument(boolean expression, Object errorMessage):
​
// 检查对象是否为 null,如果为 null,则抛出 NullPointerException 异常,并将指定的错误消息添加到异常信息中。
checkNotNull(T reference, Object errorMessage):
​
// 检查 boolean 类型的表达式是否为 true,如果不是,则抛出 IllegalStateException 异常,并将指定的错误消息添加到异常信息中。
checkState(boolean expression, Object errorMessage):
​
// 检查下标是否在 [0, size) 范围内,如果不是,则抛出 IndexOutOfBoundsException 异常,并将指定的错误消息添加到异常信息中。
checkElementIndex(int index, int size, Object errorMessage):
​
// 检查下标是否在 [0, size] 范围内,如果不是,则抛出 IndexOutOfBoundsException 异常,并将指定的错误消息添加到异常信息中。
checkPositionIndex(int index, int size, Object errorMessage):
​
// 检查下标范围 [start, end] 是否在 [0, size] 范围内,如果不是,则抛出 IndexOutOfBoundsException 异常。
checkPositionIndexes(int start, int end, int size):

四、定义全局异常类

@Slf4j
@Component
@RestControllerAdvice
@ConditionalOnWebApplication
@ConditionalOnMissingBean(UnifiedExceptionHandler.class)
public class UnifiedExceptionHandler {
    /**
     * 生产环境
     */
    private final static String ENV_PROD = "prod";
​
    @Autowired
    private UnifiedMessageSource unifiedMessageSource;
​
    /**
     * 当前环境
     */
    @Value("${spring.profiles.active}")
    private String profile;
    
    /**
     * 获取国际化消息
     *
     * @param e 异常
     * @return
     */
    public String getMessage(BaseException e) {
        String code = "response." + e.getResponseEnum().toString();
        String message = unifiedMessageSource.getMessage(code, e.getArgs());
​
        if (message == null || message.isEmpty()) {
            return e.getMessage();
        }
​
        return message;
    }
​
    /**
     * 业务异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = BusinessException.class)
    public ErrorResponse handleBusinessException(BaseException e) {
        log.error(e.getMessage(), e);
​
        return new ErrorResponse(e.getResponseEnum().getCode(), getMessage(e));
    }
​
    /**
     * 自定义异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = BaseException.class)
    public ErrorResponse handleBaseException(BaseException e) {
        log.error(e.getMessage(), e);
​
        return new ErrorResponse(e.getResponseEnum().getCode(), getMessage(e));
    }
​
    /**
     * Controller上一层相关异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler({
            NoHandlerFoundException.class,
            HttpRequestMethodNotSupportedException.class,
            HttpMediaTypeNotSupportedException.class,
            MissingPathVariableException.class,
            MissingServletRequestParameterException.class,
            TypeMismatchException.class,
            HttpMessageNotReadableException.class,
            HttpMessageNotWritableException.class,
            // BindException.class,
            // MethodArgumentNotValidException.class
            HttpMediaTypeNotAcceptableException.class,
            ServletRequestBindingException.class,
            ConversionNotSupportedException.class,
            MissingServletRequestPartException.class,
            AsyncRequestTimeoutException.class
    })
​
    public ErrorResponse handleServletException(Exception e) {
        log.error(e.getMessage(), e);
        int code = CommonResponseEnum.SERVER_ERROR.getCode();
        try {
            ServletResponseEnum servletExceptionEnum = ServletResponseEnum.valueOf(e.getClass().getSimpleName());
            code = servletExceptionEnum.getCode();
        } catch (IllegalArgumentException e1) {
            log.error("class [{}] not defined in enum {}", e.getClass().getName(), ServletResponseEnum.class.getName());
        }
​
        if (ENV_PROD.equals(profile)) {
            // 当为生产环境, 不适合把具体的异常信息展示给用户, 比如404.
            code = CommonResponseEnum.SERVER_ERROR.getCode();
            BaseException baseException = new BaseException(CommonResponseEnum.SERVER_ERROR);
            String message = getMessage(baseException);
            return new ErrorResponse(code, message);
        }
​
        return new ErrorResponse(code, e.getMessage());
    }
​
​
    /**
     * 参数绑定异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = BindException.class)
    public ErrorResponse handleBindException(BindException e) {
        log.error("参数绑定校验异常", e);
​
        return wrapperBindingResult(e.getBindingResult());
    }
​
    /**
     * 参数校验异常,将校验失败的所有异常组合成一条错误信息
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ErrorResponse handleValidException(MethodArgumentNotValidException e) {
        log.error("参数绑定校验异常", e);
​
        return wrapperBindingResult(e.getBindingResult());
    }
​
    /**
     * 包装绑定异常结果
     *
     * @param bindingResult 绑定结果
     * @return 异常结果
     */
    private ErrorResponse wrapperBindingResult(BindingResult bindingResult) {
        StringBuilder msg = new StringBuilder();
​
        for (ObjectError error : bindingResult.getAllErrors()) {
            msg.append(", ");
            if (error instanceof FieldError) {
                msg.append(((FieldError) error).getField()).append(": ");
            }
            msg.append(error.getDefaultMessage() == null ? "" : error.getDefaultMessage());
​
        }
​
        return new ErrorResponse(ArgumentResponseEnum.VALID_ERROR.getCode(), msg.substring(2));
    }
​
    /**
     * 未定义异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = Exception.class)
    public ErrorResponse handleException(Exception e) {
        log.error(e.getMessage(), e);
​
        if (ENV_PROD.equals(profile)) {
            // 当为生产环境, 不适合把具体的异常信息展示给用户, 比如数据库异常信息.
            int code = CommonResponseEnum.SERVER_ERROR.getCode();
            BaseException baseException = new BaseException(CommonResponseEnum.SERVER_ERROR);
            String message = getMessage(baseException);
            return new ErrorResponse(code, message);
        }
​
        return new ErrorResponse(CommonResponseEnum.SERVER_ERROR.getCode(), e.getMessage());
    }
    
}

参考:

全局异常,冲!.md - 程序员小猪

减少 try catch ,可以这样干! (qq.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值