Hibernate Validator使用

6 阅读4分钟

简介

Hibernate Validator 是 Java Bean Validation 规范的一个实现,提供了对 Java 类字段的全面验证功能。通过注解,开发者可以灵活地对对象属性进行校验,确保数据的有效性和一致性。它与 Spring 框架紧密集成,支持使用 @Valid 注解进行自动校验。采用 Hibernate Validator 可以使参数校验更加简洁优雅,提升代码的可维护性、健壮性和可读性。 官网

引入依赖

<dependencies>
    <!-- Hibernate Validator 依赖 -->
    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>7.0.4.Final</version>
    </dependency>

    <!-- Bean Validation API 依赖 -->
    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>javax.validation-api</artifactId>
        <version>2.0.1.Final</version>
    </dependency>

    <!-- 为了支持 Spring 验证,通常需要添加以下依赖 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
        <version>2.5.0</version>
    </dependency>
</dependencies>

配置验证

一旦依赖添加完成,就可以开始使用Hibernate Validator来验证你的Java Bean中的数据。Hibernate Validator使用注解来声明验证规则,常用的注解有:@NotNull、@Size、@Length、@Max、@Pattern等。

定义验证规则

public class User {
    @NotNull(message = "Username cannot be null")
    @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
    private String username;

    @Email(message = "Invalid email address")
    private String email;

    @Min(value = 18, message = "Age must be at least 18")
    private int age;
}

校验

@RestController
@RequestMapping("/users")
public class UserController {
    @PostMapping
    public ResponseEntity<?> createUser(@RequestBody @Valid User user, BindingResult result) {
        if (result.hasErrors()) {
            return ResponseEntity.badRequest().body(result.getAllErrors());
        }
        return ResponseEntity.ok("User created successfully!");
    }
}

如果非spring环境,就是普通Java应用程序中使用Hibernate Validator进行验证,那么需要手动构建验证器,并手动执行验证。

public class ValidatorUtil {
	private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
	
	public static void validate(Object obj, Class<?> groups) {
		// 执行验证
		Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
        if (!constraintViolations.isEmpty()) {
            ArrayList<String> list = new ArrayList<>();
            for (ConstraintViolation<Object> constraintViolation : constraintViolations) {
                list.add(constraintViolation.getPropertyPath().toString() + "->" + constraintViolation.getMessage());
            }
            throw new BizException(ResponseEnum.PARAM_CHECK_EXCEPTION, JSON.toJSONString(list));
        }
	}
}

自定义验证器

除了内置的注解,Hibernate Validator还支持自定义验证,可以自定义验证逻辑并通过注解使用它。

定义验证注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER })
@Constraint(validatedBy = EnumValidator.class)
public @interface EnumValidation {
    String message() default "Invalid error";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    Class<?> clazz();
    String method();
}

定义验证逻辑

public class EnumValidator implements ConstraintValidator<EnumValidation, Object> {
	private EnumValidation annotation;
	
	// 初始化(如果需要)
	@Override
	public void initialize(EnumValidation constraintAnnotation) {
		this.annotation = constraintAnnotation;
	}
	
	// 自定义验证逻辑
	@Override
	public boolean isValid(Integer value, ConstraintValidatorContext context) {
		if (value == null) return true;
        if (value instanceof String) {
            if (StringUtils.isBlank(String.valueOf(value))) {
                return true;
            }
        }
        Object[] objects = annotation.clazz().getEnumConstants();
        try {
            Method method = annotation.clazz().getMethod(annotation.method());
            for (Object o : objects) {
                if (method.invoke(o).equals(value)) {
                    return true;
                }
            }
        } catch (Exception e) {
            throw new BizException(e);
        }
        return false;
	}
}

国际化

Hibernate Validator 支持国际化 (i18n),可以根据不同的语言环境提供不同的错误消息。通过使用资源文件(如 .properties 文件)来管理不同语言的错误信息,开发者可以让验证错误提示信息根据用户的语言选择来变化。

配置国际化

