1. 序章

この記事では、ApacheBValライブラリによるJavaBeanValidation仕様(JSR 349)の実装について説明します。

2. Mavenの依存関係

Apache BVal を使用するには、最初に次の依存関係をpom.xmlファイルに追加する必要があります。

<dependency>
    <groupId>org.apache.bval</groupId>
    <artifactId>bval-jsr</artifactId>
    <version>1.1.2</version>
</dependency>
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>

カスタムBVal制約は、オプションのbval-extras依存関係にあります。

<dependency>
    <groupId>org.apache.bval</groupId>
    <artifactId>bval-extras</artifactId>
    <version>1.1.2</version>
</dependency>

bval-jsr bval-extras 、およびvalidation-apiの最新バージョンはMavenCentralからダウンロードできます。

3. 制約の適用

Apache BVal は、javax.validationパッケージで定義されているすべての制約の実装を提供します。 Beanのプロパティに制約を適用するために、プロパティ宣言に制約アノテーションを追加できます。

4つの属性を持つUserクラスを作成してから、 @NotNull @Size 、および@Minアノテーションを適用してみましょう。

public class User {
    
    @NotNull
    private String email;
    
    private String password;

    @Size(min=1, max=20)
    private String name;

    @Min(18)
    private int age;

    // standard constructor, getters, setters
}

4. Beanの検証

User クラスに適用された制約を検証するには、ValidatorFactoryインスタンスと1つ以上のValidatorインスタンスを取得する必要があります。

4.1. ValidatorFactoryの取得

工場での作成は要求の厳しいプロセスであるため、 Apache BVal のドキュメントでは、このクラスの単一のインスタンスを取得することをお勧めします。

ValidatorFactory validatorFactory 
  = Validation.byProvider(ApacheValidationProvider.class)
  .configure().buildValidatorFactory();

4.2. バリデーターの取得

次に、上記で定義したvalidatorFactoryからValidatorインスタンスを取得する必要があります。

Validator validator = validatorFactory.getValidator();

これはスレッドセーフな実装であるため、作成済みのインスタンスを安全に再利用できます。

Validator クラスは、Beanの有効性を判断するための3つのメソッド validate() validateProperty()および validateValue()を提供します。

これらの各メソッドは、従わなかった制約に関する情報を含むConstraintViolationオブジェクトのセットを返します。

4.3. validate() API

validate()メソッドは、Bean全体の有効性をチェックします。つまり、は、パラメーターとして渡されるオブジェクトのプロパティに適用されるすべての制約を検証します。

JUnit テストを作成して、 User オブジェクトを定義し、 validate()メソッドを使用してそのプロパティをテストしてみましょう。

@Test
public void givenUser_whenValidate_thenValidationViolations() {
    User user
      = new User("[email protected]", "pass", "nameTooLong_______________", 15);

    Set<ConstraintViolation<User>> violations = validator.validate(user);
    assertTrue("no violations", violations.size() > 0);
}

4.4. validateProperty() API

validateProperty()メソッドを使用して、Beanの単一のプロパティを検証できます。

JUnit テストを作成してみましょう。このテストでは、ageプロパティが必要な最小値の18未満のUserオブジェクトを定義し、このプロパティの検証結果を確認します。 1つの違反で:

@Test
public void givenInvalidAge_whenValidateProperty_thenConstraintViolation() {
    User user = new User("[email protected]", "pass", "Ana", 12);

    Set<ConstraintViolation<User>> propertyViolations
      = validator.validateProperty(user, "age");
 
    assertEquals("size is not 1", 1, propertyViolations.size());
}

4.5. validateValue() API

validateValue()メソッドを使用して、Beanに設定する前に、ある値がBeanのプロパティの有効な値であるかどうかをチェックできます。

Userオブジェクトを使用してJUnitテストを作成し、値20ageプロパティの有効な値であることを確認しましょう。

@Test
public void givenValidAge_whenValidateValue_thenNoConstraintViolation() {
    User user = new User("[email protected]", "pass", "Ana", 18);
    
    Set<ConstraintViolation<User>> valueViolations
      = validator.validateValue(User.class, "age", 20);
 
    assertEquals("size is not 0", 0, valueViolations.size());
}

4.6. ValidatorFactoryを閉じる

ValidatorFactoryを使用した後、最後にを閉じることを忘れないでください。

if (validatorFactory != null) {
    validatorFactory.close();
}

5. 非JSR制約

Apache BVal ライブラリは、JSR仕様の一部ではない一連の制約も提供し、追加のより強力な検証機能を提供します。

bval-jsr パッケージには、有効な電子メールアドレスを検証するための @Email と、値が空でないことを確認するための@NotEmptyの2つの追加制約が含まれています。

残りのカスタムBVal制約は、オプションのパッケージbval-extrasで提供されます。

