ConstraintValidator @Autowired 注入 null

问题描述 投票:0回答:1

我正在尝试实现一个自定义注释,该注释能够跨各种 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
类)。

我已经尝试了关于另外两个问题的一些解决方案,但无法解决它:

Autowired 在自定义约束验证器中给出 Null 值

ConstraintValidator中的@Autowired注入Null

也许我没有正确应用该解决方案,但我希望有人能指出我解决此问题的正确方向,或者是否有其他方法可以实现我在这里尝试做的事情。

P.S org.hibernate.validator 版本为 6.2.5,我正在运行 Spring 5.3.25。

java spring validation hibernate-validator
1个回答
0
投票

您需要做的就是将 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);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.