引言
数据校验在数据处理中起到保驾护航的作用,它能有效确保数据的准确性、完整性和一致性,从而为后续分析和决策提供可靠依据,避免因错误数据导致的不良后果。
本文将详细介绍一下
Hibernate Validator
,并详细说说如何自定义扩展。
一、认识Hibernate Validator
1.1 什么是Hibernate Validator?
-
Hibernate Validator 是一个实现 Java Bean Validation (JSR 380) 规范的校验框架。
-
它提供了丰富的内置校验注解,支持自定义校验逻辑,能够无缝集成到 Spring Boot 和其他 Java 框架中。
1.2 Java Bean Validation 规范
-
JSR 380 是 Java 的标准校验规范,定义了一套统一的校验机制。它是 Java EE 和 Java SE 的一部分,用于通过注解(如 @NotNull、@Min 和 @Max)来验证 Java Bean 的属性是否符合特定条件。JSR 380 是 JSR 303 的升级版本,需要 Java 8 或更高版本,并利用了 Java 8 的特性,例如类型注解和对 Optional 和 LocalDate 等类的支持。
-
Hibernate Validator 是该规范的参考实现,完全兼容规范定义的校验注解。
-
通过使用 JSR 380,开发者可以更优雅地实现参数校验,避免硬编码校验逻辑,从而提高代码的可维护性和可读性。
1.3 Hibernate Validator 的优势
-
丰富的内置注解:提供了如
@NotNull
、@Size
、@Email
等常用校验注解。 -
灵活的自定义校验:支持自定义注解和校验器,满足复杂业务需求。
-
集成友好:与 Spring Boot、Spring MVC 等框架无缝集成。
-
性能高效:校验逻辑在编译时生成,运行时性能优异。
二、Hibernate Validator 的基本使用
2.1 添加依赖
-
在 Spring Boot 项目中,Hibernate Validator 是默认集成的,无需额外添加依赖。
-
如果是纯 Java 项目,需要手动添加依赖:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
maven仓库地址:hibernate-validator
2.2 使用内置校验注解
示例代码:
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
public class User {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度必须在3到50之间")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
// 省略 getter 和 setter 方法
}
2.3 校验触发
- 在 Spring Boot 中,可以通过
@Valid
或@Validated
注解触发校验。
@RestController
public class UserController {
@PostMapping("/user")
public ResponseEntity<?> createUser(@Valid @RequestBody User user) {
return ResponseEntity.ok("用户创建成功");
}
}
2.4 校验结果处理
捕获校验异常:
- 使用
@ControllerAdvice
捕获校验异常并返回统一的错误响应。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach(error -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return ResponseEntity.badRequest().body(errors);
}
}
三、自定义注解与校验器,为枚举数据校验赋能
3.1 为什么需要自定义注解?
-
内置注解无法满足所有业务需求,例如枚举值校验、复杂逻辑校验等。
-
自定义注解可以扩展校验功能,提高代码的复用性和可维护性。
3.2 自定义枚举数据类型注解和校验器
3.2.1 枚举值校验注解
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 枚举值校验注解
*
* @author bamboo panda
* @version 1.0
* @date 2025/5/7 13:54
*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumCheckValidator.class)
public @interface EnumCheck {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends Enum<?>> enumClass();
String enumMethod();
}
3.2.2 枚举值校验器
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* 枚举值校验器
*
* @author bamboo panda
* @version 1.0
* @date 2025/5/7 13:56
*/
public class EnumCheckValidator implements ConstraintValidator<EnumCheck, Object> {
private Class<? extends Enum<?>> enumClass;
private String enumMethod;
@Override
public void initialize(EnumCheck enumCheck) {
enumMethod = enumCheck.enumMethod();
enumClass = enumCheck.enumClass();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (null == value) {
return Boolean.FALSE;
}
if (enumClass == null || enumMethod == null) {
return Boolean.FALSE;
}
Class<?> valueClass = value.getClass();
try {
Method method = enumClass.getMethod(enumMethod, valueClass);
if (!Boolean.TYPE.equals(method.getReturnType()) && !Boolean.class.equals(method.getReturnType())) {
throw new RuntimeException(String.format("%s method return is not boolean type in the %s class", enumMethod, enumClass));
}
if (!Modifier.isStatic(method.getModifiers())) {
throw new RuntimeException(String.format("%s method is not static method in the %s class", enumMethod, enumClass));
}
Boolean result = (Boolean) method.invoke(null, value);
return result == null ? false : result;
} catch (NoSuchMethodException | SecurityException e) {
throw new RuntimeException(String.format("This %s(%s) method does not exist in the %s", enumMethod, valueClass, enumClass), e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
3.2.3 对应的枚举实现
注意:这里主要关注isValid方法,是枚举注解里面使用的方法名称
/**
* 送达状态枚举
*
* @author bamboo panda
* @version 1.0
* @date 2025/5/7 14:00
*/
public enum ServiceStatusEnum {
PEPPERY(0, "待送达"),
NOT_PEPPERY(1, "已送达"),
ALL_IS_OK(2, "未送达");
private Integer value;
private String name;
ServiceStatusEnum(Integer value, String name) {
this.value = value;
this.name = name;
}
public Integer getValue() {
return value;
}
public String getName() {
return name;
}
/**
* 判断参数合法性
*
* @param value 值
* @return true 合法 false 不合法
*/
public static boolean isValid(Integer value) {
ServiceStatusEnum[] values = ServiceStatusEnum.values();
for (ServiceStatusEnum tempEnum : values) {
if (tempEnum.getValue().compareTo(value) == 0) {
return true;
}
}
return false;
}
}
3.2.4 注解使用方法
/**
* 送达状态 0待送达 1已送达 2未送达
*/
@NotNull(message = "不能为空", groups = {Edit.class})
@EnumCheck(message = "送达状态不合法", enumClass = ServiceStatusEnum.class, enumMethod = "isValid", groups = {Edit.class})
private Integer serviceStatus;
注意:我们的注解校验器里面,为空默认是不行的,所以这里也可以不要@NotNull
四、总结
到这里已经介绍清楚了Hibernate Validator是干嘛的?它给我们提供了丰富的内置注解,方便了我们快速优雅的开发。
虽然内置注解丰富,但无法满足所有业务需求。例如,对于枚举值的校验、复杂逻辑的校验等,内置注解可能无能为力。
这个时候就需要我们自己去扩展,这个扩展也非常简单,俗话说得好,自己动手,丰衣足食。
最后:如果你从本篇文章中学到一点点东西,麻烦发财的小手点一下赞,如有疑问或者错误,欢迎提出和指正。