このパッケージには、番号が有効な国際銀行口座番号であることを保証する @IBAN 注釈、 @Isbn 注釈など、さまざまな数値形式を検証するための制約が含まれています。有効な標準書籍番号と、国際商品番号を確認するための @EAN13注釈を確認します。

ライブラリには、さまざまな種類のクレジットカード番号の有効性を保証するための注釈も用意されています: @AmericanExpress @Diners @Discover @Mastercard 、および@Visa

@Domainおよび@InetAddressアノテーションを使用して、値に有効なドメインまたはインターネットアドレスが含まれているかどうかを判断できます。

そして最後に、パッケージには、Fileオブジェクトがディレクトリであるかどうかを確認するための注釈@Directoryおよび@NotDirectoryアノテーションが含まれています

User クラスに追加のプロパティを定義し、それらに非JSRアノテーションのいくつかを適用してみましょう。

public class User {
    
    @NotNull
    @Email
    private String email;
    
    @NotEmpty
    private String password;

    @Size(min=1, max=20)
    private String name;

    @Min(18)
    private int age;
    
    @Visa
    private String cardNumber = "";
    
    @IBAN
    private String iban = "";
    
    @InetAddress
    private String website = "";

    @Directory
    private File mainDirectory = new File(".");

    // standard constructor, getters, setters
}

制約は、JSR制約と同様の方法でテストできます。

@Test
public void whenValidateNonJSR_thenCorrect() {
    User user = new User("[email protected]", "pass", "Ana", 20);
    user.setCardNumber("1234");
    user.setIban("1234");
    user.setWebsite("10.0.2.50");
    user.setMainDirectory(new File("."));
    
    Set<ConstraintViolation<User>> violations 
      = validator.validateProperty(user,"iban");
 
    assertEquals("size is not 1", 1, violations.size());
 
    violations = validator.validateProperty(user,"website");
 
    assertEquals("size is not 0", 0, violations.size());

    violations = validator.validateProperty(user, "mainDirectory");
 
    assertEquals("size is not 0", 0, violations.size());
}

これらの追加のアノテーションは潜在的な検証ニーズに便利ですが、 JSR 仕様の一部ではないアノテーションを使用することの欠点のひとつは、別のJSR実装に簡単に切り替えることができないことです。後で必要になります。

6. カスタム制約

独自の制約を定義するには、最初に標準の構文に従って注釈を作成する必要があります。

ユーザーのパスワードが満たさなければならない条件を定義するPasswordアノテーションを作成しましょう。

@Constraint(validatedBy = { PasswordValidator.class })
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface Password {
    String message() default "Invalid password";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    int length() default 6;

    int nonAlpha() default 1;
}

パスワード値の実際の検証は、ConstraintValidatorインターフェイスを実装するクラス(この場合は PasswordValidator クラス)で行われます。 このクラスは、 isValid()メソッドをオーバーライドし、passwordの長さがlength属性より短いかどうか、および指定された数より少ないかどうかを確認します。 nonAlpha 属性の英数字以外の文字の数:

public class PasswordValidator 
  implements ConstraintValidator<Password, String> {

    private int length;
    private int nonAlpha;

    @Override
    public void initialize(Password password) {
        this.length = password.length();
        this.nonAlpha = password.nonAlpha();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext ctx) {
        if (value.length() < length) {
            return false;
        }
        int nonAlphaNr = 0;
        for (int i = 0; i < value.length(); i++) {
            if (!Character.isLetterOrDigit(value.charAt(i))) {
                nonAlphaNr++;
            }
        }
        if (nonAlphaNr < nonAlpha) {
            return false;
        }
        return true;
    }
}

カスタム制約をUserクラスのpasswordプロパティに適用してみましょう。

@Password(length = 8)
private String password;

JUnit テストを作成して、無効なpassword値が制約違反になることを確認できます。

@Test
public void givenValidPassword_whenValidatePassword_thenNoConstraintViolation() {
    User user = new User("[email protected]", "password", "Ana", 20);
    Set<ConstraintViolation<User>> violations 
      = validator.validateProperty(user, "password");
 
    assertEquals(
      "message incorrect",
      "Invalid password", 
      violations.iterator().next().getMessage());
}

次に、有効なpassword値を検証するJUnitテストを作成しましょう。

@Test
public void givenValidPassword_whenValidatePassword_thenNoConstraintViolation() {
    User user = new User("[email protected]", "password#", "Ana", 20);
		
    Set<ConstraintViolation<User>> violations 
      = validator.validateProperty(user, "password");
    assertEquals("size is not 0", 0, violations.size());
}

7. 結論

この記事では、 ApacheBValBean検証実装の使用例を示しました。

この記事の完全なソースコードは、GitHubにあります。