1. 概述
本文将深入探讨如何使用 Jakarta Bean Validation 3.0 定义和验证方法约束。在前一篇文章中,我们讨论了 JSR-380 的内置注解及属性验证实现。本文将聚焦以下方法约束类型:
- 单参数约束
- 跨参数约束
- 返回值约束
同时,我们将演示如何通过 Spring Validator 手动和自动验证这些约束。本文示例依赖与 Java Bean Validation 基础 完全一致。
2. 方法约束的声明
2.1 单参数约束
在单个参数上定义约束非常直接,只需为每个参数添加所需注解:
public void createReservation(@NotNull @Future LocalDate begin,
@Min(1) int duration, @NotNull Customer customer) {
// ...
}
构造函数同样适用:
public class Customer {
public Customer(@Size(min = 5, max = 200) @NotNull String firstName,
@Size(min = 5, max = 200) @NotNull String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// 属性、getter 和 setter
}
2.2 使用跨参数约束
某些场景需要同时验证多个参数(如两个数值的大小关系)。此时可定义跨参数约束,依赖两个或以上参数。
⚠️ 跨参数约束可视为方法验证中的类级约束等价物,两者都基于多个属性实现验证。
以 createReservation()
方法为例:它接收两个 LocalDate
参数(开始日期和结束日期)。需确保:
- 开始日期在未来
- 结束日期晚于开始日期
单参数约束无法满足,必须使用跨参数约束。与单参数不同,跨参数约束声明在方法或构造函数上:
@ConsistentDateParameters
public void createReservation(LocalDate begin,
LocalDate end, Customer customer) {
// ...
}
2.3 创建跨参数约束
实现 @ConsistentDateParameters
需两步:
第一步:定义约束注解
@Constraint(validatedBy = ConsistentDateParameterValidator.class)
@Target({ METHOD, CONSTRUCTOR })
@Retention(RUNTIME)
@Documented
public @interface ConsistentDateParameters {
String message() default
"End date must be after begin date and both must be in the future";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
三个必需属性说明:
-
message
:错误消息默认键,支持消息插值 -
groups
:指定验证组 -
payload
:供客户端附加自定义载荷对象
第二步:定义验证器类
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ConsistentDateParameterValidator
implements ConstraintValidator<ConsistentDateParameters, Object[]> {
@Override
public boolean isValid(
Object[] value,
ConstraintValidatorContext context) {
if (value[0] == null || value[1] == null) {
return true;
}
if (!(value[0] instanceof LocalDate)
|| !(value[1] instanceof LocalDate)) {
throw new IllegalArgumentException(
"Illegal method signature, expected two parameters of type LocalDate.");
}
return ((LocalDate) value[0]).isAfter(LocalDate.now())
&& ((LocalDate) value[0]).isBefore((LocalDate) value[1]);
}
}
关键点:
-
isValid()
包含核心验证逻辑:检查参数类型、日期有效性 -
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
注解必须添加,表明约束应用于参数而非返回值 - 规范建议将
null
视为有效值,若需禁止应使用@NotNull
2.4 返回值约束
有时需验证方法返回的对象,此时使用返回值约束:
public class ReservationManagement {
@NotNull
@Size(min = 1)
public List<@NotNull Customer> getAllCustomers() {
return null;
}
}
getAllCustomers()
的约束要求:
- 返回列表非
null
且至少包含一个元素 - 列表元素不能为
null
2.5 返回值自定义约束
复杂对象验证需自定义约束:
public class ReservationManagement {
@ValidReservation
public Reservation getReservationsById(int id) {
return null;
}
}
返回的 Reservation
对象必须满足 @ValidReservation
约束。实现步骤:
定义约束注解
@Constraint(validatedBy = ValidReservationValidator.class)
@Target({ METHOD, CONSTRUCTOR })
@Retention(RUNTIME)
@Documented
public @interface ValidReservation {
String message() default "End date must be after begin date "
+ "and both must be in the future, room number must be bigger than 0";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
定义验证器类
public class ValidReservationValidator
implements ConstraintValidator<ValidReservation, Reservation> {
@Override
public boolean isValid(
Reservation reservation, ConstraintValidatorContext context) {
if (reservation == null) {
return true;
}
if (!(reservation instanceof Reservation)) {
throw new IllegalArgumentException("Illegal method signature, "
+ "expected parameter of type Reservation.");
}
if (reservation.getBegin() == null
|| reservation.getEnd() == null
|| reservation.getCustomer() == null) {
return false;
}
return (reservation.getBegin().isAfter(LocalDate.now())
&& reservation.getBegin().isBefore(reservation.getEnd())
&& reservation.getRoom() > 0);
}
}
2.6 构造函数中的返回值
因 ValidReservation
注解的 @Target
包含 CONSTRUCTOR
,可直接注解构造函数验证实例:
public class Reservation {
@ValidReservation
public Reservation(
LocalDate begin,
LocalDate end,
Customer customer,
int room) {
this.begin = begin;
this.end = end;
this.customer = customer;
this.room = room;
}
// 属性、getter 和 setter
}
2.7 级联验证
Bean Validation API 支持通过级联验证验证对象图。使用 @Valid
注解触发递归验证,适用于方法参数和返回值。
假设 Customer
类有属性约束:
public class Customer {
@Size(min = 5, max = 200)
private String firstName;
@Size(min = 5, max = 200)
private String lastName;
// 构造函数、getter 和 setter
}
Reservation
类包含 Customer
属性及其他约束:
public class Reservation {
@Valid
private Customer customer;
@Positive
private int room;
// 其他属性、构造函数、getter 和 setter
}
当 Reservation
作为方法参数时,可强制递归验证所有属性:
public void createNewCustomer(@Valid Reservation reservation) {
// ...
}
@Valid
的双重作用:
reservation
参数:调用createNewCustomer()
时触发Reservation
对象验证customer
属性:触发嵌套对象验证
返回值同样适用:
@Valid
public Reservation getReservationById(int id) {
return null;
}
3. 验证方法约束
3.1 使用 Spring 自动验证
Spring Validation 集成了 Hibernate Validator。注意:其基于 AOP 实现,仅支持方法验证,不支持构造函数。
启用自动验证需两步:
第一步:在需验证的 Bean 上添加 @Validated
@Validated
public class ReservationManagement {
public void createReservation(@NotNull @Future LocalDate begin,
@Min(1) int duration, @NotNull Customer customer){
// ...
}
@NotNull
@Size(min = 1)
public List<@NotNull Customer> getAllCustomers(){
return null;
}
}
第二步:提供 MethodValidationPostProcessor
Bean
@Configuration
@ComponentScan({ "com.example.validation.model" })
public class MethodValidationConfig {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
约束违反时,容器将抛出 jakarta.validation.ConstraintViolationException
。
✅ Spring Boot 项目:只要类路径包含
hibernate-validator
,容器会自动注册MethodValidationPostProcessor
。
3.2 使用 CDI 自动验证
Bean Validation 1.1+ 支持 CDI(Jakarta EE 的上下文与依赖注入)。在 Jakarta EE 容器中运行时,方法调用时自动验证约束。
3.3 编程式验证
独立 Java 应用中手动验证方法,使用 jakarta.validation.executable.ExecutableValidator
接口:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
ExecutableValidator executableValidator = factory.getValidator().forExecutables();
ExecutableValidator
提供四类方法:
- 方法验证:
validateParameters()
和validateReturnValue()
- 构造函数验证:
validateConstructorParameters()
和validateConstructorReturnValue()
验证 createReservation()
参数示例:
ReservationManagement object = new ReservationManagement();
Method method = ReservationManagement.class
.getMethod("createReservation", LocalDate.class, int.class, Customer.class);
Object[] parameterValues = { LocalDate.now(), 0, null };
Set<ConstraintViolation<ReservationManagement>> violations
= executableValidator.validateParameters(object, method, parameterValues);
⚠️ 官方文档不推荐直接调用此接口,建议通过 AOP 或代理等拦截技术使用。
4. 结论
本文系统介绍了 Hibernate Validator 的方法约束使用及 JSR-380 新特性,涵盖:
- 单参数约束
- 跨参数约束
- 返回值约束
并演示了通过 Spring Validator 手动和自动验证约束的实践。完整示例代码见 GitHub 仓库。