SpringBoot中的异常处理与参数校验

原创
2020/04/21 09:13
阅读数 4.3W

兄弟们好,这次来跟老铁交流两个问题,异常和参数校验,在说参数校验之前我们先来说异常处理吧,因为后面参数的校验会牵扯到异常处理这块的内容。

异常处理

说到异常处理,我不知道大家有没有写过或者遇到过如下的写法。

public void saveUser() {
        
    try {
        // 所有的业务内容,目测几百行
    }catch (Exception e) {
        e.printStackTrace();
    }
}

如果出现上述的代码,里面包含了大量的业务代码,如果是你写的,赶紧改掉,不是你写的找写的,吐槽赶紧改掉。

存在的问题:

  • 1、会遇到性能瓶颈;
  • 2、很难定位问题;
  • 3、try嵌套过多可读性很差;

不管什么原因出现了上述代码,那么最好还是改一下,如果非要在业务代码中try,那么也应该只在可能出现异常的地方使用try,而不是try整个业务代码。

SpringBoot中的异常捕获

直接上代码

@RestControllerAdvice
public class GlobalException {

    @ExceptionHandler(value = Exception.class) // 捕获的异常类型
    public Object globalException(Exception ex) {

        // 异常处理
        ex.printStackTrace();

        return "出现异常";
    }
}

那么在SpringBoot中我们就可以通过这样的一个配置可以获取到项目中出现异常的地方,我们可以在这个方法中可以获取出现异常的类的详细信息,那么是不是所有的异常我们全部使用Exception来处理呢?那么肯定是不合适的。

我们模拟一个by zero的异常,然后再配置一个处理ArithmeticException异常的处理器,代码如下:

@RestControllerAdvice
public class GlobalException {


    @ExceptionHandler(value = Exception.class) // 捕获的异常类型
    public Object globalException(Exception ex) {

        ex.printStackTrace();

        return "出现异常";
    }

    @ExceptionHandler(value = ArithmeticException.class)
    public Object arithmeticException(ArithmeticException ex) {

        ex.printStackTrace();
        return "by zero异常";
    }
}

如果这个时候出现by zero异常,走ArithmeticException异常处理,原因就是因为如果有更小范围的异常处理类,那么会走小范围的异常处理器。不会走globalException更大的异常处理类。

这样处理之后,我们就不需要在项目中去写那么多的try了,是不是方便了很多。

除了使用这些已经存在的异常外,其实我们还可以自定义我们的异常,比如我们常用的用户未登录异常、参数错误异常等等。但是考虑到这篇文章的篇幅问题,这次就先不写了,有兴趣的朋友可以直接下面留言,人多了我尽快更新。

注意坑:

这里跟大家分享一个踩过的坑,不能再Filter过滤器中抛出异常,如果通过在过滤器中抛出异常,然后通过异常处理类来处理,那么是不可能的,因为处理器是捕获不到Filter抛出的异常的。

参数校验

老规矩,先来看一段代码

@RequestMapping(value = "/save/user")
public Object saveUser(UserPO userPO) {

    if (userPO.getAge() == null) {
        return "请求参数错误";
    }

    if (userPO.getSex() == null) {
        return "请求参数错误";
    }
    if (userPO.getUsername() == null) {
        return "请求参数错误";
    }

    // ...

    return "SUCCESS";
}

应该见过这种校验参数的吧,说实话我写过。越写感觉越low,所以狠心一下,还是趁早改吧。

@Validated注解

这个注解其实是Spring提供的,如果你的项目不是SpringBoot项目,需要引一下需要的pom文件,如果是,那么就不用管了,SpringBoot已经帮我们引入了。

网上看了好多的博客,许多都说的不是很全,大部分都是说JavaBean参数的校验,但是我们项目中有些接口可能就涉及一个参数,根本不需要写一个JavaBean,对于单一参数的校验好多博客还是没说的,那么我们这次就一次性讲清楚。

单一参数的校验

直接看代码吧

@Validated
@RestController
public class BookController {
    
    @RequestMapping(value = "/book/info", method = RequestMethod.GET)
    public Object getBookInfo(@NotBlank(message = "书籍ID不能为空") String bookId) {

        return "SUCCESS";
    }
}

