【工具】JSR-303后端参数校验框架的使用方法及说明
1. 统一校验需求
有一句话是这样说的——“前端防君子,后端防小人”。这句话说明参数的校验在前端和后端都是十分重要的。不懂技术的人一般在前端页面无法找到系统的漏洞,但是懂技术又不怀好意的人如果获取到了你后端接口的地址,而恰好你后端又没有对参数进行校验,那么就很有可能会被他人乘虚而入,对系统造成很大的影响。为了避免这一情况的发生,所有我们需要在后端也进行参数的校验。
那么我们又有一个问题了,参数校验的代码是写在Controller层还是写在Service层呢?
对于一般的参数,我们应该是放在Controller层中进行校验,而对于业务逻辑的校验则在Service层中进行。Contoller中校验请求参数的合法性,包括:必填项校验,数据格式校验,比如:是否是符合一定的日期格式,等。Service中要校验的是业务规则相关的内容,比如:已存在的用户不能重复新增。
早在JavaEE6规范中就定义了参数校验的规范,它就是 JSR-303
,它定义了Bean Validation,即对bean属性进行校验。
SpringBoot提供了JSR-303的支持,它就是 spring-boot-starter-validation
,它的底层使用Hibernate Validator,Hibernate Validator是Bean Validation 的参考实现。
所以,我们准备在Controller层使用spring-boot-starter-validation完成对请求参数的基本合法性进行校验。
2. 使用说明
2.1 引入依赖
我们在项目的pom文件中添加如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2.2 规则说明
上面引入的依赖为我们提供了很多的注解,注解的使用规则如下:
了解了使用的规则之后我们就可以开始使用了。
2.3 使用说明
1)场景描述
设定一个场景,我们需要添加一门课程,前端传入一系列的参数,我们后端使用dto去接收这些参数。同时我们还需要在Controller校验这些参数是否合法。
首先设定dto类代码如下所示:
@Data
public class AddCourseDto {
@NotEmpty(message = "课程名称不能为空")
private String name;
@NotEmpty(message = "适用人群不能为空")
@Size(message = "适用人群内容过少",min = 10)
private String users;
@NotEmpty(message = "课程分类不能为空")
private String mt;
@NotEmpty(message = "课程分类不能为空")
private String st;
@NotEmpty(message = "课程等级不能为空")
private String grade;
@NotEmpty(message = "收费规则不能为空")
private String charge;
}
我们在这个dto中用到了两个校验注解 @NotEmpty
和 @Size
,注解含义如上图所示。
2)定义接口
我们已经定义好了dto,然后我们需要在接口层对dto进行校验,校验的方法也很简单,只需要在接口的参数列表中添加一个 @Validated
注解。接口代码如下所示:
@PostMapping("/course")
public CourseBaseInfoDto createCourseBase(@RequestBody @Validated AddCourseDto addCourseDto){
//...
return courseBaseInfoService.createCourseBase(companyId,addCourseDto);
}
如果传入的参数中有不合规范的,Spring会抛出一个 MethodArgumentNotValidException
异常,我们需要在统一异常处理器中捕获异常,解析出异常信息。
3)编写异常处理逻辑
我们在统一异常处理器中对异常进行捕获,编写异常处理逻辑:
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)//状态码返回500
public RestErrorResponse doMethodArgumentNotValidException(MethodArgumentNotValidException e) {
BindingResult bindingResult = e.getBindingResult();
//校验的错误信息
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
//收集错误
StringBuffer errors = new StringBuffer();
fieldErrors.forEach(error -> {
errors.append(error.getDefaultMessage()).append(",");
});
//将错误信息以json的形式返回
return new RestErrorResponse(errors.toString());
}
我们使用httpcilent今昔测试,我们将name属性的值设为空。
POST {{content_host}}/course
Content-Type: application/json
{
"mt": "1-1",
"st": "1-1-1",
"name": "",
"pic": "",
"teachmode": "200002",
"users": "初级人员",
"tags": "标签",
"grade": "204001",
"description": "",
"charge": "201001",
"price": 10,
"originalPrice":100,
"qq": "123",
"wechat": "123",
"phone": "123",
"validDays": 365
}
进行测试:
返回信息:
这时发现,参数校验已经生效了。但是我们这又会引发出另一个问题。那就是:
假如我们有两个接口的参数都包含了相同的dto,我们都需要对其进行校验,但是这两个接口对dto的校验规则又不一样,比如第一个接口要求dto的name属性不能为空,第二个接口却要求dto的name属性可以为空。那我们怎么办呢?我们是再创建一个和dto完全相同的类去重新编写校验规则吗?当然不是,Spring为我们提供了更好的解决方案——“分组校验”。
2.4 分组校验
有时候再同一个属性上设置一个校验规则并不能满足所有要求。
比如:订单编号由系统生成,在添加订单时要求订单编号为空,在更新订单时要求订单编写不能为空。此时就用到了分组校验,同一个属性定义多个校验规则属于不同的分组,比如:添加订单定义@NULL规则属于insert分组,更新订单定义@NotEmpty规则属于update分组,insert和update是分组的名称,是可以修改的。
1)分组
我们用class类型来表示不同的分组,所以我们定义不同的接口类型(空接口)表示不同的分组。代码如下所示:
public class ValidationGroups {
public interface Inster{};
public interface Update{};
public interface Delete{};
}
2)指定分组
在dto中定义校验规则是指定分组,比如上面的name属性,我们可以为其指定两个分组。
@NotEmpty(groups = {ValidationGroups.Inster.class},message = "添加课程名称不能为空")
@NotEmpty(groups = {ValidationGroups.Update.class},message = "修改课程名称不能为空")
// @NotEmpty(message = "课程名称不能为空")
private String name;
3)接口层引用分组
在Controller方法中启动校验规则指定要使用的分组名:
@PostMapping("/course")
public CourseBaseInfoDto createCourseBase(@RequestBody @Validated({ValidationGroups.Inster.class}) AddCourseDto addCourseDto){
//。。。
return courseBaseInfoService.createCourseBase(companyId,addCourseDto);
}
再次测试,由于这里指定了Insert分组,所以抛出 异常信息:添加课程名称不能为空。
如果修改分组为ValidationGroups.Update.class,异常信息为:修改课程名称不能为空。
2.5 定制校验规则注解
如果javax.validation.constraints包下的校验规则满足不了需求怎么办?
我们有两种方法:
- 手写校验代码。
- 自定义校验规则注解。
自定义校验规则注解的方法可以查看:自定义校验注解