如何为 validation-constraints.xml 中定义的验证注册自定义 ConstraintMapping

Posted

技术标签:

【中文标题】如何为 validation-constraints.xml 中定义的验证注册自定义 ConstraintMapping【英文标题】:How to register custom ConstraintMapping for validations defined in validation-constraints.xml 【发布时间】:2021-07-22 13:45:06 【问题描述】:

TLDR:我想要单独的自定义 bean 验证定义及其在单独模块中的 ConstraintValidator 实现。为此,我必须使用 ConstraintMapping 手动注册。它适用于带注释的类。但是定义的绑定对于通过validation-constraints.xml 定义的验证不共享/可用。怎么修?我尝试调试它,找出它在哪里初始化以及为什么会出现问题,但这些初始化远非易事。

动机

I) 分离模块:不强制 API 用户带上 ConstraintValidator,如果他们不想的话。

II)validation-constraint.xml的用法:如果你必须使用java类,你不能修改。用作 xml 定义的“mixin”。

编辑:更好的实施

正如@Guillaume Smet 指出的(谢谢!)我的所有实现都可以大大简化,因为 bean 验证支持使用服务加载器进行注册。所以你文学不需要我在下面描述的任何东西——自定义映射创建、注册,甚至 Validator/LocalValidatorFactoryBean 注册。自动配置的将同样工作。我验证了,该服务加载器配置已被拾取(删除我所有的东西,在没有配置的情况下尝试,然后添加并重试,它现在可以工作)。 情况还是一样 当我们在validation-constraints.xml 中提到使用服务加载器注册的注解时,我们得到了异常,即给出的ConstraintValidator 不存在。带注释的类有效。

编辑:验证类

@Target(METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER)
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = )
@SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
@ReportAsSingleViolation
public @interface Iso8601Timestamp 
    String message() default "must have format for example '1970-01-01T01:00:00.000+01:00[CET]', " +
            "but '$validatedValue' was provided. Timestamp is parsed using parsingStrategy strategy.";

    Class<?>[] groups() default ;

    Class<? extends Payload>[] payload() default ;

    ParsingStrategy parsingStrategy() default ParsingStrategy.ISO_OFFSET_DATE_TIME;

    enum ParsingStrategy 
        STRICT,
        ISO_OFFSET_DATE_TIME,

    

public class Iso8601TimestampValidator implements ConstraintValidator<Iso8601Timestamp, Object> 

    private Iso8601Timestamp.ParsingStrategy parsingStrategy;

    @Override
    public void initialize(Iso8601Timestamp constraint) 
        parsingStrategy = Objects.requireNonNull(constraint.parsingStrategy());
    

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) 
        //...
    

实施

由于我不得不使用@Constraint(validatedBy = ) 来避免编译时依赖,注释-ConstraintValidator 绑定被外部化到文件中,并读入:

@Data
public static class Mapping 
    Class annotation;
    Class validator;

Now 方法,它将创建 Validator 实例。但它与 LocalValidatorFactoryBean 的工作方式相同,无论我是否使用 MessageInterpolator 和 ConstraintValidatorFactory 进一步自定义它,或者我根本不碰它。

public static Validator getValidator(List<CustomValidator.Mapping> mappings) 
    return getValidationConfiguration(mappings).buildValidatorFactory().getValidator();


private static Configuration<?> getValidationConfiguration(List<CustomValidator.Mapping> mappings) 
    GenericBootstrap genericBootstrap = Validation.byDefaultProvider();
    Configuration<?> configuration = genericBootstrap.configure();
    if (!(configuration instanceof HibernateValidatorConfiguration)) 
        throw new CampException("HibernateValidatorConfiguration is required.");
    

    registerMappings((HibernateValidatorConfiguration) configuration, mappings);
    return configuration;


private static void registerMappings(HibernateValidatorConfiguration hibernateValidatorConfiguration,
                                     List<CustomValidator.Mapping> mappings) 
    mappings.stream()
            .map(pair -> creteConstraintMapping(hibernateValidatorConfiguration, pair))
            .forEach(hibernateValidatorConfiguration::addMapping);


