在实际编码工作中,对异常的处理往往是复杂繁琐的,在java中到底有哪些异常,生产环境下又是如何处理的呢?
一.异常分类
在 Java 中所有异常类型都是内置类 java.lang.Throwable 类的子类,即 Throwable 位于异常类层次结构的顶层。Throwable 类是所有异常和错误的父类,下面有 Error 和 Exception 两个子类分别表示错误和异常。
Error 定义了在通常环境下不希望被程序捕获的异常。
Error 类型的异常用于 Java 运行时由系统显示与运行时系统本身有关的错误。堆栈溢出是这种错误的一例(递归代码设计不严谨便会出现这种问题)。这种异常通常是jvm级别的问题,它们通常是灾难性的致命错误,不是程序可以控制的,所以一般面对这种问题需要重构代码结构,而不是通过异常处理解决。
Exception 类用于用户程序可能出现的异常情况,它也是用来创建自定义异常类型类的类。Exception 则分为运行时异常(RuntimeException)和非运行时异常(OtherException),这两种异常有很大的区别,也称为不检查异常(Unchecked Exception)和检查异常(CheckedException)。
运行时异常都是 RuntimeException 类及其子类异常,如 NullPointerException、IndexOutOfBoundsException 等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般由程序逻辑错误引起,程序应该从逻辑角度尽可能避免这类异常的发生。
非运行时异常是指 RuntimeException 以外的异常,类型上都属于 Exception 类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如 IOException、ClassNotFoundException 等以及用户自定义的 Exception 异常,一般情况下不自定义检查异常。
二.异常处理
在 Java 中,捕获异常的基本语法是 try-catch-finally 块。try 块用于包含可能会抛出异常的代码块,catch 块用于捕获并处理异常,finally 块用于清理资源 。在 try 块中,我们可以放入需要检查的代码。如果这些代码抛出了异常,那么就会跳转到对应的 catch 块中,进行异常处理的相关操作。同时,不论是否抛出异常,finally 块中的代码都会被执行。
try {
// 可能会抛出异常的代码
} catch (Exception e) {
// 捕获并处理异常
} finally {
// 清理资源
}
另外,Java 还提供了一种带有异常声明的特殊类型的语法,称为 throw 语法。通过 throw 语法,我们可以在程序中主动抛出一个异常。在项目中,异常如果需要在上层处理的话,则会用到throw。
三.异常统一处理(ExceptionHandler)
在实际生产项目中,不同的异常情况往往设计好了不同的响应码(比如下面的例子)
微服务中使用共通异常处理:任何API产生异常都会在异常类(ExceptipnHandler)中处理。为了实现共通的处理需要使用在共通处理类中使用@ControllerAdvice,这个注解是Spring3.2提供的新注解,它是一个Controller增强器,可对controller中被 @RequestMapping注解的方法加一些逻辑处理。最常用的就是异常处理统一异常处理,需要配合@ExceptionHandler使用。同时,需要使用共通异常处理的controller也要用@Validated注解标记才能实现这个功能。当将异常抛到controller时, 可以对异常进行统一处理,规定返回的格式。
例如为了处理以下异常
HttpMessageNotReadableException 请求体无法被转换为控制器方法所需的对象,json反序列化失败,或json为空。
MethodArgumentTypeMismatchException 请求参数的类型与方法参数类型不一致,提交的表单数据与Controller方法的参数类型不匹配。
MissingRequestHeaderException 请求中缺少必需的头部信息,@RequestHeader中required属性为true,但实际请求中却没有包含该头部信息
MethodArgumentNotValidException和ConstraintViolationException都是用来处理使用注解
@Valid时出现的问题。
将请求体解析并绑定到 java bean 时,如果出错,则抛出 MethodArgumentNotValidException 异常,如@RequestBody
普通参数(非 java bean)校验出错时,会抛出 ConstraintViolationException 异常,如@RequestParam,@RequestHeader
会做如下处理
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Response> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
logger.info("Invalid arguments found : " + ex.getMessage());
// Get the error messages for invalid fields
List<FieldError> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(fieldError -> new FieldError(fieldError.getField(), fieldError.getDefaultMessage()))
.collect(Collectors.toList());
String message = messageSource.getMessage("invalid.data.message", null, LocaleContextHolder.getLocale());
Response response = new Response(false, message)
.setErrors(errors);
ResponseEntity<Response> responseEntity = ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
return responseEntity;
}