关于java异常
一般来说,当程序遇到错误,应该尽量处理错误,然后按照正确的姿势退出程序。否则,你可以想象一下当客户使用系统的时候,突然系统异常,直接报了一堆用户不知道的代码提示,这是多么难堪的局面。
一、异常的结构
异常有一个共同的祖先Throwable,其关系结构如下
如图所见,Throwable有两个重要的子类,Error和Exception。
- Error
Error代表的错误是程序无法处理的,这表示程序中出现了严重的问题,比如OutOfMemoryError、ThreadDeath,当出现这些错误,JVM一般会选择线程终止,因为他们超出了应用程序控制和处理的能力之外。 - Exception
Exception代表的异常是程序可以处理的。Exception一般分为RuntimeException及其子类和此外的Exception及其子类两部分。运行时异常RuntimeException这部分,比如NullPointerException,这些异常是不可查异常,也就是程序中可以选择捕获处理,也可以不处理;非运行时异常这部分比如SQLException是可查异常,也即是必须捕获处理或者向上抛出。 - 注:
a. 可查异常checked exceptions
是编译器要求必须处理的异常,要么用try-catch捕获处理,要么向上抛出,否则编译不通过。这些异常包括了除了RuntimeException和RuntimeException子类以外的这部分Exception。
b. 不可查异常unchecked excetions
是编译器不要求处理的异常,包括运行时异常RuntimeException及其子类和Error。
二、异常处理
- 假如通过throw语句抛出异常,则该方法应该同时使用throws子句声明抛出的异常类型。如
public void test() throws RuntimeException {
throw new MyRuntimeException();
}
假如方法没有显示使用throw抛出异常,但是仍然可能抛出异常,也可以使用throws子句声明抛出异常的类型。
- 假如一个方法使用throws抛出异常,则方法的调用者必须检查处理异常或者向上抛出。
- 如果是不可查异常,可以不适用throws子句声明抛出异常,也能通过编译,但是出现异常时仍然会被系统抛出。
- 假如是可查异常,那么必须使用try-catch捕获处理或者继续向上抛出,否则不能通过编译。
- 假如方法覆盖,则必须抛出和被覆盖方法抛出的异常是同一个异常或其子类异常。
- 假如所有方法都层层向上抛出,最终会由JVM处理,JVM将会打印异常消息和堆栈信息。
三、当发生异常时程序的执行
public void test() throws RuntimeException {
try {
testA(new A());
testB(new B());//处理错误
testC(new C());
} catch (Exception e) {
throw new MyRuntimeException();
} finally {
testD(new D());
}
}
- 假如上面程序都顺利执行,testB这里没有处理出错,则执行顺序是testA->testB->testC->testD
- 假如程序在testB方法中出现了处理错误,则testA方法是顺利执行的,testB方法则是执行失败,然后直接跳到catch里面了,不会执行testC方法,执行完catch的内容后,会执行finally的testD方法。
四、 常用的异常处理设计
- 异常枚举类
public enum ExceptionCode {
SUCCESS(0, "操作成功!"),
PARAMETER_INCORRECT(100001,"参数不正确"),
UNAUTHENTICATED(100002, "未经认证,请先登录"),
UNAHTUORIZED(100003, "未经授权,请联系客服"),
DATABASE_OPERATION_EXCEPTION(200001, "数据库操作异常"),
WECHAT_REDIRECT_EXCEPTION(900001, "微信重定向异常"),
WECHAT_FETCH_ACCESS_TOKEN_EXCEPTION(900002, "微信获取 Access Token 异常"),
SAB_USER_NOT_FOUND(700001, "用户不存在"),
SAB_ROLE_NOT_FOUND(700002, "角色不存在"),
SAB_DEPARTMENT_NOT_FOUND(700003, "部门不存在"),
LOGIN_USER_NOT_FOUNT(700004, "用户不存在"),
LOGIN_USER_PASSWORD_WRONG(700005, "用户密码错误"),
LOGIN_USER_AUTHENTICATION(700006, "用户认证失败"),
LOGIN_FAILED(700007, "登录失败");
private int code;
private String msg;
ExceptionCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
- 基础异常类
public class BaseException extends RuntimeException {
protected int code;
protected String msg;
public BaseException(ExceptionCode businessCode) {
super(businessCode.getMsg());
this.code = businessCode.getCode();
this.msg = businessCode.getMsg();
}
public BaseException(int code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
- 异常子类
public class SQLException extends BaseException {
public SQLException () {
super(ExceptionCode.DATABASE_OPERATION_EXCEPTION);
}
}
- 全局异常处理
@ControllerAdvice
public class GlobalExceptionHandler extends BaseController {
@ResponseStatus(HttpStatus.EXPECTATION_FAILED)
@ResponseBody
@ExceptionHandler(value = BaseException.class)
public JSONObject handleBaseException(BaseException e) {
return buildResponse(e.getCode(), e.getMsg(), null);
}
/*
* SQLException是BaseException的子类,假如发生SQLException异常
* 根据异常匹配规则是优先捕捉到SQLException异常的,所以是进入这个异常处理方法
*/
@ResponseStatus(HttpStatus.EXPECTATION_FAILED)
@ResponseBody
@ExceptionHandler(value = SQLException.class)
public JSONObject handleSQLException(SQLException e) {
return buildResponse(e.getCode(), e.getMsg(), null);
}
}
- 基础控制器类
public class BaseController {
public JSONObject buildResponse() {
return buildResponse(ExceptionCode.SUCCESS, null);
}
public JSONObject buildResponse(Object data) {
return buildResponse(ExceptionCode.SUCCESS, data);
}
public JSONObject buildResponse(ExceptionCode businessCode, Object data) {
return buildResponse(businessCode.getCode(), businessCode.getMsg(), data);
}
public JSONObject buildResponse(int code, String msg, Object data) {
JSONObject response = new JSONObject();
response.put("code", code);
response.put("msg", msg);
response.put("data", data);
response.put("time", System.currentTimeMillis());
return response;
}
}