@SuppressWarnings("unchecked")
private static ConstraintMapping creteConstraintMapping(HibernateValidatorConfiguration hibernateValidatorConfiguration,
                                                        CustomValidator.Mapping pair) 
    ConstraintMapping constraintMapping = hibernateValidatorConfiguration.createConstraintMapping();
    constraintMapping
            .constraintDefinition(pair.getAnnotation())
            .includeExistingValidators(false)
            .validatedBy(pair.getValidator());

    return constraintMapping;

这是我们的测试实例:

@Data
@AllArgsConstructor
public class TestInstance 
    @Iso8601Timestamp
    public CharSequence timestamp;

    public String text;

一切都会正常工作,但是如果我们创建validation-constraints.xml 文件并将以下验证定义放入其中(删除包名),一切都会发生。 &lt;class ignore-annotations="false"/&gt; 也被忽略。此元素的值被忽略,TestInstance 上的注释被忽略。

<constraint-mappings
    xmlns="http://jboss.org/xml/ns/javax/validation/mapping"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/mapping
                    validation-mapping-1.1.xsd"
    version="1.1">

  <bean class="TestInstance">
    <class ignore-annotations="false"/>
    <getter name="timestamp" ignore-annotations="true">
      <constraint annotation="Iso8601Timestamp"/>
    </getter>
    <getter name="text" ignore-annotations="true">
      <constraint annotation="javax.validation.constraints.NotBlank"/>
    </getter>
  </bean>
</constraint-mappings>

这样,当我们验证 TestInstance 的实例(错误命名)时,我们将得到以下异常:

javax.validation.UnexpectedTypeException: HV000030: 找不到约束 '....Iso8601Timestamp' 验证类型 'java.lang.CharSequence' 的验证器。检查“时间戳”的配置

但是validation-constraints.xml 本身不是问题。没关系。提供NotBlank 就像魅力一样。只有需要手动绑定的自定义验证(此处以 Iso8601Timestamp 表示)不起作用。

validation-constraints.xml 的解决方法——不要使用它。将数据从不可修改的文件重新映射到您拥有的文件,或者只是不验证并让它失败,或者……取决于您的选择。

但我真的很想知道,为什么这是一个问题,因为提供了映射。如果(也)使用了validation-constraints.xml,是否需要进行一些不同的初始化?

【问题讨论】:

【参考方案1】:

您可以使用服务加载器声明位于外部库中的约束验证器。这绝对是要走的路。

通常,您会将约束验证器的名称添加到位于库中的 META-INF/services/javax.validation.ConstraintValidator 文件中。

这样,只要您的库位于类路径中,其中的约束验证器就会自动声明为 Hibernate Validator。

您在我们的 Hibernate 博客上有一篇完整的文章来解释这一切:https://in.relation.to/2017/03/02/adding-custom-constraint-definitions-via-the-java-service-loader/。

【讨论】:

ach。这么多小时的研究如何做到这一点,我确实在任何地方看到了这份备忘录。但是太棒了!谢谢!我将验证这是否有效,因为这将大大简化我的用例。我想自己使用服务加载器进行配置,但我有点惊讶,服务加载器与注释一起工作。无论如何,谢谢你的提示和好文章。将检查并保持联系。再次感谢! 这不是您声明的注释,而是 ConstraintValidator(然后注册所需的一切)。 虽然我们将使用服务加载器维护注解-验证器对,但它用于使用它的接口 ConstraintValidator 发现所有验证器。起初我看不到他们如何创建绑定,但似乎他们可以使用方法initialize 的参数类。现在明白了。谢谢。我需要在晚上慢慢仔细地阅读你的全文。会尝试更新。从那以后不再有 cmets(来自我)! 好的,我尝试并更新了消息。你的帖子不错,我学到了一些我不知道的新东西,但遗憾的是,服务加载器方法对我来说同样失败。我将添加 bean 注释和 ConstraintViolaiton 来提问。但这是唯一剩下的东西。这两个类+服务加载器文件,没错。

以上是关于如何为 validation-constraints.xml 中定义的验证注册自定义 ConstraintMapping的主要内容,如果未能解决你的问题,请参考以下文章

如何为@Valid 指定验证组?

如何为下拉菜单制作 CSS 边框?

如何为 CAShapeLayer 路径和填充颜色设置动画

iPhone - 如何为图像着色?

如何为 RecyclerView 设置 onItemClickListener? [复制]

WCF - 如何为 NTLM 身份验证配置 netTcpBinding?