1. 概要

この記事では、Bean Validation 2.0(JSR-380)を使用してメソッド制約を定義および検証する方法について説明します。

前回の記事では、アノテーションが組み込まれたJSR-380と、プロパティ検証の実装方法について説明しました。

ここでは、次のようなさまざまなタイプのメソッド制約に焦点を当てます。

  • 単一パラメーターの制約
  • クロスパラメータ
  • リターン制約

また、SpringValidatorを使用して制約を手動および自動で検証する方法についても説明します。

次の例では、 Java Bean ValidationBasicsとまったく同じ依存関係が必要です。

2. メソッド制約の宣言

まず、メソッドパラメータの制約を宣言する方法とメソッドの戻り値について説明します。

前述のように、 javax.validation.constraints のアノテーションを使用できますが、カスタム制約を指定することもできます(e。 g。 カスタム制約またはクロスパラメータ制約の場合)。

2.1. 単一パラメータの制約

単一のパラメーターに対する制約の定義は簡単です。 必要に応じて各パラメーターに注釈を追加するだけです

public void createReservation(@NotNull @Future LocalDate begin,
  @Min(1) int duration, @NotNull Customer customer) {

    // ...
}

同様に、コンストラクターにも同じアプローチを使用できます。

public class Customer {

    public Customer(@Size(min = 5, max = 200) @NotNull String firstName, 
      @Size(min = 5, max = 200) @NotNull String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // properties, getters, and setters
}

2.2. クロスパラメータ制約の使用

場合によっては、一度に複数の値を検証する必要があります。たとえば、2つの数値が一方が他方よりも大きい場合です。

これらのシナリオでは、2つ以上のパラメーターに依存する可能性のあるカスタムのクロスパラメーター制約を定義できます。

クロスパラメーター制約は、クラスレベルの制約と同等のメソッド検証と見なすことができます。 両方を使用して、いくつかのプロパティに基づく検証を実装できます。

簡単な例を考えてみましょう。前のセクションのcreateReservation()メソッドのバリエーションは、タイプ LocalDate:の開始日と終了日の2つのパラメーターを取ります。

したがって、 begin が将来であり、endbeginの後にあることを確認する必要があります。 前の例とは異なり、単一のパラメーター制約を使用してこれを定義することはできません。

代わりに、クロスパラメータ制約が必要です。

単一パラメーター制約とは対照的に、クロスパラメーター制約はメソッドまたはコンストラクターで宣言されます。

@ConsistentDateParameters
public void createReservation(LocalDate begin, 
  LocalDate end, Customer customer) {

    // ...
}

2.3. クロスパラメータ制約の作成

@ConsistentDateParameters 制約を実装するには、2つのステップが必要です。

まず、制約アノテーションを定義する必要があります。

@Constraint(validatedBy = ConsistentDateParameterValidator.class)
@Target({ METHOD, CONSTRUCTOR })
@Retention(RUNTIME)
@Documented
public @interface ConsistentDateParameters {

    String message() default
      "End date must be after begin date and both must be in the future";

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

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

ここで、これらの3つのプロパティは、制約の注釈に必須です。

  • メッセージ– は、エラーメッセージを作成するためのデフォルトのキーを返します。これにより、メッセージ補間を使用できるようになります
  • groups –制約の検証グループを指定できます
  • payload – Bean Validation APIのクライアントは、カスタムペイロードオブジェクトを制約に割り当てるために使用できます

カスタム制約を定義する方法の詳細については、公式ドキュメントを参照してください。

その後、バリデータークラスを定義できます。

@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ConsistentDateParameterValidator 
  implements ConstraintValidator<ConsistentDateParameters, Object[]> {

    @Override
    public boolean isValid(
      Object[] value, 
      ConstraintValidatorContext context) {
        
        if (value[0] == null || value[1] == null) {
            return true;
        }

        if (!(value[0] instanceof LocalDate) 
          || !(value[1] instanceof LocalDate)) {
            throw new IllegalArgumentException(
              "Illegal method signature, expected two parameters of type LocalDate.");
        }

        return ((LocalDate) value[0]).isAfter(LocalDate.now()) 
          && ((LocalDate) value[0]).isBefore((LocalDate) value[1]);
    }
}

ご覧のとおり、 isValid()メソッドには実際の検証ロジックが含まれています。 まず、タイプ LocalDateの2つのパラメーターを取得することを確認します。その後、両方が将来であり、endbeginの後にあるかどうかを確認します。

また、 ConsistentDateParameterValidatorクラスの@SupportedValidationTarget(ValidationTarget.PARAMETERS)アノテーションが必要であることに注意することが重要です。 これは、 @ConsistentDateParameter がメソッドレベルで設定されているためですが、制約はメソッドパラメーターに適用される必要があります(次で説明するように、メソッドの戻り値には適用されません)。セクション)。

注:Bean Validation仕様では、null値を有効と見なすことを推奨しています。 null が有効な値でない場合は、代わりに@NotNull-アノテーションを使用する必要があります。

2.4. 戻り値の制約

メソッドによって返されるオブジェクトを検証する必要がある場合があります。 このために、戻り値の制約を使用できます。

次の例では、組み込みの制約を使用しています。

public class ReservationManagement {

