SpringBoot优雅地处理全局异常,返回前端

news2024/9/28 17:27:17

笔者这边提供了两种处理全局异常的方式。这两种方式各有千秋,都很优雅。至于伙伴们想用哪种方式,那就仁者见仁,智者见智了。

0、公共部分

在介绍异常处理方式前,先定义一些公共的类。这些类在两种处理方式中都会用到。

【自定义业务异常】

/**
 * 自定义业务异常
 */
@Data
public class SunException extends RuntimeException {

    private Integer code;
    private String msg;

    public SunException(SystemEnum systemEnum) {
        this.code = systemEnum.getCode();
        this.msg = systemEnum.getDesc();
    }
    public SunException(BusinessEnum businessEnum) {
        this.code = businessEnum.getCode();
        this.msg = businessEnum.getDesc();
    }

    public SunException(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

【自定义系统枚举】

/**
 * 系统枚举
 */
public enum SystemEnum {
    SUCCESS(0, "success"),
    FAIL(-1, "fail"),
    PARAM_ILLEGAL(100, "参数非法!"),
    SERVICE_TIME_OUT(200, "服务间调用超时"),
    UNEXPECTED_EXCEPTION(500, "系统内部错误,请联系管理员!"),
    OTHER(9999, "Unknown Exception.");

    private Integer code;
    private String desc;

    SystemEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public Integer getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

【自定义业务枚举】

/**
 * 业务类枚举
 *
 * @author: dong
 * @date: 2023/2/12 21:50
 * @since: 1.0
 */
public enum BusinessEnum {
    /**--------用户相关----------**/
    USER_ID_NOT_EXIST(1000, "userId not exist."),

    /**--------任务相关----------**/
    TASK_ID_NOT_EXIST(2000, "taskId not exist."),
    TASK_NAME_NOT_EXIST(2001, "taskName not exist."),
    TASK_TYPE_NOT_EXIST(2002, "taskType not exist."),
    DEADLINE_NOT_EXIST(2003, "deadline not exist."),
    CONTENT_NOT_EXIST(2004, "content not exist.")
    ;

    private Integer code;
    private String desc;

    BusinessEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}

【统一封装响应体】

/**
 * 统一封装响应体
 * @param <T>
 */
public class BaseResult<T> {
    private Integer code;
    private String msg;
    private T data;

    public BaseResult() {
    }

    public BaseResult(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public BaseResult(T data) {
        this.code = SystemEnum.SUCCESS.getCode();
        this.msg = SystemEnum.SUCCESS.getDesc();
        this.data = data;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

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

    public T getData() {
        return data;
    }

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

【响应结果工具类】

/**
 * 响应结果工具类
 */
public class ResultUtil {

    public static <R> BaseResult<R> outSuccess() {
        return new BaseResult<>(SystemEnum.SUCCESS.getCode(), SystemEnum.SUCCESS.getDesc(), null);
    }
    public static <R> BaseResult<R> outSuccess(R data) {
        return new BaseResult<>(data);
    }

    public static <R> BaseResult<R> outFail(String errorMsg) {
        return new BaseResult<>(SystemEnum.FAIL.getCode(), errorMsg, null);
    }
    public static <R> BaseResult<R> outFail(Integer errorCode, String errorMsg) {
        return new BaseResult<>(errorCode, errorMsg, null);
    }
}

方式一、@RestControllerAdvice + @ExceptionHandler

如下所示,

a. 新建一个全局异常处理类,并在类名前加上@RestControllerAdvice注解,该注解可以拦截项目中抛出的异常;

b. 同时在新建一个处理异常的方法,并在方法上加上@ExceptionHandler注解,并在该注解的属性中指定具体的异常。如下代码中指定的具体异常即是 SunException(也就是上一小节中笔者自定义的业务异常)。

c. 在处理异常的方法中,通过ResultUtil.outFail() 方法统一封装返回给前端的响应体。

/**
 * 全局异常处理
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(SunException.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public BaseResult handlerBusinessException(SunException sunException) {
        LOGGER.error("exception happened at {}", sunException.getMsg());
        return ResultUtil.outFail(sunException.getCode(), sunException.getMsg());
    }
}

那么在具体的业务接口中如何抛出异常能被GlobalExceptionHandler所捕获呢?

其实很简单,只要用 throw new SunException(...); 就可以了。注意:这里只能抛出SunException,不能抛出RuntimeException或者任何其他异常。因为@ExceptionHandler已经指定了具体的异常类型。

    @GetMapping("/hello")
    public String sayHello(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new SunException(SystemEnum.PARAM_ILLEGAL);
        }
        return "The sun is rising.";
    }

Swagger测试效果:

方式二、AOP实现

很明显,aop天生就是干这个的料。aop在业务解耦方面简直如鱼得水,像统一打印日志,统一捕获异常等等。talk is cheap,show me the code.

如下代码所示:

a. 新建这个aop监控类,新增切点,切所有模块的Controller层的所有方法;

b. 实现一个环绕通知接口,打印方法入参以及请求结果;

c. 注意代码40行到45行,就是捕获项目中所有的SunException,并封装成统一的响应体返回前端。

/**
 * aop监控类
 **/
@Slf4j
@Aspect
@Component
public class AspectMonitor {

    /**
     * 日志切点,切所有模块controller层中的所有方法
     */
    @Pointcut("execution(* com.bxbro.*..controller..*.*(..))")
    public void logPointCut() {
        // do nothing.
    }


    /**
     * 对接口做统一的日志及异常处理
     * @param pjp
     * @return
     */
    @Around("logPointCut()")
    public Object apiMonitor(ProceedingJoinPoint pjp) {
        Object[] args = pjp.getArgs();
        Object[] arguments = new Object[args.length];
        for (int i=0;i<args.length;i++) {
            // ServletRequest不能序列化,从入参里排除,
            // 否则报异常:java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
            if(args[i] instanceof ServletRequest || args[i] instanceof ServletResponse || args[i] instanceof MultipartFile) {
                continue;
            }
            arguments[i] = args[i];
        }
        log.info("请求入参:{},方法名:{}.{}", JSON.toJSONString(arguments),pjp.getSignature().getDeclaringTypeName(),pjp.getSignature().getName());
        Object result;
        try {
            result = pjp.proceed();
        } catch (Throwable throwable) {
            if (throwable instanceof SunException) {
                SunException sunException = (SunException) throwable;
                return ResultUtil.outFail(sunException.getCode(), sunException.getMsg());
            }
            log.error("接口请求异常:{}", throwable);
            return ResultUtil.outFail("系统内部错误" + throwable.getMessage());
        }
        log.info("请求结果:{}", JSON.toJSONString(result));
        return result;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/366565.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

jupyter的安装步骤

1.安装python文件 首先去官网python去下载python的安装包&#xff0c;点击donwload,选择合适的系统。这里我是windown系统&#xff0c;点击进去&#xff0c;如图找到有installer的去下载。不建议下载最新版本的&#xff0c;会有兼容问题。 2.安装python 点击第二个选项是自己配…

深度学习如何训练出好的模型

深度学习在近年来得到了广泛的应用&#xff0c;从图像识别、语音识别到自然语言处理等领域都有了卓越的表现。但是&#xff0c;要训练出一个高效准确的深度学习模型并不容易。不仅需要有高质量的数据、合适的模型和足够的计算资源&#xff0c;还需要根据任务和数据的特点进行合…

【2023/图对比/增强】MA-GCL: Model Augmentation Tricks for Graph Contrastive Learning

如果觉得我的分享有一定帮助&#xff0c;欢迎关注我的微信公众号 “码农的科研笔记”&#xff0c;了解更多我的算法和代码学习总结记录。或者点击链接扫码关注【2023/图对比/增强】MA-GCL: Model Augmentation Tricks for Graph Contrastive Learning 【2023/图对比/增强】MA-…

备份策略从“3-2-1”到“4-3-2-1”

在数据存储备份领域&#xff0c;说起“3-2-1”备份策略真是无人不知、如雷贯耳&#xff01;笔者也经常把“3-2-1”备份策略挂在嘴边&#xff0c;那简直就是确保数据安全的圭臬&#xff01;但是&#xff0c;最近有一位读者问我&#xff1a;“3-2-1”备份策略的出处在哪里&#x…

MySQL - 多表查询

目录1. 多表查询示例2. 多表查询分类2.1 等/非等值连接2.1.1 等值连接2.1.2非等值连接2.2 自然/非自然连接2.3 内/外连接2.3.1 内连接2.3.2 外连接3.UNION的使用3.1 合并查询结果3.1.1 UNION操作符3.1.2 UNION ALL操作符4. 7种JOIN操作多表查询&#xff0c;也称为关联查询&…

LocalDateTime使用

开发中常常需要用到时间&#xff0c;随着jdk的发展&#xff0c;对于时间的操作已经摒弃了之前的Date等方法&#xff0c;而是采用了LocalDateTime方法&#xff0c;因为LocalDateTime是线程安全的。 下面我们来介绍一下LocalDateTime的使用。 时间转换 将字符串转换为时间格式…

五分钟搞懂POM设计模式

今天&#xff0c;我们来聊聊Web UI自动化测试中的POM设计模式。 为什么要用POM设计模式 前期&#xff0c;我们学会了使用PythonSelenium编写Web UI自动化测试线性脚本 线性脚本&#xff08;以快递100网站登录举栗&#xff09;&#xff1a; import timefrom selenium import …

R统计绘图 | 物种组成堆叠柱形图(绝对/相对丰度)

一、数据准备 数据使用的不同处理土壤样品的微生物组成数据&#xff0c;包含物种丰度&#xff0c;分类单元和样本分组数据。此数据为虚构&#xff0c;可用于练习&#xff0c;请不要作他用。 # 1.1 设置工作路径 #knitr::opts_knit$set(root.dir"D:\\EnvStat\\PCA")#…

[python入门(51)] - python时间日期格式time和datetime

目录 ❤ 预备知识 ❤ UTC time Coordinated Universal Time ❤ epoch time ❤ timestamp&#xff08;时间戳&#xff09; ❤ stamptime时间戳 ❤ struct_time时间元组 ❤ format time 格式化时间 ❤ time模块​编辑 ❤ 获取当前时间的方法 ❤ 当传入默认参…

阿里云ECS TOP性能提升超20%!KeenTune助力倚天+Alinux3达成开机即用的全栈性能调优 | 龙蜥技术

文/KeenTune SIG01阿里云 ECS 上售卖页新增“应用加速”功能2023年1月12日 阿里云 ECS 的售卖页有了一些新的变化&#xff0c;在用户选择倚天 Alinux3 新建实例时&#xff0c;多了一个新的选项“应用加速”。这个功能是 阿里云 ECS 基于 KeenTune 提供典型云场景的开机即用的全…

翻转链表k个元素——递归

题目描述25. K 个一组翻转链表难度困难1920收藏分享切换为英文接收动态反馈给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那…

【Python入门第十六天】Python If ... Else

Python 条件和 If 语句 Python 支持来自数学的常用逻辑条件&#xff1a; 等于&#xff1a;a b不等于&#xff1a;a ! b小于&#xff1a;a < b小于等于&#xff1a;a < b大于&#xff1a;a > b大于等于&#xff1a;a > b 这些条件能够以多种方式使用&#xff0c…

再析jvm

前言 希望自己每一次学习都有不同的理解 文章目录前言1. jvm的组成取消永久代使用元空间原因2. 运行时数据区3. 堆栈区别队列和栈&#xff0c;队列先进先出&#xff0c;栈先进后出从栈顶弹出4. GC、内存溢出、垃圾回收4.1 如何确定引用是否会被回收4.1.1 Java中的引用类型4.1.…

OpenStack手动分布式部署Glance【Queens版】

目录 Glance简介 1、登录数据库配置&#xff08;在controller执行&#xff09; 1.1登录数据库 1.2数据库里创建glance 1.3授权对glance数据库的正确访问 1.4退出数据库 1.5创建glance用户密码为000000 1.6增加admin角色 1.7创建glance服务 1.8创建镜像服务API端点 2、安装gla…

LeetCodeHOT100热题02

写在前面 主要是题目太多&#xff0c;所以和前面的分开来记录。有很多思路的图都来源于力扣的题解&#xff0c;如侵权会及时删除。不过代码都是个人实现的&#xff0c;所以有一些值得记录的理解。之前的算法系列参看&#xff1a; 剑指offer算法题01剑指offer算法题02 七、动…

SQL零基础入门学习(八)

SQL零基础入门学习&#xff08;七&#xff09; SQL 连接(JOIN) SQL join 用于把来自两个或多个表的行结合起来。 下图展示了 LEFT JOIN、RIGHT JOIN、INNER JOIN、OUTER JOIN 相关的 7 种用法。 SQL JOIN SQL JOIN 子句用于把来自两个或多个表的行结合起来&#xff0c;基…

TensorBoard自定义修改单条及多条曲线颜色

在深度学习可视化训练过程中&#xff0c;曲线颜色是随机的&#xff0c;想要将好看的曲线颜色图放到论文中&#xff0c;就得自定义曲线颜色&#xff0c;具体方法见下文。 目录一、下载svg文件二、修改svg文件三、修改后曲线颜色对比四、总结一、下载svg文件 在TensorBoard界面中…

webman 连接 oracle

composer require topthink/think-oraclev2.1 配置文件 thinkorm.php return [ default > oracle, connections > [ oracle > [ // 数据库类型 type > oracle, // 服务器地址 hostname > 192…

多语言解决方案

文章目录背景整体方案多语言管理端客户端流水线其他背景 多语言是一个比较麻烦的事情&#xff0c;特别是当 App 比较大的时候&#xff0c;还会涉及到多个部门的开发以及翻译人员&#xff0c;这中间可能会存在比较大的沟通成本&#xff0c;而且还可能会阻塞开发的进度。以下是我…

【JavaEE初阶】第二节.多线程( 进阶篇 ) 锁的优化、JUC的常用类、线程安全的集合类

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、synchronized的优化操作 1.1 锁膨胀/锁升级 1.2 锁消除 1.3 锁粗化二、JUC 2.1 Callable接口 2.2 ReentrantLock类&…