1.定义验证规则

public class User {
    @NotNull(message = "{user.name.not.null}")
    @Size(min = 3, max = 50, message = "{user.name.length.limit}")
    private String username;

    @Email(message = "{email.format.limt}")
    private String email;

    @Min(value = 18, message = "{age.min.limit}")
    private int age;
}

2.多语言资源文件定义

validation_zh.properties
user.name.not.null=Username cannot be null
user.name.length.limit=Username must be between {min} and {max} characters
email.format.limt=Invalid email address
age.min.limit=Age must be at least {value}

validation_en.properties
user.name.not.null=用户名不能为空
user.name.length.limit=用户名长度必须在{min}-{max}个字符内
email.format.limt=邮箱格式无效
age.min.limit=年龄必须大于18

3.多语言环境配置

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
	@Bean
    public FilterRegistrationBean requestContextFilter(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
		// RequestContextFilter doFilterInternal的initContextHolders方法将请求头的Accept-Language定义的语言设置到LocaleContextHolder
		// 这样LocaleContextMessageInterpolator#interpolate就可以获取到当前语言
        RequestContextFilter requestContextFilter = new RequestContextFilter();
        registrationBean.setFilter(requestContextFilter);
        registrationBean.addUrlPatterns("/*");
        registrationBean.setName("requestContextFilter");
        registrationBean.setOrder(1);
        return registrationBean;
    }
}

Valid注解方式验证

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
	
	@Override
	public Validator getValidator() {
		ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
		// 设置文件字符编码
		messageSource.setDefaultEncoding("UTF-8");
		// 设置多语言文件路径,validation_zh.properties、validation_en.properties、validation.properties(未匹配时默认使用)
		messageSource.setBasenames("i18n/validation");
        LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
        localValidatorFactoryBean.setMessageInterpolator(new LocaleContextMessageInterpolator(new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(messageSource))));
        return localValidatorFactoryBean;
    }
}

编程式验证

public class ValidatorUtil {
	private static Validator validator;
	static {
		ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
		// 设置文件字符编码
		messageSource.setDefaultEncoding("UTF-8");
		// 设置多语言文件路径
		messageSource.setBasenames("i18n/validation");
		MessageInterpolator messageInterpolator = new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(messageSource));
		validator = Validation.byDefaultProvider()
                            .configure()
                            // 多语言拦截处理
                            .messageInterpolator(messageInterpolator)
                            .buildValidatorFactory().getValidator();
	}
	
	public static void validate(Object obj, Class<?> groups) {
            // 执行验证
            Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
            if (!constraintViolations.isEmpty()) {
                ArrayList<String> list = new ArrayList<>();
                for (ConstraintViolation<Object> constraintViolation : constraintViolations) {
                    list.add(constraintViolation.getPropertyPath().toString() + "->" + constraintViolation.getMessage());
                }
                throw new BizException(ResponseEnum.PARAM_CHECK_EXCEPTION, JSON.toJSONString(list));
        }
    }
}

自定义资源

如果资源文件需要通过其他方式获取,比如数据库,那么通过ResourceBundleLocator接口来实现,替换MessageSourceResourceBundleLocator。

public class DatabaseResourceBundleLocator implements ResourceBundleLocator {

    @Override
    public ResourceBundle getResourceBundle(Locale locale) {
        // 从数据库中加载资源
        Properties properties = loadResourceBundleFromDatabase(locale);
        return new DatabaseResourceBundle(properties);
    }

    private Properties loadResourceBundleFromDatabase(Locale locale) {
        // 编写从数据库加载资源的逻辑
        return null;
    }

    private static class DatabaseResourceBundle extends ResourceBundle {
        private final Properties properties;

        public DatabaseResourceBundle(Properties properties) {
            this.properties = properties;
        }

        @Override
        protected Object handleGetObject(String key) {
            return properties.get(key);
        }

        @Override
        public Enumeration<String> getKeys() {
            return properties.stringPropertyNames().asIterator();
        }
    }
}