基于spring boot实现一个全局异常处理器

 更新时间:2023年09月10日 16:39:54   作者:Java小卷  
在项目开发中,我们可以基于spring boot提供的切面特性,来很轻松的实现全局异常的处理,所以本文主要为大家介绍了如何基于spring boot实现一个全局异常处理器,有需要的可以参考下

还记得前面我们的实现逻辑,在service层如果有校验失败我们会抛出异常,而controller中我们对其并没有处理,实际上spring boot有自己全局的错误处理形式。我们可以基于spring boot提供的切面特性,来很轻松的实现全局异常的处理,现在我们约定,只要后台逻辑处理失败的情况,我们都将抛出异常,包括controller中的处理逻辑。

实现全局异常处理器

现在我们就来编写全局异常处理器。

package com.xiaojuan.boot.common.web.support;
import ...
import static com.xiaojuan.boot.common.enums.BusinessError.*;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /** 维护一个错误码与http状态码的映射 */
    private Map<Integer, HttpStatus> httpStatusMap;
    @PostConstruct
    private void init() {
        httpStatusMap = new HashMap<>();
        httpStatusMap.put(NO_LOGIN.getValue(), HttpStatus.UNAUTHORIZED);
        httpStatusMap.put(HAS_NO_ROLE.getValue(), HttpStatus.FORBIDDEN);
    }
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<Response<?>> handleException(BusinessException ex) {
        log.error(ex.getMessage(), ex);
        // 默认服务器端错误,返回500状态码
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        if (!StringUtils.isEmpty(ex.getErrCode()) && httpStatusMap.containsKey(ex.getErrCode())) {
            status = httpStatusMap.get(ex.getErrCode());
        }
        return ResponseEntity.status(status).body(Response.fail(ex.getMessage(), ex.getErrCode(), ex.getData()));
    }
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Response<?>> handleException(Exception ex) {
        log.error(ex.getMessage(), ex);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Response.fail("小卷生鲜电商系统异常:" + ex.getMessage()));
    }
}

代码详解

这里我们用了@RestControllerAdvice注解对所有的@RestController修饰的controller都进行拦截,只要抛出了异常,就交给这个处理器来处理。

这里要说下响应的http状态码,一般都返回200,某些情况下我们将返回一个可以让前端特别处理的状态码,比如用户未登录的请求被拦截了我们可以返回401,用户登录了却没有权限的操作,我们返回403,这样前端对这块直接从http状态就能识别请求结果的状态。而对于我们业务开发来说,关注的是业务处理的错误码,在这里我们和请求http状态码之间做了一个映射转换,维护在httpStatusMap成员变量中。

接下来,我们写了两个异常处理方法,它们都必须用@ExceptionHandler注解来指定我们要处理的异常的类型,这里我们只考虑两种异常:我们自定义的业务异常BusinessException和最大的Exception

对于BusinessException,我们要考虑HttpStatus的判断逻辑,如果我们将某些业务错误码和http状态码做了映射,那么对于这些业务错误码我们就取映射到的http状态码来返回,否则我们返回服务器端错误的500状态码。

注意处理方法最后的返回结果,我们使用的是ResponseEntity<Response<?>>,spring web模块提供的ResponseEntity可以帮助我们按照指定的http状态码并解析指定格式的内容体(Spring Boot默认配置json形式)进行前端响应。而要响应的对象就是我们之前定义的Response,只不过我们将字段errCode的类型从原来的String改成了Integer,因为业务错误码我们就定义为数值型,同样的还有BusinessException类中的errCode字段类型的调整。

除了BusinessException,我们只处理最大的Exception,http状态码固定为500,错误消息也统一以固定的形式开头。

错误码枚举类

将应用中业务错误码我们定义在一个枚举中进行维护:

package com.xiaojuan.boot.common.enums;
import ...
@Getter
@AllArgsConstructor
public enum BusinessError {
    PARAM_INVALID("参数校验失败", 10001),
    RECORD_EXISTS("后台记录已存在", 10002),
    HAS_NO_ROLE("用户未授权,不能访问", 403),
    NO_LOGIN("请先登录再操作", 401);
    private final String label;
    private final Integer value;
}

