【笑小枫的SpringBoot系列】【五】SpringBoot返回统一异常处理

简介: 【笑小枫的SpringBoot系列】【五】SpringBoot返回统一异常处理

SpringBoot异常统一处理


本文基于SpringBoot返回统一结果包装 有一些类在上文中已经创建,这里不再赘述。(这是一篇长篇连载文😂)

上面我们我们介绍了统一返回格式,如果程序抛异常了,我们是否也可以返回统一的格式呢?


答案是,当然可以的,不光可以抛出我们想要的格式,还可以对指定的异常类型进行特殊处理


例如使用@Validated对入参校验的异常,我们自定义的异常等等


未处理的返回情况


首先我们模拟一个异常的场景,代码如下👇

package com.maple.demo.controller;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author 笑小枫
 * @date 2022/07/15
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/example")
public class TestErrorResultController {
    /**
     * 模拟系统空指针异常
     */
    @GetMapping("/testResultError")
    public Test testResultError() {
        Test test = null;
        test.setName("简单点,抛个空指针吧");
        return test;
    }
    @Data
    static class Test {
        private String name;
        private Integer age;
        private String remark;
    }
}

我们先看看这是调用返回的结果,可以看到,由于我们对结果进行了统一封装,将错误信息放到了data里面。 但显然,status显示true,后台都空指针了,这显然不是我们想要的结果🥶。

{
  "status": true,
  "code": "0000",
  "msg": "",
  "data": {
    "timestamp": "2021-12-22T06:24:19.332+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "message": "",
    "path": "/testResultError"
  }
}


贴一下控制台错误信息👇

笑小枫控制台- 2022-07-15 22:25:38 [http-nio-6666-exec-1] ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
java.lang.NullPointerException: null
  at com.maple.controller.TestResultController.testResultError(TestResultController.java:19)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:498)
    ...


统一异常处理配置

这里使用了一个异常编码的枚举类ErrorCode.java,在config.bean包下创建,代码如下👇

package com.maple.demo.config.bean;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * 统一异常信息枚举类
 *
 * @author 笑小枫
 * @date 2022/07/15
 */
@AllArgsConstructor
@Getter
public enum ErrorCode {
    /**
     * 异常信息
     */
    PARAM_ERROR("9001", "请求参数有误,请重试"),
    /**
     * 抛出此异常码,请重新在ErrorMsg定义msg
     */
    COMMON_ERROR("9998", "笑小枫太懒,居然没有定义异常原因"),
    OTHER_ERROR("9999", "未知异常,请联系笑小枫:https://www.xiaoxiaofeng.site");
    private final String code;
    private final String msg;
}


接下来我们开始表演,在config目录下创建一个配置类ExceptionAdvice.java,配置一下对异常拦截处理,代码如下👇

package com.maple.demo.config;
import com.maple.demo.config.bean.ErrorCode;
import com.maple.demo.util.ResultJson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
 * 异常信息统一处理
 *
 * @author 笑小枫
 * @date 2022/07/15
 */
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {
    /**
     * 系统异常.
     *
     * @param e 异常信息
     * @return R
     */
    @ExceptionHandler(Exception.class)
    public ResultJson exception(Exception e) {
        log.error("系统异常信息 ex={}", e.getMessage(), e);
        // 未知异常统一抛出9999
        return new ResultJson(ErrorCode.OTHER_ERROR.getCode(), ErrorCode.OTHER_ERROR.getMsg());
    }
}

我们再看一下这个时候返回的结果,显然,这个结果我们就可以接受了。程序中尽量避免出现空指针哈,这里只是模拟😅…

{
  "status": false,
  "code": "9999",
  "msg": "未知异常,请联系笑小枫:https://www.xiaoxiaofeng.site",
  "data": null
}


自定义异常的处理


刚刚对统一异常做了处理,那像我们自定义的异常怎么处理呢?

首先我们创建一个自定义异常,我们在config包下创建一个新的package吧,命名为exception,简单粗暴🤪

然后先创建一个BaseException的自定义异常,然后不同的小类的异常再继承BaseException异常。 这样我们可以直接拦截BaseException异常就可以了。

BaseException异常这里取名为MapleBaseException.java

ps.Maple枫叶的意思😁;本项目名称亦为maple-demo💕

代码如下👇

package com.maple.demo.config.exception;
import com.maple.demo.config.bean.ErrorCode;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * 自定义异常-Base父类,细化的自定义异常,应该继承此类
 * 统一异常处理时,会根据此异常类型做判断,返回结果时,如果是自定义异常自动解析code和errorMsg返回
 *
 * @author 笑小枫
 * @date 2022/07/15
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class MapleBaseException extends RuntimeException {
    private final String code;
    private final String errorMsg;
    public MapleBaseException(String code, String errorMsg) {
        super(errorMsg);
        this.code = code;
        this.errorMsg = errorMsg;
    }
    public MapleBaseException(ErrorCode code) {
        super(code.getMsg());
        this.code = code.getCode();
        this.errorMsg = code.getMsg();
    }
    public MapleBaseException(ErrorCode code, String errorMsg) {
        super(errorMsg);
        this.code = code.getCode();
        this.errorMsg = errorMsg;
    }
}

