乐趣区

关于后端:笑小枫的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 参数校验抛出的异样

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

退出移动版