如何优雅的处理Spring项目中的异常

什么是异常?

Java中的异常(Exception)又称为例外,是一个在程序执行期间发生的事件,它中断正在执行的程序的正常指令流。
复制代码

而用最通俗的语句就是,在程序执行过程中,出现了中断程序运行的各种情况。例如你正在使用一个计算器的除法功能,用1去除以0,因为0不能作为被除数,所以是一次异常操作。 在程序中,错误可能产生于程序员没有预料到的各种情况,或者超出程序员可控范围的环境,例如用户的坏数据、试图打开一个不存在的文件等等。

这里再做一下java中的异常分类:
复制代码

在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出)。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。

Throwable:有两个重要的子类:Exception(异常)和Error(错误),二者都是Java异常处理的重要子类,各自都包含大量子类。异常和错误的区别是:异常能被程序本身可以处理,错误是无法处理。

Exception(异常):是程序本身可以处理的异常。
Exception类有一个重要的子类RuntimeException。RuntimeException类及其子类表示“JVM常用操作”引发的错误。例如,若试图使用空值对象引用、除数为零或数组越界,则分别引发运行时异常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。
复制代码

Exception(异常)分两大类:运行时异常和非运行时异常(编译异常)。程序中应当尽可能去处理这些异常。

1.运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

2.非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
复制代码

我们异常的处理方式基本秉持一个原则,已知的异常我们要尽量捕获处理,未知的异常向外抛出,对外提供的服务自己处理掉异常,原则上应该减少对外直接暴露异常。 上面都是些理论的介绍,下面是代码部分,直接告诉大家怎么在项目中使用全局异常捕获机制和前端页面进行交互。

首先我们建立一个抽象的基本异常类,实现不同的异常区别处理

public abstract class BaseException extends RuntimeException {

public BaseException(int code) {
    super(String.valueOf(code));
}

public BaseException(int code, Throwable cause) {
    super(String.valueOf(code), cause);
}

public BaseException(String msg) {
    super(msg);
}

public BaseException(String msg, Throwable cause) {
    super(msg, cause);
}

protected BaseException(int code, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
    super(String.valueOf(code), cause, enableSuppression, writableStackTrace);
}
复制代码

}

自定义一个异常类,业务异常全部用这个异常捕获。当然了,你也可以根据自己项目的需要,自定义不同的异常类型。例如说定义一个系统操作异常,数据换算处理异常等等。

public class BusinessException extends BaseException {

public BusinessException(int code) {
    super(code);
}

public BusinessException(int code, Throwable cause) {
    super(code, cause);
}

public BusinessException(String msg, Throwable cause) {
    super(msg, cause);
}

public BusinessException(String msg) {
    super(msg);
}

protected BusinessException(int code, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
    super(code, cause, enableSuppression, writableStackTrace);
}
复制代码

}

public class DBException extends BaseException {

public BusinessException(int code) {
    super(code);
}

public BusinessException(int code, Throwable cause) {
    super(code, cause);
}

public BusinessException(String msg, Throwable cause) {
    super(msg, cause);
}

public BusinessException(String msg) {
    super(msg);
}

protected BusinessException(int code, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
    super(code, cause, enableSuppression, writableStackTrace);
}
复制代码

}

public class ResponseObject {

/**
 * 正常返回的状态码
 */
private static final String NORMAL_CODE = "0";

/**
 * 正常返回的描述
 */
private static final String NORMAL_MSG = "正常";

/**
 * 接口返回码
 */
private String statusCode;

/**
 * 接口返回描述
 */
private String msg;

/**
 * 接口响应的实际数据
 */
private Object data;

public String getStatusCode() {
    return statusCode;
}

public void setStatusCode(String statusCode) {
    this.statusCode = statusCode;
}

public String getMsg() {
    return msg;
}

public void setMsg(String msg) {
    this.msg = msg;
}

public Object getData() {
    return data;
}

public void setData(Object data) {
    this.data = data;
}

public ResponseObject(String statusCode, String msg) {
    this.statusCode = statusCode;
    this.msg = msg;
}
复制代码

}

在@ControllerAdvice注解下的类,里面的方法用@ExceptionHandler注解修饰的方法,会将对应的异常交给对应的方法处理

@ControllerAdvice

public class GlobalExceptionHandler {

/**
 * 业务操作异常
 */
private static final String UNNORMAL_EXCEPTION_CODE = "100001";

/**
 * 数据库查询异常
 */
private static final String DB_EXCEPTION_CODE = "100002";

@ExceptionHandler(value = BusinessException.class)
@ResponseBody
public ResponseObject businessExceptionHandler(HttpServletRequest req, Exception e) {
    String code = UNNORMAL_EXCEPTION_CODE;
    String msg = "业务操作异常";
    return new ResponseObject(code, msg);
}
@ExceptionHandler(value = DBException.class)
@ResponseBody
public ResponseObject businessExceptionHandler(HttpServletRequest req, Exception e) {
    String code = DB_EXCEPTION_CODE;
    String msg = "数据库查询异常";
    return new ResponseObject(code, msg);
}
复制代码

}

延伸一下这两个注解的解释

//该注解作用对象为方法

@Target({ElementType.METHOD}) //在运行时有效

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface ExceptionHandler {

//value()可以指定异常类

Class<? extends Throwable>[] value() default {};
复制代码

}

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

//bean对象交给spring管理生成

@Component

public @interface ControllerAdvice {

@AliasFor("basePackages")

String[] value() default {};

@AliasFor("value")
String[] basePackages() default {};

Class<?>[] basePackageClasses() default {};

Class<?>[] assignableTypes() default {};

Class<? extends Annotation>[] annotations() default {};
复制代码

}

注意

1.不一定必须在 controller 层本身抛出异常才能被 GlobalExceptionHandler 处理,只要异常最后是从 contoller 层抛出去的就可以被全局异常处理器处理。

2.异步方法中的异常不会被全局异常处理。

3.抛出的异常如果被代码内的 try/catch 捕获了,就不会被 GlobalExceptionHandler 处理了。

总结:

优点

减少代码冗余,代码便于维护

缺点

只能处理 controller 层抛出的异常,对例如

Interceptor(拦截器)层的异常、定时任务中的异常、异步方法中的异常,不会进行处理。

猜你喜欢

转载自juejin.im/post/5d478bdc6fb9a06b1e7f2001