プリミティブのコレクションの冬眠検証
-
29-09-2019 - |
質問
私は次のようなことをすることができるようになりたいです:
@Email
public List<String> getEmailAddresses()
{
return this.emailAddresses;
}
言い換えれば、リスト内の各アイテムをメールアドレスとして検証したいと考えています。もちろん、このようなコレクションに注釈を付けることは受け入れられません。
これを行う方法はありますか?
解決
JSR-303もHibernate Validatorも、コレクションの各要素を検証できる既製の制約を持っていません。
この問題に対処するための可能な解決策の1つは、カスタムを作成することです @ValidCollection
制約と対応するバリデーターの実装 ValidCollectionValidator
.
コレクションの各要素を検証するには、のインスタンスが必要です Validator
中身 ValidCollectionValidator
;そして、そのようなインスタンスを取得するには、のカスタム実装が必要です ConstraintValidatorFactory
.
次の解決策が好きかどうかを確認してください...
単に、
- これらすべてのJavaクラスをコピーします(および再生クラスをインポートします)。
- ClassPathに検証API、Hibenate-Validator、Slf4J-Log4J12、およびtestng Jarsを追加します。
- テストケースを実行します。
有効なコレクション
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;
}
}
validatorcontextawareconstraintalidator
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
制約。 constraints
Beanに制約がない場合は、属性が必要です。
他のヒント
私は元の答えについてこれをコメントするのに十分な評判を持っていませんが、おそらくこの質問に注目する価値があります JSR-308 最終リリース段階にあり、リリースされるとこの問題に対処します!ただし、少なくともJava 8が必要になります。
唯一の違いは、検証注釈がタイプ宣言の内部に入ることです。
//@Email
public List<@Email String> getEmailAddresses()
{
return this.emailAddresses;
}
この情報を探している他の人のためにこの情報を最もよく入れることができると思う場所を教えてください。ありがとう!
詳細についてはPS、 これをチェックしてください。投稿してください.
のような一般的なラッパー注釈を書くことはできません @EachElement
Javaアノテーション自体の制限により、制約注釈をラップするため。ただし、すべての要素の実際の検証を既存の制約検証装置に委任する汎用制約検証装置クラスを記述できます。すべての制約に対してラッパー注釈を書く必要がありますが、1つのバリデーターだけです。
このアプローチを実装しました Jirutka/Validator-Collection (Maven Centralで利用可能)。例えば:
@EachSize(min = 5, max = 255)
List<String> values;
このライブラリを使用すると、「擬似制約」を簡単に作成できます どれか すべてのコレクションに追加のバリデーターまたは不要なラッパークラスを作成することなく、シンプルなタイプのコレクションに注釈を付けるための検証制約。 EachX
制約は、すべての標準のBean検証制約と冬眠比の制約に対してサポートされています。
を作成する @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();
}
編集:現在のバージョンのライブラリに更新されました。
Becomputer06からの素晴らしい答えをありがとう。しかし、次の注釈は有効なコレクション定義に追加する必要があると思います。
@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidCollectionValidator.class)
そして、私はまだ、@size、@min、@maxなどのような注釈を制約する原始的なタイプのラッパーのコレクションをどうするかを理解していません。
もちろん、アプリケーションのすべてのケースに対してカスタムコントレントアノテーションを作成できますが、とにかくこれらの注釈のプロパティをCollectionElementBeanに追加する必要があります。そして、それは十分な悪い解決策のようです。
JSR-303には、組み込みの制約のターゲットタイプを拡張する機能があります。 7.1.2。 XMLのオーバーライド制約定義.
aを実装できます ConstraintValidator<Email, List<String>>
これは、指定された回答と同じことを行い、プリミティブバリデーターに委任します。その後、モデル定義を維持して適用できます @Email
の上 List<String>
.
非常に簡単な回避策が可能です。代わりに、Simple Valueプロパティをラップするクラスのコレクションを検証できます。これが機能するには、使用する必要があります @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());
}
}