    @NotNull
    @Size(min = 1)
    public List<@NotNull Customer> getAllCustomers() {
        return null;
    }
}

getAllCustomers()の場合、次の制約が適用されます。

  • まず、返されるリストは null であってはならず、少なくとも1つのエントリが含まれている必要があります
  • さらに、リストにnullエントリを含めることはできません

2.5. 戻り値のカスタム制約

場合によっては、複雑なオブジェクトを検証する必要もあります。

public class ReservationManagement {

    @ValidReservation
    public Reservation getReservationsById(int id) {
        return null;
    }
}

この例では、返される Reservation オブジェクトは、次に定義する@ValidReservationで定義された制約を満たす必要があります。

繰り返しますが、最初に制約アノテーションを定義する必要があります。

@Constraint(validatedBy = ValidReservationValidator.class)
@Target({ METHOD, CONSTRUCTOR })
@Retention(RUNTIME)
@Documented
public @interface ValidReservation {
    String message() default "End date must be after begin date "
      + "and both must be in the future, room number must be bigger than 0";

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

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

その後、バリデータークラスを定義します。

public class ValidReservationValidator
  implements ConstraintValidator<ValidReservation, Reservation> {

    @Override
    public boolean isValid(
      Reservation reservation, ConstraintValidatorContext context) {

        if (reservation == null) {
            return true;
        }

        if (!(reservation instanceof Reservation)) {
            throw new IllegalArgumentException("Illegal method signature, "
            + "expected parameter of type Reservation.");
        }

        if (reservation.getBegin() == null
          || reservation.getEnd() == null
          || reservation.getCustomer() == null) {
            return false;
        }

        return (reservation.getBegin().isAfter(LocalDate.now())
          && reservation.getBegin().isBefore(reservation.getEnd())
          && reservation.getRoom() > 0);
    }
}

2.6. コンストラクターの戻り値

以前にValidReservationインターフェイス内でMETHODCONSTRUCTORtargetとして定義したので、Reservationのコンストラクターに注釈を付けることもできます。構築されたインスタンスの検証

public class Reservation {

    @ValidReservation
    public Reservation(
      LocalDate begin, 
      LocalDate end, 
      Customer customer, 
      int room) {
        this.begin = begin;
        this.end = end;
        this.customer = customer;
        this.room = room;
    }

    // properties, getters, and setters
}

2.7. カスケード検証

最後に、Bean Validation APIを使用すると、いわゆるカスケード検証を使用して、単一のオブジェクトだけでなくオブジェクトグラフも検証できます。

したがって、複雑なオブジェクトを検証する場合は、カスケード検証に@Validを使用できます。 これは、メソッドパラメータと戻り値に対して機能します。

Customerクラスにいくつかのプロパティ制約があると仮定します。

public class Customer {

    @Size(min = 5, max = 200)
    private String firstName;

    @Size(min = 5, max = 200)
    private String lastName;

    // constructor, getters and setters
}

Reservation クラスには、 Customer プロパティと、制約付きのその他のプロパティが含まれる場合があります。

public class Reservation {

    @Valid
    private Customer customer;
	
    @Positive
    private int room;
	
