Java Bean Validation

LocalValidatorFactoryBean, Validator, Constraint, ConstraintValidator, SpringConstraintValidatorFactory, Method Validation, MethodValidationPostProcessor :description: Spring Framework 提供对 Bean Validation API 的支持,该 API 提供了一个通用方法来验证 Java 应用程序中的数据。通过使用约束注释声明验证约束并由运行时强制执行,Bean Validation 允许开发人员实施复杂的验证规则。Spring 集成了 Bean Validation,允许将验证器注入应用程序中需要验证的任何位置,并且可以通过注入 Validator 或 ValidatorFactory 接口直接使用 Bean Validation API。此外,Spring 提供了 LocalValidatorFactoryBean,它将 Bean 验证提供程序自举为 Spring bean,并允许自定义约束验证器实现和方法验证特性。

Spring Framework 提供对https://beanvalidation.org[Java Bean Validation] API 的支持。

Overview of Bean Validation

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

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

  • Java

  • Kotlin

public class PersonForm {
	private String name;
	private int age;
}
class PersonForm(
		private val name: String,
		private val age: Int
)

Bean 验证允许你声明约束,如下例所示:

  • Java

  • Kotlin

public class PersonForm {

	@NotNull
	@Size(max=64)
	private String name;

	@Min(0)
	private int age;
}
class PersonForm(
	@get:NotNull @get:Size(max=64)
	private val name: String,
	@get:Min(0)
	private val age: Int
)

然后,Bean 验证验证器基于声明的约束验证此类的实例。有关 API 的一般信息,请参阅 Bean 验证。有关特定约束,请参阅 Hibernate Validator 文档。要了解如何将 Bean 验证提供程序设置为 Spring Bean,请继续阅读。

Configuring a Bean Validation Provider

Spring 完全支持 Bean Validation API,包括将 Bean 验证提供程序自举为 Spring bean。这允许你在应用程序中需要验证的任何地方注入 jakarta.validation.ValidatorFactoryjakarta.validation.Validator

可以使用 LocalValidatorFactoryBean 将默认验证器配置为 Spring bean,如下例所示:

  • Java

  • XML

import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class AppConfig {

	@Bean
	public LocalValidatorFactoryBean validator() {
		return new LocalValidatorFactoryBean();
	}
}
<bean id="validator"
	class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

前一个示例中的基本配置触发 bean 验证使用其默认引导机制进行初始化。预计 Bean 验证提供程序(例如 Hibernate Validator)会在类路径中,并且会自动检测到它们。

Inject Jakarta Validator

LocalValidatorFactoryBean 同时实现了 jakarta.validation.ValidatorFactoryjakarta.validation.Validator,所以你可以注入后者的引用以应用验证逻辑(如果你更愿意直接使用 Bean Validation API),如下例所示:

  • Java

  • Kotlin

import jakarta.validation.Validator;

@Service
public class MyService {

	@Autowired
	private Validator validator;
}
import jakarta.validation.Validator;

@Service
class MyService(@Autowired private val validator: Validator)

Inject Spring Validator

除了实现 jakarta.validation.Validator 之外,LocalValidatorFactoryBean 还适应于 org.springframework.validation.Validator,所以如果 bean 要求使用 Spring Validation API,你就可以注入后者的引用。

例如:

  • Java

  • Kotlin

import org.springframework.validation.Validator;

@Service
public class MyService {

	@Autowired
	private Validator validator;
}
import org.springframework.validation.Validator

@Service
class MyService(@Autowired private val validator: Validator)

当用作 org.springframework.validation.Validator 时,LocalValidatorFactoryBean 调用底层的 jakarta.validation.Validator,然后将 ContraintViolation 适应为 FieldError,并用它们向传递给 validate 方法的 Errors 对象注册。

Configure Custom Constraints

每个 bean 验证约束包含两部分:

  • 声明约束及其可配置属性的 `@Constraint`注释。

  • 实现 `jakarta.validation.ConstraintValidator`接口的一个实现,该接口实现约束行为。

为了将声明与实现关联起来,每个 @Constraint 注解引用对应的 ConstraintValidator 实现类。在运行时,当约束注解在你的领域模型中被使用时,ConstraintValidatorFactory 实例化引用的实现。

默认情况下,LocalValidatorFactoryBean 配置一个 SpringConstraintValidatorFactory,它使用 Spring 来创建 ConstraintValidator 实例。这样,自定义的 ConstraintValidators 像其他 Spring bean 一样可以从依赖注入中受益。

以下示例展示了一个自定义 @Constraint 声明,后面是一个相关的 ConstraintValidator 实现,它使用 Spring 进行依赖注入:

  • Java

  • Kotlin

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)
public @interface MyConstraint {
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator::class)
annotation class MyConstraint
  • Java

  • Kotlin

import jakarta.validation.ConstraintValidator;

public class MyConstraintValidator implements ConstraintValidator {

	@Autowired;
	private Foo aDependency;

	// ...
}
import jakarta.validation.ConstraintValidator

class MyConstraintValidator(private val aDependency: Foo) : ConstraintValidator {

	// ...
}

如前例所示,ConstraintValidator 实现可以像其他 Spring bean 一样对其依赖项 @Autowired

Spring-driven Method Validation

你可以通过 MethodValidationPostProcessor bean 定义将 Bean Validation 的方法验证特性集成到 Spring 上下文中:

  • Java

  • Kotlin

  • Xml

@Configuration
public class ApplicationConfiguration {

	@Bean
	public static MethodValidationPostProcessor validationPostProcessor() {
		return new MethodValidationPostProcessor();
	}
}
@Configuration
class ApplicationConfiguration {

