如何解决创建运行时约束并验证字段图
我有一个输入字段图,我想通过在运行时创建动态约束来使用JSR-303进行验证。
比方说,我的地图结构如下:
name: Foo
description: Foo description
现在,如果我在一组配置中存储了一组规则,则说:
name:
required: true
minLength: 3
maxLength: 30
description:
required: false
maxLength: 200
我想使用在运行时根据上述配置创建的约束来验证输入映射。我知道我们可以create custom ConstraintMapping
s using Hibernate Validator进行以下操作(示例取自Hibernate Validator文档):
HibernateValidatorConfiguration configuration = Validation
.byProvider(HibernateValidator.class)
.configure();
ConstraintMapping constraintMapping = configuration.createConstraintMapping();
constraintMapping
.type(Car.class)
.property("manufacturer",FIELD)
.constraint( new NotNullDef())
.property("licensePlate",FIELD)
.ignoreAnnotations(true)
.constraint(new NotNullDef())
.constraint(new SizeDef().min(2).max(14))
Validator validator = configuration.addMapping(constraintMapping)
.buildValidatorFactory()
但是显然,这不适用于java.util.Map
,因为地图不是JavaBean。那么我该怎么做呢?我考虑过的几件事(与每种方法有关):
- 要在运行时使用ByteBuddy或类似的东西创建类,并使用它来使用Hibernate Validator进行验证(听起来像是过分杀伤)
- 要使用一系列
if
条件进行手动验证并抛出ConstraintViolationException
(我将在这里重新发明很多轮子)
我想了解在这种情况下您会怎么做。
解决方法
要回答的问题太多了,但我会努力的。
动态创建的约束映射
正如示例所描述的那样,它是可用的。有两种方法可以创建自定义映射。
以编程方式创建自定义约束映射
假设有一个 POJO
import java.util.Map;
public class Foo {
private Map<String,String> rules;
public Map<String,String> getRules() {
return rules;
}
public void setRules(Map<String,String> rules) {
this.rules = rules;
}
}
描述的约束是:
- 地图不能为空
- 最大。地图大小为2
- 地图的关键字至少应为 3 个字符
- 值不能为空
- 值必须为最大值。 5 个字符
像这样:
@NotNull
@Size(max=3)
private Map<@Size(min=3)String,@NotBlank @Size(max=5)String> rules;
约束映射是:
var configuration = Validation
.byProvider(HibernateValidator.class)
.configure();
ConstraintMapping constraintMapping = configuration.createConstraintMapping();
constraintMapping
.type(Foo.class)
.field("rules")
.constraint(new NotNullDef())
.constraint(new SizeDef().max(2))
.containerElementType(0)
.constraint(new SizeDef().min(3))
.containerElementType(1)
.constraint(new NotBlankDef())
.constraint(new SizeDef().max(5))
;
var validator = configuration.addMapping(constraintMapping)
.buildValidatorFactory()
.getValidator();
工作示例是:
public class FooValidatorTest {
@Test
void name() {
var configuration = Validation
.byProvider(HibernateValidator.class)
.configure();
ConstraintMapping constraintMapping = configuration.createConstraintMapping();
constraintMapping
.type(Foo.class)
.field("rules")
.constraint(new NotNullDef())
.constraint(new SizeDef().max(2))
.containerElementType(0)
.constraint(new SizeDef().min(3))
.containerElementType(1)
.constraint(new NotBlankDef())
.constraint(new SizeDef().max(5))
;
var validator = configuration.addMapping(constraintMapping)
.buildValidatorFactory()
.getValidator();
assertAll(
() -> {
var obj = new Foo();
/*
expected violation(s)
map is null
*/
var result = validator.validate(obj).stream().toList();
assertAll("Only @NotNull should violated",() -> assertEquals(1,result.size()),() -> assertEquals("{jakarta.validation.constraints.NotNull.message}",result.get(0).getMessageTemplate()
)
);
},() -> {
var obj = new Foo();
/*
expected violation(s)
map contains too much elements (max: 2)
note: keys and values are not validated
*/
obj.setRules(Map.of("name1","desc1","name2","desc2","name3","desc3"));
var result = validator.validate(obj).stream().toList();
assertAll("Only @Size should violated",() -> assertEquals("{jakarta.validation.constraints.Size.message}",() -> {
var obj = new Foo();
/*
expected violation(s)
first key is too short (min: 3)
first value is blank
second value is too long (max:5)
*/
obj.setRules(Map.of("aa","","bbb","longValue"));
var result = validator.validate(obj).stream().toList();
var templates = result.stream().map(ConstraintViolation::getMessageTemplate).toList();
var expectedTemplates = new String[]{
"{jakarta.validation.constraints.NotBlank.message}","{jakarta.validation.constraints.Size.message}","{jakarta.validation.constraints.Size.message}"
};
assertAll(
() -> assertEquals(3,() -> assertThat(templates,containsInAnyOrder(expectedTemplates))
);
}
);
}
}
使用 xml 创建自定义约束映射
.addMapping
方法有另一个接受输入流的签名。
xml 定义为:
<?xml version="1.0" encoding="UTF-8"?>
<constraint-mappings
xmlns="https://jakarta.ee/xml/ns/validation/mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"https://jakarta.ee/xml/ns/validation/mapping https://jakarta.ee/xml/ns/validation/validation-mapping-3.0.xsd"
version="3.0">
<bean class="Foo">
<field name="rules">
<container-element-type type-argument-index="0">
<container-element-type>
<constraint annotation="jakarta.validation.constraints.Size">
<element name="min">3</element>
</constraint>
</container-element-type>
</container-element-type>
<container-element-type type-argument-index="1">
<container-element-type>
<constraint annotation="jakarta.validation.constraints.NotBlank"/>
<constraint annotation="jakarta.validation.constraints.Size">
<element name="max">5</element>
</constraint>
</container-element-type>
</container-element-type>
<constraint annotation="jakarta.validation.constraints.NotNull"/>
<constraint annotation="jakarta.validation.constraints.Size">
<element name="max">2</element>
</constraint>
</field>
</bean>
</constraint-mappings>
而且这个修改是必要的:
var validator = configuration
.addMapping(
getClass().getClassLoader()
.getResourceAsStream("foo_mapping.xml")
)
.buildValidatorFactory()
.getValidator();
加载外部类
运行时字节码生成完成后,这些类应该加载,加载的类应该传递到 validator
。
所以首先要做的是创建一个类加载器(ucl
)。之后它可以传递给HibernateValidatorConfiguration
var configuration = Validation
.byProvider(HibernateValidator.class)
.configure()
.externalClassLoader(ucl);
从技术上讲,它可以动态创建和加载 POJO 并运行自定义验证映射,但我认为这不是一个好方法。
每次创建类或创建自定义映射时,都应创建一个新的 Validator
实例。虽然实例化一个新的 Validator 很便宜,但它需要一个新的 ValidatorFactory
,这非常昂贵(约 400 毫秒)。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。