Bean Validation2.0によるメソッドの制約

1。概要

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

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

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

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

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

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

2.メソッド制約の宣言

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

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

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[] payload() default {}; }

ここで、次の3つのプロパティは制約アノテーションに必須です。

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

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

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

@SupportedValidationTarget(ValidationTarget.PARAMETERS) public class ConsistentDateParameterValidator implements ConstraintValidator { @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つのパラメーターを取得することを確認しますその後、両方が将来であり、終了開始後であるかどうかを確認します

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

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

2.4。戻り値の制約

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

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

public class ReservationManagement { @NotNull @Size(min = 1) public List getAllCustomers() { return null; } }

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

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

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

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

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

この例では、返される予約オブジェクトは、次に定義する@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[] payload() default {}; }

After that, we define the validator class:

public class ValidReservationValidator implements ConstraintValidator { @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. Return Value in Constructors

As we defined METHOD and CONSTRUCTOR as target within our ValidReservation interface before, we can also annotate the constructor of Reservation to validate constructed instances:

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. Cascaded Validation

Finally, the Bean Validation API allows us to not only validate single objects but also object graphs, using the so-called cascaded validation.

Hence, we can use @Valid for a cascaded validation, if we want to validate complex objects. This works for method parameters as well as for return values.

Let's assume that we have a Customer class with some property constraints:

public class Customer { @Size(min = 5, max = 200) private String firstName; @Size(min = 5, max = 200) private String lastName; // constructor, getters and setters }

A Reservation class might have a Customer property, as well as further properties with constraints:

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

If we now reference Reservation as a method parameter, we can force the recursive validation of all properties:

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

As we can see, we use @Valid at two places:

  • On the reservation-parameter: it triggers the validation of the Reservation-object, when createNewCustomer() is called
  • As we have a nested object graph here, we also have to add a @Valid on the customer-attribute: thereby, it triggers the validation of this nested property

This also works for methods returning an object of type Reservation:

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

3. Validating Method Constraints

After the declaration of constraints in the previous section, we can now proceed to actually validate these constraints. For that, we have multiple approaches.

3.1. Automatic Validation With Spring

Spring Validation provides an integration with Hibernate Validator.

Note: Spring Validation is based on AOP and uses Spring AOP as the default implementation. Therefore, validation only works for methods, but not for constructors.

If we now want Spring to validate our constraints automatically, we have to do two things:

Firstly, we have to annotate the beans, which shall be validated, with @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 getAllCustomers(){ return null; } }

Secondly, we have to provide a MethodValidationPostProcessor bean:

@Configuration @ComponentScan({ "org.baeldung.javaxval.methodvalidation.model" }) public class MethodValidationConfig { @Bean public MethodValidationPostProcessor methodValidationPostProcessor() { return new MethodValidationPostProcessor(); } }

The container now will throw a javax.validation.ConstraintViolationException, if a constraint is violated.

If we are using Spring Boot, the container will register a MethodValidationPostProcessor bean for us as long as hibernate-validator is in the classpath.

3.2. Automatic Validation With CDI (JSR-365)

As of version 1.1, Bean Validation works with CDI (Contexts and Dependency Injection for Jakarta EE).

If our application runs in a Jakarta EE container, the container will validate method constraints automatically at the time of invocation.

3.3. Programmatic Validation

For manual method validation in a standalone Java application, we can use the javax.validation.executable.ExecutableValidator interface.

We can retrieve an instance using the following code:

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

ExecutableValidator offers four methods:

  • validateParameters() and validateReturnValue() for method validation
  • validateConstructorParameters() and validateConstructorReturnValue() for constructor validation

Validating the parameters of our first method createReservation() would look like this:

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

Note: The official documentation discourages to call this interface directly from the application code, but to use it via a method interception technology, like AOP or proxies.

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

4.結論

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

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

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

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

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