我正在尝试实现一个自定义注释,该注释能够跨各种 DTO 类验证数据字段。具体来说,验证规则将基于正则表达式字符串,我打算在这些(很多很多)DTO 中重用这些字符串。
为了添加上下文,假设我有一组数据字段,如
phoneNumber
、emailAddress
、ipAddress
等,这些字段本质上将以不同的组合出现在许多 DTO 中。每个字段都有自己的正则表达式验证。
我研究了如何使用
ConstraintValidator<A extends Annotation, T>
接口创建这些注释和验证器。但是,我希望将所有验证规则捕获在单个 JSON 字符串中,该字符串可以写入我的配置服务器(从而使其动态且易于重新配置)并通过 @Value
注释读取。
关键问题在于我的 ConstraintValidator 类无法访问存储在
@Configuration class
中的 @Value 对象。让我们来看看下面的课程吧。
这是我的自定义注释类:
@Target({ ElementType.FIELD })
@Retention( RetentionPolicy.RUNTIME )
@Constraint(validatedBy = DynamicRegexValidator.class)
public @interface RegexValidatedField {
String fieldName();
String message() default "Invalid field";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
这是我的 ConstraintValidator 实现:
public class DynamicRegexValidator implements ConstraintValidator<RegexValidatedField, String> {
@Autowired
private ValidatorConfiguration validatorConfiguration;
private String fieldName;
@Override
public void initialize(RegexValidatedField constraintAnnotation) {
this.fieldName = constraintAnnotation.fieldName();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
ValidationRule rule = validatorConfiguration.getRuleForField(fieldName);
if (rule == null || value == null) {
return true;
}
if (!value.matches(rule.getRegex())) {
return false;
}
return true;
}
}
最后,注入 ConstraintValidator 的验证器配置类:
@Configuration
public class ValidatorConfiguration {
@Value("${validation.rules:}")
private String validationRulesJson;
private Map<String, ValidationRule> validationRules;
@PostConstruct
public void init() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
validationRules = objectMapper.readValue(validationRulesJson, new TypeReference<Map<String, ValidationRule>>() {});
}
public ValidationRule getRuleForField(String fieldName) {
return validationRules.get(fieldName);
}
}
您可以想象,定义验证规则的
@Value
字符串只是数据字段名称及其相应的正则表达式字符串的键值对。当我在调试模式下使用不同的断点运行应用程序时,我可以看到错误发生在 isValid()
方法中,因为 validatorConfiguration
始终为 null。
经过一些阅读,我对此的有限理解是 ConstraintValidator 类是由 Hibernate 而不是 Spring 管理的,因此它无法访问 Spring 管理的 bean(我的
ValidatorConfiguration
类)。
我已经尝试了关于另外两个问题的一些解决方案,但无法解决它:
ConstraintValidator中的@Autowired注入Null
也许我没有正确应用该解决方案,但我希望有人能指出我解决此问题的正确方向,或者是否有其他方法可以实现我在这里尝试做的事情。
P.S org.hibernate.validator 版本为 6.2.5,我正在运行 Spring 5.3.25。
您需要做的就是将 Spring 的依赖注入集成到 Hibernate Validator 中。您需要告诉 Spring 上下文来管理您的 ConstraintValidator 实例,从而允许您使用 @Autowired 来注入依赖项。
第 1 步:工厂将 ConstraintValidator 实例的创建委托给 Spring 上下文,确保正确注入依赖项
@Component
public class SpringConstraintValidatorFactory implements ConstraintValidatorFactory {
@Autowired
private ApplicationContext applicationContext;
@Override
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
// Retrieve the validator from the Spring context, allowing for dependency injection
return applicationContext.getAutowireCapableBeanFactory().createBean(key);
}
@Override
public void releaseInstance(ConstraintValidator<?, ?> instance) {
// No specific release action needed, just a no-op
}
}
第 2 步:告诉 Spring Boot 在管理 ConstraintValidator 实例时使用 SpringConstraintValidatorFactory。
@Configuration
public class ValidationConfig {
@Bean
public Validator validator(SpringConstraintValidatorFactory springConstraintValidatorFactory) {
LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
validatorFactoryBean.setConstraintValidatorFactory(springConstraintValidatorFactory);
return validatorFactoryBean;
}
}
最后你的
ValidatorConfiguration
应该能够正确注入和设置
@Configuration
public class ValidatorConfiguration {
@Value("${validation.rules:}")
private String validationRulesJson;
private Map<String, ValidationRule> validationRules = new HashMap<>();
@PostConstruct
public void init() throws JsonProcessingException {
if (!validationRulesJson.isEmpty()) {
ObjectMapper objectMapper = new ObjectMapper();
validationRules = objectMapper.readValue(validationRulesJson, new TypeReference<Map<String, ValidationRule>>() {});
}
}
public ValidationRule getRuleForField(String fieldName) {
return validationRules.get(fieldName);
}
}