Java Bean 验证
Spring框架支持Java Bean Validation API。
Bean验证概述
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验证提供者
Spring全面支持Bean Validation API,包括将Bean Validation提供者作为Spring Bean进行初始化。这使你可以在应用程序中任何需要验证的地方注入jakartavalidation.ValidatorFactory或jakartavalidation.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验证,通过使用其默认的引导机制进行初始化。期望类路径中存在一个Bean验证提供者(如Hibernate Validator),并且该提供者会自动被检测到。
注入 Jakarta Validator
LocalValidatorFactoryBean同时实现了jakarta.validation.ValidatorFactory和jakarta_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.springframeworkvalidation.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验证约束由两部分组成:
-
一个
@Constraint注解,用于声明约束及其可配置的属性。 -
一个实现
jakarta_validation.ConstraintValidator接口的类,该类实现了该约束的行为。
为了将声明与实现关联起来,每个@Constraint注解都会引用一个对应的ConstraintValidator实现类。在运行时,当在领域模型中遇到该约束注解时,ConstraintValidatorFactory会实例化所引用的实现类。
默认情况下,LocalValidatorFactoryBean 配置了一个 SpringConstraintValidatorFactory,该工厂使用 Spring 来创建 ConstraintValidator 实例。这使得您的自定义 ConstraintValidator 可以像其他 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的实现可以像其他Springbean一样,将其依赖项通过@Autowired进行注入。
Spring驱动的方法验证
你可以通过定义一个MethodValidationPostProcessorbean,在Spring上下文中集成Bean Validation的方法验证功能:
- 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。
方法验证异常
默认情况下,jakarta_validation.ConstraintViolationException 会与 jakartavalidation.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.Errors 接口的 ParameterErrors,并且以 FieldError 的形式暴露验证错误。
自定义验证错误
经过适配的MessageSourceResolvable错误可以通过配置的MessageSource以及特定于区域设置(locale)和语言的资源包(resource bundles)转换成错误消息,以便向用户显示。本节提供了一个示例来进行说明。
根据以下类声明:
- 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”、10和1(字段名和约束属性)
-
默认消息:“大小必须在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(Bean验证)功能。有关这些选项的更多信息,请参阅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.addValidators 和 dataBinder.replaceValidators 方法为 DataBinder 配置多个 Validator 实例。当将全局配置的 Bean 验证与在 DataBinder 实例上本地配置的 Spring Validator 结合使用时,这种方法非常有用。请参阅 Spring MVC 验证配置。
Spring MVC 验证
请参阅Spring MVC章节中的验证。