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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 15:42:52  来源:igfitidea点击:

Hibernate Validation of Collections of Primitives

javacollectionsbean-validationhibernate-validator

提问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 @ValidCollectionconstraint and corresponding validator implementation ValidCollectionValidator.

解决此问题的一种可能解决方案是创建自定义@ValidCollection约束和相应的验证器实现ValidCollectionValidator

To validate each element of collection we need an instance of Validatorinside ValidCollectionValidator; and to get such instance we need custom implementation of ConstraintValidatorFactory.

为了验证集合的每个元素,我们需要一个Validatorinside实例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 constraintsattribute of @ValidCollectionconstraint. The constraintsattribute is necessary when bean has no constraint.

注意:-当 bean 有约束时,不要指定约束的constraints属性@ValidCollectionconstraints当 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 @EachElementto 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. EachXconstraint is supported for all standard Bean Validation constraints and Hibernate specific constraints.

该库允许您轻松地为任何验证约束创建“伪约束”以注释简单类型的集合,而无需为每个集合编写额外的验证器或不必要的包装类。EachX所有标准 Bean 验证约束和 Hibernate 特定约束都支持约束。

To create an @EachAwesomefor your own @Awesomeconstraint, just copy&paste the annotation class, replace @Constraintannotation 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 @Emailon List<String>.

您可以实现ConstraintValidator<Email, List<String>>与给定答案执行相同操作的a ,委托给原始验证器。然后您可以保留您的模型定义并@EmailList<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 @Validannotation 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());
  }

}