SpringBoot 中的异常统一处理

异常的统一处理可以显著提升用户提升体验,也可以方便开发人员发现程序问题所在。

服务器端的异常如果不能返回给客户,会让客户端的使用感觉很糟糕,比如常见的 500 错误,客户端看到的是:
SpringBoot 中的异常统一处理_第1张图片

然后一头雾水,用户难免会想这系统这么烂?

如果我们把异常信息返回给前台,让前端进行处理,比如弹窗,用户了解到错误信息后,进而修改错误使用行为或者联系开发人员解决。

异常统一处理也不只有一种方法,本篇通过 Springboot 使用 @ControllerAdvice 和 @ExceptionHandler 两个注解来实现异常的统一处理。

1. 添加依赖

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-configuration-processorartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintagegroupId>
                    <artifactId>junit-vintage-engineartifactId>
                exclusion>
            exclusions>
        dependency>
        
        <dependency>
            <groupId>com.auth0groupId>
            <artifactId>java-jwtartifactId>
            <version>3.5.0version>
        dependency>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.47version>
        dependency>

2. 创建异常接口和实现它的异常信息枚举类

通过接口可以很方便的获取枚举类中的信息。

BaseErrorInfoInterface 接口:

public interface BaseErrorInfoInterface {
    /**
     * 错误码
     */
    String getResultCode();

    /**
     * 错误描述
     */
    String getResultMsg();
}

异常信息枚举类 CommonEnum :

public enum CommonEnum implements BaseErrorInfoInterface {
    // 数据操作错误定义
    SUCCESS("200", "成功!"),
    BODY_NOT_MATCH("400", "请求的数据格式不符!"),
    SIGNATURE_NOT_MATCH("401", "请求的数字签名不匹配!"),
    NOT_FOUND("404", "未找到该资源!"),
    INTERNAL_SERVER_ERROR("500", "服务器内部错误!"),
    SERVER_BUSY("503", "服务器正忙,请稍后再试!");

    /**
     * 错误码
     */
    private String resultCode;

    /**
     * 错误描述
     */
    private String resultMsg;

    CommonEnum(String resultCode, String resultMsg) {
        this.resultCode = resultCode;
        this.resultMsg = resultMsg;
    }

    @Override
    public String getResultCode() {
        return resultCode;
    }

    @Override
    public String getResultMsg() {
        return resultMsg;
    }

}

使用枚举类可以规范异常信息,防止不同的开发人员同一个异常,各有各的写法。

3. 创建返回结果统一处理包装类

无论是正常数据还是异常数据,统统用一个返回结果类包装起来,并通过 fastJSON 转为 json 对象。

创建返回结果类 BaseResult :

@Getter
@Setter
@AllArgsConstructor
public class BaseResult implements Serializable {

    private static final long serialVersionUID = 1L;
    public static final int RESULT_FAIL = 0;
    public static final int RESULT_SUCCESS = 1;

    //返回代码
    private String code;

    //返回消息
    private String message;

    //返回对象
    private Object result;

    /**
     * 构造重载
     * @param code
     * @param message
     */
    public BaseResult(String code, String message) {
        this.code = code;
        this.message = message;
    }

    /**
     * 成功
     *
     * @return
     */
    public static BaseResult success() {
        return success(null);
    }

    /**
     * 成功
     *
     * @param data
     * @return
     */
    public static BaseResult success(Object data) {
        //枚举在这里就用上了
        BaseResult br = new BaseResult(CommonEnum.SUCCESS.getResultCode(), CommonEnum.SUCCESS.getResultMsg(), data);
        return br;
    }

    /**
     * 失败
     *
     * @param errorInfoInterface
     * @return
     */
    public static BaseResult error(BaseErrorInfoInterface errorInfoInterface) {
        BaseResult br = new BaseResult(errorInfoInterface.getResultCode(), errorInfoInterface.getResultMsg(), null);
        return br;
    }

    /**
     * 失败
     *
     * @param code
     * @param message
     * @return
     */
    public static BaseResult error(String code, String message) {
        BaseResult br = new BaseResult(code, message, null);
        return br;
    }

    /**
     * 失败
     *
     * @param message
     * @return
     */
    public static BaseResult error(String message) {
        BaseResult br = new BaseResult("-1", message, null);
        return br;
    }

    public String toString() {
        return JSONObject.toJSONString(this);
    }
}

4. 创建自定义业务异常类,继承 RuntimeException 类

BizException 类(biz=business):

