spring boot 异常处理

127 阅读3分钟
本文已参与「新人创作礼」活动,一起开启掘金创作之路。
spring boot 异常处理

程序在运行过程中,由于某些原因(操作不当或其他外部原因)导致程序执行发生意外,导致程序提前终止。

通过上面的内容,我们了解到,异常就是程序执行过程中发生了意外。而这些意外可以按照其严重性以及我们对于意外的处理能力分成不同的类型,下面我们就一起看看异常有哪些类型吧。

Java 中有非常完整的异常机制,所有的异常类型都有一个共同 ——Throwable。Throwable 下面有两个分支一个是 Error(致命异常),另一个是 Exception(非致命异常)。

Error

Error 比较特殊,它有两个子类:OutOfMemoryError 和 StackOverflowError。

checked 异常 与 unchecked 异常

Exception 这一分支下面分为了两个类型:checked 异常和 unchecked 异常。

checked 异常

checked 异常继承自 Exception,需要进行显式处理(try 或者 throws)的异常,否则发生编译错误,IDE 中会有错误提示。Java 中的 checked 是一个庞大的家族,除了 RuntimeException 和 Error 以外的类都属于 checked 异常, IOException,ClassNotFoundException。

unchecked 异常

unchecked 异常也是 Exception 下的分支,并且它们全部是 RuntimeException 的子类。这类异常是我们日常开发中真正需要特别关心的(Error 我们的程序处理不了,checked 异常必须按规定处理),而我们在程序中编写的自定义异常,通常来说也应该继承自 RuntimeException(这里需要注意:Spring 的事务管理只有遇到 RuntimeException 的异常是才会回滚)。

需要注意的是,unchecked 异常中有一些异常是我们可以通过编写更健壮的代码来避免的,没错,就是避免,可以做到完全避免。这类异常包括最常见的 NullPointerException、IndexOutOtBoundsException 等,需要避免这类异常的出现并不难,只需要我们在调用对象方法、获取集合元素时先做安全检查即可(比如先判断 person 对象是否为 null,在调用 person.getName () 方法)

全局异常捕获

Spring Boot 中进行全局异常捕获非常简单,其核心就是一个注解

@ControllerAdvice/@RestControllerAdvice。两者的区别类似 @Controller 与 @RestControllerAdvice,这里就不过多赘述了。我们直接看代码:

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Result handle(Exception e) {
        Result result = new Result();
        result.setCode(MessageEnum.UNKNOW_ERROR.getCode());
        result.setMessage(e.getMessage() == null ? MessageEnum.UNKNOW_ERROR.getMessage() : e.getMessage());
        log.error(e.getMessage(),e);
        return result;
    }
    
    @ExceptionHandler(ApiException.class)
    public Result handle(ApiException e) {
        Result result = new Result();
        result.setCode(e.getCode());
        result.setMessage(e.getMessage());
        log.error(e.getMessage(),e);
        return result;
    }
}

GlobalExceptionHandler 的代码很简单,核心逻辑就是捕获异常,然后将错误信息进行封装,最后以 JSON 格式返回给前端。这里我们只是粗略的对 APIException 和 Exception 进行了分别捕获

辅助类

为了让我们的代码更加的优雅,我们需要添加两个辅助类 ——MessageEnum 和 Result。

错误消息封装

MessageEnum 类封装了错误信息和错误代码,将它们集中起来统一管理为的是更好的应对将来的变化。假如程序中有 100 处都使用了 “操作成功!” 这句话作为 message 的话,我们就要写 100 遍。

public enum MessageEnum {
    UNKNOW_ERROR(-1, "未知错误!"),
    ERROR(500, "系统错误"),
    SUCCESS(0, "操作成功!"),
    ;
    private Integer code;
    private String message;

    public Integer getCode() {
        return code;
    }
    
    public String getMessage() {
        return message;
    }
    
    MessageEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}

接口返回值统一结构

Result 类的作用是让我们的接口返回值变得更加优雅,不管什么接口返回值都是三大件:code、message 和 业务数据。

public class Result<T> {

    private Integer code;
    
    private String message;
    
    private T data;
    
    public static Result sucess() {
        return sucess(null);
    }
    
    public static Result sucess(Object data) {
        Result result = new Result();
        result.setMessage(MessageEnum.SUCCESS.getMessage());
        result.setCode(MessageEnum.SUCCESS.getCode());
        result.setData(data);
        return result;
    }
    
    public static Result error(Integer code, String message) {
        Result result = new Result();
        result.setMessage(message);
        result.setCode(code);
        return result;
    }


    // getter and setter
    ......

}

异常接口

@Api
@RestController
@RequestMapping("/exception")
public class ExceptionController {

    @GetMapping("/apiexception")
    public Result apiException() {
        throw new ApiException(MessageEnum.ERROR);
    }
    
    @GetMapping("/runtimeexception")
    public Result runtimeException() {
        throw new RuntimeException();
    }
}