Spring Data RESTバリデーターガイド

Spring Data REST Validatorsのガイド

1. 概要

この記事では、Spring Data REST Validatorsの基本的な概要を説明します。 最初にSpringData RESTの基本を確認する必要がある場合は、必ずthis articleにアクセスして基本をブラッシュアップしてください。

簡単に言えば、Spring Data RESTを使用すると、REST APIを介してデータベースに新しいエントリを追加できますが、もちろん、実際にデータを永続化する前にデータが有効であることを確認する必要もあります。

この記事はexisting articleに続き、そこで設定した既存のプロジェクトを再利用します。

 

そして、最初のget started with Spring Data RESTを探している場合は、次の方法で実行を開始できます。

[.iframe-fluid] ##

2. Validatorsの使用

Spring 3以降、フレームワークはValidatorインターフェースを備えています。これはオブジェクトの検証に使用できます。

2.1. 動機

前回の記事では、nameemailの2つのプロパティを持つエンティティを定義しました。

したがって、新しいリソースを作成するには、次を実行するだけです。

curl -i -X POST -H "Content-Type:application/json" -d
  '{ "name" : "Test", "email" : "[email protected]" }'
  http://localhost:8080/users

このPOSTリクエストは、提供されたJSONオブジェクトをデータベースに保存し、操作は以下を返します。

{
  "name" : "Test",
  "email" : "[email protected]",
  "_links" : {
    "self" : {
        "href" : "http://localhost:8080/users/1"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/1"
    }
  }
}

有効なデータを提供したため、肯定的な結果が期待されました。 しかし、プロパティnameを削除するか、値を空のStringに設定するとどうなりますか?

最初のシナリオをテストするために、前から変更されたコマンドを実行し、プロパティnameの値として空の文字列を設定します。

curl -i -X POST -H "Content-Type:application/json" -d
  '{ "name" : "", "email" : "Baggins" }' http://localhost:8080/users

このコマンドを使用すると、次の応答が返されます。

{
  "name" : "",
  "email" : "Baggins",
  "_links" : {
    "self" : {
        "href" : "http://localhost:8080/users/1"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/1"
    }
  }
}

2番目のシナリオでは、プロパティnameをリクエストから削除します。

curl -i -X POST -H "Content-Type:application/json" -d
  '{ "email" : "Baggins" }' http://localhost:8080/users

そのコマンドに対して、次の応答を取得します。

{
  "name" : null,
  "email" : "Baggins",
  "_links" : {
    "self" : {
        "href" : "http://localhost:8080/users/2"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/2"
    }
  }
}

ご覧のとおり、どちらのリクエストもOKであり、201のステータスコードとオブジェクトへのAPIリンク.で確認できます。

データベースへの部分的なデータの挿入を避けたいため、この動作は受け入れられません。

2.2. Spring DataRESTイベント

Spring Data REST APIを呼び出すたびに、Spring Data RESTエクスポーターは以下にリストされているさまざまなイベントを生成します。

  • BeforeCreateEvent

  • AfterCreateEvent

  • BeforeSaveEvent

  • AfterSaveEvent

  • BeforeLinkSaveEvent

  • AfterLinkSaveEvent

  • BeforeDeleteEvent

  • AfterDeleteEvent

すべてのイベントは同様の方法で処理されるため、新しいオブジェクトがデータベースに保存される前に生成されるbeforeCreateEventの処理方法のみを示します。

2.3. Validatorの定義

独自のバリデーターを作成するには、supportsメソッドとvalidateメソッドを使用してorg.springframework.validation.Validatorインターフェイスを実装する必要があります。

Supportsは、バリデーターが提供された要求をサポートしているかどうかをチェックし、validateメソッドは、要求で提供されたデータを検証します。

WebsiteUserValidatorクラスを定義しましょう:

public class WebsiteUserValidator implements Validator {

    @Override
    public boolean supports(Class clazz) {
        return WebsiteUser.class.equals(clazz);
    }

    @Override
    public void validate(Object obj, Errors errors) {
        WebsiteUser user = (WebsiteUser) obj;
        if (checkInputString(user.getName())) {
            errors.rejectValue("name", "name.empty");
        }

        if (checkInputString(user.getEmail())) {
            errors.rejectValue("email", "email.empty");
        }
    }

    private boolean checkInputString(String input) {
        return (input == null || input.trim().length() == 0);
    }
}

Errorsオブジェクトは、validateメソッドで提供されるすべてのエラーを含むように設計された特別なクラスです。 この記事の後半では、Errorsオブジェクトに含まれる提供されたメッセージを使用する方法を示します。 新しいエラーメッセージを追加するには、errors.rejectValue(nameOfField, errorMessage)を呼び出す必要があります。

バリデーターを定義したら、リクエストが受け入れられた後に生成される特定のイベントにバリデーターをマッピングする必要があります。