这里要跟大家特别说明下,如果是单一参数的校验,那么我们必须要在类上面添加@Validated注解,不然我们整个单个参数校验是不会生效的,可以看到我们在校验参数bookId的时候,使用了@NotBlank那么顾名思义,就是这个参数不能为null,在调用了trim()方法之后也不能是空字符。

如果参数不满足要求,那么会抛出ConstraintViolationException异常,这个异常只有在单一参数校验的时候抛出,如果你的参数是JavaBean,那么就不是这个异常了。

既然我们知道了它会抛出异常,并且我们也知道是什么异常类型,那么久超级简单了,我们可以直接使用上面刚学的异常处理类来处理我们的异常。

我找个里面写的比较简单,如果你想写的复杂一点,其实也是可以的,但是作为后端来说,我觉得没必要,因为我们不能给前端提示太过明显的错误提示,防止别人恶意攻击我们,就像用户名密码错误,不能明确的告诉用户到底是用户名错误还是密码错误,只能提示用户名或密码错误。

如果大家非要把详细的错误信息打出来,要看到到底是哪个参数校验不通过,也可以通过下面的方式将具体的参数错误信息打印出来。输出的错误结果其实就是上面message里面的内容。

@RestControllerAdvice
public class ExceptionCatch {
    /**
     * 单个参数异常处理
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public Object constraintViolationException(ConstraintViolationException ex) {

        // 获取具体的错误信息
        Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
        // 打印数据
        violations.forEach(e -> System.out.println(e.getMessage()));
        
        return "单个-请求参数错误";
    }
}

JavaBean参数校验(form-data)

JavaBean的写法

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserPO {

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

    @NotNull(message = "年龄不能为空")
    @Min(value = 1, message = "年龄最小为1")
    @Max(value = 200, message = "年龄最大为200")
    private Integer age;

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

Controller写法

@RequestMapping(value = "/save/user")
public Object saveUser(@Validated UserPO userPO) {

    // ...

    return "SUCCESS";
}

跟单一参数校验不一样的是JavaBean的校验方式需要将@Validated写在方法参数,而不是类上。如果出现了参数校验不通过,同样的也会抛出一个异常,BindException。

/**
 * 一般参数校验绑定异常处理
 *
 * @param ex
 * @return
 */
@ExceptionHandler(value = BindException.class)
public Object bindException(BindException ex) {

    BindingResult bindingResult = ex.getBindingResult();

    // 获取所有的错误信息
    List<ObjectError> allErrors = bindingResult.getAllErrors();

    // 输出
    allErrors.forEach(e -> System.out.println(e.getDefaultMessage()));

    return "请求参数错误";
}

注意:大家要注意post请求有两种方式,一种是基于form-data格式的数据传递,另外一种就是基于json格式的数据传递,两种传递方式引发的异常也是不一样的,所以我们还要单独处理基于json的参数校验异常处理。

JavaBean参数校验(json)

我们先来看下Controller接收方式

@RequestMapping(value = "/save/user")
public Object saveUser(@Validated @RequestBody UserPO userPO) {

    // ...

    return "SUCCESS";
}

对应的参数异常处理

/**
 * JSON参数校验绑定异常处理
 *
 * @param ex
 * @return
 */
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Object methodArgumentNotValidException(MethodArgumentNotValidException ex) {

    BindingResult bindingResult = ex.getBindingResult();

    // 获取所有的错误信息
    List<ObjectError> allErrors = bindingResult.getAllErrors();

    // 输出
    allErrors.forEach(e -> System.out.println(e.getDefaultMessage()));

    return "请求参数错误-json";
}

最后的话

那么到这里,我们本篇文章就结束了,主要介绍了两部分内容,异常的处理和参数的校验。虽然很简单,但是我个人感觉还是挺常用的技能。所以与大家进行分享,如果对你有点帮助,就来点个赞吧。如果有什么不明白的也欢迎下方留言,一起来交流。

更多内容请关注微信公众号:一个程序员的成长

展开阅读全文
加载中

作者的其它热门文章

打赏
11
139 收藏
分享
打赏
13 评论
139 收藏
11
分享
返回顶部
顶部
返回顶部
顶部