在开发中,我们经常遇到参数校验的需求,比如用户注册的时候,要校验用户名不能为空、用户名长度不超过 20 个字符、手机号是合法的手机号格式等等。如果使用普通方式,我们会把校验的代码和真正的业务处理逻辑耦合在一起,而且如果未来要新增一种校验逻辑也需要在修改多个地方。而 Spring validation 允许通过注解的方式来定义对象校验规则,把校验和业务逻辑分离开,让代码编写更加方便。 Spring Validation 其实就是对 Hibernate Validator 进一步的封装,方便在 Spring 中使用。
在Spring中有多种校验的方式
- 第一种是通过实现 org.springframework.validation.Validator 接口,然后在代码中调用这个类
- 第二种是按照 Bean Validation 方式来进行校验,即通过注解的方式。
- 第三种是基于方法实现校验
- 除此之外,还可以实现自定义校验
通过实现 Validator 接口实现校验
-
创建模块、引入依赖
<dependencies> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>7.0.5.Final</version> </dependency> <dependency> <groupId>org.glassfish</groupId> <artifactId>jakarta.el</artifactId> <version>4.0.1</version> </dependency> </dependencies>
-
创建实体类,定义属性和 getter、setter 方法
package site.penghao.spring6.bean; /** * @author hope * @date 2023/4/4 - 11:33 */ 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; } }
-
创建类,实现 Validator 接口,编写校验逻辑
package site.penghao.spring6.validator; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; import site.penghao.spring6.bean.Person; /** * @author hope * @date 2023/4/4 - 11:36 */ public class PersonValidator implements Validator { @Override public boolean supports(Class<?> clazz) { // 判断能否使用此校验规则 return Person.class.equals(clazz); } @Override public void validate(Object target, Errors errors) { // 校验规则: // name 和 age 不能为空,如果为空,将添加错误编码和错误信息 ValidationUtils.rejectIfEmpty(errors, "name", "name.empty", "name is null"); // age 不能为空,不能小于 0,不能大于 200 Person p = (Person) target; if (p.getAge() == null) { errors.rejectValue("age", "age.empty", "age is null"); } else if (p.getAge() < 0) { errors.rejectValue("age", "age.value.error", "age < 0"); } else if (p.getAge() > 200) { errors.rejectValue("age", "age.value.error", "age > 200"); } } }
-
测试校验
@Test public void test() { // 创建 Person Person person = new Person(); person.setName("爱尔奎特·布伦史塔德"); person.setAge(900); // 创建 person 对应的 data binder DataBinder binder = new DataBinder(person); // 设置校验器 binder.setValidator(new PersonValidator()); // 调用方法执行校验 binder.validate(); // 输出校验结果 BindingResult result = binder.getBindingResult(); System.out.println(result.getAllErrors());// [Field error in object 'target' on field 'age': rejected value [800]; codes [age.value.error.target.age,age.value.error.age,age.value.error.java.lang.Integer,age.value.error]; arguments []; default message [age > 200]] }
基于 ValidatorFactoryBean 注解实现
-
创建模块、引入依赖
-
创建配置类,配置 LocalValidatorFactoryBean
package site.penghao.spring6.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; /** * @author hope * @date 2023/4/4 - 12:05 */ @Configuration @ComponentScan("site.penghao.spring6") public class ValidationConfig { @Bean public LocalValidatorFactoryBean getValidator() { return new LocalValidatorFactoryBean(); } }
由于 LocalValidatorFactoryBean 实现了 ValidatorFactoryBean 接口,当需要一个 Validator 类型的 Bean 时,调用
getValidator
方法。 -
创建实体类,定义属性,生成 getter 和 setter 方法,在属性上面使用注解设置校验规则
public class Person { @NotNull private String name; @NotNull @Min(0) @Max(200) private Integer age; // getter and setter }
-
创建校验器
方式一:使用
jakarta.validation.Validator
@Component public class PersonValidator1 { @Autowired private Validator validator; public boolean validate(Person person) { Set<ConstraintViolation<Person>> validate = validator.validate(person); return validate.isEmpty(); } }
方式二:使用 Spring 进一步封装的
org.springframework.validation.Validator
@Component public class PersonValidator2 { @Autowired private Validator validator; public boolean validate(Person person) { BindException bindException = new BindException(person, person.getName()); validator.validate(person, bindException); return !bindException.hasErrors(); // 有错返回 false,没错返回 true } }
-
测试
@Test public void test1() { ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); PersonValidator1 validator1 = context.getBean(PersonValidator1.class); Person person = new Person(); person.setName("爱尔奎特·布伦史塔德"); person.setAge(900); boolean msg = validator1.validate(person); System.out.println(msg); // false } @Test public void test2() { ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); PersonValidator2 validator2 = context.getBean(PersonValidator2.class); Person person = new Person(); person.setName("爱尔奎特·布伦史塔德"); person.setAge(17); boolean msg = validator2.validate(person); System.out.println(msg); // true }
基于方法实现校验
-
创建模块、引入依赖
-
创建配置类,配置 MethodValidationPostProcessor
@Configuration @ComponentScan("site.penghao.spring6") public class MethodValidationConfig { @Bean public MethodValidationPostProcessor getValidationPostProcessor() { return new MethodValidationPostProcessor(); } }
顾名思义,它的含义是方法执行前启动执行这个后置处理器,进行方法的参数的校验。
-
创建实体类,使用注解设置校验规则
public class User { @NotNull private String name; @Min(0) @Max(150) private int age; @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$", message = "手机号码格式错误") @NotBlank(message = "手机号码不能为空") private String phone; // getter and setter // toString() }
-
定义 Service 类,通过注解操作对象
@Service @Validated // 表示开启基于方法进行校验 public class MyService { // @NotNull 表示非空,@Valid 表示需要符合在 User 中定义的校验规则 public String testMethod(@NotNull @Valid User user) { return user.toString(); } }
-
测试
@Test void testMethod() { ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); MyService service = context.getBean(MyService.class); User user = new User(); String s = service.testMethod(user); System.out.println(s); }
自定义校验
-
自定义校验注解
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint(validatedBy = {CannotBlankValidator.class}) public @interface CannotBlank { //默认错误消息 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 @interface List { CannotBlank[] value(); } }
-
实现前面指定的 CannotBlankValidator 校验类
public class CannotBlankValidator implements ConstraintValidator<CannotBlank, String> { @Override public void initialize(CannotBlank constraintAnnotation) { } @Override public boolean isValid(String value, ConstraintValidatorContext context) { //null时不进行校验 if (value != null && value.contains(" ")) { //获取默认提示信息 String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate(); System.out.println("default message :" + defaultConstraintMessageTemplate); //禁用默认提示信息 context.disableDefaultConstraintViolation(); //设置提示语 context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation(); return false; } return true; } }
-
测试(可以使用基于注解或者基于方法的实现来校验)
小结
- 数据校验用于校验某个属性的值是否符合要求,使用 Spring Validation 可以将校验逻辑和业务代码解耦,Spring Validation 是基于 Hibernate Validation 的,在它上面进行了进一步的封装。
- 通过直接实现 org.springframework.validation.Validator 接口进行校验。
- 通过在需要校验的属性添加各种校验属性,并在配置文件或配置类中设置 LocalValidatorFactoryBean。在校验类中注入前面设置的 Validator,使用这个 Validator 进行校验。
- 通过在需要校验的属性添加各种校验属性,并在配置文件或配置类中设置 MethodValidationPostProcessor,然后在需要校验的方法参数前面设置 @Valid 进行校验。
- 通过自定义校验注解进行校验。