这里我们给出默认的说明,一般在抛出业务异常时我们可以指定更具体的错误,而不会使用这里默认的错误消息。

service层抛出异常调整

现在我们对service层抛出异常做下调整,一般我们只要传入错误信息来构造BusinessException。有些时候我们还可以传入错误码来进一步区分这些错误。

实现自己的Assert工具

既然我们先前定义了自己的BusinessException,并且在其中维护了业务错误码errCode,就没必要用spring的Assert工具了,因为它抛出来的是IllegalStateException异常,最终被我们全局异常处理器以最大的Exception的处理逻辑进行包装响应结果,自然“参数校验失败”的错误码类型就丢了,因此这里我们基于spring对Assert的实现,我们包装为自己的Assert

package com.xiaojuan.boot.util;
import ...
public class Assert {
    public static void hasText(@Nullable String text, String message) {
        if (!StringUtils.hasText(text)) {
            throw new BusinessException(message, BusinessError.PARAM_INVALID.getValue());
        }
    }
}

controller层抛出异常

最后我们将原来预留todo注释的地方改为抛出异常的形式:

最后,我们再基于test.http对抛出异常的测试场景进行测试,看是否达到预期的http状态码和json结构,这个大家自行测试。

到此这篇关于基于spring boot实现一个全局异常处理器的文章就介绍到这了,更多相关spring boot全局异常处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java设计模式之外观模式解析

    Java设计模式之外观模式解析

    这篇文章主要介绍了Java设计模式之外观模式解析,外观模式提供了一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层接口,让子系统更容易使用,需要的朋友可以参考下
    2024-01-01
  • java音乐播放器实现代码

    java音乐播放器实现代码

    这篇文章主要介绍了java音乐播放器的实现代码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06
  • 学习Java之如何正确地跳出循环结构

    学习Java之如何正确地跳出循环结构

    我们在利用循环执行重复操作的过程中,存在着一个需求:如何中止,或者说提前结束一个循环,所以就给大家讲解一下,如何在java代码中返回一个结果,如何结束和跳出一个循环,需要的朋友可以参考下
    2023-05-05
  • Java本地方法(JNA)详解及常见问题

    Java本地方法(JNA)详解及常见问题

    JNA(Java Native Access)是一个开源Java框架,用于无需编写JNI代码即可动态访问本地系统库如Windows的dll,它允许Java程序直接调用本地方法,这篇文章主要介绍了Java本地方法(JNA)详解及常见问题,需要的朋友可以参考下
    2024-09-09
  • 100行java写的微信跳一跳辅助程序

    100行java写的微信跳一跳辅助程序

    本篇文章给大家分享了用java写的一个微信跳一跳辅助脚本程序,有兴趣的朋友参考学习下。
    2018-01-01
  • java实战之桌球小游戏

    java实战之桌球小游戏

    这篇文章主要为大家详细介绍了java实战之桌球小游戏,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-07-07
  • SpringBoot2 task scheduler 定时任务调度器四种方式

    SpringBoot2 task scheduler 定时任务调度器四种方式

    这篇文章主要介绍了SpringBoot2 task scheduler 定时任务调度器四种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • 每日六道java新手入门面试题,通往自由的道路--线程池

    每日六道java新手入门面试题,通往自由的道路--线程池

    这篇文章主要为大家分享了最有价值的6道线程池面试题,涵盖内容全面,包括数据结构和算法相关的题目、经典面试编程题等,对hashCode方法的设计、垃圾收集的堆和代进行剖析,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • 一篇文章彻底拆解Java HashMap扩容机制

    一篇文章彻底拆解Java HashMap扩容机制

    在Java中HashMap是一个非常常用的数据结构,基于哈希表实现,它通过键值对的形式存储数据,这篇文章主要介绍了Java HashMap扩容机制的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2026-04-04
  • Java之jdbc连接mysql数据库的方法步骤详解

    Java之jdbc连接mysql数据库的方法步骤详解

    这篇文章主要介绍了Java之jdbc连接mysql数据库的方法步骤详解,JCBC技术是java开发必备的只是,jdbc连接mysql数据库,这是一个比较简单的方法,有兴趣的可以了解一下
    2020-07-07

最新评论