Java Bean 验证

Spring 框架支持Java Bean Validation API。

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 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 提供程序设置为 Spring Bean,请继续阅读。

配置 Bean Validation 提供程序

Spring 完全支持 Bean Validation API,包括将 Bean Validation 提供程序引导为 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 Validation 的初始化。Bean Validation 提供程序(例如 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实例。这允许您的自定义ConstraintValidators像任何其他 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 提供程序的设置细节,请参阅MethodValidationPostProcessor

方法验证依赖于AOP 代理环绕目标类,对于接口上的方法使用 JDK 动态代理,或者使用 CGLIB 代理。使用代理存在某些限制,其中一些在理解 AOP 代理中进行了描述。此外,请记住始终使用代理类上的方法和访问器;直接字段访问将不起作用。

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

方法验证异常

默认情况下,会引发jakarta.validation.ConstraintViolationException,其中包含jakarta.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列表,该列表按方法参数对错误进行分组,每个ParameterValidationResult都公开一个MethodParameter、参数值和一个从ConstraintViolation改编的MessageSourceResolvable错误列表。对于具有级联字段和属性违规的@Valid方法参数,ParameterValidationResult是实现org.springframework.validation.ErrorsParameterErrors,并将验证错误公开为FieldError

自定义验证错误

可以通过配置的MessageSource以及特定于区域设置和语言的资源包,将已改编的MessageSourceResolvable错误转换为显示给用户的错误消息。本节提供一个示例进行说明。

给定以下类声明

  • 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"本身就是一个具有错误代码"person.name""name"MessageSourceResolvable,也可以对其进行自定义。例如:

属性
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

您可以使用Validator配置DataBinder实例。配置后,您可以通过调用binder.validate()来调用Validator。任何验证Errors都会自动添加到绑定器的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.replaceValidators使用多个Validator实例配置DataBinder。当将全局配置的bean验证与在DataBinder实例上本地配置的Spring Validator结合使用时,这很有用。请参见Spring MVC验证配置

Spring MVC 3 验证

请参见Spring MVC章节中的验证