如何校验普通的方法参数

测试

测试非DTO参数

准备如下测试代码:

1
2
3
4
5
6
7
8
9

@Validated
@Service
public class TmpService {
    public void doService(@JNotBlank String inputParam) {
        // do something
    }
}

这份代码有两个地方需要注意:

  1. 类上的注解只能为@Validated,@Valid并不会生效。
  2. 方法参数不需要@Validated@Valid,我看网上有些教程写了,其实是不需要的。

测试DTO参数

准备如下代码:

1
2
3
4
5
6
7
8
9

@Validated
@Service
public class TmpService {
    public void doService(@Valid TestRequest request) {
        // do something
    }
}

在我的测试中,类上的@Validated注解和方法上的@Valid注解,缺一不可,我觉得这是一种非常让人迷惑的写法,一不小心就可能翻车。

小结

message提示的问题

实验中我其实是有其他收获的,我发现当方法校验失败时,得到的返回结果是(基于我小小开发了一下的校验框架):

1
2
3
4
5
6

{
    "code": 0,
    "msg": "doService.inputParam 为空或长度为0"
}

也就是说basePath会指明方法及方法的参数。这说明了一个问题,我们开发的用于Request的注解,最好只用于Controller层,否则的话会将内部实现的一些细节暴露给用户。当然不仅仅是我们开发的注解,框架的注解也同样需要存在这个问题,甚至因为没有足够的提示信息,框架的提示信息更让人迷惑。

1
2
3
4
5
6

{
    "code": 0,
    "msg": "不能为空"
}

貌似说我们为注解加上message就可以完事了,这样用户就可以得到清晰明了的信息了,但是,我们应该好好思考一下,我们应该将service层参数校验的信息抛给用户么?我决定在下面好好讨论一下。

1
2
3
4
5
6

{
    "code": 0,
    "msg": "inputParam不能为空"
}

关于异常的讨论

异常目前主要分为两种:系统异常和业务异常,数据库无法获取链接,属于系统异常,一个给定的Id无法从数据库中找到数据,属于业务异常。这些异常都能很好的区分。

但是因为我们开发的Service往往需要提供给第其他人用,我们需要对传入的参数进行非空等校验,校验失败的时候,我们该抛出什么样的异常呢?系统的还是业务的?我目前的想法还是算作业务异常吧,因为我们写的Service本质上就是业务Service,所以这些Service抛出的异常理应为业务异常。

从这个分析的角度,我们Controller中对Request的校验失败属于业务异常、Service层方法对参数的校验数据也属于业务异常,我们可以用相同的方式处理。

异常捕获的问题

目前在参数校验方面,我发现了三种不同的异常,但是我目前用到的主要是两种:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

    @ResponseBody
    @ExceptionHandler(value = {
            MethodArgumentNotValidException.class,
            ConstraintViolationException.class,
            BindException.class})
    public Response exceptionHandler(Exception e) {

        String message = "参数校验失败";

        if (e instanceof BindException) {
            message = ((BindException) e).getBindingResult().getFieldError().getDefaultMessage();
        } else if (e instanceof ConstraintViolationException) {
            Optional<String> messageOptional = ((ConstraintViolationException) e).getConstraintViolations()
                    .stream()
                    .map(ConstraintViolation::getMessage)
                    .findFirst();
            message = messageOptional.get();
        } else {
            message = ((MethodArgumentNotValidException) e).getBindingResult().getFieldError().getDefaultMessage();
        }

        return new Response(0, message);
    }

Service层校验失败,抛出来的就是ConstraintViolationException异常。

另外,关于异常处理,我收集了一段非常不错的代码,之所以觉得它用的好,是因为它使用了orElse,这个方法我使用的次数非常的少。

1
2
3
4
5
6
7

message e.getConstraintViolations()
        .stream()
        .findFirst()
        .map(ConstraintViolation::getMessage)
        .orElse("参数校验失败"))

这是我目前收集的信息。

参考资料

  1. spring boot 参数校验这么做简洁实用