共计 5069 个字符,预计需要花费 13 分钟才能阅读完成。
1. 前言
简述 JSR303/JSR-349,hibernate validation,spring validation 之间的关系。JSR303 是一项标准,JSR-349 是其的升级版本,添加了一些新特性,他们规定一些校验规范即校验注解,如 @Null,@NotNull,@Pattern,他们位于 javax.validation.constraints 包下,只提供规范不提供实现。而 hibernate validation 是对这个规范的实践(不要将 hibernate 和数据库 orm 框架联系在一起),他提供了相应的实现,并增加了一些其他校验注解,如 @Email,@Length,@Range 等等,他们位于 org.hibernate.validator.constraints 包下。而万能的 spring 为了给开发者提供便捷,对 hibernate validation 进行了二次封装,显示校验 validated bean 时,你可以使用 spring validation 或者 hibernate validation,而 spring validation 另一个特性,便是其在 springmvc 模块中添加了自动校验,并将校验信息封装进了特定的类中。这无疑便捷了我们的 web 开发。本文主要介绍在 springmvc 中自动校验的机制。
2. 常用的校验方式
限制
说明
@Null
限制只能为 null
@NotNull
限制必须不为 null
@AssertFalse
限制必须为 false
@AssertTrue
限制必须为 true
@DecimalMax(value)
限制必须为一个不大于指定值的数字
@DecimalMin(value)
限制必须为一个不小于指定值的数字
@Digits(integer,fraction)
限制必须为一个小数,且整数部分的位数不能超过 integer,小数部分的位数不能超过 fraction
@Future
限制必须是一个将来的日期
@Max(value)
限制必须为一个不大于指定值的数字
@Min(value)
限制必须为一个不小于指定值的数字
@Past
限制必须是一个过去的日期
@Pattern(value)
限制必须符合指定的正则表达式
@Size(max,min)
限制字符长度必须在 min 到 max 之间
@Past
验证注解的元素值(日期类型)比当前时间早
@NotEmpty
验证注解的元素值不为 null 且不为空(字符串长度不为 0、集合大小不为 0)
@NotBlank
验证注解的元素值不为空(不为 null、去除首位空格后长度为 0),不同于 @NotEmpty,@NotBlank 只应用于字符串且在比较时会去除字符串的空格
@Email
验证注解的元素值是 Email,也可以通过正则表达式和 flag 指定自定义的 email 格式
更多方式请查看源代码路径下提供的全量校验方式。
3. 自定义注解添加校验方法
例如:我们校验手机号或身份证号,官方提供的注解中没有支持的,当然我们可以通过官方提供的正则表达式来校验:
@Pattern(regexp = “^1(3|4|5|7|8)\\d{9}$”,message = “ 手机号码格式错误 ”)
@NotBlank(message = “ 手机号码不能为空 ”)
private String phone;
但是这种方式并不是很方便,我们可以自定义一个校验规则的注解
3.1 定义手机号校验注解 @Phone
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
/**
* 校验不通过的 message
*/
String message() default “ 请输入正确的手机号 ”;
/**
* 分组校验
*/
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
3.2 定义校验方式
public class PhoneValidator implements ConstraintValidator<Phone, String> {
@Override
public void initialize(Phone constraintAnnotation) {
}
@Override
public boolean isValid(String phone, ConstraintValidatorContext constraintValidatorContext) {
if(!StringUtils.isEmpty(phone)){
// 获取默认提示信息
String defaultConstraintMessageTemplate = constraintValidatorContext.getDefaultConstraintMessageTemplate();
System.out.println(“default message :” + defaultConstraintMessageTemplate);
// 禁用默认提示信息
constraintValidatorContext.disableDefaultConstraintViolation();
// 设置提示语
constraintValidatorContext.buildConstraintViolationWithTemplate(“ 手机号格式错误 ”).addConstraintViolation();
String regex = “^1(3|4|5|7|8)\\d{9}$”;
return phone.matches(regex);
}
return true;
}
}
4. 引入依赖
我们使用 maven 构建 springboot 应用来进行 demo 演示。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
我们只需要引入 spring-boot-starter-web 依赖即可,如果查看其子依赖,可以发现如下的依赖:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
验证了我之前的描述,web 模块使用了 hibernate-validation,并且 databind 模块也提供了相应的数据绑定功能。
5. 构建简单 Demo 项目
5.1 构建启动类
无需添加其他注解,一个典型的启动类
@SpringBootApplication
public class ValidateApp {
public static void main(String[] args) {
SpringApplication.run(ValidateApp.class, args);
}
}
5.2 创建需要被校验的实体类
@Data
public class UserEntity {
@NotBlank
private String name;
@Range(max = 150, min = 1, message = “ 年龄范围应该在 1 -150 内。”)
private Integer age;
@Email(message = “ 邮箱格式错误 ”)
private String email;
@NotEmpty(message = “ 密码不能为空 ”)
@Length(min = 6, max = 8, message = “ 密码长度为 6 - 8 位。”)
private String password;
@Pattern(regexp = “^1(3|4|5|7|8)\\d{9}$”,message = “ 手机号码格式错误 ”)
@NotBlank(message = “ 手机号码不能为空 ”)
private String phone;
@IdCard
private String idCard;
}
5.3 在 Controller 中开启校验
在 Controller 中 请求参数上添加 @Validated 标签开启验证
@RestController
@Slf4j
public class TestController {
@PostMapping(“/user”)
public String test1(@RequestBody @Validated UserEntity userEntity){
log.info(“user is {}”,userEntity);
return “success”;
}
}
5.4 校验结果
{
“timestamp”: “2019-03-10T09:29:20.978+0000”,
“status”: 400,
“error”: “Bad Request”,
“errors”: [
{
“codes”: [
“NotBlank.userEntity.name”,
“NotBlank.name”,
“NotBlank.java.lang.String”,
“NotBlank”
],
“arguments”: [
{
“codes”: [
“userEntity.name”,
“name”
],
“arguments”: null,
“defaultMessage”: “name”,
“code”: “name”
}
],
“defaultMessage”: “ 不能为空 ”,
“objectName”: “userEntity”,
“field”: “name”,
“rejectedValue”: “”,
“bindingFailure”: false,
“code”: “NotBlank”
}
],
“message”: “Validation failed for object=’userEntity’. Error count: 1”,
“path”: “/user”
}
这个结果我们可以进行统一处理,筛选出适合给前端返回的错误提示文案。关于统一处理异常,在这篇文章中已经提到:https://segmentfault.com/a/11…
5.5 异常处理
@ControllerAdvice
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 分隔符
*/
private static final String SEPARATOR = “,”;
/**
* 拦截数据校验异常
*
* @param request 请求
* @param e 校验异常
* @return 通用返回格式
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ZingResult notValidException(HttpServletRequest request, MethodArgumentNotValidException e) {
log.error(“ 请求的 url 为 {} 出现数据校验异常, 异常信息为:”, request.getRequestURI(), e);
BindingResult bindingResult = e.getBindingResult();
List<String> errorMsgList = new ArrayList();
for (FieldError fieldError : bindingResult.getFieldErrors()) {
errorMsgList.add(fieldError.getDefaultMessage());
}
return ZingResult.error(ExceptionEnum.PARAM_ERROR,errorMsgList);
}
}
6. 总结
这个框架校验还有其他多种用法,如,分组校验、手动校验等,我总结的这篇博客也是参照该文章。详情参看:https://blog.csdn.net/u013815…
最后该作者的总结也非常好:
我推崇的方式,是仅仅使用自带的注解和自定义注解,完成一些简单的,可复用的校验。寻求一个易用性和封装复杂性之间的平衡点是我们作为工具使用者应该考虑的。