一文带你搞懂java中的统一异常处理

 更新时间:2026年02月12日 09:30:57   作者:麦芽糖0219  
这篇文章主要为大家详细介绍了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();
    }
}

没有全局异常处理的错误返回值:

开启全局异常处理的返回值:

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

三 异常与意外

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

以上就是一文带你搞懂java中的统一异常处理的详细内容,更多关于java统一异常处理的资料请关注脚本之家其它相关文章!

相关文章

  • Spring注解配置IOC,DI的方法详解

    Spring注解配置IOC,DI的方法详解

    这篇文章主要为大家介绍了vue组件通信的几种方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-01-01
  • 你可知HashMap为什么是线程不安全的

    你可知HashMap为什么是线程不安全的

    这篇文章主要介绍了你可知HashMap为什么是线程不安全的,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • mybatis入门_动力节点Java学院整理

    mybatis入门_动力节点Java学院整理

    这篇文章主要为大家详细介绍了mybatis入门的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-09-09
  • 基于java实现租车管理系统

    基于java实现租车管理系统

    这篇文章主要为大家详细介绍了基于java实现租车管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-12-12
  • 详解springboot的多种配置方式

    详解springboot的多种配置方式

    这篇文章主要介绍了springboot的多种配置方式,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • Java链表使用解读

    Java链表使用解读

    Java中的链表(LinkedList)是一种动态数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的引用,LinkedList实现了List和Deque接口,支持高效的插入和删除操作,Java提供了.standard.util.LinkedList类来实现链表
    2025-01-01
  • 详解SpringBoot实现ApplicationEvent事件的监听与发布

    详解SpringBoot实现ApplicationEvent事件的监听与发布

    这篇文章主要为大家详细介绍了SpringBoot如何实现ApplicationEvent事件的监听与发布,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-03-03
  • 掌握模块化开发Spring Boot子模块使用技巧

    掌握模块化开发Spring Boot子模块使用技巧

    这篇文章主要为大家介绍了掌握模块化开发Spring Boot子模块使用技巧详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06
  • 浅谈Zookeeper开源客户端框架Curator

    浅谈Zookeeper开源客户端框架Curator

    这篇文章主要介绍了浅谈Zookeeper开源客户端框架Curator的相关内容,具有一定参考价值,需要的朋友可以了解下。
    2017-10-10
  • 浅谈slf4j中的桥接器是如何运作的

    浅谈slf4j中的桥接器是如何运作的

    这篇文章主要介绍了slf4j中的桥接器是如何运作的,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12

最新评论