SpringBoot使用Hibernate Validator(后端参数校验)
# SpringBoot使用Hibernate Validator(后端参数校验)
- 表单提交,前端做的了参数校验,服务器端还需要做参数校验吗?
- 服务器端怎么做参数校验?怎么样做参数校验最快捷?
- JSR-303 是什么?如何使用?SpringBoot下如何使用?
- 使用一个实体接收提交内容时,新增和更新操作的参数校验标准不同时如何解决?
- 如何自定义校验?
SpringBoot 使用 JSR303 实现参数验证。
SpringBoot 使用 Hibernate Validator 校验。
示例代码,下文所有源代码都在这个仓库。
Validator-demo Github地址 (opens new window)
# JSR-303
看 JSR-303,首先我们得看看 JSR 是什么含义。
是指向 JCP(Java Community Process) 提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。
JSR,Java Specification Requests 的缩写,意思是 Java 规范提案。
JSR-303 是JAVA EE 6 中的一项子规范,叫做 Bean Validation。
Hibernate Validator 是 Bean Validation 的参考实现,Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。
# SpringBoot使用JSR-303
SpringBoot 使用 JSR303 实现参数验证。
SpringBoot 使用 Hibernate Validator 实现参数验证。
# 基本使用
- 在接收参数的 DTO 上加上相关验证条件;
@Data
public class TestDTO1 {
@NotNull
private Long id;
@NotBlank
private String name;
@NotNull
private Integer age;
@NotNull
private Integer status;
}
2
3
4
5
6
7
8
9
10
11
- 开启校验功能 @Valid;
@PostMapping("/test1")
public R test1(@Valid @RequestBody TestDTO1 dto) {
log.info("[TestDTO1 <{}>]", dto);
return R.ok();
}
2
3
4
5
- 给校验的 bean 后紧跟一个 BindingResult,就可以获取到校验的结果;
@PostMapping("/test2")
public R test2(@Valid @RequestBody TestDTO1 dto, BindingResult result) {
log.info("[TestDTO1 <{}>]", dto);
log.error("[BindingResult <{}>]", result);
if (result != null && result.getErrorCount() > 0) {
return R.failed(result.getAllErrors());
}
return R.ok();
}
2
3
4
5
6
7
8
9
# 全局异常处理
加上全局异常处理
@Slf4j
@RestControllerAdvice
public class GlobalBizExceptionHandler {
/**
* 全局异常.
* @param e the e
* @return R
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public R handleGlobalException(Exception e) {
log.error("全局异常信息 ex={}", e.getMessage(), e);
return R.failed(e.getLocalizedMessage());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
使用 @RequestBody 来接收参数时,当传入的参数不符合要求时,抛出的异常为 org.springframework.web.bind.MethodArgumentNotValidException,所以我们对这个异常进行处理。
/**
* validation Exception
* @param exception
* @return R
*/
@ExceptionHandler({ MethodArgumentNotValidException.class })
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R handleBodyValidException(MethodArgumentNotValidException exception) {
List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
log.warn("[参数绑定异常,ex = {}]", fieldErrors.get(0).getDefaultMessage());
return R.failed(fieldErrors.get(0).getDefaultMessage());
}
2
3
4
5
6
7
8
9
10
11
12
使用以 form-data 形式传参时,当传入的参数不符合要求时,抛出的异常为 org.springframework.validation.BindException,所以我们对这个异常进行处理。
/**
* validation Exception (以form-data形式传参)
* @param exception
* @return R
*/
@ExceptionHandler({ BindException.class })
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R bindExceptionHandler(BindException exception) {
List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
log.warn("[参数绑定异常,ex = {}]", fieldErrors.get(0).getDefaultMessage());
return R.failed(fieldErrors.get(0).getDefaultMessage());
}
2
3
4
5
6
7
8
9
10
11
12
加上全局异常捕获后,参数若有不符合,请求的结果如下:
{
"code": 1,
"msg": "id必须为空",
"data": null
}
{
"code": 1,
"msg": "age不能为空",
"data": null
}
2
3
4
5
6
7
8
9
10
# 分组校验
使用一个实体接收提交内容时,新增和更新操作的参数校验标准不同时如何解决?
例:新增操作不需要传入 id,更新操作必须传入 id。
上面说的问题都是最基础的,那如何实现呢?
- 创建分组标识。
public interface AddGroup {}
public interface UpdateGroup {}
2
- 在接收参数的 DTO 上标识,给校验注解标注什么情况需要进行校验。
@NotNull(message = "id不能为空", groups = {UpdateGroup.class})
@Null(message = "id必须为空", groups = {AddGroup.class})
private Long id;
2
3
- 在 Controller 的方法上,使用 @Validated 指定分组标识。
@PostMapping("/test3")
public R test3(@Validated(AddGroup.class) @RequestBody TestDTO3 dto) {
log.info("[TestDTO3 <{}>]", dto);
return R.ok();
}
2
3
4
5
注意点: 默认没有指定分组的校验注解,在分组校验情况下不生效。
(例:我们有个参数的校验规则没有分组, @NotBlank 没有进行分组,它无论在 AddGroup 或 UpdateGroup 下都是不生效的)
# 顺序校验
在一些场景下,参数校验是需要有顺序的。
JSR-303 校验中专门提供了 @GroupSequence 来实现。
@GroupSequence(value = {ValidateSequence.Validate1.class,
ValidateSequence.Validate2.class,
ValidateSequence.Validate3.class,
ValidateSequence.Validate4.class,
ValidateSequence.Default.class,})
public interface ValidateSequence {
interface Validate1{}
interface Validate2{}
interface Validate3{}
interface Validate4{}
interface Default{}
}
2
3
4
5
6
7
8
9
10
11
12
@Data
public class TestDTO4 {
@NotNull(message = "id不能为空", groups = {ValidateSequence.Validate1.class})
private Long id;
@NotBlank(message = "name不能为空", groups = { ValidateSequence.Validate2.class})
private String name;
@NotNull(message = "age不能为空", groups = { ValidateSequence.Validate2.class})
private Integer age;
@NotNull(message = "status不能为空")
private Integer status;
}
2
3
4
5
6
7
8
9
10
11
@PostMapping("/test5")
public R test5(@Validated(ValidateSequence.class) @RequestBody TestDTO4 dto) {
log.info("[TestDTO4 <{}>]", dto);
return R.ok();
}
2
3
4
5
# 自定义校验
有些时候,我们需要的一些校验规则,库中没有提供,这时候我们就需要自定义了。
一些简单的,可以通过正则表达式匹配可以实现。
@Data
public class TestDTO5 {
@NotNull(message = "id不能为空")
private Long id;
@NotBlank(message = "name不能为空")
@Pattern(regexp = "^[A-Za-z][A-Za-z0-9]{5,17}$", message = "用户名由字母开头的6-18位数字和字母组成")
private String name;
@NotBlank(message = "phone不能为空")
@Pattern(regexp = "^1[3|4|5|6|8|7|9]\\d{9}$", message = "手机号格式不正确")
private String phone;
}
2
3
4
5
6
7
8
9
10
11
但是有一些,正则表达式无法判断。
例:传入的是一个 List,但我们要限制传入的值,只能是固定的一些数。
1.编写一个自定义的校验注解;
@Documented
// 可以指定多个不同的校验器
@Constraint(validatedBy = {ListInValuesValiadator.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface ListInValues {
// 默认提示值可自定义,词条文件:ValidationMessages.properties
String message() default "{javax.validation.constraints.NotBlank.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] values() default {};
}
2
3
4
5
6
7
8
9
10
11
12
2.编写一个自定义的校验器;
public class ListInValuesValiadator implements ConstraintValidator<ListInValues, Integer> {
private Set<Integer> values = new HashSet<>();
@Override
public void initialize(ListInValues constraintAnnotation) {
// 初始化
int[] vals = constraintAnnotation.values();
for (int val : vals) {
values.add(val);
}
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
// 校验
return values.contains(value);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
3.关联自定义的校验器和自定义的校验注解,@Constraint(validatedBy = {ListInValuesValiadator.class});
4.使用;
@NotNull
@ListInValues(values = {0,1}, message = "错误值")
private Integer status;
2
3
# 源码
Validator-demo Github地址 (opens new window)
更多好文,请关注我的博客 (opens new window)