1. 概要

このチュートリアルでは、オープンソースフレームワークJerseyを使用したBeanValidationを見ていきます。

以前の記事ですでに見たように、 Jerseyは、RESTfulWebサービスを開発するためのオープンソースフレームワークです。 ジャージーの詳細については、方法の紹介をご覧ください。 JerseyとSpringでAPIを作成します。

2. ジャージーでのBeanValidation

検証は、一部のデータが1つ以上の事前定義された制約に従っていることを検証するプロセスです。 もちろん、これはほとんどのアプリケーションで非常に一般的なユースケースです。

Java Bean Validation フレームワーク(JSR-380)は、Javaでこの種の操作を処理するためのデファクトスタンダードになりました。 Java Bean Validationの基本を要約するには、以前のチュートリアルを参照してください。

Jerseyには、BeanValidationをサポートするための拡張モジュールが含まれています。 この機能をアプリケーションで使用するには、最初に構成する必要があります。 次のセクションでは、アプリケーションを構成する方法を説明します。

3. アプリケーションのセットアップ

それでは、優れた Jersey MVCSupportの記事からの簡単なFruitAPIの例に基づいて構築しましょう。

3.1. Mavenの依存関係

まず、BeanValidationの依存関係をpom.xmlに追加しましょう。

<dependency>
    <groupId>org.glassfish.jersey.ext</groupId>
    <artifactId>jersey-bean-validation</artifactId>
    <version>2.27</version>
</dependency>

最新バージョンはMavenCentralから入手できます。

3.2. サーバーの構成

ジャージーでは、通常、カスタムリソース構成クラスで使用する拡張機能を登録します。

ただし、Bean Validation拡張機能の場合、この登録を行う必要はありません。 幸い、これは、Jerseyフレームワークが自動的に登録する数少ない拡張機能の1つです。

最後に、検証エラーをクライアントに送信するためにカスタムリソース構成にサーバープロパティを追加します

public ViewApplicationConfig() {
    packages("com.baeldung.jersey.server");
    property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
}

4. JAX-RSリソースメソッドの検証

このセクションでは、制約アノテーションを使用して入力パラメーターを検証する2つの異なる方法について説明します。

  • 組み込みのBeanValidationAPI制約の使用
  • カスタム制約とバリデーターの作成

4.1. 組み込みの制約アノテーションの使用

組み込みの制約注釈を確認することから始めましょう。

@POST
@Path("/create")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void createFruit(
    @NotNull(message = "Fruit name must not be null") @FormParam("name") String name, 
    @NotNull(message = "Fruit colour must not be null") @FormParam("colour") String colour) {

    Fruit fruit = new Fruit(name, colour);
    SimpleStorageService.storeFruit(fruit);
}

この例では、namecolourの2つのフォームパラメータを使用して、新しいFruitを作成します。 すでにBeanValidationAPIの一部である@NotNullアノテーションを使用します。

これにより、フォームパラメータにnullではない単純な制約が課せられます。 パラメータの1つがnullの場合、アノテーション内で宣言されたメッセージが返されます

当然、これを単体テストで示します。

@Test
public void givenCreateFruit_whenFormContainsNullParam_thenResponseCodeIsBadRequest() {
    Form form = new Form();
    form.param("name", "apple");
    form.param("colour", null);
    Response response = target("fruit/create").request(MediaType.APPLICATION_FORM_URLENCODED)
        .post(Entity.form(form));

    assertEquals("Http Response should be 400 ", 400, response.getStatus());
    assertThat(response.readEntity(String.class), containsString("Fruit colour must not be null"));
}

上記の例では、JerseyTestサポートクラスを使用してフルーツリソースをテストしています。 null colour でPOSTリクエストを送信し、応答に予期されたメッセージが含まれていることを確認します。

組み込みの検証制約のリストについては、ドキュメントを参照してください。

4.2. カスタム制約アノテーションの定義

より複雑な制約を課す必要がある場合があります。 独自のカスタムアノテーションを定義することでこれを行うことができます。

簡単なFruitAPIの例を使用して、すべてのフルーツに有効なシリアル番号があることを検証する必要があると想像してみましょう。

@PUT
@Path("/update")
@Consumes("application/x-www-form-urlencoded")
public void updateFruit(@SerialNumber @FormParam("serial") String serial) {
    //...
}

この例では、パラメーター serial は、次に定義する@SerialNumberで定義された制約を満たす必要があります。

まず、制約アノテーションを定義します。

