在开发中,会存在参数校验的情况,如:注册时,校验用户名不能为空、用户名长度不超过20个字符,手机号格式合法等。如果使用普通方式,会将校验代码和处理逻辑耦合在一起,在需要新增一种校验时,也需要修改很多地方。
spring validation
允许通过注解的方式来定义校验规则,把校验和业务分离开。它其实就是对Hibernate Validation
进一步的封装。
org.springframework.validation.Validator
接口,然后在代码中调用这个类。<dependency>
<groupId>org.hibernate.validatorgroupId>
<artifactId>hibernate-validatorartifactId>
<version>8.0.1.Finalversion>
dependency>
<dependency>
<groupId>org.glassfishgroupId>
<artifactId>jakarta.elartifactId>
<version>5.0.0-M1version>
dependency>
public class Person {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
public class PersonValidator implements Validator {
/**
* 此方法用来表示此校验用在哪个类型上
* @param clazz
* @return
*/
@Override
public boolean supports(Class<?> clazz) {
return Person.class.equals(clazz);
}
/**
* 此方法是设置校验逻辑的地点,其中ValidatorUtils,是Spring封装的工具类。帮助快速实现校验
* @param target
* @param errors
*/
//校验规则
@Override
public void validate(Object target, Errors errors) {
//name不能为空
ValidationUtils.rejectIfEmpty(errors, "name", "name Empty", "name is null");
//age不能小于0,不能大于200
Person p = (Person)target;
if (p.getAge() <= 0){
errors.rejectValue("age", "age.value.error", "age<0");
} else if (p.getAge() >= 200) {
errors.rejectValue("age", "age.value.error.max", "age>200");
}
}
}
@Test
public void testValidation(){
//创建Person对象
Person person = new Person();
person.setAge(30);
person.setName(null);
//创建person对应对象
DataBinder binder = new DataBinder(person);
//设置校验器
binder.setValidator(new PersonValidator());
//调用方法、执行校验
binder.validate();
//输出校验结果
BindingResult result = binder.getBindingResult();
System.out.println("result.getAllErrors() = " + result.getAllErrors());
}
/*
* Empty.name,name Empty.java.lang.String,name Empty]; arguments []; default message [name is null]]
* */
使用Bean Validation校验方式,需要使用到
javax.validation.ValidatorFactory
和javax.validation.Validator
注入到容器中,spring默认有一个实现类LocalValidatorFacoryBean
,它实现了上面Bean Validation中的接口,并且也实现了org.springframeworkvalidation.Validator
接口。
@Configuration
@ComponentScan("com.louis.testvalidationtwo")
public class ValidationConfig {
@Bean
public LocalValidatorFactoryBean validator(){
return new LocalValidatorFactoryBean();
}
}
定义属性,生成get、set方法,在属性上面使用注解设置校验规则。
public class User {
@NotNull
private String name;
@Min(0)
@Max(200)
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
①使用原生的校验器
@Service
public class MyValidationOne {
//使用原生的校验器
@Autowired
private Validator validator;
public boolean testValidator(User user){
Set<ConstraintViolation<User>> validate = validator.validate(user);
return validate.isEmpty();
}
②使用spring中的validation
@Service
public class MyValidationTwo {
//使用spring中的validation
@Autowired
private Validator validator;
public List testValidatorTwo(User user){
BindException bindException = new BindException(user, user.getName());
validator.validate(user, bindException);
List<ObjectError> allErrors = bindException.getAllErrors();
return allErrors;
}
}
使用方式①
@Test
public void testValidator(){
ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);
MyValidationOne validationOne = context.getBean(MyValidationOne.class);
User user = new User();
boolean result = validationOne.testValidator(user);
System.out.println("result = " + result);
/*result = false*/
}
使用方式②
@Test
public void testValidatorTwo(){
ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);
MyValidationTwo validationTwo = context.getBean(MyValidationTwo.class);
User user = new User();
List result = validationTwo.testValidatorTwo(user);
System.out.println("result = " + result);
/*Object name must not be null*/
}
注解 | 说明 |
---|---|
@NotNull |
限制必须不为空 |
@NotEmpty |
只作用于字符串类型,字符串不为空,且长度不为0 |
@NotBlank |
只作用于字符串类型,字符串不为空且trim()后也并不为空 |
@DecimalMax(value) |
限制必须为一个不大于指定值的数字,小数存在精度 |
@DecimalMin(value) |
限制必须为一个不小于指定值的数字,小数存在精度 |
@Max(value) |
限制必须为一个不大于指定值的数字 |
@Min(value) |
限制必须为一个不小于指定值的数字 |
@Pattern(value) |
限制必须符合指定的正则表达式 |
@Size(max, min) |
限制字符串长度必须在min到max之间 |
@Email |
验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式 |
@Configuration
@ComponentScan("com.louis.validationbymethod")
public class ValidationConfig {
@Bean
public MethodValidationPostProcessor validationPostProcessor(){
return new MethodValidationPostProcessor();
}
}
使用注解设置校验规则
public class User {
@NotNull
private String name;
@Max(200)
@Min(0)
private int age;
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$", message = "手机号格式错误")
private String phone;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
@Service
@Validated
public class MyService {
public String testMethod(@NotNull @Valid User user){
return user.toString();
}
}
@Test
public void testValidationByMethod(){
ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);
MyService service = context.getBean(MyService.class);
service.testMethod(new User());
/*testMethod.arg0.phone: 手机号不能为空, testMethod.arg0.name: 不能为null*/
}
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {CannotHaveBlankValidator.class})//表示指定校验器的类,实现真正的校验器规则
public @interface CannotHaveBlank {
//默认的出现错误的提示信息
String message() default "不能包含空格";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface List {
CannotHaveBlank[] value();
}
}
public class CannotHaveBlankValidator implements ConstraintValidator<CannotHaveBlank, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if(value != null &&value.contains(" ")){
//获取默认提示信息
String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate();
System.out.println("default message:"+defaultConstraintMessageTemplate);
//禁用默认提示信息
context.disableDefaultConstraintViolation();
//设置提示语
context.buildConstraintViolationWithTemplate("can not constrains blank").addConstraintViolation();
return false;
}
return false;
}
}
public class User {
@NotNull
private String name;
@Max(200)
@Min(0)
private int age;
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$", message = "手机号格式错误")
private String phone;
@CannotHaveBlank
private String message;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
//使用基于方法实现校验的校验器
public class TestValidationByMethod {
@Test
public void testValidationByMethod(){
ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class);
MyService service = context.getBean(MyService.class);
User user = new User();
user.setAge(30);
user.setName("Louie");
user.setPhone("18111111111");
user.setMessage("L o u i s");
service.testMethod(user);
/*default message:不能包含空格
jakarta.validation.ConstraintViolationException: testMethod.arg0.message: can not constrains blank*/
}
}