Spring 框架提供了对 Java Bean Validation API 的支持。

Bean Validation 概述

Bean Validation 为 Java 应用程序提供了一种通过约束声明和元数据进行验证的通用方法。为了使用它,你用声明性的验证约束来注解领域模型属性,然后由运行时强制执行。有一些内置的约束,你也可以定义你自己的自定义约束。

考虑下面的例子,它显示了一个有两个属性的简单 PersonForm 模型:

  1. public class PersonForm {
  2. private String name;
  3. private int age;
  4. }

Bean Validation 可以让你声明约束条件,如下例所示:

  1. public class PersonForm {
  2. @NotNull
  3. @Size(max=64)
  4. private String name;
  5. @Min(0)
  6. private int age;
  7. }

然后,Bean Validation 验证器根据声明的约束条件来验证该类的实例。关于 API 的一般信息,请参见 Bean Validation 。关于具体的约束条件,见 Hibernate Validator 文档。要了解如何将 Bean 验证提供者设置为 Spring Bean,请继续阅读。

配置 Bean Validation Provider

Spring 提供了对 Bean Validation API 的全面支持,包括将 Bean Validation 提供者作为 Spring Bean 的引导。这让你可以在应用中需要验证的地方注入一个 javax.validation.ValidatorFactoryjavax.validator

你可以使用 LocalValidatorFactoryBean 来配置一个默认的 Validator 作为 Spring Bean,如下例所示:

  1. import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
  2. @Configuration
  3. public class AppConfig {
  4. @Bean
  5. public LocalValidatorFactoryBean validator() {
  6. return new LocalValidatorFactoryBean();
  7. }
  8. }

前面例子中的基本配置通过使用其默认的引导机制来触发 Bean Validation 的初始化。一个 Bean Validation 提供者,例如 Hibernate Validator,应该存在于 classpath 中并被自动检测(意思就是说要添加 Hibernate Validator 的依赖)。

比如:添加 Hibernate Validator 的依赖,上面的 @Size 注解才可以使用

  1. // LocalValidatorFactoryBean 中有说明,支持 hibernate-validator 5.2 和 6.0 版本
  2. implementation 'org.hibernate.validator:hibernate-validator:6.0.0.Final'
  3. // 该依赖在 org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator 中有用到,所以需要添加
  4. // 可以在
  5. implementation group: 'org.glassfish', name: 'javax.el', version: '3.0.0'

再注入使用
image.png
可以看到上图有一个 defaultMessage 的错误信息,这个就是在国际化里面的消息文件中来的
image.png
这个在内容应该是在 将 errorCode 转换为错误消息的问题 章节里面提到过的,但是这一章内容不怎么详细,如果要深入了解可能还是需要再去展开研究研究

注入 Validator

LocalValidatorFactoryBean 同时实现了 javax.validatorFactoryjavax.validator,以及Spring 的 org.springframework.validation.Validator。你可以将这些接口的引用注入到需要调用验证逻辑的 Bean 中。

如果你喜欢直接使用 Bean Validation API,你可以注入对 javax.validation.Validator的引用,如下例所示:

  1. import javax.validation.Validator;
  2. @Service
  3. public class MyService {
  4. @Autowired
  5. private Validator validator;
  6. }

如果你的 Bean 需要 Spring Validation API,你可以注入对 org.springframework.validation.Validator的引用,如下例所示:

  1. import org.springframework.validation.Validator;
  2. @Service
  3. public class MyService {
  4. @Autowired
  5. private Validator validator;
  6. }

配置自定义约束

每个 bean 验证约束由两部分组成:

  • 一个 @Constraint注解,声明了约束及其可配置的属性。(比如前面用到的 @Size 注解上就有这个元注解)
  • javax.validation.ConstraintValidator接口的一个实现,实现约束的行为。

为了将声明与实现联系起来,每个 @Constraint 注解都引用了一个相应的 ConstraintValidator 实现类。在运行时,当你的领域模型中遇到约束注解时,ConstraintValidatorFactory 会实例化所引用的实现。