然后再定义一个校验的自定义异常(MapleCheckException.java),继承MapleBaseException.java,代码如下👇

package com.maple.demo.config.exception;
import com.maple.demo.config.bean.ErrorCode;
/**
 * 检测结果不一致时,抛出此异常
 *
 * @author 笑小枫
 * @date 2022/07/15
 */
public class MapleCheckException extends MapleBaseException {
    public MapleCheckException(String code, String errorMsg) {
        super(code, errorMsg);
    }
    public MapleCheckException(ErrorCode code) {
        super(code);
    }
    public MapleCheckException(ErrorCode code, String errorMsg) {
        super(code, errorMsg);
    }
}

调整一下我们的异常拦截配置类,添加对我们自定义异常的拦截

  /**
     * 自定义异常处理
     *
     * @param e 异常信息
     * @return 返回结果
     */
    @ExceptionHandler(MapleBaseException.class)
    public ResultJson mapleBaseException(MapleBaseException e) {
        log.error("自定义异常信息 ex={}", e.getMessage(), e);
        return new ResultJson(e.getCode(), e.getErrorMsg());
    }


完整代码如下👇

注意Exception异常拦截上面的@Order(99),因为我们自定义异常也属于Exception异常,所以使用Order执行时往后排。


package com.maple.demo.config;
import com.maple.demo.config.bean.ErrorCode;
import com.maple.demo.config.exception.MapleBaseException;
import com.maple.demo.util.ResultJson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
 * 异常信息统一处理
 *
 * @author 笑小枫
 * @date 2022/07/15
 */
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {
    /**
     * 自定义异常处理
     *
     * @param e 异常信息
     * @return 返回结果
     */
    @ExceptionHandler(MapleBaseException.class)
    public ResultJson mapleBaseException(MapleBaseException e) {
        log.error("自定义异常信息 ex={}", e.getMessage(), e);
        return new ResultJson(e.getCode(), e.getErrorMsg());
    }
    /**
     * 系统异常.
     *
     * @param e 异常信息
     * @return R
     */
    @ExceptionHandler(Exception.class)
    @Order(99)
    public ResultJson exception(Exception e) {
        log.error("系统异常信息 ex={}", e.getMessage(), e);
        // 未知异常统一抛出9999
        return new ResultJson(ErrorCode.OTHER_ERROR.getCode(), ErrorCode.OTHER_ERROR.getMsg());
    }
}

我们再模拟一下抛出自定义异常

TestErrorResultController.java添加新的接口testMapleError

    @GetMapping("/testMapleError")
    public Test testMapleError() {
        Test test = new Test();
        test.setName("笑小枫");
        if (test.getAge() == null) {
            throw new MapleCheckException(ErrorCode.COMMON_ERROR);
        }
        return test;
    }


完整代码如下👇

package com.maple.demo.controller;
import com.maple.demo.config.bean.ErrorCode;
import com.maple.demo.config.exception.MapleCheckException;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author 笑小枫
 * @date 2022/07/15
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/example")
public class TestErrorResultController {
    /**
     * 模拟系统空指针异常
     */
    @GetMapping("/testResultError")
    public Test testResultError() {
        Test test = null;
        test.setName("简单点,抛个空指针吧");
        return test;
    }
    /**
     * 模拟自定义异常
     */
    @GetMapping("/testMapleError")
    public Test testMapleError() {
        Test test = new Test();
        test.setName("笑小枫");
        if (test.getAge() == null) {
            throw new MapleCheckException(ErrorCode.COMMON_ERROR);
        }
        return test;
    }
    @Data
    static class Test {
        private String name;
        private Integer age;
        private String remark;
    }
}


最后,我们来看一下结果👇

{
  "status": false,
  "code": "9998",
  "msg": "笑小枫太懒,居然没有定义异常原因",
  "data": null
}


贴一下控制台异常信息👇

笑小枫控制台- 2022-07-15 22:38:26 [http-nio-6666-exec-2] ERROR com.maple.config.ExceptionAdvice - 自定义异常信息 ex=笑小枫太懒,居然没有定义异常原因
com.maple.config.exception.MapleCheckException: 笑小枫太懒,居然没有定义异常原因
  at com.maple.controller.TestResultController.testResultError(TestResultController.java:23)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:498)
  ...


@Validated对入参校验的异常处理


