SpringBoot教程(九) | SpringBoot统一异常处理

SpringBoot教程(九) | SpringBoot统一异常处理

异常大家应该都很清楚,我们的项目总是不可避免的出现异常,那么应该如何优雅的进行异常处理使我们需要关注的一个问题,合理的异常封装既可以方便前端的处理,也能够简化后端的开发。

一般情况下,我们应该在我们的项目中,根据不同的异常场景,定义不同的异常类型,然后不同的异常类型,返回不同的状态码,然后和前端约定好,根据不同的状态码,做不同的展现。

SpringBoot中为我们提供一个统一的异常处理类,也是利用了AOP的思想,我们可以向外抛出各种类型的异常,然后在这个统一的处理类中,针对每一种不同类型的异常,做不同的数据封装,返回给前端。

代码编写:主要就是通过一个 @ControolerAdvice注解,实现对所有请求的拦截,很像AOP。

(注意,下面的代码仅供展示,如果大家直接粘贴,可能需要引入一些三方jar包才行)

java复制代码@RestControllerAdvice
@Order(1)
public class GlobalExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    public GlobalExceptionHandler() {
    }

    @ExceptionHandler({ParamException.class, MethodArgumentNotValidException.class, ConstraintViolationException.class, BindException.class, HttpMessageNotReadableException.class, MissingServletRequestPartException.class, MissingServletRequestParameterException.class, MultipartException.class})
    public Result<?> paramsExceptionHandler(HttpServletRequest request, Exception e) {
        String msg;
        if (e instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException ex = (MethodArgumentNotValidException)e;
            msg = this.handlerErrors(ex.getBindingResult());
        } else if (e instanceof BindException) {
            BindException ex = (BindException)e;
            msg = this.handlerErrors(ex.getBindingResult());
        } else if (e instanceof ConstraintViolationException) {
            ConstraintViolationException ex = (ConstraintViolationException)e;
            Optional<ConstraintViolation<?>> first = ex.getConstraintViolations().stream().findFirst();
            msg = (String)first.map(ConstraintViolation::getMessage).get();
        } else {
            msg = e.getMessage();
        }

        Result<?> result = Result.error(ResultCode.PARAM_ERROR.getCode(), msg);
        return this.printLogAndReturn(request, result, e);
    }

    private String handlerErrors(BindingResult bindingResult) {
        List<FieldError> errors = bindingResult.getFieldErrors();
        FieldError error = (FieldError)errors.get(0);
        return error.getDefaultMessage();
    }

    @ExceptionHandler({BizException.class})
    public Result<?> bizExceptionHandler(HttpServletRequest request, BizException e) {
        Result<?> result = Result.error(e.getCode() == null ? ResultCode.BIZ_ERROR.getCode() : e.getCode(), e.getMessage());
        return this.printLogAndReturn(request, result, e);
    }

    @ExceptionHandler({HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class})
    public Result<?> httpRequestMethodNotSupportedExceptionHandler(HttpServletRequest request, Exception e) {
        Result<?> result = Result.error(ResultCode.REQ_MODE_NOT_SUPPORTED);
        return this.printLogAndReturn(request, result, e);
    }

    @ExceptionHandler({JSONException.class})
    public Result<?> jsonExceptionHandler(HttpServletRequest request, Exception e) {
        Result<?> result = Result.error(ResultCode.JSON_FORMAT_ERROR);
        return this.printLogAndReturn(request, result, e);
    }

    @ExceptionHandler({DataAccessException.class})
    public Result<?> sqlExceptionHandler(HttpServletRequest request, Exception e) {
        Result<?> result = Result.error(ResultCode.SQL_ERROR);
        return this.printLogAndReturn(request, result, e);
    }

    @ExceptionHandler({Exception.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<?> exceptionHandler(HttpServletRequest request, Exception e) {
        Result<?> result = Result.error(ResultCode.SYS_ERROR);
        return this.printLogAndReturn(request, result, e);
    }

    private Result<?> printLogAndReturn(HttpServletRequest request, Result<?> result, Exception e) {
        String requestUrl = request.getRequestURL().toString() + (StringUtil.isEmpty(request.getQueryString()) ? "" : "?" + request.getQueryString());
        log.error("<-异常返回-> 请求接口:{} | 异常时间:{} | 异常结果:{}", new Object[]{requestUrl, System.currentTimeMillis(), JSON.toJSONString(result)});
        log.error("<--异常堆栈信息-->");
        log.error(Throwables.getStackTraceAsString(e));
        return result;
    }
}

@ExceptionHandler 标识对哪种异常进行拦截。这里可以有我们自己定义的异常。当我们在业务代码中有一些异常处理的时候,我们可以根据具体的业务场景,将其抛出为我们自己定义的异常,然后在统一的异常处理类中,根据不同的异常类型,返回我们统一封装的结果。

大家可以看看上面的代码,对于所有的错误都封装成了Result对象,并且打印了异常的信息。

好的,接下来我们来写一个案例。首先把前面的统一结果的封装加入到项目中

  1. 在exception 自定义一个业务异常类
java复制代码public class BizException extends RuntimeException {
    private Integer code;

    public BizException() {
    }

    public BizException(String message) {
        super(message);
    }

    public BizException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public BizException(ResultCode resultCode) {
        super(resultCode.getMsg());
        this.code = resultCode.getCode();
    }

    public BizException(String message, Throwable cause) {
        super(message, cause);
    }

    public BizException(int code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    }

    public BizException(ResultCode resultCode, Throwable cause) {
        super(resultCode.getMsg(), cause);
        this.code = resultCode.getCode();
    }

    public Integer getCode() {
        return this.code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }
}
  1. 然后在把刚才的异常处理类也加到exception包下。
java复制代码@RestControllerAdvice
@Order(1)
public class GlobalExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    public GlobalExceptionHandler() {
    }


    private String handlerErrors(BindingResult bindingResult) {
        List<FieldError> errors = bindingResult.getFieldErrors();
        FieldError error = (FieldError)errors.get(0);
        return error.getDefaultMessage();
    }

    @ExceptionHandler({BizException.class})
    public Result<?> bizExceptionHandler(HttpServletRequest request, BizException e) {
        Result<?> result = Result.error(e.getCode() == null ? ResultCode.BIZ_ERROR.getCode() : e.getCode(), e.getMessage());
        return this.printLogAndReturn(request, result, e);
    }

    @ExceptionHandler({HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class})
    public Result<?> httpRequestMethodNotSupportedExceptionHandler(HttpServletRequest request, Exception e) {
        Result<?> result = Result.error(ResultCode.REQ_MODE_NOT_SUPPORTED);
        return this.printLogAndReturn(request, result, e);
    }


    @ExceptionHandler({Exception.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<?> exceptionHandler(HttpServletRequest request, Exception e) {
        Result<?> result = Result.error(ResultCode.SYS_ERROR);
        return this.printLogAndReturn(request, result, e);
    }

    private Result<?> printLogAndReturn(HttpServletRequest request, Result<?> result, Exception e) {
        ObjectMapper mapper = new ObjectMapper();

        String requestUrl = request.getRequestURL().toString() + (!StringUtils.hasLength(request.getQueryString()) ? "" : "?" + request.getQueryString());
        try {
            log.error("<-异常返回-> 请求接口:{} | 异常时间:{} | 异常结果:{}", new Object[]{requestUrl, System.currentTimeMillis(), mapper.writeValueAsString(result)});

        } catch (JsonProcessingException jsonProcessingException) {
            jsonProcessingException.printStackTrace();
        }
        log.error("<--异常堆栈信息-->");
        StringWriter stringWriter = new StringWriter();
        e.printStackTrace(new PrintWriter(stringWriter));
        log.error(stringWriter.toString());
        return result;
    }
}

接下来我们就可以在程序中使用。

  1. 开发一个Controller进行测试,直接抛出异常
java复制代码@RestController
public class ThirdExceptionController {

    @GetMapping("exception")
    public User second(){
        System.out.println(1);
        throw new BizException(ResultCode.BIZ_ERROR.getCode(), "用户名密码错误");
    }


}

记得传token,因为有我们的拦截器。

image.png

异常成功按照我们想要的格式返回了。当然我们可以在抛出异常的时候,自己的定义我们的code和message, 其实还可以和Assert联合使用,让代码更加的优雅。

我们同时修改一下我们之前的拦截器,之前的拦截器在拦截token的时候,如果没传token就直接返回false这种方式不是很友好,因为在浏览器上看到就是一个空白页。http请求不会继续执行,我们可以在这里不返会false,而是直接封装一个我们自己定义的异常。

java复制代码@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 核心业务逻辑,判断是否登录等
        String token = request.getHeader("token");
        // 正常token是的登录后签发的,前端携带过来
        if(!StringUtils.hasLength(token)){
            throw new BizException(9001, "token不能为空");
        }
        return true;
    }

浏览器验证效果:

image.png 好了关于异常的处理我们就讲解到这里,希望对你有帮助。

另: 配套项目代码已托管中gitCode: gitcode.net/lsqingfeng/…

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

uniap vue3 组件使用uni.createSelectorQuery() 获取dom报错

由于vue3中没有this&#xff0c;所以使用uni.createSelectorQuery().in(this)时&#xff0c;会报错 使用 getCurrentInstance 获取组件实例 使用 uni.createSelectorQuery() 批量查询时&#xff0c;结果是按照查询的顺序返回的 使用示例 import { getCurrentInstance } from…

MySQl导入与导出远程备份

文章目录 一. navicat导入导出 二. mysqldump命令导入导出导入导出 三. load data infile命令导入导出导入导出 四. 远程备份导入导出思维导图 一. navicat 导入 右键——>运行SQL文件 导出 选中要导出的表➡右键➡转储SQL文件➡数据和结构 二. mysqldump命令导入导出…

uniapp 打包成 apk(原生APP-云打包)免费

修改APP配置 根据需求&#xff0c;修改 manifest.json 配置&#xff0c;常见的修改有&#xff1a; 应用名称&#xff0c;应用版本名称&#xff0c;应用版本号 升级版本时&#xff0c;应用版本名称和应用版本号必须高于上一版的值 应用图标 点浏览选择png格式的图片后&#x…

HTML--CSS--超链接样式以及鼠标样式自定义

超链接伪类 再复习一下,超链接的定义方式如下&#xff1a; <!DOCTYPE html> <html> <head> <title>这是一个标题</title><meta charset"utf-8"/><style></style> </head> <body><a href"http…

操作系统概述

概述 文章目录 概述定义功能特征并发共享并发与共享的关系虚拟异步 发展与分类手工操作阶段批处理阶段分时操作系统实时操作系统网络操作系统分布式操作系统个人计算机操作系统 运行机制程序是如何运行的&#xff1f;内核程序应用程序特权指令非特权指令内核态用户态内核态与用…

人力资源智能化管理项目(day01:基础架构拆解)

学习源码可以看我的个人前端学习笔记 (github.com):qdxzw/frontlearningNotes 觉得有帮助的同学&#xff0c;可以点心心支持一下哈 一、基础架构拆解 1.拉取模板代码 git clone GitHub - PanJiaChen/vue-admin-template: a vue2.0 minimal admin template 项目名 2.core-js…

Swift 周报 第四十五期

文章目录 前言新闻和社区苹果或将扩充健康版图&#xff0c;为Apple Watch X铺路更新后的《Apple Developer Program 许可协议》现已发布 提案通过的提案 Swift论坛推荐博文话题讨论关于我们 前言 本期是 Swift 编辑组整理周报的第四十五期&#xff0c;每个模块已初步成型。各位…

概率论与数理统计————古典概型、几何概型和条件概率

一、古典概型 特点 &#xff08;1&#xff09;有限性&#xff1a;试验S的样本空间的有限集合 &#xff08;2&#xff09; 等可能性&#xff1a;每个样本点发生的概率是相等的 公式&#xff1a;P&#xff08;A&#xff09; A为随机事件的样本点数&#xff1b;S是样本…

【OpenCV学习笔记12】- 更改颜色空间

关于 OpenCV 官方文档的核心操作告一段落&#xff0c;接下来开始图像处理的学习。学习笔记中会记录官方给出的例子&#xff0c;也会给出自己根据官方的例子完成的更改代码&#xff0c;同样彩蛋的实现也会结合多个知识点一起实现一些小功能&#xff0c;来帮助我们对学会的知识点…

如何实现本地USB设备共享服务映射到外网实现跨网USB共享通信访问

文章目录 前言1. 安装下载软件1.1 内网安装使用USB Redirector1.2 下载安装cpolar内网穿透 2. 完成USB Redirector服务端和客户端映射连接3. 设置固定的公网地址 前言 USB Redirector是一款方便易用的USB设备共享服务应用程序&#xff0c;它提供了共享和访问本地或互联网上的U…

只要3步,教你搞定网工领导满意的年终总结

你们好&#xff0c;我是老杨。 2024年的总结你们都写完了吗&#xff1f; 早的大厂都已经收了一波总结了&#xff0c;等着大年三十之前再搞个年终述职&#xff0c;这一年就算是齐活了。 老生常谈&#xff0c;但每年又不得不谈的总结&#xff0c;咱们今天就聊聊这个。 不管你是…

树莓派4B +Ubuntu20.04+ROS1的使用(2)

首先确定一下主机与从机的ip地址&#xff08;非常重要&#xff09; 在这次实验中&#xff0c;主机是一台Ubuntu20.04.03系统的台式机&#xff0c;我们间通过这台准备来远程遥控树莓派上的ros1系统&#xff0c;它的ip地址是192.168.230.181 从机是一台搭载Ubuntu20.04桌面版ro…

pod控制器的作用

pod控制器的作用 1、动态pv和pvc deployment是控制器 pod空气器:工作负载&#xff0c;workload用于管理pod的中间层&#xff0c;确保podi资源符合预期的状态 预期状态 1、副本数 2、容器重启策略 3、镜像拉取策略 pod、出现故障时重启等等 pod的控制器类型 1、replic…

Spring MVC文件上传及全局异常处理器

添加依赖 <!--文件上传--> <dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.1</version> </dependency>配置文件上传解析器 <!--配置文件上传解析器-…

mysql复制表的几种常用方法

遇到需要拷贝一个表及其数据的情况,总结了一下几种方法 1.使用 show create table 旧表 将结果拷贝出来,将旧表名换成新表名即可. 注意:该方法仅适用于拷贝表结构,不需要同步数据的情况 show create table 旧表名2.create table 新表 like 旧表 该语句将完全拷贝旧表结构, …

RT-Thread: 控制台调试串口波特率更改

说明&#xff1a;rt_kprintf 函数是RT 的一个调试接口使用的函数&#xff0c;波特率默认是 115200 &#xff0c;本文介绍更改这个波特率。 1.根据截图路径找到文件 serial.h 修改如下代码中关于波特率定义部分。 /* Default config for serial_configure structure */ #defin…

华为设备端口镜像设置

核心代码&#xff1a; observe-port int 编号 int 编号 mirror to observe-port both | inbound | outbound #both:将镜像端口的入和出流量同时复制到观察者端口 #inbound:将镜像端口的入流量复制到观察者端口 #outbound:将镜像端口的出流量复制到观察者端口配置后可使出入端口…

NLP论文阅读记录 - 2021 | WOS HG-News:基于生成式预训练模型的新闻标题生成

文章目录 前言0、论文摘要一、Introduction1.1目标问题1.2相关的尝试1.3本文贡献 二.相关工作三.本文方法四 实验效果4.1数据集4.2 对比模型4.3实施细节4.4评估指标4.5 实验结果4.6 细粒度分析 五 总结思考 前言 HG-News: News Headline Generation Based on a Generative Pre-…

特征工程-特征处理(二)

特征处理 二、时间特征处理 将原本的具体时间拆分为年月日等多个特征变量&#xff0c;同时可以引入在一天的某个时间段&#xff0c;或者是当天是否为节假日等其他条件&#xff0c;还可以进一步结合其他特征&#xff0c;进行前后一个时间段或是多个时间段时间的特征差值。 dt.…

初学者学习质量管理应怎样做?

建议有时间可以去学习下PMP的课程&#xff0c;里面包含质量管理&#xff0c;且是系统性的知识体系&#xff0c;都用得到的。 分享下质量管理七大工具&#xff0c;也是很好用的工具&#xff0c;可以学习下&#xff1a; 1. 因果图(鱼骨图) 因果图又称鱼骨图、石川图。它将问题…