java异常处理以及统一异常(结果)处理

无论你的代码写得多么无懈可击,也不可能完全避免意外发生。而我们能做的是,在意外发生以后将影响降到最低,使用更加温和的方式将问题反馈出来,让程序不至于直接崩溃。要达到这个目的,我们需要进行异常处理。在进行异常处理之前,我们需要对Java中的异常有一个简单的了解。

一 异常体系

简单来说,异常就是程序运行时遇到的我们预想之外的情况,而这些意外情况可以按照其严重性及我们对意外的处理能力分成不同的类型。Java异常体系如图所示。
在这里插入图片描述
Java中有非常完整的异常机制,所有的异常类型都有一个共同的“祖先”— —Throwable。由图可以看出,Throwable下面有两个分支:一个是Error,另一个是Exception。

Error
Error 属 于 非 常 严 重 的 系 统 错 误 , 如 OutOfMemoryError 和StackOverflowError,类似于现实世界中的地震、台风等不可抗力。一旦这类问题发生,我们基本上就束手无策了,能做的通常是预防和事后补救。

Exception
Exception 属 于 我 们 能 够 处 理 的 范 畴 , 如 NullPointerException 和FileNotFoundException 。 Exception 还 可 以 进 一 步 细 分 为 受 检 异 常(checked)和非受检异常(unchecked)。

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

checked异常比Error更可控一些,虽然我们不能避免这类异常的发生,但是因为编译器的强制要求,我们必须对这类异常进行显式处理,所以即使发生了checked异常,程序也不会因此崩溃。
好比下雨导致原定的室外活动受到影响,但我们可以选择雨停了再举行,或者找一个合适的室内场所来举行。类似地,当程序读取一个文件时,如果发现文件不存在,那么我们可以等一下再试(可能文件还没生成),或者直接返回一个默认的内容。

unchecked异常
unchecked异常是最容易掌控的,甚至可以通过良好的编码习惯来避免(没错 ,就是避免) , 比 如 , NullPointerException 、IndexOutOtBoundsException等。好比我们可以通过培养良好的习惯来避免生活中的很多不必要的麻烦,例如,我们可以提前出门,以避免因为堵车而赶不上飞机。同样地,在使用一个对象前,先判断该对象是否为null,就可以避免NullPointerException的发生。因为Error并不是我们能够处理的,所以一般我们所说的异常指的是Exception,unchecked异常指的是RuntimeException及其子类,checked异常指的是Exception下的其他子类。

二 全局异常处理

现在我们从理论层面对异常有了很全面的了解,接下来动手实践一下全局异常处理。

全局异常捕获
在Spring Boot中进行全局异常捕获非常简单,其核心就是一个注解— —@ ControllerAdvice/ @ RestControllerAdvice 。 两 者 的 区 别 类 似 于 @Controller@RestControllerAdvice,这里就不再赘述了。示例代码如下:


import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;


@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Result<Boolean> globalException(Exception e) {
        Result<Boolean> result = new Result<>();
        result.setCode(MessageEnum.ERROR.getCode());
        result.setMessage(e.getMessage() == null ? MessageEnum.ERROR.getMessage() : e.getMessage());
        log.error(e.getMessage(),e);
        return result;
    }

    @ExceptionHandler(ApiException.class)
    public Result<Boolean> apiException(ApiException e) {
        Result<Boolean> 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类
MessageEnum类封装了错误信息和错误代码,并将它们集中起来统一管理,以更好地应对将来的变化。假如程序中有100处都使用了“操作成功!”这句话作为message,我们就要写100遍。而如果有一天,产品经理说“4个字太多了”,让我们改成“成功!”,就需要将那100个“操作成功!”修改成“成功!”,太令人崩溃了。示例代码如下:

import lombok.Getter;


@Getter
public enum MessageEnum {
    ERROR(500, "系统错误"),
    SUCCESS(0, "操作成功!"),
    ;
    private final Integer code;
    private final String message;

    MessageEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}

集中管理的好处显而易见,如果产品经理提出上面的需求,那么我们只需要在这个类中去掉两个字即可。

Result类
Result类的作用是让接口返回值变得更加优雅。无论什么接口返回值都是“三大件”— —code、message和data(业务数据)。示例代码如下:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;


@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {

    private Integer code;

    private String message;

    private T data;

    public static <T> Result<T> success() {
        return success(null);
    }

    public static <T> Result<T> success(T data) {
        return new Result<>(MessageEnum.SUCCESS.getCode(), MessageEnum.SUCCESS.getMessage(), data);
    }

    public static <T> Result<T> error() {
        return error(MessageEnum.ERROR);
    }

    public static <T> Result<T> error(MessageEnum messageEnum) {
        return new Result<>(messageEnum.getCode(),messageEnum.getMessage(),null);
    }

    public static <T> Result<T> error(String message) {
        return error(message, MessageEnum.ERROR.getCode());
    }

    protected static <T> Result<T> error(String message,Integer code) {
        return new Result<>(code,message,null);
    }
}

效果
统一结构
先将原来的接口改造成统一结构的返回值,即使用Result类封装返回值:
在这里插入图片描述
改造前的返回值:
在这里插入图片描述
改造后的返回值:
在这里插入图片描述
经过对比可知,改造前,接口返回值的外层结构是随着接口不同而不同的,而改造后,不管什么接口,返回值的外层永远都是固定的3个字段:code、message和data。

统一异常处理
做好上面的准备工作后,我们写一个抛出异常的接口:


import com.shuijing.boot.exception.common.ApiException;
import com.shuijing.boot.exception.common.MessageEnum;
import com.shuijing.boot.exception.common.Result;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/exception")
@Api(value = "异常",tags = "异常")
public class ExceptionController {
    @GetMapping("/runtimeexception")
    public Result<Boolean> runtimeException() {
        throw new RuntimeException();
    }
}

没有全局异常处理的错误返回值:
在这里插入图片描述
开启全局异常处理的返回值:
在这里插入图片描述
开启全局异常处理以后,即使出现异常,接口返回值的结构也是稳定的,这样对于调用方(前端、移动端、其他系统)来说是更加友好的。我们按照约定好的结构处理数据,可以大大地降低接口处理的复杂度

三 异常与意外

程序中的异常就像生活中的意外,有些我们无能为力,有些我们可以制定处理措施,有些则可以避免。人们总说“意外和明天,你永远不知道哪个会先来”,虽然我们无法左右谁先来,但我们能做的是:把握住自己能够掌控的,尽力改善我们能影响的,坦然接受我们无能为力的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

麦芽糖0219

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值