SpringBoot系列(十)统一异常处理与统一结果返回

SpringBoot系列(十)统一异常处理与统一结果返回

往期推荐

SpringBoot系列(一)idea新建Springboot项目

SpringBoot系列(二)入门知识

springBoot系列(三)配置文件详解

SpringBoot系列(四)web静态资源配置详解

SpringBoot系列(五)Mybatis整合完整详细版

SpringBoot系列(六)集成thymeleaf详解版

Springboot系列(七) 集成接口文档swagger,使用,测试

SpringBoot系列(八)分分钟学会Springboot多种解决跨域方式

SpringBoot系列(九)文件上传的正确姿势

目录

引言

 日常开发过程中,难免有的程序会因为某些原因抛出异常,而这些异常一般都是利用try ,catch的方式处理异常或者throw,throws的方式抛出异常不管。这种方法对于程序员来说处理也比较麻烦,对客户来说也不太友好,所以我们希望既能方便程序员编写代码,不用过多的自己去处理各种异常编写重复的代码又能提升用户的体验,这时候全局异常处理就显得很重要也很便捷了,是一种不错的选择。

1. 全局异常捕获与处理

 因为现在主流的都是前后端分离的项目,所以我们的异常处理也根据前后端分离来讲述。

 Springboot对于异常的处理也做了不错的支持,它提供了一个 @ControllerAdvice注解以及 @ExceptionHandler注解,前者是用来开启全局的异常捕获,后者则是说明捕获哪些异常,对那些异常进行处理。

@ControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler(value =Exception.class)
    public String exceptionHandler(Exception e){
        System.out.println("发生了一个异常"+e);
        return e.getMessage();
    }
}

 上面这段代码就是说,只要是代码运行过程中有异常就会进行捕获,并输出出这个异常。然后我们随便编写一个会发生异常的代码,测试出来的异常是这样的。


1.png

 这对于我们前后端分离来说并不好,前后端分离之后唯一的交互就是json了,我们也希望将后端的异常变成json返回给前端处理。下面我们看看统一结果处理。

2. 统一结果返回与统一异常

代码:

public class Result<T> {
    //是否成功
    private Boolean success;
    //状态码
    private Integer code;
    //提示信息
    private String msg;
    //数据
    private T data;
    public Result() {

    }
    //自定义返回结果的构造方法
    public Result(Boolean success,Integer code, String msg,T data) {
        this.success = success;
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
    //自定义异常返回的结果
    public static Result defineError(DefinitionException de){
        Result result = new Result();
        result.setSuccess(false);
        result.setCode(de.getErrorCode());
        result.setMsg(de.getErrorMsg());
        result.setData(null);
        return result;
    }
    //其他异常处理方法返回的结果
    public static Result otherError(ErrorEnum errorEnum){
        Result result = new Result();
        result.setMsg(errorEnum.getErrorMsg());
        result.setCode(errorEnum.getErrorCode());
        result.setSuccess(false);
        result.setData(null);
        return result;
    }
}

 说明:其中省略了get,set方法。另外方法之中包含了一个自定义的枚举。代码如下:

public enum ErrorEnum {
    // 数据操作错误定义
    SUCCESS(200, "nice"),
    NO_PERMISSION(403,"你没得权限"),
    NO_AUTH(401,"你能不能先登录一下"),
    NOT_FOUND(404, "未找到该资源!"),
    INTERNAL_SERVER_ERROR(500, "服务器跑路了"),
    ;

    /** 错误码 */
    private Integer errorCode;

    /** 错误信息 */
    private String errorMsg;

    ErrorEnum(Integer errorCode, String errorMsg) {
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public Integer getErrorCode() {
        return errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }
}

说明:枚举类中定义了常见的错误码以及错误的提示信息。
这里我们就定义好了统一的结果返回,其中里面的静态方法是用来当程序异常的时候转换成异常返回规定的格式。

 然后我们需要自定义异常处理类。代码如下:

public class DefinitionException extends RuntimeException{

    protected Integer errorCode;
    protected String errorMsg;

    public DefinitionException(){

    }
    public DefinitionException(Integer errorCode, String errorMsg) {
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public Integer getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(Integer errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }
}

 其中包含了错误的状态码,错误的提示信息。然后我们可以自定义一个全局异常处理类,来处理各种异常,包括自己定义的异常和内部异常。这样可以简化不少代码,不用自己对每个异常都使用try,catch的方式来实现。

@ControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理自定义异常
     *
     */
    @ExceptionHandler(value = DefinitionException.class)
    @ResponseBody
    public Result bizExceptionHandler(DefinitionException e) {
        return Result.defineError(e);
    }

    /**
     * 处理其他异常
     *
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Result exceptionHandler( Exception e) {
        return Result.otherError(ErrorEnum.INTERNAL_SERVER_ERROR);
    }
}

说明:每个方法上面加上一个 @ResponseBody的注解,用于将对象解析成json,方便前后端的交互,也可以使用 @ResponseBody放在异常类上面。

3. controller代码测试与结果

 controller代码:

@RestController
@RequestMapping("/result")
public class ResultController {
    @GetMapping("/getStudent")
    public Result getStudent(){
        Student student = new Student();
        student.setAge(21);
        student.setId(111);
        student.setName("学习笔记");
        Result result = new Result();
        result.setCode(200);
        result.setSuccess(true);
        result.setData(student);
        result.setMsg("学生列表信息");
        return result;
    }
    @RequestMapping("/getDeException")
    public Result DeException(){
        throw new DefinitionException(400,"我出错了");
    }
    @RequestMapping("/getException")
    public Result Exception(){
        Result result = new Result();
        int a=1/0;
        return result;
    }
}

 其中的Student类就是前面一直在用的类了。包含三个属性。其中省略了get,set方法。

public class Student  {
    /**
    * 唯一标识id
    */
    private Integer id;
    /**
    * 姓名
    */
    private String name;
    /**
    * 年龄
    */
    private Integer age;

}

 然后启动项目,来挨个测试。首先测试正常没有异常发生的数据。
浏览器输入:localhost:8095/result/getStudent


2.png

 可以看到数据是正常返回json串。没有异常。
然后我们测试第二个自定义异常处理接口。浏览器输入localhost:8095/result/getDeException。


3.png

 可以看到这个自定义的异常是捕获到了,并且返回了一个json串。
最后我们测试一下其他的异常。浏览器输入:localhost:8095/result/getException


4.png

 到这里我们就处理完了异常并且正确的返回了前端。
这里说一下,测试接口又很多方法,可以使用postman,或者idea自带的接口测试工具都很好用。

 但是,你可能会发现一个问题,这种方法是不能处理404异常的,捕获不到。该怎么办呢?

4. 404异常特殊处理。

 默认情况下,SpringBoot是不会抛出404异常的,所以@ControllerAdvice也不能捕获到404异常。我们可以通过以下配置来让这个注解能捕获到404异常。

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

其中第一句是表示:当发现404异常时直接抛出异常。第二句关闭默认的静态资源路径映射。这样404错误也能被捕获到,但是这个配置会让你的静态资源访问出现问题,也就是不适合前后端不分离的情况。

5. 总结

 本文讲解了如何处理捕获全局异常以及怎么自定义异常,顺便说明了统一结果的返回格式,并特殊处理的404,not found的异常,将其作为统一结果返回。如果你觉得本文有用,点个赞吧!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容