@Getter
@Setter
public class BizException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    /**
     * 错误码
     */
    protected String errorCode;
    /**
     * 错误信息
     */
    protected String errorMsg;

    /**
     * 无参构造
     */
    public BizException() {
        super();
    }

    /**
     * BaseErrorInfoInterface 参数构造
     * @param errorInfoInterface
     */
    public BizException(BaseErrorInfoInterface errorInfoInterface) {
        super(errorInfoInterface.getResultCode());
        this.errorCode = errorInfoInterface.getResultCode();
        this.errorMsg = errorInfoInterface.getResultMsg();
    }

    /**
     * BaseErrorInfoInterface,Throwable 参数构造
     * @param errorInfoInterface
     * @param cause
     */
    public BizException(BaseErrorInfoInterface errorInfoInterface, Throwable cause) {
        super(errorInfoInterface.getResultCode(), cause);
        this.errorCode = errorInfoInterface.getResultCode();
        this.errorMsg = errorInfoInterface.getResultMsg();
    }

    /**
     * 错误信息参数构造
     * @param errorMsg
     */
    public BizException(String errorMsg) {
        super(errorMsg);
        this.errorMsg = errorMsg;
    }

    /**
     * 错误信息、编码 参数构造
     * @param errorCode
     * @param errorMsg
     */
    public BizException(String errorCode, String errorMsg) {
        super(errorCode);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    /**
     * 编码、信息、异常父类 参数构造
     * @param errorCode
     * @param errorMsg
     * @param cause
     */
    public BizException(String errorCode, String errorMsg, Throwable cause) {
        super(errorCode, cause);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    @Override
    public Throwable fillInStackTrace() {
        return this;
    }

}

通过这个异常类,可以通过 throw new BizException() 来定制异常信息。

5. 创建统一异常处理器 GlobalExceptionHandler

前面四个步骤都是在为这一步做基础工作,没有前面的的步骤只有这一个类也可以完成统一异常处理,只是没那么规范。

重点是 @ControllerAdvice 注解

@ControllerAdvice //声明当前类为控制器增强类,除了统一异常处理可以用到,还可以用在全局数据绑定和数据初始化。
@ResponseBody  //表示返回的对象,Spring会自动把该对象进行json转化,最后写入到Response中。
public class GlobalExceptionHandler {
    Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 处理自定义的业务异常
     *
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value = BizException.class) // 匹配异常类,这里匹配了我们定制的异常类。
    public BaseResult bizExceptionHandler(HttpServletRequest req, BizException e) {
        logger.error("发生业务异常!原因是:{}", e.getErrorMsg());//用日志记录方便查阅
        return BaseResult.error(e.getErrorCode(), e.getErrorMsg());
    }

    /**
     * 处理空指针的异常
     *
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value = NullPointerException.class)
    public BaseResult exceptionHandler(HttpServletRequest req, NullPointerException e) {
        logger.error("发生空指针异常!原因是:", e);
        return BaseResult.error(e.getMessage());
    }
    
    /**
     * 处理其他异常
     *
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public BaseResult exceptionHandler(HttpServletRequest req, Exception e) {
        logger.error("未知异常!原因是:", e);
        return BaseResult.error(e.getMessage());
    }

    // 不嫌麻烦的话可以定义更多处理其他类异常的方法
}

做完上面的步骤就完成了统一异常的处理,接下来写个简单的接口做测试。

6. 写点简单的业务代码做测试

实体类 Dog:

@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class Dog {
    private String name;
    private int age;
    private String id;
}

service 层 DogService:

@Service
public class DogService {

    /**
     * 测试自定义异常
     */
    public void exceptionTest(){
        throw new BizException(CommonEnum.INTERNAL_SERVER_ERROR);
    }

    /**
     * 正常业务
     * @param id
     * @return
     */
    public Dog findDog(String id){
        if(id.equals("1001"))
            return new Dog("kaka",2,id);
        return null;
    }
}

controller 层 DogController:

@RestController
public class DogController {
    @Autowired
    private DogService dogService;

    /**
     * 找狗
     * @param id
     * @return
     */
    @GetMapping("/dog")
    public String findDog(@RequestParam String id){
        Dog dog = dogService.findDog(id);
        if(dog == null)
            return BaseResult.error("未找到这只狗").toString();
        return BaseResult.success(dog).toString();//对返回结果做统一处理,有些情况下我们还需要在 success 方法中传入 HttpServletRequest 来用日志记录请求中的一些信息,有需要了再在 BaseResult 中创建对应的方法就行了。
    }

    /**
     * 模拟业务类中抛出异常
     * @return
     */
    @PostMapping("/exception")
    public String exception(){
        dogService.exceptionTest();
        return BaseResult.success().toString();// 由于考虑到 BaseResult 类在别处会用到,所以没有直接把类方法的返回值定为 JSON 如果嫌弃 .toString 这种写法(toString 已被重写为返回JSON),可以写个另外的类来继承这个类并重写里面的方法。
    }
}

测试:

启动项目:

按照业务逻辑 当参数 id 不是 1001 时,controller 返回“未找到狗”的JSON 信息,否者返回正确狗的信息:

SpringBoot 中的异常统一处理_第2张图片

SpringBoot 中的异常统一处理_第3张图片

然后测试异常服务:

SpringBoot 中的异常统一处理_第4张图片

此时前台就可以通过解析 json 中的数据想办法提示给客户了。


通过异常统一处理和返回数据格式化,已经可以把服务器处理请求的结果清楚的返回给客户端了。

你可能感兴趣的:(日常笔记)