基于springboot使用hibernate validator校验数据
在开发中经常需要写一些字段校验的代码,比如字段非空,字段长度限制,邮箱格式验证等等
hibernate validator
(官方文档)提供了一套比较完善、便捷的验证实现方式。
spring-boot-starter-web
包里面有hibernate-validator
包,不需要引用hibernate validator
依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
如果是Spring Mvc
,那可以直接添加hibernate-validator
依赖
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17.Final</version>
</dependency>
1. 常见的注解
注解 | 说明 |
---|---|
@Null |
被注释的元素必须为 null |
@NotNull |
被注释的元素必须不为 null |
@AssertTrue |
被注释的元素必须为 true |
@AssertFalse |
被注释的元素必须为 false |
@Min(value=) |
被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value=) |
被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value=, inclusive=) |
被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value=, inclusive=) |
被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Future |
检查被注释的日期是否是将来的日期 |
@FutureOrPresent |
检查被注释的日期是现在还是将来 |
@NotEmpty |
检查被注释的元素是否不为null或为空 |
@Negative |
检查被注释的元素是否严格为负。零值被视为无效。 |
@NegativeOrZero |
检查被注释的元素是负数还是零。 |
@Past |
检查被注释的日期是否是过去的日期 |
@PastOrPresent |
检查被注释的日期是过去还是现在 |
@Pattern(regex=, flags=) |
检查被注释的字符串是否与正则表达式匹配match |
@Positive |
检查被注释元素是否严格为正。零值被视为无效。 |
@PositiveOrZero |
检查被注释元素是正数还是零。 |
@Size(min=, max=) |
检查被注释的元素的大小是否介于min 和之间max (包括) |
@Email |
检查被注释的元素是否为有效的电子邮件地址。 |
@Digits(integer=, fraction=) |
被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Currency(value=) |
检查带注释的货币单位javax.money.MonetaryAmount 是否为指定货币单位的一部分。 |
@ISBN |
检查带注释的字符序列是有效的ISBN。type 确定ISBN的类型。默认值为ISBN-13。 |
@Length(min=, max=) |
被注释的字符串的大小必须在指定的范围内 |
@Range(min=, max=) |
被注释的元素必须在合适的范围内 |
@SafeHtml(whitelistType= , additionalTags=, additionalTagsWithAttributes=, baseURI=) |
检查带注释的值是否包含潜在的恶意片段,例如<script/> 。为了使用此约束,jsoup库必须是类路径的一部分。通过该whitelistType 属性,可以选择预定义的白名单类型,可以通过additionalTags 或进行细化additionalTagsWithAttributes 。前者允许添加没有任何属性的标签,而后者则允许使用注释指定标签和可选的允许属性以及该属性的可接受协议@SafeHtml.Tag 。另外,baseURI 允许指定用于解析相对URI的基本URI。 |
@UniqueElements |
检查被注释的集合仅包含唯一元素 |
@URL(protocol=, host=, port=, regexp=, flags=) |
根据RFC2396检查带注释的字符序列是否为有效URL。如果任何可选参数protocol ,host 或port 指定时,相应的URL片段必须在指定的值相匹配。可选参数,regexp 并flags 允许指定URL必须匹配的其他正则表达式(包括正则表达式标志)。默认情况下,此约束使用java.net.URL 构造函数来验证给定的字符串是否表示有效的URL。也提供基于正则表达式的版本RegexpURLValidator -可以通过XML(请参见第8.2节“通过映射约束constraint-mappings ”)或编程API(请参见第12.14.2节“以编程方式添加约束定义”)进行配置。 |
2. 动手实践(快速上手)
使用idea新建一个springboot
项目并引入web
包
新建一个User的bean
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.*;
/**
* @author john
* @date 2020/4/9 - 10:46
*/
@Slf4j
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String id;
@NotBlank(message = "用户名不能为空")
@Length(min = 4, max = 8, message = "用户名长度不在4-8之间")
private String name;
@NotNull(message = "密码不能为空")
@Pattern(regexp = "[0-9]\\d+", message = "密码不符合规范")
private String password;
@NotNull(message = "年龄不能为空")
@Range(min = 1, max = 140, message = "年龄值不太正常")
private Integer age;
@Max(value = 10, message = "级别超过最大值了")
@Min(value = 1, message = "级别低于最小值")
private Integer level;
private String mobile;
}
在创建一个响应数据的bean
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
/**
* @author john
* @date 2020/4/9 - 11:16
*/
@Slf4j
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> {
private boolean status;
private T data;
private List<String> messages;
}
在创建一个控制器
package com.example.validatordemo.controller;
import com.example.validatordemo.bean.User;
import com.example.validatordemo.response.ApiResponse;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
/**
* @author john
* @date 2020/4/9 - 11:04
*/
@RestController
@RequestMapping("/user")
public class UserController {
//创建用户
@PostMapping
public ApiResponse<User> register(@Valid @RequestBody User user, BindingResult errors) {
//判断传入用户数据是否合法
List<String> errs = new ArrayList<>();
if (errors.hasErrors()) {
errors.getAllErrors().stream().forEach(error -> {
FieldError fieldError = (FieldError) error;
errs.add(fieldError.getDefaultMessage());
});
return new ApiResponse<User>(false, null, errs);
}
//输入入库
System.out.println("数据插入成功");
return new ApiResponse<User>(false, user, errs);
}
//修改用户
@PutMapping
public ApiResponse<User> update(@Valid @RequestBody User user, BindingResult errors) {
//判断传入用户数据是否合法
List<String> errs = new ArrayList<>();
if (errors.hasErrors()) {
errors.getAllErrors().stream().forEach(error -> {
FieldError fieldError = (FieldError) error;
errs.add(fieldError.getDefaultMessage());
});
return new ApiResponse<>(false, null, errs);
}
//输入入库
System.out.println("数据修改成功");
return new ApiResponse<>(false, user, errs);
}
}
上面分别定义了创建用户和修改用户的操作
测试创建用户结果
由此可见当传入的数据不符合规范时会被识别并处理
3 . hibernate的校验模式
上面例子中一次性返回了所有验证不通过的集合,通常按顺序验证到第一个字段不符合验证要求时,就可以直接拒绝请求了.
Hibernate Validator
有以下两种验证模式:
1 、普通模式(默认是这个模式)
普通模式(会校验完所有的属性,然后返回所有的验证失败信息)
2、快速失败返回模式
快速失败返回模式(只要有一个验证失败,则返回)
> 默认是普通模式
配置校验模式
基于上面的案例,继续操作,配置hibernate Validator
为快速失败返回模式:
增加如下代码
package com.example.validatordemo.conf;
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
/**
* @author john
* @date 2020/4/9 - 11:47
*/
@Configuration
public class ValidatorConfiguration {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.addProperty("hibernate.validator.fail_fast", "true")
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
}
再次重启项目,使用postman进行测试
可见此时在遇到第一个不匹配的数据后就结束校验,并快速返回
多个参数的,可以加多个@Valid和BindingResult,如:
public void test()(@RequestBody @Valid DemoModel demo, BindingResult result)
public void test()(@RequestBody @Valid DemoModel demo, BindingResult result,@RequestBody @Valid DemoModel demo2, BindingResult result2)
4. 分组的使用
在上面的例子中,我们定义了user的两个方法一个添加用户,一个是修改用户,正常来说当添加用户操作时,此时的用户id属性应该为空,而当用户处理修改状态时,用户的id应该已经存在,因此我们对id在两种不同的情况下应该区别对待,如何校验用户的id呢,这时我们可以使用分组实现.
具体操作如下
先新建组
package com.example.validatordemo.validator;
import javax.validation.groups.Default;
/**
* @author john
* @date 2020/4/9 - 12:00
*/
public interface AddUserGroup extends Default {
}
package com.example.validatordemo.validator;
import javax.validation.groups.Default;
/**
* @author john
* @date 2020/4/9 - 12:00
*/
public interface UpdateUserGroup extends Default {
}
修改User的bean,修改部分如下
public class User {
...
@NotNull(message = "id不能为空", groups = {UpdateUserGroup.class})
private String id;
...
}
修改控制器代码
public class UserController {
...
//修改用户
@PutMapping
public ApiResponse<User> update(@Validated(UpdateUserGroup.class) @RequestBody User user, BindingResult errors) {
...
}
}
执行测试,当执行插入用户时
当执行修改操作时
可见此时的不同操作时,对id有不同的判断
5. 自定义校验的使用
在刚才的操作中,我们对一个字段一直没有校验,那就是mobile,使用内置的校验应该无法满足需求,这时候我们可以自定义校验来实现对mobile字段的校验
定义自定义约束,有三个步骤
- 创建约束注解
- 实现一个验证器
- 定义默认的错误信息
创建约束注解
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @author john
* @date 2020/4/9 - 12:39
*/
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Constraint(validatedBy = {MobileValidator.class})
@Retention(RUNTIME)
@Repeatable(Mobile.List.class)
public @interface Mobile {
/**
* 错误提示信息,可以写死,也可以填写国际化的key
*/
String message() default "手机号码不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String regexp() default "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@interface List {
Mobile[] value();
}
}
实现一个验证器
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;
/**
* @author john
* @date 2020/4/9 - 12:41
*/
public class MobileValidator implements ConstraintValidator<Mobile, String> {
/**
* 手机验证规则
*/
private Pattern pattern;
@Override
public void initialize(Mobile mobile) {
pattern = Pattern.compile(mobile.regexp());
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return pattern.matcher(value).matches();
}
}
定义默认的错误信息
public class User {
....
@Mobile(message = "手机号码格式异常")
private String mobile;
}
重启项目执行测试
此时手机格式校验失败
6. 代码
7. 参考
springboot使用hibernate validator校验
如何优雅的做数据校验-Hibernate Validator详细使用说明
Hibernate Validator 6.1.2.Final - Jakarta Bean Validation Reference Implementation: Reference Guide