转载

异常、异常处理和统一异常处理

异常、异常处理和统一异常处理

欢迎关注公众号:其实是白羊。干货持续更新中......

异常、异常处理和统一异常处理

废话不多说,先来几个基础垫吧垫吧。

一、异常

顾名思义,异常就是不正常呗,这是一种现象,也是Java为我们提供的程序安全退出的通道。一旦出现异常,异常处理机制会将代码执行交给异常处理器,而不再执行原有方法。

为了描述不同的这种不正常现象,我们定义了各种各样的异常类型。

首先来张图:

异常、异常处理和统一异常处理

1)Throwable

没错它属所有异常和错误的“祖宗”,下面介绍几个常用的方法:

  1. getMessage():获取detail message即相关的错误描述信息。
  2. toString():返回包含异常类名+getMessage()。
  3. printStackTrace():打印详细异常信息和异常抛出路径(不推荐使用,可使用log代替)。

2)Error

Error是Throwable的子类之一,包含的方法主要来自于继承自Throwable的那些。Error我们开发者接触到的不多,一般都是和虚拟机相关的问题,如:系统崩溃、虚拟机错误、系统资源如内存不足等,如:OutOfMemoryError等。Error靠程序自身是无法解决的,一般JVM会选择终止程序。

3)Exception

Exception也是Throwable的子类之一。相较于Error而言,但是它可是和我们开发者有很大的关系,也经常会遇到他或他的子类,Exception的子类是用来抽象主要都是程序自身出现类问题,我们开发者需要关注和处理这些问题,保证程序业务正常。

Exception拥有大量的子类,但是大致分为两种:

  1. RuntimeException以及他的子类:这类异常都属于 不可检查异常 (unchecked exceptions)
  2. 除去1中的非运行时异常:这类异常属于 可检查异常 (checked exceptions)
不可检查异常 :程序经过编译时,编译器不会要求对此类异常进行处理(throws/try-catch)。RuntimeException以及他的子类和Error都属于不可检查异常,对于RuntimeException的子类我们开发者要特别注意,因为编译器不会提示我们,我们要给与合适的处理(但像空指针、数组下标越界等异常都可以通过代码规避掉,还有些如NumberFormatException等在不确定的情况下要使用异常处理机制哦,举例:在解析JSON字符串形式的数字时,可能存在数字格式错误)。

可检查异常:程序经过编译时,编译器会要求对此类异常进行处理(throws/try-catch)。Exception子类中除去RuntimeException以及他的子类的异常都是可检查异常。处理方式也就是:throws/try-catch,下面会细讲。

二、异常处理

在出现 可检查异常 和可能出现 运行时异常 时,需要采用异常处理机制。常见的异常处理机制:

  1. 使用throws向方法栈上层抛出:

    public void test() thros NumberFormatException{
        //TODO
        //抛出一个可检查异常或可能抛出运行时异常
    }
  2. 使用try-catch处理相应的异常:执行记录日志、返回给前端数据等操作(后面也会介绍使用统一异常处理来解决对异异常的处理)。

    之前写的关于异常处理的运行流程( https://blog.csdn.net/zll_zll... )在这里既不赘述了。

三、统一异常处理

上面的内容都是偏向于理论,这里我们讨论下,实际项目中对于异常的处理方式。

而且在JavaWeb应用中,一旦发生异常,正常的代码逻辑就不能执行了,而去执行异常处理:

我们想要的:

  1. 前后端分离时:我们要返回给用户固定格式的包含错误信息的数据,有好的提示用户。
  2. 前后端未分离时:我们要跳转到错误页面(对用户友好),而不是默认情况下的直接把错误信息打印在页面上。

为了实现上面的:

  1. 发生可检查异常时:如果发生在非Controller层,我们一般会使用throws来向上抛出异常,知道Controller层,如果前后端分离那就返回固定数据,未分离则跳转到错误页面。但是会写很多的try-catch代码,不仅不美观不易读、而且不规范难维护(很难保证每个开发人员在catch里的处理都很规范)。
  2. 对于不可检查异常(运行时异常)来说:要么只能靠祈祷每个开发人员都能写出完美的代码逻辑,保证不发生运行时异常(显然不可能),要么就在每个Controller层方法里都加入try-catch保证万一发生异常能有完成上面的操作(和1中面临的困难一样了)。

那么怎么才能,既能保证实现我们的预期,又能规避上面操作的缺点呢?

那就是做统一异常处理。

实现的方式/思路:

1)AOP方式:

使用Around Advice(环绕通知),在调用原方法的语句上加上try-catch,完成异常的捕获、日志记录等其他操作。

@Around(value = "execution(public * *Controller.*(..))")
  public BaseResult catchException(ProceedingJoinPoint joinPoint) {
    try {
      //统一返回数据类型(前后端为分离时直接跳转页面即可)
      BaseResult baseResult = new BaseResult(SUCCESS);
      baseResult.setData(joinPoint.proceed());
      return baseResult;
    } catch (Throwable e) {
      //捕获异常
      String className = joinPoint.getTarget().getClass().getName();
      String methodName = joinPoint.getSignature().getName();
      //打上LOG
      logger.error(className + ":" + methodName + ":" + e);
      return new BaseResult(FAIL);
    }
  }

怎么样?够不够惊艳?什么,还嫌太麻烦?

2)@ControllerAdvice/@RestControllerAdvice+@ExceptionHandler

@Slf4j
@ControllerAdvice
@ResponseBody
//或者@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * @valid参数校验异常处理
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultData validationException(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        if (bindingResult.hasErrors()) {
            List<ObjectError> allErrors = bindingResult.getAllErrors();
            List<String> messages = new ArrayList<>();
            allErrors.forEach(p->{
                FieldError fieldError = (FieldError) p;
                log.error("参数格式错误:参数对象:{};字段:{};错误原因:{}", fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage());
                messages.add(fieldError.getDefaultMessage());
            });
            return ResultData.error(StringUtils.join(messages,","));
        }
        return ResultData.error("参数格式错误");
    }

    /**
     * 全局异常处理
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public ResultData globalExceptionHandler(Exception e) {
        //MyBaseException自定义异常
        if (e instanceof MyBaseException) {
            //自定义异常
            log.error("自定义异常",e);
            return ResultData.error(e.getMessage());
        } else {
            //其他异常
            log.error("未知异常", e);
            //给用户有好的提示(不能直接把异常信息传回前端)
            return ResultData.error("系统开小差了");
        }
    }
}

注意 @ExceptionHandler标识的方法的顺序和拦截的顺序一致,如果异常背前面的捕获了,那么后面的就能捕获了,所以具体的异常要放在前面。

上面的代码来自我自己的开源项目(地址: https://gitee.com/zhanglinlu/... )中的com.zll.dms.aop.GlobalExceptionHandler,需要的可以参考下。

四、最后

点个赞啊亲

如果你认为本文对你有帮助,可以「在看/转发/赞/star」,多谢

如果你还发现了更好或不同的想法,还可以在留言区一起探讨下

更多项目:gitee地址( https://gitee.com/zhanglinlu )

欢迎关注公众号:「其实是白羊」干货持续更新中......

异常、异常处理和统一异常处理

异常、异常处理和统一异常处理

原文  https://segmentfault.com/a/1190000022462506
正文到此结束
Loading...