	companion object {

		@Bean
		@JvmStatic
		fun validationPostProcessor() = MethodValidationPostProcessor()
	}
}
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

为了符合 Spring 驱动的验证验证方法,目标类需要使用 Spring 的 @Validated`注释,该注释可以选择还声明要使用的验证组。请参阅https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.html[`MethodValidationPostProcessor],以了解使用 Hibernate Validator 和 Bean Validation 提供程序时的设置详情。

方法验证依赖于目标类周围的 AOP Proxies,或者 JDK 动态代理(用于接口方法)或 CGLIB 代理。使用代理存在一定的局限性,其中一些在 Understanding AOP Proxies 中进行了描述。此外,请务必始终对代理类使用的方法和访问器;直接字段访问将不起作用。

Spring MVC 和 WebFlux 内置对相同底层方法验证的支持,但无需 AOP。因此,请查看本节的其余部分,并了解 Spring MVCValidationError Responses部分,以及 WebFluxValidationError Responses部分。

Method Validation Exceptions

默认情况下,jakarta.validation.ConstraintViolationExceptionjakarta.validation.Validator 返回的 ConstraintViolation 集合引发。作为替代,你可以改为引发 MethodValidationException,其中 ConstraintViolation 适应了 MessageSourceResolvable 错误。要启用,请设置以下标志:

  • Java

  • Kotlin

  • Xml

@Configuration
public class ApplicationConfiguration {

	@Bean
	public static MethodValidationPostProcessor validationPostProcessor() {
		MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
		processor.setAdaptConstraintViolations(true);
		return processor;
	}
}
@Configuration
class ApplicationConfiguration {

	companion object {

		@Bean
		@JvmStatic
		fun validationPostProcessor() = MethodValidationPostProcessor().apply {
			setAdaptConstraintViolations(true)
		}
	}
}
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
	<property name="adaptConstraintViolations" value="true"/>
</bean>

MethodValidationException 包含一个 ParameterValidationResult 列表,该列表按方法参数对错误进行分组,每个错误都公开一个 MethodParameter、参数值以及一个从 ConstraintViolation 转换而来的 MessageSourceResolvable 错误列表。对于在字段和属性上具有级联违规的 @Valid 方法参数,ParameterValidationResultParameterErrors,它实现了 org.springframework.validation.Errors 并将验证错误作为 FieldError 公开。

Customizing Validation Errors

适配的 MessageSourceResolvable 错误可以转换成错误消息,通过配置的 MessageSource 使用特定于区域设置和语言的资源包显示给用户。本节提供了一个例子来说明这一点。

给定以下类声明:

  • Java

  • Kotlin

record Person(@Size(min = 1, max = 10) String name) {
}

@Validated
public class MyService {

	void addStudent(@Valid Person person, @Max(2) int degrees) {
		// ...
	}
}
@JvmRecord
internal data class Person(@Size(min = 1, max = 10) val name: String)

@Validated
class MyService {

	fun addStudent(person: @Valid Person?, degrees: @Max(2) Int) {
		// ...
	}
}

Person.name() 上的 ConstraintViolation 适应了带有以下内容的 FieldError

  • 错误代码 "Size.student.name""Size.name""Size.java.lang.String"`和 `"Size"

  • 消息参数 "name"10`和 `1(字段名称和约束属性)

  • 默认信息"大小必须介于 1 和 10 之间"

要自定义默认消息,则可以使用上述错误代码和消息参数将属性添加到 MessageSource 资源捆绑包中。另请注意,消息参数 "name" 本身是带有错误代码 "student.name""name"MessageSourceResolvable,也可以自定义。例如:

Properties
Size.student.name=Please, provide a {0} that is between {2} and {1} characters long
student.name=username

degrees 方法参数上的 ConstraintViolation 适应了带有以下内容的 MessageSourceResolvable

  • 错误代码 "Max.myService#addStudent.degrees""Max.degrees""Max.int""Max"

  • 消息参数"degrees2 和 2(字段名称和约束属性)

  • 默认信息"必须小于或等于 2"

要自定义上述默认消息,你可以添加这样的属性:

Properties
Max.degrees=You cannot provide more than {1} {0}

Additional Configuration Options

默认的`LocalValidatorFactoryBean`配置对于大多数情况来说已经足够了。对于各种 Bean 验证构造,有许多配置选项,从消息内插到遍历解决。有关这些选项的更多信息,请参见https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/validation//beanvalidation/LocalValidatorFactoryBean.html[LocalValidatorFactoryBean] javadoc。

Configuring a DataBinder

你可以使用 Validator 配置一个 DataBinder 实例。配置完毕后,你可以通过调用 binder.validate() 来调用 Validator。所有验证 Errors 会自动添加到 binder 的 BindingResult 中。

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

  • Java

  • Kotlin

Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());

// bind to the target object
binder.bind(propertyValues);

// validate the target object
binder.validate();

// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();
val target = Foo()
val binder = DataBinder(target)
binder.validator = FooValidator()

// bind to the target object
binder.bind(propertyValues)

// validate the target object
binder.validate()

// get BindingResult that includes any validation errors
val results = binder.bindingResult

您还可以通过`dataBinder.addValidators`和`dataBinder.replaceValidators`使用多个`Validator`实例配置一个`DataBinder`。当将全局配置的 bean 验证与 Spring `Validator`在 DataBinder 实例上局部配置的 bean 验证相结合时,这非常有用。见Spring MVC Validation Configuration

Spring MVC 3 Validation

请参阅 Spring MVC 章节中的Validation