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 章中的 驗證