目录
1 理解 Throwable、Exception、Error 的设计和分类
3 自定义全局异常处理(springBoot)
本文主要想解释两个问题:
1、对比Exception 和 Error
2、运行时异常和一般的异常有什么区别?
3、如何处理运行时异常?
回答1:Exception 和 Error 都是继承了 Throwable 类,Java 中只有 Throwable 类的实例才可以被抛出,或者被捕获。Error 顾名思义,是错误。它会导致程序处于非正常、不可恢复的状态。Exception 是程序中可以预料到的情况,可以被程序捕获并做相应的处理。
回答2:Exception 又分为可检查异常和不可检查异常。
非运行时异常是指通过编译可以发现的异常,比如IOException;Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
运行时异常就是运行时异常,包括NullPointerException、ArrayIndexOutOfBoundsException等。编译阶段无法捕获的异常,在运行时由系统抛出。
出现运行时异常后,如果没有捕获处理这个异常(即没有catch),系统会把异常一直往上层抛,一直到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。也就是说,你如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。
如果不想终止,则必须捕获所有的运行时异常,不让这个处理线程退出。
1 理解 Throwable、Exception、Error 的设计和分类
常见的RuntimeException(运行时异常):
IndexOutOfBoundsException(下标越界异常)
NullPointerException(空指针异常)
NumberFormatException (String转换为指定的数字类型异常)
ArithmeticException -(算术运算异常 如除数为0)
ArrayStoreException - (向数组中存放与声明类型不兼容对象异常)
SecurityException -(安全异常)
IOException(其他异常)
FileNotFoundException(文件未找到异常。)
IOException(操作输入流和输出流时可能出现的异常。)
EOFException (文件已结束异常)
2 异常处理机制
捕捉并处理知道如何处理的异常,而抛出不知道如何处理的异常。当Java内置的异常都不能明确的说明异常情况的时候,需要创建自己的异常。
2.1 异常抛出 throws
通过Java的throw语句抛出异常。从方法中抛出的任何异常都必须使用throws子句。
throws Exception 有两种用法:
第一种是方法后面,表示:本方法不处理异常,交给调用方处理(如果你不希望异常层层往上抛,你就要用throws Exception) ,在调用该方法时,必须加上try...catch才可以(你加上throw exception。调用的地方就必须try catch,不然编译都不过。这样代码就更健壮了)。
如果没有加throws Exception的方法抛异常了,调用方也没有处理,异常就会一层一层抛出,最终交给JVM去处理的话,程序会中断,如果在程序中捕获的话,不会中断程序。
第二种是throw new Exception 表示人为的抛出一个异常。
2.2 异常捕获 catch
捕捉异常通过try-catch语句或者try-catch-finally语句实现。
try {
// 可能会发生异常的程序代码
} catch (Type1 id1){
// 捕获并处置try抛出的异常类型Type1
} catch (Type2 id2){
//捕获并处置try抛出的异常类型Type2
}
Java通过异常类描述异常类型,对于有多个catch子句的异常程序而言,应该尽量将捕获底层异常类的catch子句放在前面,同时尽量将捕获相对高层的异常类的catch子句放在后面。否则,捕获底层异常类的catch子句将可能会被屏蔽。
例如:RuntimeException异常类包括运行时各种常见的异常,ArithmeticException类和ArrayIndexOutOfBoundsException类都是它的子类。因此,RuntimeException异常类的catch子句应该放在 最后面,否则可能会屏蔽其后的特定异常处理或引起编译错误。
总结: 异常要及早捕获、延迟处理。要尽量捕获具体类型的异常。
3 自定义全局异常处理
但是在实际项目中,我们都不可能在每个接口实现里面都try-catch一下。一般在业务中,都会自定义自己的运行时异常。因为笔者的项目是基于springBoot,所以在项目中对异常进行全局统一处理。
项目需求:1、前后端分离,只需要返回apiException; 2、不只是404或者500的异常;3、捕捉到异常之后需要做特殊化的处理。
方式:基于@ExceptionHandle @ControllerAdvice注解的Controller层的全局异常统一处理。
在spring 3.2中,新增了@ControllerAdvice 注解,这个注解注释的类实现控制器增强的功能,在其中可以定义@ExceptionHandler、@InitBinder、@ModelAttribute,并应用到所有@RequestMapping注释的方法中。
1、首先自定义全局异常处理类:
/**
* 系统通用的业务Exception,最终会转化成JsonResult形式
*
*/
public class ApiException extends RuntimeException { // 继承RuntimeException
private int status;
private String message;
/**
* 自定义业务异常,最终会转化成JsonResult形式
* @param status 对应JsonResult.status
* @param message 对应JsonResult.message
*/
public ApiException(int status, String message) {
this.status = status;
this.message = message;
}
/**
* 通过通用错误来构建异常,最终会转化成JsonResult形式
* @param apiError 通用的错误
*/
public ApiException(ApiError apiError) {
this(apiError.value(), apiError.getMessage());
}
public ApiException(Throwable cause) {
super(cause);
this.status = ApiError.SERVER_ERROR.value();
this.message = ApiError.SERVER_ERROR.getMessage();
}
public int getStatus() {
return status;
}
@Override
public String getMessage() {
return message;
}
}
2、@ExceptionHandle 实现自定义全局类处理
@ControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE)
public class AlarmExceptionHandler {
private Logger logger = LoggerFactory.getLogger(AlarmExceptionHandler.class);
@ResponseBody
@ExceptionHandler
public JsonResult processException(Exception ex) {
if (null == ex) {
logger.warn("拦截器抛出空异常");
} else if (null != ex && (ex instanceof BindException)) {
logger.warn(ex.getMessage(), ex);
} else {
logger.error(ex.getMessage(), ex);
}
JsonResult jsonResult;
if (ex instanceof ApiException) {
ApiException error = ((ApiException) ex);
jsonResult = JsonResult.buildFailResult(error.getStatus(), "server error reason:" + error.getMessage(), null);
} else if (ex instanceof BindException) {
BindException c = (BindException) ex;
List<FieldError> errors = c.getBindingResult().getFieldErrors();
StringBuilder errorMsg = new StringBuilder();
for (FieldError error : errors) {
errorMsg.append(error.getField()).append(":").append(error.getDefaultMessage()).append(";");
}
jsonResult = JsonResult.buildFailResult(ApiError.PARAMS_ERROR.value(), errorMsg.toString(), null);
} else {
jsonResult = JsonResult.buildFailResult(ApiError.SERVER_ERROR.value(), "server error reason:" + ex.getMessage(), null);
}
return jsonResult;
}
}
经过测试发现可以捕获controller层的异常,前提是controller 层不对异常进行处理。如果在service 中抛出异常,也不做处理的话,那么在这里也可以捕获的到。
因为不仅仅有404和500类型的错误,还需要自定义接口错误码。
public enum ApiError {
// ---------------------------------------------
// 通用错误码
// ---------------------------------------------
SERVER_ERROR(101, "Server error."),
PARAMS_ERROR(102, "Parameter error: %s"),
TICKET_ERROR(104, "Ticket error"),
SIGN_ERROR(105, "Sign error"),
;
ApiError(int code, String message) {
this.code = code;
this.message = message;
}
private final int code;
private final String message;
private Object[] parameters = null;
public ApiError withParams(final Object... parameters) {
this.parameters = parameters;
return this;
}
/**
* 返回枚举值
* @return
*/
public int value() {
return this.code;
}
/**
* 错误信息格式,如果有参数拼接参数
* @return
*/
public String getMessage() {
return (null == parameters) ? message : String.format(message, parameters);
}
/**
* 根据枚举值返回实例
* @param value
* @return
*/
public static ApiError valueOf(int value) {
if (value >= 1 && value <= ApiError.values().length) {
return ApiError.values()[value - 1];
} else {
return null;
}
}
@Override
public String toString() {
return "[" + code + "]: " + message;
}
public Object[] getParameters() {
return parameters;
}
}
上面的@ExceptionHandle 还可以拦截到 BindException 类型的错误,这种异常主要是由于 在使用注解 @Validator 时捕获的异常,可以参见另外一篇文章: @NotNull 和 @NotEmpty 和@NotBlank 区别。
以上。?