默认情况下,LocalValidatorFactoryBean 配置了一个 SpringConstraintValidatorFactory,使用 Spring 来创建 ConstraintValidator 实例。这让你的自定义约束验证器像其他 Spring Bean 一样受益于依赖性注入。

下面的例子显示了一个自定义的 @Constraint声明和一个相关的 ConstraintValidator 实现,它使用 Spring 进行依赖注入。

  1. package cn.mrcode.study.springdocsread.data;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. import javax.validation.Constraint;
  7. import javax.validation.Payload;
  8. /**
  9. * @author mrcode
  10. */
  11. @Target({ElementType.METHOD, ElementType.FIELD})
  12. @Retention(RetentionPolicy.RUNTIME)
  13. @Constraint(validatedBy = MyConstraintValidator.class)
  14. public @interface MyConstraint {
  15. String message() default "{cn.mrcode.constraints.MyConstraint.message}";
  16. Class<?>[] groups() default { };
  17. Class<? extends Payload>[] payload() default { };
  18. }

这个注解如果要用在 Hibernate Validator 里面的话,就需要写上面的 message、groups、payload 属性

  1. package cn.mrcode.study.springdocsread.data;
  2. import javax.validation.ConstraintValidator;
  3. import javax.validation.ConstraintValidatorContext;
  4. /**
  5. * @author mrcode
  6. */
  7. // 这里要写泛型:这个验证器绑定的注解类,要验证的对象类型
  8. public class MyConstraintValidator implements ConstraintValidator<MyConstraint, Object> {
  9. /**
  10. * 实现验证逻辑;
  11. *
  12. * @param value
  13. * @param context
  14. * @return
  15. */
  16. @Override
  17. public boolean isValid(Object value, ConstraintValidatorContext context) {
  18. // 这里写你的验证逻辑,这里假设就判断下是否为空
  19. if (value == null) {
  20. return false;
  21. }
  22. return true;
  23. }
  24. }

由于注解绑定了具体的验证器处理程序,所以这里不用注册之类的,就可以将 MyConstraint 注解使用了。

另外,验证器里面也可以注入其他 bean,如下所示

  1. import javax.validation.ConstraintValidator;
  2. public class MyConstraintValidator implements ConstraintValidator {
  3. @Autowired;
  4. private Foo aDependency;
  5. // ...
  6. }

Spring 驱动的方法验证

你可以通过 MethodValidationPostProcessor Bean 定义将 Bean Validation 1.1 支持的方法验证功能(作为自定义扩展,Hibernate Validator 4.3 也支持)集成到 Spring 上下文中。

  1. import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
  2. @Configuration
  3. public class AppConfig {
  4. @Bean
  5. public MethodValidationPostProcessor validationPostProcessor() {
  6. return new MethodValidationPostProcessor();
  7. }
  8. }

为了符合 Spring 驱动的方法验证的条件,所有目标类都需要用 Spring 的 @Validated 注解来注解,也可以选择声明使用的验证组。参见 MethodValidationPostProcessor,了解与 Hibernate Validator 和 Bean Validation 1.1 提供者的设置细节。

下面是一个例子:

  1. package cn.mrcode.study.springdocsread.web;
  2. import org.springframework.stereotype.Component;
  3. import org.springframework.validation.annotation.Validated;
  4. import javax.validation.constraints.NotNull;
  5. /**
  6. * @author mrcode
  7. */
  8. @Component
  9. @Validated
  10. public class DemoService {
  11. public void insert(@NotNull Integer id) {
  12. System.out.println(id);
  13. }
  14. }

你引用 DemoService 然后调用 insert 方法的时候,如果传递 null 进来,这个验证器就会工作了,报错信息如下

  1. Exception in thread "main" javax.validation.ConstraintViolationException: insert.id: 不能为null
  2. at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:120)
  3. at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
  4. at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)
  5. at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698)
  6. at cn.mrcode.study.springdocsread.web.DemoService$$EnhancerBySpringCGLIB$$232cf10c.insert(<generated>)
  7. at cn.mrcode.study.springdocsread.TestDemo.main(TestDemo.java:17)