たとえば、この場合、データベースに新しいオブジェクトを挿入するため、beforeCreateEventが生成​​されます。 ただし、リクエスト内のオブジェクトを検証するため、最初にバリデーターを定義する必要があります。

これは、次の3つの方法で実行できます。

  • beforeCreateWebsiteUserValidator」という名前のComponent注釈を追加します。 Spring Bootは、キャッチするイベントを決定するプレフィックスbeforeCreateを認識し、Component名からWebsiteUserクラスも認識します。

    @Component("beforeCreateWebsiteUserValidator")
    public class WebsiteUserValidator implements Validator {
        ...
    }
  • @Beanアノテーションを使用してアプリケーションコンテキストにBeanを作成します。

    @Bean
    public WebsiteUserValidator beforeCreateWebsiteUserValidator() {
        return new WebsiteUserValidator();
    }
  • 手動登録:

    @SpringBootApplication
    public class SpringDataRestApplication
      extends RepositoryRestConfigurerAdapter {
        public static void main(String[] args) {
            SpringApplication.run(SpringDataRestApplication.class, args);
        }
    
        @Override
        public void configureValidatingRepositoryEventListener(
          ValidatingRepositoryEventListener v) {
            v.addValidator("beforeCreate", new WebsiteUserValidator());
        }
    }
    • この場合、WebsiteUserValidatorクラスにアノテーションは必要ありません。

2.4. イベント検出バグ

現時点では、bug exists in Spring Data REST –イベントの検出に影響します。

beforeCreateイベントを生成するPOSTリクエストを呼び出すと、このバグのためにイベントが検出されないため、アプリケーションはバリデーターを呼び出しません。

この問題の簡単な回避策は、すべてのイベントをSpring Data RESTValidatingRepositoryEventListenerクラスに挿入することです。

@Configuration
public class ValidatorEventRegister implements InitializingBean {

    @Autowired
    ValidatingRepositoryEventListener validatingRepositoryEventListener;

    @Autowired
    private Map validators;

    @Override
    public void afterPropertiesSet() throws Exception {
        List events = Arrays.asList("beforeCreate");
        for (Map.Entry entry : validators.entrySet()) {
            events.stream()
              .filter(p -> entry.getKey().startsWith(p))
              .findFirst()
              .ifPresent(
                p -> validatingRepositoryEventListener
               .addValidator(p, entry.getValue()));
        }
    }
}

3. テスト

Section 2.1.で、バリデーターがないと、nameプロパティのないオブジェクトをデータベースに追加できることを示しました。これは、データの整合性をチェックしないため、望ましくない動作です。

nameプロパティなしで、提供されたバリデーターを使用して同じオブジェクトを追加する場合、次のエラーが発生します。

curl -i -X POST -H "Content-Type:application/json" -d
  '{ "email" : "[email protected]" }' http://localhost:8080/users
{
   "timestamp":1472510818701,
   "status":406,
   "error":"Not Acceptable",
   "exception":"org.springframework.data.rest.core.
    RepositoryConstraintViolationException",
   "message":"Validation failed",
   "path":"/users"
}

ご覧のとおり、要求からの欠落データが検出され、オブジェクトはデータベースに保存されませんでした。 要求は、500 HTTPコードと内部エラーのメッセージとともに返されました。

エラーメッセージには、リクエストの問題については何も書かれていません。 より情報的なものにしたい場合は、応答オブジェクトを変更する必要があります。

Exception Handling in Spring articleで、フレームワークによって生成された例外を処理する方法を示したので、この時点で間違いなく良い読み物です。

アプリケーションはRepositoryConstraintViolationException例外を生成するため、この特定の例外のハンドラーを作成して、応答メッセージを変更します。

これは私たちのRestResponseEntityExceptionHandlerクラスです:

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends
  ResponseEntityExceptionHandler {

    @ExceptionHandler({ RepositoryConstraintViolationException.class })
    public ResponseEntity handleAccessDeniedException(
      Exception ex, WebRequest request) {
          RepositoryConstraintViolationException nevEx =
            (RepositoryConstraintViolationException) ex;

          String errors = nevEx.getErrors().getAllErrors().stream()
            .map(p -> p.toString()).collect(Collectors.joining("\n"));

          return new ResponseEntity(errors, new HttpHeaders(),
            HttpStatus.PARTIAL_CONTENT);
    }
}


このカスタムハンドラーを使用すると、返されるオブジェクトには、検出されたすべてのエラーに関する情報が含まれます。

4. 結論

この記事では、データ挿入に追加のセキュリティ層を提供するすべてのSpring Data REST APIにバリデーターが不可欠であることを示しました。

また、アノテーションを使用して新しいバリデータを作成するのがいかに簡単かを示しました。

いつものように、このアプリケーションのコードはGitHub projectにあります。