    // further properties, constructor, getters and setters
}

メソッドパラメータとしてReservationを参照すると、すべてのプロパティの再帰的検証を強制できます

public void createNewCustomer(@Valid Reservation reservation) {
    // ...
}

ご覧のとおり、@Validは次の2か所で使用しています。

  • reserved パラメーター: createNewCustomer()が呼び出されると、Reservationオブジェクトの検証がトリガーされます。
  • ここにはネストされたオブジェクトグラフがあるため、customer属性に@Validを追加する必要があります。これにより、このネストされたプロパティの検証がトリガーされます。

これは、タイプReservationのオブジェクトを返すメソッドでも機能します。

@Valid
public Reservation getReservationById(int id) {
    return null;
}

3. メソッド制約の検証

前のセクションで制約を宣言した後、これらの制約を実際に検証することができます。 そのために、複数のアプローチがあります。

3.1. Springによる自動検証

Spring Validationは、HibernateValidatorとの統合を提供します。

注:Spring ValidationはAOPに基づいており、デフォルトの実装としてSpringAOPを使用します。 したがって、検証はメソッドに対してのみ機能し、コンストラクターに対しては機能しません。

Springに制約を自動的に検証させたい場合は、次の2つのことを行う必要があります。

最初に、検証されるBeanに@Validatedで注釈を付ける必要があります。

@Validated
public class ReservationManagement {

    public void createReservation(@NotNull @Future LocalDate begin, 
      @Min(1) int duration, @NotNull Customer customer){

        // ...
    }
	
    @NotNull
    @Size(min = 1)
    public List<@NotNull Customer> getAllCustomers(){
        return null;
    }
}

次に、MethodValidationPostProcessorBeanを提供する必要があります。

@Configuration
@ComponentScan({ "org.baeldung.javaxval.methodvalidation.model" })
public class MethodValidationConfig {

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        return new MethodValidationPostProcessor();
    }
}

制約に違反した場合、コンテナはjavax.validation.ConstraintViolationExceptionをスローするようになりました。

Spring Bootを使用している場合、 hibernate-validator がクラスパスにある限り、コンテナーは MethodValidationPostProcessorbeanを登録します。

3.2. CDIによる自動検証(JSR-365)

バージョン1.1以降、Bean ValidationはCDI(Jakarta EEのコンテキストと依存性注入)で機能します。

アプリケーションがJakartaEEコンテナーで実行されている場合、コンテナーは呼び出し時にメソッド制約を自動的に検証します。

3.3. プログラムによる検証

スタンドアロンJavaアプリケーションでの手動メソッド検証には、javax.validation.executable.ExecutableValidatorインターフェースを使用できます。

次のコードを使用してインスタンスを取得できます。

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
ExecutableValidator executableValidator = factory.getValidator().forExecutables();

ExecutableValidatorは、次の4つのメソッドを提供します。

  • メソッド検証用のvalidateParameters()および validateReturnValue()
  • コンストラクター検証用のvalidateConstructorParameters()および validateConstructorReturnValue()

最初のメソッドcreateReservation()のパラメーターを検証すると、次のようになります。

ReservationManagement object = new ReservationManagement();
Method method = ReservationManagement.class
  .getMethod("createReservation", LocalDate.class, int.class, Customer.class);
Object[] parameterValues = { LocalDate.now(), 0, null };
Set<ConstraintViolation<ReservationManagement>> violations 
  = executableValidator.validateParameters(object, method, parameterValues);

注:公式ドキュメントでは、このインターフェースをアプリケーションコードから直接呼び出すことは推奨されていませんが、AOPやプロキシなどのメソッドインターセプトテクノロジーを介して使用することはお勧めしません。

ExecutableValidator インターフェースの使用方法に興味がある場合は、公式ドキュメントをご覧ください。

4. 結論

このチュートリアルでは、Hibernate Validatorでメソッド制約を使用する方法を簡単に説明し、JSR-380のいくつかの新機能についても説明しました。

最初に、さまざまなタイプの制約を宣言する方法について説明しました。

  • 単一パラメーターの制約
  • クロスパラメータ
  • 戻り値の制約

また、SpringValidatorを使用して制約を手動および自動で検証する方法についても説明しました。

いつものように、例の完全なソースコードは、GitHubから入手できます。