我希望能够做到这样的事情:
@Email
public List<String> getEmailAddresses()
{
return this.emailAddresses;
}
换句话说,我希望列表中的每个项目都被验证为电子邮件地址。当然,注释这样的集合是不可接受的。
有办法做到这一点吗?
答案 0 :(得分:53)
JSR-303和Hibernate Validator都没有任何现成的约束可以验证Collection的每个元素。
解决此问题的一种可能解决方案是创建自定义@ValidCollection
约束和相应的验证程序实现ValidCollectionValidator
。
要验证集合的每个元素,我们需要在Validator
内部ValidCollectionValidator
的实例;为了获得这样的实例,我们需要ConstraintValidatorFactory
的自定义实现。
看看你是否喜欢以下解决方案...
简单地说,
<强> 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有约束时,请勿指定constraints
约束的@ValidCollection
属性。当bean没有约束时,constraints
属性是必需的。
答案 1 :(得分:18)
我没有足够的声誉在最初的答案上对此发表评论,但也许值得注意的是JSR-308处于最终发布阶段的问题并将在发布时解决此问题!但是,它至少需要Java 8。
唯一的区别是验证注释将进入类型声明。
//@Email
public List<@Email String> getEmailAddresses()
{
return this.emailAddresses;
}
请告诉我您认为我最好将此信息提供给正在寻找的其他人。谢谢!
P.S。有关详细信息,请check out this SO post。
答案 2 :(得分:15)
由于Java Annotations本身的限制,不可能编写像@EachElement
这样的通用包装器注释来包装任何约束注释。但是,您可以编写一个通用约束验证器类,它将每个元素的实际验证委托给现有的约束验证器。您必须为每个约束编写一个包装器注释,但只需要一个验证器。
我在jirutka/validator-collection(Maven Central中提供)中实现了这种方法。例如:
@EachSize(min = 5, max = 255)
List<String> values;
该库允许您轻松地为任何验证约束创建“伪约束”以注释简单类型的集合,而无需为每个集合编写额外的验证器或不必要的包装类。所有标准Bean Validation约束和Hibernate特定约束都支持EachX
约束。
要为您自己的@EachAwesome
约束创建@Awesome
,只需复制并粘贴注释类,将@Constraint
注释替换为@Constraint(validatedBy = CommonEachValidator.class)
并添加注释{{1} }。这就是全部!
@EachConstraint(validateAs = Awesome.class)
编辑:针对当前版本的库进行了更新。
答案 3 :(得分:4)
感谢becomputer06的精彩回答。 但我认为应该在ValidCollection定义中添加以下注释:
@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidCollectionValidator.class)
我仍然不明白如何处理原始类型包装器的集合并约束@Size,@ Min,@ Max等注释,因为值不能通过becomputer06的方式传递。
当然,我可以为我的应用程序中的所有案例创建自定义约束注释,但无论如何我必须将这些注释的属性添加到CollectionElementBean。这似乎是一个糟糕的解决方案。
答案 4 :(得分:1)
JSR-303能够扩展内置约束的目标类型:请参阅7.1.2. Overriding constraint definitions in XML。
您可以实现一个ConstraintValidator<Email, List<String>>
,它与给定的答案做同样的事情,委托给原始验证器。然后,您可以保留模型定义并在@Email
上应用List<String>
。
答案 5 :(得分:0)
可以采用非常简单的解决方法。您可以改为验证包含简单值属性的类的集合。为此,您需要在集合上使用 Example::
class Table(tables.Table):
name = tables.Column(orderable=False)
country = tables.Column(orderable=False)
def before_render(self, request):
if request.user.has_perm('foo.delete_bar'):
self.columns.hide('country')
else:
self.columns.show('country')
注释。
示例:强>
@Valid