如何为 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
文件并将以下验证定义放入其中(删除包名),一切都会发生。 <class ignore-annotations="false"/>
也被忽略。此元素的值被忽略,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的主要内容,如果未能解决你的问题,请参考以下文章