文章目录
前言
数据校验是系统接口对接、网站交互等场景不可或缺的功能,涉及页面交互的一般通过js在录入时进行校验,但是还是存在绕过前端校验的可能,还有系统间调用的接口,或者是其他意外情况绕过校验,而一旦系统中出现非法的数据,这对运维来说是件很头疼的事情,所以服务端的数据校验也是十分必要的,本文介绍的validation就是进行这样的数据校验。
一、JSR303简介
- JSR-303/JSR-349 是JAVA EE 6 中的一项子规范,叫做Bean Validation。JSR-349 是其升级版本,添加了一些新特性,他们规定了一些校验规范即校验注解,如 @Null,@NotNull,@Pattern等,这些规范位于 JDK的javax.validation.constraints 包下,只提供规范不提供实现,包名validation-api-x.x.x.jar。
- Hibernate Validator是JSR-303/JSR-349的主要实现,这里的Hibernate不是数据库ORM框架的Hibernate,他提供了基本的实现,并增加了一些其他校验注解,如 @Email,@Length,@Range 等等,他们位于 org.hibernate.validator.constraints 包下,包名hibernate-validator-x.x.x.jar。
- Spring Vlidation是JSR-303/JSR-349的另外一种实现,Spring Vlidation是Spring给开发者提供的一个便捷的校验实现,它对Hibernate Validator进行了二次封装,在需要使用校验时,你可以选择使用Spring Vlidation或者Hibernate Validator,都能达到同样的目的,Spring Vlidation有一个特性,便是其在 SpringMVC 模块中添加了自动校验,并将校验信息封装进了特定的类中,这无疑便捷了我们的 web 开发。
- JSR-303/JSR-349其他的实现还有Dubbo Validation,只要在Dubbo注册时配置validation = true,并在参数上使用 JSR303 标准的验证 annotation 就可以生效了。dubbo启用参数验证也要依赖hiberate-validator包,最终底层八成也是调用Hibernate Validator执行校验。
二、JSR303定义的注解
注解类型 | 功能描述 |
---|---|
@Valid | 被注释的元素是一个对象,需要检查此对象的所有字段值 |
@Null | 被注释的元素必须为 null |
@NotNull | 被注释的元素必须不为 null,任何对象的value不能为null |
@AssertTrue | 被注释的元素必须为 true |
@AssertFalse | 被注释的元素必须为 false |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max, min) | 被注释的元素的大小必须在指定的范围内 |
@Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
@Pattern(value) | 被注释的元素必须符合指定的正则表达式 |
三、Hibernate Validator定义的注解
注解类型 | 功能描述 |
---|---|
被注释的元素必须是电子邮箱地址 | |
@Length(min=, max=) | 被注释的字符串的大小必须在指定的范围内 |
@NotEmpty | 被注释的字符串的必须非空,集合对象的元素不为0,即集合不为空,也可以用于字符串不为null |
@Range(min=, max=) | 被注释的元素必须在合适的范围内 |
@NotBlank | 被注释的字符串的必须非空,只能用于字符串不为null,并且字符串trim()以后length要大于0 |
@URL(protocol=,host=, port=, regexp=, flags=) | 被注释的字符串必须是一个有效的url |
@CreditCardNumber | 被注释的字符串必须通过Luhn校验算法,银行卡,信用卡等号码一般都用Luhn计算合法性 |
@ScriptAssert(lang=, script=, alias=) | 要有Java Scripting API 即JSR 223 (“Scripting for the JavaTM Platform”)的实现 |
@SafeHtml(whitelistType=, additionalTags=) | classpath中要有jsoup包 |
hibernate补充的注解中,最后3个不常用,可忽略
四、使用方式
以下是基本的使用方法,其他用法诸如分组校验、方法校验等读者可自行查阅相关文档学习,这里就不做介绍了。
1、Hibernate Validator使用方法
- pom导入依赖项
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.18.Final</version>
<exclusions>
<exclusion>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</exclusion>
</exclusions>
</dependency>
或者直接导入Spring Boot提供的starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.4.5</version>
</dependency>
- 添加注解
@Data
public class UserInfo {
@javax.validation.constraints.NotNull
private Long id;
@javax.validation.constraints.NotEmpty
private String name;
@org.hibernate.validator.constraints.Range
private Integer age;
@javax.validation.constraints.Email
private String email;
private LocalDate birthdate;
}
- 使用校验
public void ValidatorTest {
private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
UserInfo userInfo = new UserInfo ();
//如果校验对象userInfo 不通过,则存在校验不通过信息
Set<ConstraintViolation<UserInfo>> set = validator.validate(userInfo);
set.stream().forEach(cv -> {
System.out.println("属性:"+cv.getPropertyPath()+",属性值:"+cv.getInvalidValue()+",校验不通过,触发规则:"+cv.getMessage());
});
}
2、Spring Vlidation使用方法
- pom导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.5</version>
</dependency>
- 添加注解
@Data
public class UserInfo {
@javax.validation.constraints.NotNull
private Long id;
@javax.validation.constraints.NotEmpty
private String name;
@org.hibernate.validator.constraints.Range
private Integer age;
@javax.validation.constraints.Email
private String email;
private LocalDate birthdate;
}
- 使用@Valid/@Validated开启注解
Void updateUser(@RequestBody @Valid UserInfo req);
@Valid和@Validated区别的区别如下
- 使用校验
spring-mvc自动解析参数,并调用Hibernate Validator进行校验
2、Dubbo Vlidation使用方法
- pom引入依赖项
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.10.Final</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
- 添加校验注解
- 开启校验
<dubbo:reference id="validationService" interface="org.apache.dubbo.examples.validation.api.ValidationService" validation="true" />
只要在dubbo注册时配置validation = true,并在参数上使用 JSR303 标准的验证 annotation 就可以生效了
五、附录
这里分享一个Hibernate Validator方式校验常用工具类,在实际使用中,很多场景下都需要手动发起校验,例如批量导入对文件中数据的校验等,仅供大家参考。
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.groups.Default;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @description:自定义校验
* @author Mr.bin
* @date 2022/7/4
*/
public class CustomValidator {
private CustomValidator() {
}
private static Validator validator;
static {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
public static <T> Map<String,StringBuffer> validate(T obj){
Map<String,StringBuffer> errorMap = null;
Set<ConstraintViolation<T>> set = validator.validate(obj, Default.class);
if(set != null && set.size() >0 ){
errorMap = new HashMap<String,StringBuffer>();
String property = null;
for(ConstraintViolation<T> cv : set){
//这里循环获取错误信息,可以自定义格式
property = cv.getPropertyPath().toString();
if(errorMap.get(property) != null){
errorMap.get(property).append("," + cv.getMessage());
}else{
StringBuffer sb = new StringBuffer();
sb.append(cv.getMessage());
errorMap.put(property, sb);
}
}
}
return errorMap;
}
public static <T> Map<String,StringBuffer> validate(T obj, Class<?>... groups){
Map<String,StringBuffer> errorMap = new HashMap<>();
Set<ConstraintViolation<T>> set = validator.validate(obj, groups);
if(set != null && set.size() >0 ){
errorMap = new HashMap<String,StringBuffer>();
String property = null;
for(ConstraintViolation<T> cv : set){
//这里循环获取错误信息,可以自定义格式
property = cv.getPropertyPath().toString();
if(errorMap.get(property) != null){
errorMap.get(property).append("," + cv.getMessage());
}else{
StringBuffer sb = new StringBuffer();
sb.append(cv.getMessage());
errorMap.put(property, sb);
}
}
}
return errorMap;
}
}