异常、异常处理和统一异常处理
日常求赞,感谢老板。欢迎关注公众号:其实是白羊。干货持续更新中......
废话不多说,先来几个基础垫吧垫吧。
一、异常
顾名思义,异常就是不正常呗,这是一种现象,也是Java为我们提供的程序安全退出的通道。一旦出现异常,异常处理机制会将代码执行交给异常处理器,而不再执行原有方法。
为了描述不同的这种不正常现象,我们定义了各种各样的异常类型。
首先来张图:
1)Throwable
没错它属所有异常和错误的“祖宗”,下面介绍几个常用的方法:
- getMessage():获取detail message即相关的错误描述信息。
- toString():返回包含异常类名+getMessage()。
- printStackTrace():打印详细异常信息和异常抛出路径(不推荐使用,可使用log代替)。
2)Error
Error是Throwable的子类之一,包含的方法主要来自于继承自Throwable的那些。Error我们开发者接触到的不多,一般都是和虚拟机相关的问题,如:系统崩溃、虚拟机错误、系统资源如内存不足等,如:OutOfMemoryError等。Error靠程序自身是无法解决的,一般JVM会选择终止程序。
3)Exception
Exception也是Throwable的子类之一。相较于Error而言,但是它可是和我们开发者有很大的关系,也经常会遇到他或他的子类,Exception的子类是用来抽象主要都是程序自身出现类问题,我们开发者需要关注和处理这些问题,保证程序业务正常。
Exception拥有大量的子类,但是大致分为两种:
- RuntimeException以及他的子类:这类异常都属于不可检查异常(unchecked exceptions)
- 除去1中的非运行时异常:这类异常属于可检查异常(checked exceptions)
不可检查异常:程序经过编译时,编译器不会要求对此类异常进行处理(throws/try-catch)。RuntimeException以及他的子类和Error都属于不可检查异常,对于RuntimeException的子类我们开发者要特别注意,因为编译器不会提示我们,我们要给与合适的处理(但像空指针、数组下标越界等异常都可以通过代码规避掉,还有些如NumberFormatException等在不确定的情况下要使用异常处理机制哦,举例:在解析JSON字符串形式的数字时,可能存在数字格式错误)。可检查异常:程序经过编译时,编译器会要求对此类异常进行处理(throws/try-catch)。Exception子类中除去RuntimeException以及他的子类的异常都是可检查异常。处理方式也就是:throws/try-catch,下面会细讲。
二、异常处理
在出现可检查异常和可能出现运行时异常时,需要采用异常处理机制。常见的异常处理机制:
-
使用throws向方法栈上层抛出:
public void test() thros NumberFormatException{ //TODO //抛出一个可检查异常或可能抛出运行时异常 }
- 使用try-catch处理相应的异常:执行记录日志、返回给前端数据等操作(后面也会介绍使用统一异常处理来解决对异异常的处理)。
之前写的关于异常处理的运行流程(https://blog.csdn.net/zll_zll...)在这里既不赘述了。
三、统一异常处理
上面的内容都是偏向于理论,这里我们讨论下,实际项目中对于异常的处理方式。
而且在JavaWeb应用中,一旦发生异常,正常的代码逻辑就不能执行了,而去执行异常处理:
我们想要的:
- 前后端分离时:我们要返回给用户固定格式的包含错误信息的数据,有好的提示用户。
- 前后端未分离时:我们要跳转到错误页面(对用户友好),而不是默认情况下的直接把错误信息打印在页面上。
为了实现上面的:
- 发生可检查异常时:如果发生在非Controller层,我们一般会使用throws来向上抛出异常,知道Controller层,如果前后端分离那就返回固定数据,未分离则跳转到错误页面。但是会写很多的try-catch代码,不仅不美观不易读、而且不规范难维护(很难保证每个开发人员在catch里的处理都很规范)。
- 对于不可检查异常(运行时异常)来说:要么只能靠祈祷每个开发人员都能写出完美的代码逻辑,保证不发生运行时异常(显然不可能),要么就在每个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 allErrors = bindingResult.getAllErrors();
List 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)
欢迎关注公众号:「其实是白羊」干货持续更新中......