关于java异常

一般来说,当程序遇到错误,应该尽量处理错误,然后按照正确的姿势退出程序。否则,你可以想象一下当客户使用系统的时候,突然系统异常,直接报了一堆用户不知道的代码提示,这是多么难堪的局面。

一、异常的结构

异常有一个共同的祖先Throwable,其关系结构如下

aop java 全局异常 java全局异常捕获_子类

如图所见,Throwable有两个重要的子类,Error和Exception。

  1. Error
    Error代表的错误是程序无法处理的,这表示程序中出现了严重的问题,比如OutOfMemoryError、ThreadDeath,当出现这些错误,JVM一般会选择线程终止,因为他们超出了应用程序控制和处理的能力之外。
  2. Exception
    Exception代表的异常是程序可以处理的。Exception一般分为RuntimeException及其子类和此外的Exception及其子类两部分。运行时异常RuntimeException这部分,比如NullPointerException,这些异常是不可查异常,也就是程序中可以选择捕获处理,也可以不处理;非运行时异常这部分比如SQLException是可查异常,也即是必须捕获处理或者向上抛出。
  3. 注:
    a. 可查异常checked exceptions
    是编译器要求必须处理的异常,要么用try-catch捕获处理,要么向上抛出,否则编译不通过。这些异常包括了除了RuntimeException和RuntimeException子类以外的这部分Exception。
    b. 不可查异常unchecked excetions
    是编译器不要求处理的异常,包括运行时异常RuntimeException及其子类和Error。

二、异常处理

  1. 假如通过throw语句抛出异常,则该方法应该同时使用throws子句声明抛出的异常类型。如
public void test() throws RuntimeException {
    throw new MyRuntimeException();
}

假如方法没有显示使用throw抛出异常,但是仍然可能抛出异常,也可以使用throws子句声明抛出异常的类型。

  1. 假如一个方法使用throws抛出异常,则方法的调用者必须检查处理异常或者向上抛出。
  2. 如果是不可查异常,可以不适用throws子句声明抛出异常,也能通过编译,但是出现异常时仍然会被系统抛出。
  3. 假如是可查异常,那么必须使用try-catch捕获处理或者继续向上抛出,否则不能通过编译。
  4. 假如方法覆盖,则必须抛出和被覆盖方法抛出的异常是同一个异常或其子类异常。
  5. 假如所有方法都层层向上抛出,最终会由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());
    }
}
  1. 假如上面程序都顺利执行,testB这里没有处理出错,则执行顺序是testA->testB->testC->testD
  2. 假如程序在testB方法中出现了处理错误,则testA方法是顺利执行的,testB方法则是执行失败,然后直接跳到catch里面了,不会执行testC方法,执行完catch的内容后,会执行finally的testD方法。

四、 常用的异常处理设计

  1. 异常枚举类
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;
    }
}
  1. 基础异常类
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;
    }
}
  1. 异常子类
public class SQLException extends BaseException {

    public SQLException () {
        super(ExceptionCode.DATABASE_OPERATION_EXCEPTION);
    }
}
  1. 全局异常处理
@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);
    } 
}
  1. 基础控制器类
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;
    }
}