@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { SerialNumber.Validator.class })
    public @interface SerialNumber {

    String message()

    default "Fruit serial number is not valid";

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

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

次に、バリデータークラスSerialNumber.Validatorを定義します。

public class Validator implements ConstraintValidator<SerialNumber, String> {
    @Override
    public void initialize(SerialNumber serial) {
    }

    @Override
    public boolean isValid(String serial, 
        ConstraintValidatorContext constraintValidatorContext) {
        
        String serialNumRegex = "^\\d{3}-\\d{3}-\\d{4}$";
        return Pattern.matches(serialNumRegex, serial);
    }
}

ここで重要なのは、Validatorクラスは implement ConstraintValidatorでなければならないということです。ここで、Tは validate 、この場合、Stringです。

最後に、isValidメソッドにカスタム検証ロジックを実装します。

5. リソースの検証

さらに、Bean Validation APIを使用すると、@Validアノテーションを使用してオブジェクトを検証することもできます。

次のセクションでは、このアノテーションを使用してリソースクラスを検証する2つの異なる方法について説明します。

  • まず、リソースの検証をリクエストします
  • 次に、応答リソースの検証

@MinアノテーションをFruitオブジェクトに追加することから始めましょう。

@XmlRootElement
public class Fruit {

    @Min(value = 10, message = "Fruit weight must be 10 or greater")
    private Integer weight;
    //...
}

5.1. リソース検証のリクエスト

まず、FruitResourceクラスで@Validを使用して検証を有効にします。

@POST
@Path("/create")
@Consumes("application/json")
public void createFruit(@Valid Fruit fruit) {
    SimpleStorageService.storeFruit(fruit);
}

上記の例では、重量が10未満の果物を作成しようとすると、検証エラーが発生します。

5.2. 応答リソースの検証

同様に、次の例では、応答リソースを検証する方法を確認します。

@GET
@Valid
@Produces("application/json")
@Path("/search/{name}")
public Fruit findFruitByName(@PathParam("name") String name) {
    return SimpleStorageService.findByName(name);
}

同じ@Validアノテーションをどのように使用するかに注意してください。 ただし、今回はリソースメソッドレベルで使用して、応答が有効であることを確認します。

6. カスタム例外ハンドラー

この最後の部分では、カスタム例外ハンドラーを作成する方法を簡単に見ていきます。 これは、特定の制約に違反した場合にカスタム応答を返したい場合に役立ちます。

FruitExceptionMapperを定義することから始めましょう。

public class FruitExceptionMapper implements ExceptionMapper<ConstraintViolationException> {

    @Override
    public Response toResponse(ConstraintViolationException exception) {
        return Response.status(Response.Status.BAD_REQUEST)
            .entity(prepareMessage(exception))
            .type("text/plain")
            .build();
    }

    private String prepareMessage(ConstraintViolationException exception) {
        StringBuilder message = new StringBuilder();
        for (ConstraintViolation<?> cv : exception.getConstraintViolations()) {
            message.append(cv.getPropertyPath() + " " + cv.getMessage() + "\n");
        }
        return message.toString();
    }
}

まず、カスタム例外マッピングプロバイダーを定義します。 これを行うために、ConstraintViolationExceptionを使用してExceptionMapperインターフェイスを実装します。

したがって、この例外がスローされるとカスタム例外マッパーインスタンスのtoResponseメソッドが呼び出されることがわかります。

また、この簡単な例では、すべての違反を繰り返し処理し、応答で返送される各プロパティとメッセージを追加します。

次に、カスタム例外マッパーを使用するには、プロバイダーを登録する必要があります

@Override
protected Application configure() {
    ViewApplicationConfig config = new ViewApplicationConfig();
    config.register(FruitExceptionMapper.class);
    return config;
}

最後に、エンドポイントを追加して、無効な Fruit を返し、例外ハンドラーの動作を示します。

@GET
@Produces(MediaType.TEXT_HTML)
@Path("/exception")
@Valid
public Fruit exception() {
    Fruit fruit = new Fruit();
    fruit.setName("a");
    fruit.setColour("b");
    return fruit;
}

7. 結論

要約すると、このチュートリアルでは、Jersey BeanValidationAPI拡張機能について説明しました。

まず、JerseyでBeanValidationAPIを使用する方法を紹介することから始めました。 また、サンプルのWebアプリケーションを構成する方法についても見てきました。

最後に、Jerseyで検証を行ういくつかの方法と、カスタム例外ハンドラーを作成する方法について説明しました。

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