Java Bean Validation

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

Bean Validation 概述

Bean Validation 通过为 Java 应用提供约束声明和元数据,实现了通用的验证方式。使用时,您可以用声明式验证约束注解域模型属性,这些约束随后会由运行时强制执行。Bean Validation 提供了内置约束,您也可以定义自己的自定义约束。

请看以下示例,它展示了一个带有两个属性的简单 PersonForm 模型

  • Java

  • Kotlin

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

Bean Validation 允许您声明约束,如下例所示

  • 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 Validation 验证器会根据声明的约束来验证该类的实例。有关该 API 的一般信息,请参阅 Bean Validation。有关特定约束,请参阅 Hibernate Validator 文档。要了解如何将 bean validation provider 设置为 Spring bean,请继续阅读。

配置 Bean Validation Provider

Spring 为 Bean Validation API 提供了全面支持,包括将 Bean Validation provider 引导为 Spring bean。这使得您可以在应用中需要验证的任何地方注入 jakarta.validation.ValidatorFactoryjakarta.validation.Validator

您可以使用 LocalValidatorFactoryBean 配置默认的 Validator 作为 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 validation 使用其默认引导机制进行初始化。Bean Validation provider(例如 Hibernate Validator)应存在于类路径中并会被自动检测到。

注入 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)

注入 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,然后将 ConstraintViolation 适配为 FieldError,并将其注册到传递给 validate 方法的 Errors 对象中。

配置自定义约束

每个 bean validation 约束由两部分组成

  • 一个 @Constraint 注解,用于声明约束及其可配置属性。

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

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

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

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

  • 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 驱动的方法验证

您可以通过 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 注解进行标注,该注解也可以选择性地声明要使用的验证组。有关 Hibernate Validator 和 Bean Validation provider 的设置详情,请参阅 MethodValidationPostProcessor 文档。

方法验证依赖于目标类周围的 AOP 代理,可以是接口方法的 JDK 动态代理,也可以是 CGLIB 代理。使用代理存在一些限制,其中一些限制在 理解 AOP 代理 中有所描述。此外,请记住始终使用代理类上的方法和访问器;直接字段访问将不起作用。

Spring MVC 和 WebFlux 对相同的基础方法验证提供了内置支持,但无需 AOP。因此,请查看本节的其余部分,并参阅 Spring MVC 验证错误响应 部分,以及 WebFlux 验证错误响应 部分。

方法验证异常

默认情况下,会抛出 jakarta.validation.ConstraintViolationException,其中包含由 jakarta.validation.Validator 返回的 ConstraintViolation 集合。作为替代方案,您可以抛出 MethodValidationException,其中包含适配为 MessageSourceResolvable 错误的 ConstraintViolation。要启用此功能,请设置以下标志

  • 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

自定义验证错误

适配后的 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.person.name""Size.name""Size.java.lang.String""Size"

  • 消息参数 "name"101(字段名和约束属性)

  • 默认消息 "大小必须在 1 到 10 之间"

要自定义默认消息,您可以使用上述任何错误码和消息参数向 MessageSource 资源包添加属性。另请注意,消息参数 "name" 本身就是一个 MessageSourceResolvable,其错误码为 "person.name" 和 "name",也可以进行自定义。例如

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

degrees 方法参数上的 ConstraintViolation 被适配为带有以下信息的 MessageSourceResolvable

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

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

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

要自定义上面的默认消息,您可以添加一个属性,例如

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

附加配置选项

默认的 LocalValidatorFactoryBean 配置足以满足大多数情况。Bean Validation 的各种构造(从消息插值到遍历解析)都有许多配置选项。有关这些选项的更多信息,请参阅 LocalValidatorFactoryBean 的 Javadoc 文档。

配置 DataBinder

您可以为 DataBinder 实例配置一个 Validator。配置完成后,可以通过调用 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.addValidatorsdataBinder.replaceValidatorsDataBinder 配置多个 Validator 实例。这对于将全局配置的 bean validation 与在 DataBinder 实例上本地配置的 Spring Validator 结合使用非常有用。请参阅 Spring MVC 验证配置

Spring MVC 3 验证

请参阅 Spring MVC 章中的 验证