第四节 SpringBoot处理异常(异常设计的思考)

一、基本异常

         我常常自己问自己,如果为某个系统设计一套异常体系,我应该怎么开始我的工作。

       (1) 首先就是基本异常响应。前端们希望得到的异常响应肯定是有 响应码code 、错误信息message。在JAVA异常体系中,错误原因message字段异常体系已经帮我们定义了,所以不用重复定义。因此,我们可以编写一个基础的基类。构造两个常见的构造函数,来自定义错误响应码code。并提供get方法来让同事获取到错误信息。

package com.zhoutianyu.learnspringboot.exception;

public abstract class AbstractBusinessException extends RuntimeException {

    private int code;

    public AbstractBusinessException(int code, String message) {
        super(message);
        this.code = code;
    }

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

    public int getCode() {
        return code;
    }
}

      (2)我的主管批评过我,要有面向对象的思想。因此,在设计过程中,我积极让定义的类,符合抽象类规则。

        接着,我们可以定义一个错误响应对象Response对象。因为在前端同事们不希望的到的是一个错误对象,他们希望得到的是一个封装后的响应(响应包括:响应码code 与 响应信息message)。因此,再编写如下的类,作为异常响应类。

        在下面的代码中,因为我们面向接口编程,首先判断异常的分类。使用 instanceof 关键字来判断具体异常来自哪个模块。即使这个异常实现类还没有定义,但是只要是继承的AbstractBusinessException,都能进入我们异常处理。这里就能体现到抽象类的好处。

package com.zhoutianyu.learnspringboot.response;

import com.zhoutianyu.learnspringboot.exception.AbstractBusinessException;
import lombok.Data;

@Data
public class ExceptionResponse {

    private int code;
    private String message;

    public ExceptionResponse(Exception exception) {

        if (exception instanceof AbstractBusinessException) {
            this.code = ((AbstractBusinessException) exception).getCode();
            this.message = exception.getMessage();
            return;
        }

        //todo:add other abstract exception type

        //here is default code and message
        this.code = 500;
        this.message = "Server internal error";
    }
}

         (3)最后编写一个用于捕获全局异常的类。

package com.zhoutianyu.learnspringboot.exception;

import com.zhoutianyu.learnspringboot.response.ExceptionResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;


@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理所有异常
     *
     * @param exception 异常对象
     * @return 异常标准响应 {code : XX , message : XXX}
     * @see ExceptionResponse 主要处理逻辑在ExceptionResponse中
     */
    @ExceptionHandler(value = Exception.class)
    public ExceptionResponse handlerException(Exception exception) {
        return new ExceptionResponse(exception);
    }
}

         主要异常体系的基础框架就搭建好了。处理异常的最终目的就是封装code与message给前端同事。因此,考虑到扩展性(大宇破音)在设计异常的过程中,将某一类异常设计一个基类,即使具体异常实现类此刻没有创建,我们也不用担心。因为在ExceptionResponse的构造函数中,我们能够通过JAVA的多态特性,使用instanceof关键字捕获到这个异常。

          所有关于code与message的解析,全部封装在ExceptionResponse的构造函数中。

       (4)现在,让我们编写一个具体的异常来继承AbstractBusinessException。假设现在有一个参数校验问题。我们定义一个FieldInvalidException。假如我们规定,参数校验不通过,code返回-1。可以像下面一样设计。

package com.zhoutianyu.learnspringboot.exception;

public class FieldInvalidException extends AbstractBusinessException {

    private static final int FIELD_INVALID_CODE = -1;

    public FieldInvalidException(String message) {
        super(FIELD_INVALID_CODE, message);
    }

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

二、测试

         编写一个测试Controller。手动抛出一个参数校验异常。

package com.zhoutianyu.learnspringboot.test;


import com.zhoutianyu.learnspringboot.exception.FieldInvalidException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SomethingController {

    @GetMapping(value = "/exception/test")
    public void function(String paramType) {
        throw new FieldInvalidException("paramType is null");
    }
}

        启动服务器,访问:http://localhost:8081/study/springboot/exception/test

        返回结果:{"code":-1,"message":"paramType is null"}  。符合预期。

        

三、实战

         再编写一个Controller接口,使用一个@Valid注解,使用BindingResult来保存校验结果。最后把校验结果处理字符串,使用异常抛出。此异常将会被全局异常捕获到,最终以ExceptionResponse的形式呈现给前端。

    @GetMapping(value = "/exception/test2")
    public Computer function(@Valid Computer computer, BindingResult result) {
        if (result.hasErrors()) {
            List<FieldError> fieldErrors = result.getFieldErrors();
            StringBuilder stringBuilder = new StringBuilder();
            fieldErrors.forEach(error -> {
                        
                        stringBuilder.append("字段:").append(error.getField()).
                                append(",原因:").append(error.getDefaultMessage()).append(";");
                    }
            );
            throw new FieldInvalidException(stringBuilder.toString());
        }

        return computer;
    }
@Data
class Computer {

    @NotBlank(message = "CPU不能为空")
    @Length(max = 5, message = "CPU不能超过{max}个字符")
    private String cpu;

    @NotNull(message = "内存大小不能为空")
    private Long memory;
}

        访问:http://localhost:8081/study/springboot/exception/test2?cpu=&memory=

 

        访问:http://localhost:8081/study/springboot/exception/test2?cpu=abcdef&memory=123

四、源码下载

        本章节项目源码:点我下载源代码

        目录贴:跟着大宇学SpringBoot-------目录帖

posted @ 2022-07-17 12:14  小大宇  阅读(158)  评论(0编辑  收藏  举报