文章目录
背景介绍
开发过程中,后台参数校验必不可少,经常看到如下代码:
public BaseResult save(User user) {
if (StringUtils.isBlank(user.name)) {
return BaseResult.failure("用户名不能为空");
}
if (StringUtils.isBlank(user.password)) {
return BaseResult.failure("密码不能为空");
}
。。。
}
Spring Boot 的Validation
-
javax.validation
包下的validation-api
是基于JSR-303标准开发出来的,使用注解方式实现,及其方便,但是这只是一个接口 -
Hibernate-Validator
是一个hibernate独立的包,它实现了javax.validation同时有做了扩展 -
Spring Validation
也是一个接口,可以用于验证需求
validation-api
注解 | 描述 |
---|---|
@AssertFalse | 被注释的元素必须为 false |
@AssertTrue | 被注释的元素必须为 true |
@DecimalMax | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Digits | 被注释的元素是数字 |
@Future | 被注解的元素必须为未来的一个时间 |
@Max(value) | 被注解的元素必须为数字,其值必须小于等于最小值 |
@Min(value) | 被注解的元素必须为数字,其值必须大于等于最小值 |
@NotNull | 被注解的元素必须不为null |
@Null | 被注解的元素必须为null |
@Past | 被注解的元素必须为过去的一个时间 |
@Pattern | 被注解的元素必须符合指定的正则表达式 |
@Szie(max,min) | 被注解的元素的大小必须在指定范围内 |
Spring Validation
看文档好像不支持注解
参看 https://docs.spring.io/spring/docs/5.0.5.RELEASE/spring-framework-reference/core.html#validation
Hibernate-Validator
参看 https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#preface
可以和Spring Validation 混着用,SpringBoot在内部集成了hibernate-validation,可以直接使用
注解 | 描述 |
---|---|
元素必须是格式良好的电子邮箱地址 | |
@Length(max,min) | 字符串的大小必须在指定的范围内,有min和max参数 |
@NotEmpty | 字符串的不能是空 |
@NotBlank | 字符串不能使空,但是与@NotEmpty不同的是尾随的空白被忽略 |
@URL | 字符串必须是一个URL |
引入jar
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.4.4</version>
</dependency>
字段级别约束
约束可以应用于任何访问类型(公共,私有等)的字段。但是,不支持对静态字段的约束
使用字段级约束时,将使用字段访问策略
来访问要验证的值
@Data
public class LoginForm {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
@Length(min = 6, message = "密码长度至少6位")
private String password;
}
属性级别约束
必须注释该属性的getter方法,而不是其setter。这样,还可以限制没有setter方法的只读属性
使用属性级别约束时,将使用属性访问策略
来访问要验证的值,即,验证引擎通过属性访问器方法访问状态
@Setter
public class LoginForm {
private String username;
private String password;
@NotBlank(message = "用户名不能为空")
public String getUsername() {
return username;
}
@NotBlank(message = "密码不能为空")
@Length(min = 6, message = "密码长度至少6位")
public String getPassword() {
return password;
}
}
容器元素约束
可以直接在集合的泛型参数上指定约束,约束集合中每一个元素:这些约束称为容器元素约束。如
private List<@ValidPart String> parts = new ArrayList<>();
Hibernate Validator验证在标准Java容器上指定了的容器元素约束,同时可以自定义注解,对容器进行约束
类级别约束
在这种情况下,验证的对象不是单个属性,而是完整的对象。如果验证依赖于对象的多个属性之间的相关性,则类级别的约束很有用。
类级别约束需要自定义
@ValidPassengerCount
public class Car {
private int seatCount;
private List<Person> passengers;
//...
}
约束继承
当一个类实现接口或扩展另一个类时,在超类型上声明的所有约束注释都以与在该类本身上指定的约束相同的方式应用
@Data
public class Base {
@NotBlank(message = "用户名不能为空")
private String username;
}
@Data
public class LoginForm extends Base {
@NotBlank(message = "密码不能为空")
@Length(min = 6, message = "密码长度至少6位")
private String password;
}
如果get方法被覆盖,约束注释将被聚合,父类和子类的约束必须要全部满足,两者任何一个不满足就会返回错误
@Data
public class Base {
private String username;
@NotBlank(message = "用户名不能为空")
public String getUsername() {
return username;
}
}
@Data
public class LoginForm extends Base{
private String username;
@NotBlank(message = "密码不能为空")
@Length(min = 6, message = "密码长度至少6位")
private String password;
@Override
@Length(min = 2, message = "密码长度至少2位")
public String getUsername() {
return username;
}
}
注意:约束继承不能用在属性复写上,会导致父类约束一直出错
即使username不为空,也会一直提示错误 “用户名不能为空”
@Data
public class Base {
@NotBlank(message = "用户名不能为空")
private String username;
}
@Data
public class LoginForm extends Base{
@Length(min = 2, message = "密码长度至少2位")
private String username;
@NotBlank(message = "密码不能为空")
@Length(min = 6, message = "密码长度至少6位")
private String password;
}
级联验证(对象图)@Valid
Jakarta Bean验证API不仅允许验证单个类实例,还可以验证引用对象(需添加@Valid)
验证是递归的,避免两个对象相互引用发生无限循环
public class Car {
@NotNull
@Valid
private Person driver;
}
public class Person {
@NotNull
private String name;
}
错误验证
注意:验证的是bean约束,因此下面写法是错误的
@PostMapping
@ResponseBody
public String login(@NotBlank(message = "用户名不能为空") String username, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
for (ObjectError error : bindingResult.getAllErrors()) {
return error.getDefaultMessage();
}
}
return "login success";
}
正确验证
BindingResult 盛装错误信息
@PostMapping
@ResponseBody
public List<String> login(@Valid LoginForm loginForm, BindingResult bindingResult) {
List<String> errorInfos = new ArrayList<>();
if (bindingResult.hasErrors()) {
for (ObjectError error : bindingResult.getAllErrors()) {
errorInfos.add(error.getDefaultMessage());
}
}
return errorInfos;
}
如果在校验的对象后面再加上Model对象的话,如果返回的是ModelAndView就可以将这个Model设置到其中,这样在页面就可以取到错误消息了
springMVC先前端传递参数
@PostMapping("/login2")
public String login2(@Valid LoginForm loginForm, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
for (ObjectError error : bindingResult.getAllErrors()) {
DefaultMessageSourceResolvable argument = (DefaultMessageSourceResolvable) error.getArguments()[0];
model.addAttribute(argument.getDefaultMessage(), error.getDefaultMessage());
}
}
return "login2";
}
快速失败模式
文档查看
使用快速失败模式时,Hibernate Validator允许在发生第一个约束冲突时立即从当前验证中返回
@Configuration
public class ValidatorConfig {
@Bean
public Validator validator() {
Validator validator = Validation.byProvider( HibernateValidator.class )
.configure()
.failFast( true )
.buildValidatorFactory()
.getValidator();
return validator;
}
}
自定义校验规则
https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#validator-customconstraints-simple
注意:@Constraint(validatedBy = { XX.class }) 的使用,借鉴官方示例
@Validated和@Valid区别
可以参考:https://blog.csdn.net/qq_27680317/article/details/79970590
Spring Validation验证框架对参数的验证机制提供了@Validated(Spring’s JSR-303规范,是标准JSR-303的一个变种),javax提供了@Valid(标准JSR-303规范),配合BindingResult可以直接提供参数验证结果
@Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
@Valid:可以用在构造函数、方法、方法参数和成员属性(字段)上
@Valid才能做级联校验;@Validated不能用在成员属性上,所以不能做级联校验
@Validated 失效情况:
- 对象参数,只有放在参数前可以用
@Validated
public class LoginController {
@PostMapping
@ResponseBody
public List<String> login( LoginForm loginForm, BindingResult bindingResult) {
@Validated
public List<String> login( LoginForm loginForm, BindingResult bindingResult) {
- 非对象参数校验,只有在类上可以用
@Validated
public ModelAndView login3(@NotBlank(message = "用户名不能为空") String username,
@NotBlank(message = "密码不能为空")
@Length(min = 6, message = "密码长度至少6位")
String password) {
public ModelAndView login3(@Validated @NotBlank(message = "用户名不能为空") String username,
@Validated @NotBlank(message = "密码不能为空")
@Length(min = 6, message = "密码长度至少6位")
String password) {
@Valid失效情况
加在方法上都失效
@Valid
public String login2( LoginForm loginForm, BindingResult bindingResult, Model model) {
@PostMapping("/login4")
public String login4(@Valid @NotBlank(message = "用户名不能为空") String username) {
return "login4";
}