SpringBoot 统一异常处理

1.前言

在做项目的时候,常常会遇到这些情况,

​ (一)、繁多的if..else 造成大量的参数代码判断,于是就在实体类字段中添加@NotBlank,@NotNull等注解代替,可是注解上自定义的message 消息无法规范的返回到前端;

​ (二)、在业务层代码中,当方法层层嵌套,对最深处的代码进行不满足的参数做判断时,直接返回响应体并不是很合适(这个时候就需要抛出自定义异常)

​ (三)、通过Assert 断言去除冗余的if..else 时,发现断言抛出的异常也没有规范的返回前端;

​ 为了解决这些情况,我们需要做一个统一异常处理,无论是注解的异常抛出,自定义异常抛出、以及断言的异常抛出都需要统一获取其中的message。这个统一异常处理即今天所要说的 @ExceptionHandler+@ControllerAdvice ,统一异常处理。

2.直接实战

原理也不解释,废话也不多说,直接记录怎么配置,怎么使用。想知道原理的,自行百度。

2.1.创建项目

​ 通过IDEA 中的SpringInitializr 创建springboot项目,并一次创建各个层的文件夹,controller、service、entity、model、exception、handler等文件夹。如下所示:

2.2 统一异常配置

pom.xml 添加 以下依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.7</version>
</dependency>

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.4.1.Final</version>
</dependency>

2.2.1 Result 响应实体类,用来返回前端的实体类。

/**
 * @Author: DZBiao
 * @Date : 2021/12/11
 * @Description : 描述:
 **/
public class Result {

    /**
     * 响应状态码
     */
    private Integer code;
    /**
     * 响应成功与否
     */
    private boolean success;
    /**
     * 响应消息
     */
    private String msg;
    /**
     * 响应数据
     */
    private Object data;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public Result() {
    }

    public Result(Integer code, boolean success, String msg) {
        this.code = code;
        this.success = success;
        this.msg = msg;
    }

    public Result(Integer code, boolean success, String msg, Object data) {
        this.code = code;
        this.success = success;
        this.msg = msg;
        this.data = data;
    }

    /**
     * 成功 返回默认成功信息
     *
     * @return
     */
    public static Result SUCCESS() {
        return new Result(1, true, "操作成功", null);
    }

    /**
     * 成功 返回(data数据)成功信息
     *
     * @param data
     * @return
     */
    public static Result SUCCESS(Object data) {
        return new Result(1, true, "操作成功", data);
    }

    /**
     * 成功 返回自定义(消息、data数据)成功信息
     *
     * @param msg
     * @param data
     * @return
     */
    public static Result SUCCESS(String msg, Object data) {
        return new Result(1, true, msg, data);
    }

    /**
     * 失败 返回默认失败信息
     *
     * @return
     */
    public static Result ERROR() {
        return new Result(-1, false, "操作失败", null);
    }

    /**
     * 失败 返回自定义(消息)失败信息
     *
     * @param msg
     * @return
     */
    public static Result ERROR(String msg) {
        return new Result(-1, false, msg, null);
    }

    /**
     * 失败 返回自定义(消息、状态码)失败信息
     *
     * @param code
     * @param msg
     * @return
     */
    public static Result ERROR(Integer code, String msg) {
        return new Result(code, false, msg, null);
    }
}

2.2.2 @Controller 和 @RestControllerAdvice 配置

在controller层中新建IndexController类,传参实体类使用简单的User类;

@RestController
@RequestMapping("/index")
public class IndexController {


    @PostMapping("/list")
    public Result index(@Valid @RequestBody User user) throws Exception {

        return Result.SUCCESS();

    }

}

实体类User ,当我想要在传参时进行非空判断,则添加@NotBlank注解,并在Controller层 添加@Valid和@RequestBody 注解。(@Data 注解为Lombok 插件,idea中安装,如果没有直接在实体类中生成get和set 方法代替.)

@Data
public class User {

    private String id ;         // ID

    @NotBlank(message = "用户名不能为空.")
    private String username ;   // 用户名

    @NotBlank(message = "性别不能为空")
    private String sex ;  // 性别

    @NotBlank(message = "地址不能为空")
    private String address ;    // 地址

    private String status ;


}

在handler 文件夹中新建GlobalExceptionController类,并配置@RestControllerAdvice注解。对于实体类参数上的注解的异常捕获,我们在统一异常处理类中,通过MethodArgumentNotValidException进行捕获处理。

@RestControllerAdvice
public class GlobalExceptionController {
	
    
        /**
     * 处理方法参数异常,如 实体类上的@NotBlank注解异常,@NotNull注解等异常信息返回。
     * @param ex
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result handleMethodArgumentException(MethodArgumentNotValidException  ex){

        return Result.ERROR(ex.getBindingResult().getFieldError().getDefaultMessage()) ;
    }
}

至此我们解决了三个问题中的其中一个问题。下面解决抛出自定义异常 和断言异常的捕获。

在GlobalExceptionController中再添加 处理自定义异常和断言异常的捕获方法。

@RestControllerAdvice
public class GlobalExceptionController {
	
    
        /**
     * 处理方法参数异常,如 实体类上的@NotBlank注解异常,@NotNull注解等异常信息返回。
     * @param ex
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result handleMethodArgumentException(MethodArgumentNotValidException  ex){

        return Result.ERROR(ex.getBindingResult().getFieldError().getDefaultMessage()) ;
    }
    
    
        /**
     * 断言、自定义异常等处理
     * @param ex
     * @return
     */
    @ExceptionHandler(Exception.class)
    public Result handleException(Exception  ex){

        return Result.ERROR(ex.getMessage()) ;
    }
   
}

3.测试

我们在exception文件夹中创建自定义异常类:CustomException,并继承RuntimeException

public class CustomException extends RuntimeException {

    private String message ;

    public CustomException(String message){
        this.message = message ;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public CustomException setMessage(String message) {
        this.message = message;
        return this;
    }
}

在controller中添加一些判断进行观察,看看我们配置的情况。

@RestController
@RequestMapping("/index")
public class IndexController {


    @PostMapping("/list")
    public Result index(@Valid @RequestBody User user) throws Exception {

        // 抛出Exception异常
        if (user.getUsername().equals("xxx")){
            throw new Exception("用户名错误.");
        }

        // 自定义异常
        if (user.getUsername().equals("xxxxx")){
            throw new CustomException("自定义异常");
        }

        // 断言异常
        Assert.notNull(user.getStatus(), "状态不能为空");

        return Result.SUCCESS();

    }

}

我们通过Postman或者ApiPost观察。当我们什么参数都不传时,就会捕获注解上的异常message,当username传“xxx”,就会捕获用户名错误.异常,同理,可以观察到其他的情况,不多赘述。

统一异常处理配置很简单。至此配置完成。

通过统一异常处理的配置,我们可以去除大量的if..else 冗余代码,全部交由注解或者Assert断言,或者自定义异常来解决。

需要注意的一点是:该@Valid 注解 和@NotBlank 注解 是在springboot3.6以下版本基础之上的。本地demo使用的版本是2.6.1,springboot3.6 需要使用 @Validated 注解 。

原文地址:https://www.cnblogs.com/duanxiaobiao/p/15679110.html