对原始集合的Hibernatevalidation
我希望能够做到这样的事情:
@Email public List<String> getEmailAddresses() { return this.emailAddresses; }
换句话说,我希望列表中的每个项目都被validation为一个电子邮件地址。 当然,注释这样的集合是不可接受的。
有没有办法做到这一点?
JSR-303和Hibernate Validator都没有任何现成的约束可以validationCollection的每个元素。
解决这个问题的一个可能的解决scheme是创build一个自定义的@ValidCollection
约束和相应的validation器实现ValidCollectionValidator
。
为了validation集合的每个元素,我们需要ValidCollectionValidator
一个Validator
实例; 为了得到这样的实例,我们需要自定义ConstraintValidatorFactory
实现。
看看你是否喜欢以下解决scheme…
只是,
- 复制粘贴所有这些Java类(和导入相关类);
- 在classpath中添加validation-api,hibenate-validator,slf4j-log4j12和testng jar;
- 运行testing用例。
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
public interface ValidatorContextAwareConstraintValidator { void setValidatorContext(ValidatorContext validatorContext); }
CollectionElementBean
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
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; } }
雇员
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; } }
球队
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; } }
购物车
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("me@gmail.com"); 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()); } } }
产量
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}
注意:当bean有约束时,不要指定@ValidCollection
约束的constraints
属性。 当bean没有约束时, constraints
属性是必要的。
由于Java注释本身的限制,不可能编写像@EachElement
这样的通用包装注释来包装任何约束注释。 但是,您可以编写一个通用的约束validation器类,将每个元素的实际validation委托给现有的约束validation器。 你必须为每个约束写一个包装注解,但只需要一个validation器。
我已经在jirutka / validator-collection (在Maven Central中可用)中实现了这个方法。 例如:
@EachSize(min = 5, max = 255) List<String> values;
这个库允许您轻松地为任何validation约束创build一个“伪约束”来注释一个简单types的集合,而无需为每个集合编写额外的validation器或不必要的包装器类。 EachX
约束支持所有标准的Beanvalidation约束和Hibernate特定的约束。
要为您自己的@Awesome
约束创build一个@EachAwesome
,只需复制和粘贴注释类,使用@Constraint(validatedBy = CommonEachValidator.class)
replace@Constraint
注释并添加注释@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(); }
编辑:更新库的当前版本。
我没有足够的知名度来评论这个原始答案,但也许值得注意的是, JSR-308正处于最终发布阶段,并将在发布时解决这个问题! 但是至less需要Java 8。
唯一的区别是validation注释会进入types声明中。
//@Email public List<@Email String> getEmailAddresses() { return this.emailAddresses; }
请让我知道你认为我最好把这个信息给其他正在寻找的人。 谢谢!
PS欲了解更多信息, 看看这个SOpost 。
感谢来自becomputer06的很好的回答。 但是我认为应该将下面的注释添加到ValidCollection定义中:
@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = ValidCollectionValidator.class)
我仍然不明白如何处理原始types包装的集合,并限制@Size,@Min,@Max等注释,因为值不能通过becomputer06的方式传递。
当然,我可以为我的应用程序中的所有情况创build自定义的禁忌注释,但无论如何,我必须将这些注释的属性添加到CollectionElementBean。 这似乎是一个足够糟糕的解决scheme。
JSR-303能够扩展内置约束的目标types:见7.1.2。 重写XML中的约束定义 。
您可以实现一个ConstraintValidator<Email, List<String>>
,它与给定的答案做同样的事情,委托给原始validation器。 然后你可以保留你的模型定义,并在List<String>
上应用@Email
。
一个非常简单的解决方法是可能的。 您可以改为validation包装简单值属性的类的集合。 为了这个工作,你需要在集合上使用@Valid
注解。
例:
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()); } }