
在使用java bean validation(jsr 380/303)进行数据校验时,@notnull和@asserttrue是两个常用的约束注解。@notnull用于确保字段值不为null,而@asserttrue则作用于方法上,要求该方法返回true,通常用于实现更复杂的业务逻辑校验。
然而,当一个DTO(Data Transfer Object)中同时存在被@NotNull修饰的字段和依赖该字段进行校验的@AssertTrue方法时,可能会遇到一个常见问题。考虑以下DTO示例:
import lombok.Data;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;
@Data
public class Dto {
@NotNull
private Integer anInt;
@AssertTrue
public boolean isIntCustomValid() {
// 此处逻辑依赖于anInt的值
return anInt == 123 || anInt == 999;
}
}在Spring MVC控制器中使用@Valid注解对Dto进行校验时,如果anInt字段的值为null,你可能会观察到HV000090: Unable to access错误。这是因为Bean Validation的默认行为是尝试执行所有相关的约束。即使anInt已经被@NotNull标记为不允许为null,在某些情况下,isIntCustomValid()方法仍然会在anInt为null时被调用。当isIntCustomValid()方法试图访问null值的anInt并执行比较操作时,就会抛出空指针异常,进而被Bean Validation框架捕获并转换为HV000090错误,指示无法访问属性。这导致了校验流程的中断,并且@NotNull的错误信息可能无法正常返回。
理想情况下,我们希望@NotNull优先验证,如果字段为null,则直接返回@NotNull的错误,而不再执行依赖该字段的@AssertTrue校验。
解决此问题的最直接且优雅的方法是,在@AssertTrue注解修饰的方法内部,显式地对所依赖的字段进行null值检查。如果依赖字段为null,则让@AssertTrue方法直接返回true。这样做的逻辑是:如果字段为null,那么它的null性应该由@NotNull约束来处理;@AssertTrue的自定义逻辑只在字段非null时才有意义。
以下是修改后的Dto示例:
import lombok.Data;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;
import java.util.Objects; // 导入Objects工具类
@Data
public class Dto {
@NotNull(message = "anInt字段不能为空") // 建议添加message以提供更友好的错误提示
private Integer anInt;
@AssertTrue(message = "anInt的值必须是123或999") // 建议添加message
public boolean isIntCustomValid() {
// 关键:在执行自定义逻辑前,首先检查anInt是否为null
if (Objects.nonNull(anInt)) {
// 只有当anInt不为null时,才执行自定义的业务逻辑校验
return anInt == 123 || anInt == 999;
}
// 如果anInt为null,则此@AssertTrue校验直接返回true。
// anInt的null值问题将由@NotNull约束来处理。
return true;
}
}工作原理:
这种方法将@AssertTrue的执行条件与其依赖的字段的非空性紧密结合,从而避免了在不应该执行自定义逻辑时出现异常。
除了上述方法,Bean Validation还提供了分组校验(@GroupSequence和@Groups)来控制验证顺序。通过定义不同的验证组,可以将@NotNull放在一个组,@AssertTrue放在另一个组,并使用@GroupSequence指定它们的执行顺序。
例如:
// 定义标记接口
public interface FirstValidation {}
public interface SecondValidation {}
@Data
@GroupSequence({FirstValidation.class, SecondValidation.class, Dto.class}) // Dto.class代表默认组
public class Dto {
@NotNull(groups = FirstValidation.class)
private Integer anInt;
@AssertTrue(groups = SecondValidation.class)
public boolean isIntCustomValid() {
return anInt == 123 || anInt == 999;
}
}然后在控制器中指定校验组:
@PostMapping("/validate")
public ResponseEntity<String> validateDto(@Validated(Dto.class) @RequestBody Dto dto) {
// ...
}这种方法确实能够精确控制验证顺序,即只有当FirstValidation组通过后,才会执行SecondValidation组。然而,它的缺点在于需要创建额外的空标记接口,这增加了代码的复杂性和冗余。对于仅仅是处理@NotNull和@AssertTrue之间简单依赖的场景,在@AssertTrue方法内部进行空值检查通常是更简洁、易读且维护成本更低的解决方案。
当在Bean Validation中同时使用@NotNull和@AssertTrue时,为了避免因@AssertTrue在依赖字段为null时尝试访问而导致的HV000090错误,最简洁有效的策略是在@AssertTrue修饰的方法内部,增加对依赖字段的null值检查。如果字段为null,则让@AssertTrue方法直接返回true,将null值的校验责任完全交给@NotNull。这种方法既保证了验证的正确性,又避免了引入额外的复杂性,是处理此类常见问题的推荐实践。
以上就是解决Spring Boot中@NotNull与@AssertTrue组合验证顺序问题的策略的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号