上面的说的@Validated对入参校验的异常处理,系统抛出异常格式太丑,这里做了简单优化。这里简单贴一下代码,这里不做详细概述,后面用到的地方再详细介绍。


  /**
     * 参数校验异常处理
     *
     * @param e 异常信息
     * @return 返回结果
     */
    @ExceptionHandler(value = {BindException.class, MethodArgumentNotValidException.class})
    public ResultJson validException(MethodArgumentNotValidException e) {
        log.error("参数校验异常信息 ex={}", e.getMessage(), e);
        BindingResult result = e.getBindingResult();
        StringBuilder stringBuilder = new StringBuilder();
        if (result.hasErrors()) {
            List<ObjectError> errors = result.getAllErrors();
            errors.forEach(p -> {
                FieldError fieldError = (FieldError) p;
                stringBuilder.append(fieldError.getDefaultMessage());
            });
        }
        return new ResultJson(ErrorCode.PARAM_ERROR.getCode(), stringBuilder.toString());
    }

完整代码如下👇

package com.maple.demo.config;
import com.maple.demo.config.bean.ErrorCode;
import com.maple.demo.config.exception.MapleBaseException;
import com.maple.demo.util.ResultJson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.List;
/**
 * 异常信息统一处理
 *
 * @author 笑小枫
 * @date 2022/07/15
 */
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {
    /**
     * 自定义异常处理
     *
     * @param e 异常信息
     * @return 返回结果
     */
    @ExceptionHandler(MapleBaseException.class)
    public ResultJson mapleBaseException(MapleBaseException e) {
        log.error("自定义异常信息 ex={}", e.getMessage(), e);
        return new ResultJson(e.getCode(), e.getErrorMsg());
    }
    /**
     * 参数校验异常处理
     *
     * @param e 异常信息
     * @return 返回结果
     */
    @ExceptionHandler(value = {BindException.class, MethodArgumentNotValidException.class})
    public ResultJson validException(MethodArgumentNotValidException e) {
        log.error("参数校验异常信息 ex={}", e.getMessage(), e);
        BindingResult result = e.getBindingResult();
        StringBuilder stringBuilder = new StringBuilder();
        if (result.hasErrors()) {
            List<ObjectError> errors = result.getAllErrors();
            errors.forEach(p -> {
                FieldError fieldError = (FieldError) p;
                stringBuilder.append(fieldError.getDefaultMessage());
            });
        }
        return new ResultJson(ErrorCode.PARAM_ERROR.getCode(), stringBuilder.toString());
    }
    /**
     * 系统异常.
     *
     * @param e 异常信息
     * @return R
     */
    @ExceptionHandler(Exception.class)
    @Order(99)
    public ResultJson exception(Exception e) {
        log.error("系统异常信息 ex={}", e.getMessage(), e);
        // 未知异常统一抛出9999
        return new ResultJson(ErrorCode.OTHER_ERROR.getCode(), ErrorCode.OTHER_ERROR.getMsg());
    }
}


小结


好啦,本文就到这里了,我们简单的总结一下,主要介绍了以下内容👇👇

  • 统一异常信息处理
  • 自定义异常创建及使用
  • 统一异常拦截自定义异常
  • 统一异常拦截Spring的@Validated参数校验抛出的异常


关于笑小枫


本章到这里结束了,喜欢的朋友关注一下我呦😘😘,大伙的支持,就是我坚持写下去的动力。

老规矩,懂了就点赞收藏;不懂就问,日常在线,我会就会回复哈~🤪

后续文章会陆续更新,文档会同步在微信公众号、个人博客、CSDN和GitHub保持同步更新。😬

微信公众号:笑小枫

笑小枫个人博客:https://www.xiaoxiaofeng.com

CSDN:https://zhangfz.blog.csdn.net

GitHub源码:https://github.com/hack-feng/maple-demo

目录
相关文章
|
缓存 前端开发 Java
SpringBoot&SpringMVC统一异常处理之RestControllerAdvice
SpringBoot&SpringMVC统一异常处理之RestControllerAdvice
128 0
|
2月前
|
JSON 缓存 前端开发
SpringBoot的 ResponseEntity类讲解(具体讲解返回给前端的一些事情)
本文讲解了SpringBoot中的`ResponseEntity`类,展示了如何使用它来自定义HTTP响应,包括状态码、响应头和响应体,以及如何将图片从MinIO读取并返回给前端。
84 3
|
6月前
|
Java
springboot之异常
springboot之异常
35 1
|
Java
SpringBoot通用异常处理
通用异常返回一般用在该异常服务器无法处理的时候,进行消息的返回。所以返回代码只有 500。
95 0
|
7月前
|
JSON Java 应用服务中间件
SpringBoot之异常处理
SpringBoot之异常处理
SpringBoot2.0(Lombok,SpringBoot统一返回封装)
SpringBoot2.0(Lombok,SpringBoot统一返回封装)
|
Java Spring
springboot异常处理之404
springboot异常处理之404
390 1
springboot异常处理之404
|
JSON 前端开发 Java
【SpringBoot学习笔记 十一】深入理解SpringBoot异常处理
【SpringBoot学习笔记 十一】深入理解SpringBoot异常处理
445 0
|
JSON Java API
SpringBoot 处理异常的几种常见姿势
SpringBoot 处理异常的几种常见姿势
104 0
|
JSON Java 应用服务中间件
SpringBoot 学习笔记之 “异常处理”
SpringBoot 学习笔记之 “异常处理”