:::tips 方法验证依赖于目标类周围的 AOP 代理,无论是 JDK 动态代理还是 CGLIB 代理的接口上的方法。使用代理有一些限制,其中一些在《理解 AOP 代理》中有所描述。此外,请记住一定要在代理的类上使用方法和访问器;直接的字段访问是不行的(也就是说,自己在类中的方法里面使用 this 调用方法是不会触发 AOP 的)。 :::

配置 DataBinder

从 Spring 3 开始,你可以给 DataBinder 实例配置一个验证器。一旦配置好,你可以通过调用 binder.validate() 来调用验证器。任何验证错误都会自动添加到绑定器的 BindingResult 中。

下面的例子显示了如何以编程方式使用 DataBinder,在绑定到目标对象后调用验证逻辑:

这个是实体类

  1. package cn.mrcode.study.springdocsread.data;
  2. /**
  3. * @author mrcode
  4. */
  5. public class PersonForm {
  6. private String name;
  7. private int age;
  8. public String getName() {
  9. return name;
  10. }
  11. public void setName(String name) {
  12. this.name = name;
  13. }
  14. public int getAge() {
  15. return age;
  16. }
  17. public void setAge(int age) {
  18. this.age = age;
  19. }
  20. }

这是一个验证器

  1. package cn.mrcode.study.springdocsread.data;
  2. import org.springframework.validation.Errors;
  3. import org.springframework.validation.ValidationUtils;
  4. import org.springframework.validation.Validator;
  5. /**
  6. * @author mrcode
  7. */
  8. public class PersonFormValidator implements Validator {
  9. @Override
  10. public boolean supports(Class<?> clazz) {
  11. return PersonForm.class.equals(clazz);
  12. }
  13. @Override
  14. public void validate(Object target, Errors errors) {
  15. ValidationUtils.rejectIfEmpty(errors, "name", "name.empty");
  16. }
  17. }

这是 DataBinder 的使用方式

  1. package cn.mrcode.study.springdocsread.data;
  2. import org.springframework.beans.MutablePropertyValues;
  3. import org.springframework.validation.BindingResult;
  4. import org.springframework.validation.DataBinder;
  5. /**
  6. * @author mrcode
  7. */
  8. public class DemoTest {
  9. public static void main(String[] args) {
  10. // 你想要的实例对象
  11. final PersonForm target = new PersonForm();
  12. DataBinder binder = new DataBinder(target);
  13. binder.setValidator(new PersonFormValidator());
  14. // 这个是从其他地方获取到的数据,比如前端提交的数据,现在要把这些属性设置到你的 target 对上上去
  15. // PropertyValues 接口的默认实现。允许对属性进行简单操作,并提供构造函数以支持从 Map 进行深度复制和构造。
  16. MutablePropertyValues propertyValues = new MutablePropertyValues();
  17. propertyValues.add("age", "15");
  18. // 绑定到目标对象
  19. binder.bind(propertyValues);
  20. // 验证目标对象
  21. binder.validate();
  22. // 获得包括任何验证错误的 BindingResult
  23. final BindingResult bindingResult = binder.getBindingResult();
  24. System.out.println(bindingResult);
  25. }
  26. }

执行后输出的信息如下: name 字段为空 错误

  1. org.springframework.validation.BeanPropertyBindingResult: 1 errors
  2. Field error in object 'target' on field 'name': rejected value [null]; codes [name.empty.target.name,name.empty.name,name.empty.java.lang.String,name.empty]; arguments []; default message [null]

你也可以通过 dataBinder.addValidatorsdataBinder.replaceValidators将一个 DataBinder 配置成多个验证器实例。当把全局配置的 bean 验证与 DataBinder 实例上本地配置的 Spring 验证器结合起来时,这很有用。参见 Spring MVC 验证配置

Spring MVC 3 Validation

请参阅 Spring MVC 章节中的验证。