Java 原语集合的休眠验证
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4308224/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Hibernate Validation of Collections of Primitives
提问by scrotty
I want to be able to do something like:
我希望能够执行以下操作:
@Email
public List<String> getEmailAddresses()
{
return this.emailAddresses;
}
In other words, I want each item in the list to be validated as an email address. Of course, it is not acceptable to annotate a collection like this.
换句话说,我希望列表中的每个项目都作为电子邮件地址进行验证。当然,像这样注释一个集合是不可接受的。
Is there a way to do this?
有没有办法做到这一点?
采纳答案by dira
Neither JSR-303 nor Hibernate Validator has any ready-made constraint that can validate each elements of Collection.
JSR-303 和 Hibernate Validator 都没有任何现成的约束可以验证 Collection 的每个元素。
One possible solution to address this issue is to create a custom @ValidCollection
constraint and corresponding validator implementation ValidCollectionValidator
.
解决此问题的一种可能解决方案是创建自定义@ValidCollection
约束和相应的验证器实现ValidCollectionValidator
。
To validate each element of collection we need an instance of Validator
inside ValidCollectionValidator
; and to get such instance we need custom implementation of ConstraintValidatorFactory
.
为了验证集合的每个元素,我们需要一个Validator
inside实例ValidCollectionValidator
;为了获得这样的实例,我们需要自定义实现ConstraintValidatorFactory
.
See if you like following solution...
看看您是否喜欢以下解决方案...
Simply,
简单地,
- copy-paste all these java classes (and import relavent classes);
- add validation-api, hibenate-validator, slf4j-log4j12, and testng jars on classpath;
- run the test-case.
- 复制粘贴所有这些 java 类(并导入相关类);
- 在类路径上添加validation-api、hibenate-validator、slf4j-log4j12和testng jar;
- 运行测试用例。
ValidCollection
有效集合
public @interface ValidCollection {
Class<?> elementType();
/* Specify constraints when collection element type is NOT constrained
* validator.getConstraintsForClass(elementType).isBeanConstrained(); */
Class<?>[] constraints() default {};
boolean allViolationMessages() default true;
String message() default "{ValidCollection.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
ValidCollectionValidator
有效集合验证器
public class ValidCollectionValidator implements ConstraintValidator<ValidCollection, Collection>, ValidatorContextAwareConstraintValidator {
private static final Logger logger = LoggerFactory.getLogger(ValidCollectionValidator.class);
private ValidatorContext validatorContext;
private Class<?> elementType;
private Class<?>[] constraints;
private boolean allViolationMessages;
@Override
public void setValidatorContext(ValidatorContext validatorContext) {
this.validatorContext = validatorContext;
}
@Override
public void initialize(ValidCollection constraintAnnotation) {
elementType = constraintAnnotation.elementType();
constraints = constraintAnnotation.constraints();
allViolationMessages = constraintAnnotation.allViolationMessages();
}
@Override
public boolean isValid(Collection collection, ConstraintValidatorContext context) {
boolean valid = true;
if(collection == null) {
//null collection cannot be validated
return false;
}
Validator validator = validatorContext.getValidator();
boolean beanConstrained = validator.getConstraintsForClass(elementType).isBeanConstrained();
for(Object element : collection) {
Set<ConstraintViolation<?>> violations = new HashSet<ConstraintViolation<?>> ();
if(beanConstrained) {
boolean hasValidCollectionConstraint = hasValidCollectionConstraint(elementType);
if(hasValidCollectionConstraint) {
// elementType has @ValidCollection constraint
violations.addAll(validator.validate(element));
} else {
violations.addAll(validator.validate(element));
}
} else {
for(Class<?> constraint : constraints) {
String propertyName = constraint.getSimpleName();
propertyName = Introspector.decapitalize(propertyName);
violations.addAll(validator.validateValue(CollectionElementBean.class, propertyName, element));
}
}
if(!violations.isEmpty()) {
valid = false;
}
if(allViolationMessages) { //TODO improve
for(ConstraintViolation<?> violation : violations) {
logger.debug(violation.getMessage());
ConstraintViolationBuilder violationBuilder = context.buildConstraintViolationWithTemplate(violation.getMessage());
violationBuilder.addConstraintViolation();
}
}
}
return valid;
}
private boolean hasValidCollectionConstraint(Class<?> beanType) {
BeanDescriptor beanDescriptor = validatorContext.getValidator().getConstraintsForClass(beanType);
boolean isBeanConstrained = beanDescriptor.isBeanConstrained();
if(!isBeanConstrained) {
return false;
}
Set<ConstraintDescriptor<?>> constraintDescriptors = beanDescriptor.getConstraintDescriptors();
for(ConstraintDescriptor<?> constraintDescriptor : constraintDescriptors) {
if(constraintDescriptor.getAnnotation().annotationType().getName().equals(ValidCollection.class.getName())) {
return true;
}
}
Set<PropertyDescriptor> propertyDescriptors = beanDescriptor.getConstrainedProperties();
for(PropertyDescriptor propertyDescriptor : propertyDescriptors) {
constraintDescriptors = propertyDescriptor.getConstraintDescriptors();
for(ConstraintDescriptor<?> constraintDescriptor : constraintDescriptors) {
if(constraintDescriptor.getAnnotation().annotationType().getName().equals(ValidCollection.class.getName())) {
return true;
}
}
}
return false;
}
}
ValidatorContextAwareConstraintValidator
ValidatorContextAwareConstraintValidator
public interface ValidatorContextAwareConstraintValidator {
void setValidatorContext(ValidatorContext validatorContext);
}
CollectionElementBean
集合元素Bean
public class CollectionElementBean {
/* add more properties on-demand */
private Object notNull;
private String notBlank;
private String email;
protected CollectionElementBean() {
}
@NotNull
public Object getNotNull() { return notNull; }
public void setNotNull(Object notNull) { this.notNull = notNull; }
@NotBlank
public String getNotBlank() { return notBlank; }
public void setNotBlank(String notBlank) { this.notBlank = notBlank; }
@Email
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
ConstraintValidatorFactoryImpl
ConstraintValidatorFactoryImpl
public class ConstraintValidatorFactoryImpl implements ConstraintValidatorFactory {
private ValidatorContext validatorContext;
public ConstraintValidatorFactoryImpl(ValidatorContext nativeValidator) {
this.validatorContext = nativeValidator;
}
@Override
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
T instance = null;
try {
instance = key.newInstance();
} catch (Exception e) {
// could not instantiate class
e.printStackTrace();
}
if(ValidatorContextAwareConstraintValidator.class.isAssignableFrom(key)) {
ValidatorContextAwareConstraintValidator validator = (ValidatorContextAwareConstraintValidator) instance;
validator.setValidatorContext(validatorContext);
}
return instance;
}
}
Employee
员工
public class Employee {
private String firstName;
private String lastName;
private List<String> emailAddresses;
@NotNull
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
@ValidCollection(elementType=String.class, constraints={Email.class})
public List<String> getEmailAddresses() { return emailAddresses; }
public void setEmailAddresses(List<String> emailAddresses) { this.emailAddresses = emailAddresses; }
}
Team
团队
public class Team {
private String name;
private Set<Employee> members;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@ValidCollection(elementType=Employee.class)
public Set<Employee> getMembers() { return members; }
public void setMembers(Set<Employee> members) { this.members = members; }
}
ShoppingCart
购物车
public class ShoppingCart {
private List<String> items;
@ValidCollection(elementType=String.class, constraints={NotBlank.class})
public List<String> getItems() { return items; }
public void setItems(List<String> items) { this.items = items; }
}
ValidCollectionTest
有效集合测试
public class ValidCollectionTest {
private static final Logger logger = LoggerFactory.getLogger(ValidCollectionTest.class);
private ValidatorFactory validatorFactory;
@BeforeClass
public void createValidatorFactory() {
validatorFactory = Validation.buildDefaultValidatorFactory();
}
private Validator getValidator() {
ValidatorContext validatorContext = validatorFactory.usingContext();
validatorContext.constraintValidatorFactory(new ConstraintValidatorFactoryImpl(validatorContext));
Validator validator = validatorContext.getValidator();
return validator;
}
@Test
public void beanConstrained() {
Employee se = new Employee();
se.setFirstName("Santiago");
se.setLastName("Ennis");
se.setEmailAddresses(new ArrayList<String> ());
se.getEmailAddresses().add("segmail.com");
Employee me = new Employee();
me.setEmailAddresses(new ArrayList<String> ());
me.getEmailAddresses().add("[email protected]");
Team team = new Team();
team.setMembers(new HashSet<Employee>());
team.getMembers().add(se);
team.getMembers().add(me);
Validator validator = getValidator();
Set<ConstraintViolation<Team>> violations = validator.validate(team);
for(ConstraintViolation<Team> violation : violations) {
logger.info(violation.getMessage());
}
}
@Test
public void beanNotConstrained() {
ShoppingCart cart = new ShoppingCart();
cart.setItems(new ArrayList<String> ());
cart.getItems().add("JSR-303 Book");
cart.getItems().add("");
Validator validator = getValidator();
Set<ConstraintViolation<ShoppingCart>> violations = validator.validate(cart, Default.class);
for(ConstraintViolation<ShoppingCart> violation : violations) {
logger.info(violation.getMessage());
}
}
}
Output
输出
02:16:37,581 INFO main validation.ValidCollectionTest:66 - {ValidCollection.message}
02:16:38,303 INFO main validation.ValidCollectionTest:66 - may not be null
02:16:39,092 INFO main validation.ValidCollectionTest:66 - not a well-formed email address
02:17:46,460 INFO main validation.ValidCollectionTest:81 - may not be empty
02:17:47,064 INFO main validation.ValidCollectionTest:81 - {ValidCollection.message}
Note:-When bean has constraints do NOT specify the constraints
attribute of @ValidCollection
constraint. The constraints
attribute is necessary when bean has no constraint.
注意:-当 bean 有约束时,不要指定约束的constraints
属性@ValidCollection
。constraints
当 bean 没有约束时,该属性是必需的。
回答by Sergey Morozov
Thanks for great answer from becomputer06. But I think the following annotations should be added to ValidCollection definition:
感谢 becomputer06 的精彩回答。但我认为应该在 ValidCollection 定义中添加以下注释:
@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidCollectionValidator.class)
And I still don't understant what to do with collections of primitive type wrappers and constrains annotations like @Size, @Min, @Max etc., because value can't be passed through becomputer06's way.
而且我仍然不明白如何处理原始类型包装器和约束注释(如@Size、@Min、@Max 等)的集合,因为值不能通过 becomputer06 的方式传递。
Of course, I can create custom contraint annotations for all cases in my application, but anyway I have to add properties for these annotations to CollectionElementBean. And it seems to be a bad enough solution.
当然,我可以为应用程序中的所有情况创建自定义约束注释,但无论如何我必须将这些注释的属性添加到 CollectionElementBean。这似乎是一个足够糟糕的解决方案。
回答by Jakub Jirutka
It's not possible to write a generic wrapper annotation like @EachElement
to wrap any constraint annotation — due to limitations of Java Annotations itself. However, you can write a generic constraint validator class which delegates actual validation of every element to an existing constraint validator. You have to write a wrapper annotation for every constraint, but just one validator.
@EachElement
由于 Java 注释本身的限制,不可能编写一个通用的包装注释来像包装任何约束注释一样。但是,您可以编写一个通用约束验证器类,它将每个元素的实际验证委托给现有的约束验证器。您必须为每个约束编写一个包装器注释,但只需一个验证器。
I've implemented this approach in jirutka/validator-collection(available in Maven Central). For example:
我已经在jirutka/validator-collection(在 Maven Central 中可用)中实现了这种方法。例如:
@EachSize(min = 5, max = 255)
List<String> values;
This library allows you to easily create a “pseudo constraint” for anyvalidation constraint to annotate a collection of simple types, without writing an extra validator or unnecessary wrapper classes for every collection. EachX
constraint is supported for all standard Bean Validation constraints and Hibernate specific constraints.
该库允许您轻松地为任何验证约束创建“伪约束”以注释简单类型的集合,而无需为每个集合编写额外的验证器或不必要的包装类。EachX
所有标准 Bean 验证约束和 Hibernate 特定约束都支持约束。
To create an @EachAwesome
for your own @Awesome
constraint, just copy&paste the annotation class, replace @Constraint
annotation with @Constraint(validatedBy = CommonEachValidator.class)
and add the annotation @EachConstraint(validateAs = Awesome.class)
. That's all!
要@EachAwesome
为您自己的@Awesome
约束创建一个,只需复制并粘贴注释类,用 替换@Constraint
注释@Constraint(validatedBy = CommonEachValidator.class)
并添加注释@EachConstraint(validateAs = Awesome.class)
。就这样!
// common boilerplate
@Documented
@Retention(RUNTIME)
@Target({METHOD, FIELD, ANNOTATION_TYPE})
// this is important!
@EachConstraint(validateAs = Awesome.class)
@Constraint(validatedBy = CommonEachValidator.class)
public @interface EachAwesome {
// copy&paste all attributes from Awesome annotation here
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String someAttribute();
}
EDIT: Updated for the current version of library.
编辑:针对当前版本的库进行了更新。
回答by Markus Malkusch
JSR-303 has the ability to extend the target types of built in constraints: See 7.1.2. Overriding constraint definitions in XML.
JSR-303 能够扩展内置约束的目标类型:见7.1.2。覆盖 XML 中的约束定义。
You can implement a ConstraintValidator<Email, List<String>>
which does the same thing as the given answers, delegating to the primitive validator. Then you can keep your model definition and apply @Email
on List<String>
.
您可以实现ConstraintValidator<Email, List<String>>
与给定答案执行相同操作的a ,委托给原始验证器。然后您可以保留您的模型定义并@Email
在List<String>
.
回答by daniel.caspers
I don't have a high enough reputation to comment this on the original answer, but perhaps it is worth noting on this question that JSR-308is in its final release stage and will address this problem when it is released! It will at least require Java 8, however.
我没有足够高的声誉来对原始答案发表评论,但也许在这个问题上值得注意的是,JSR-308正处于最终发布阶段,并将在发布时解决这个问题!但是,它至少需要 Java 8。
The only difference would be that the validation annotation would go inside the type declaration.
唯一的区别是验证注释将放在类型声明中。
//@Email
public List<@Email String> getEmailAddresses()
{
return this.emailAddresses;
}
Please let me know where you think I could best put this information for others who are looking. Thanks!
请让我知道您认为我可以将这些信息放在何处最适合正在寻找的其他人。谢谢!
P.S. For more info, check out this SO post.
PS 有关更多信息,请查看此 SO 帖子。
回答by aux
A very simple workaround is possible. You can instead validate a collection of your classes that wrap the simple value property. For this to work you need to use @Valid
annotation on the collection.
一个非常简单的解决方法是可能的。您可以改为验证包装简单值属性的类的集合。为此,您需要@Valid
在集合上使用注释。
Example:
例子:
public class EmailAddress {
@Email
String email;
public EmailAddress(String email){
this.email = email;
}
}
public class Foo {
/* Validation that works */
@Valid
List<EmailAddress> getEmailAddresses(){
return this.emails.stream().map(EmailAddress::new).collect(toList());
}
}