Spring MVCのカスタム検証

Spring MVCカスタム検証

1. 概要

一般に、ユーザー入力を検証する必要がある場合、Spring MVCは標準の事前定義済みバリデーターを提供します。

ただし、より特定のタイプの入力、we have the possibility of creating our own, custom validation logicを検証する必要がある場合。

この記事では、まさにそれを行います。電話番号フィールドを使用してフォームを検証するカスタムバリデーターを作成してから、複数のフィールドのカスタムバリデーターを表示します。

2. セットアップ

APIを利用するには、依存関係をpom.xmlファイルに追加します。


    org.hibernate
    hibernate-validator
    6.0.10.Final

依存関係の最新バージョンは、hereで確認できます。

Spring Bootを使用している場合は、hibernate-validatorの依存関係ももたらすspring-boot-starter-web,のみを追加できます。

3. カスタム検証

カスタムバリデーターを作成するには、独自の注釈を展開し、それをモデルで使用して検証ルールを実施する必要があります。

それでは、custom validator – which checks phone numbersを作成しましょう。 電話番号は8桁以上11桁以下の数字でなければなりません。

4. 新しい注釈

新しい@interfaceを作成して、アノテーションを定義しましょう。

@Documented
@Constraint(validatedBy = ContactNumberValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ContactNumberConstraint {
    String message() default "Invalid phone number";
    Class[] groups() default {};
    Class[] payload() default {};
}

@Constraintアノテーションを使用して、フィールドを検証するクラスを定義しました。message()は、ユーザーインターフェイスに表示されるエラーメッセージであり、追加のコードは、に準拠するほとんどの定型コードです。春の基準。

5. バリデーターの作成

次に、検証のルールを適用するバリデータークラスを作成しましょう。

public class ContactNumberValidator implements
  ConstraintValidator {

    @Override
    public void initialize(ContactNumberConstraint contactNumber) {
    }

    @Override
    public boolean isValid(String contactField,
      ConstraintValidatorContext cxt) {
        return contactField != null && contactField.matches("[0-9]+")
          && (contactField.length() > 8) && (contactField.length() < 14);
    }

}

検証クラスはConstraintValidatorインターフェースを実装し、isValidメソッドを実装する必要があります。検証ルールを定義したのはこのメソッドです。

当然、ここでは簡単な検証ルールを使用して、バリデーターがどのように機能するかを示します。

ConstraintValidator dは、特定のオブジェクトの特定の制約を検証するロジックを定義します。 実装は、次の制限に従う必要があります。

  • オブジェクトは、パラメータ化されていない型に解決する必要があります

  • オブジェクトのジェネリックパラメーターは無制限のワイルドカードタイプである必要があります

6. 検証アノテーションの適用

この例では、検証ルールを適用するための1つのフィールドを持つ単純なクラスを作成しました。 ここでは、検証する注釈付きフィールドを設定しています。

@ContactNumberConstraint
private String phone;

文字列フィールドを定義し、カスタムアノテーション@ContactNumberConstraint.でアノテーションを付けました。コントローラーでマッピングを作成し、エラーがあれば処理しました。

@Controller
public class ValidatedPhoneController {

    @GetMapping("/validatePhone")
    public String loadFormPage(Model m) {
        m.addAttribute("validatedPhone", new ValidatedPhone());
        return "phoneHome";
    }

    @PostMapping("/addValidatePhone")
    public String submitForm(@Valid ValidatedPhone validatedPhone,
      BindingResult result, Model m) {
        if(result.hasErrors()) {
            return "phoneHome";
        }
        m.addAttribute("message", "Successfully saved phone: "
          + validatedPhone.toString());
        return "phoneHome";
    }
}

単一のJSPページを持つこの単純なコントローラーを定義し、submitFormメソッドを使用して電話番号の検証を実施します。

7. 景色

私たちのビューは、単一のフィールドを持つフォームを持つ基本的なJSPページです。 ユーザーがフォームを送信すると、フィールドはカスタムバリデーターによって検証され、検証の成功または失敗のメッセージとともに同じページにリダイレクトされます。


    
    
    
    

8. テスト

次に、コントローラーをテストして、適切な応答とビューが得られるかどうかを確認します。

@Test
public void givenPhonePageUri_whenMockMvc_thenReturnsPhonePage(){
    this.mockMvc.
      perform(get("/validatePhone")).andExpect(view().name("phoneHome"));
}

また、ユーザー入力に基づいて、フィールドが検証されていることをテストしましょう。

@Test
public void
  givenPhoneURIWithPostAndFormData_whenMockMVC_thenVerifyErrorResponse() {

    this.mockMvc.perform(MockMvcRequestBuilders.post("/addValidatePhone").
      accept(MediaType.TEXT_HTML).
      param("phoneInput", "123")).
      andExpect(model().attributeHasFieldErrorCode(
          "validatedPhone","phone","ContactNumberConstraint")).
      andExpect(view().name("phoneHome")).
      andExpect(status().isOk()).
      andDo(print());
}

テストでは、ユーザーに「123」の入力を提供し、予想どおり、すべてが機能し、we’re seeing the error on the client sideを提供します。

9. カスタムクラスレベル検証

クラスの複数の属性を検証するために、クラスレベルでカスタム検証注釈を定義することもできます。

このシナリオの一般的な使用例は、クラスの2つのフィールドに一致する値があるかどうかを確認することです。

9.1. 注釈の作成

後でクラスに適用できるFieldsValueMatchという新しいアノテーションを追加しましょう。 注釈には、比較するフィールドの名前を表す2つのパラメーターfieldfieldMatchがあります。

@Constraint(validatedBy = FieldsValueMatchValidator.class)
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldsValueMatch {

    String message() default "Fields values don't match!";

    String field();

    String fieldMatch();

    @Target({ ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @interface List {
        FieldsValueMatch[] value();
    }
}

カスタムアノテーションには、クラスで複数のFieldsValueMatchアノテーションを定義するためのListサブインターフェイスも含まれていることがわかります。

9.2. バリデーターの作成

次に、実際の検証ロジックを含むFieldsValueMatchValidatorクラスを追加する必要があります。

public class FieldsValueMatchValidator
  implements ConstraintValidator {

    private String field;
    private String fieldMatch;

    public void initialize(FieldsValueMatch constraintAnnotation) {
        this.field = constraintAnnotation.field();
        this.fieldMatch = constraintAnnotation.fieldMatch();
    }

    public boolean isValid(Object value,
      ConstraintValidatorContext context) {

        Object fieldValue = new BeanWrapperImpl(value)
          .getPropertyValue(field);
        Object fieldMatchValue = new BeanWrapperImpl(value)
          .getPropertyValue(fieldMatch);

        if (fieldValue != null) {
            return fieldValue.equals(fieldMatchValue);
        } else {
            return fieldMatchValue == null;
        }
    }
}

isValid()メソッドは、2つのフィールドの値を取得し、それらが等しいかどうかを確認します。

9.3. 注釈の適用

ユーザー登録に必要なデータを対象としたNewUserFormモデルクラスを作成しましょう。このクラスには、2つのemail属性とpassword属性、および2つのverifyEmail属性とverifyPassword属性があります。 2つの値を再入力します。

対応する一致するフィールドに対してチェックする2つのフィールドがあるので、NewUserFormクラスに2つの@FieldsValueMatchアノテーションを追加しましょう。1つはemail値用、もう1つはpassword値用です。

@FieldsValueMatch.List({
    @FieldsValueMatch(
      field = "password",
      fieldMatch = "verifyPassword",
      message = "Passwords do not match!"
    ),
    @FieldsValueMatch(
      field = "email",
      fieldMatch = "verifyEmail",
      message = "Email addresses do not match!"
    )
})
public class NewUserForm {
    private String email;
    private String verifyEmail;
    private String password;
    private String verifyPassword;

    // standard constructor, getters, setters
}

Spring MVCでモデルを検証するために、@Validで注釈が付けられたNewUserFormオブジェクトを受け取り、検証エラーがあるかどうかを検証する/userPOSTマッピングを使用してコントローラーを作成しましょう。

@Controller
public class NewUserController {

    @GetMapping("/user")
    public String loadFormPage(Model model) {
        model.addAttribute("newUserForm", new NewUserForm());
        return "userHome";
    }

    @PostMapping("/user")
    public String submitForm(@Valid NewUserForm newUserForm,
      BindingResult result, Model model) {
        if (result.hasErrors()) {
            return "userHome";
        }
        model.addAttribute("message", "Valid form");
        return "userHome";
    }
}

9.4. 注釈のテスト

カスタムクラスレベルのアノテーションを検証するために、一致する情報を/userエンドポイントに送信し、応答にエラーが含まれていないことを確認するJUnitテストを作成しましょう。

public class ClassValidationMvcTest {
  private MockMvc mockMvc;

    @Before
    public void setup(){
        this.mockMvc = MockMvcBuilders
          .standaloneSetup(new NewUserController()).build();
    }

    @Test
    public void givenMatchingEmailPassword_whenPostNewUserForm_thenOk()
      throws Exception {
        this.mockMvc.perform(MockMvcRequestBuilders
          .post("/user")
          .accept(MediaType.TEXT_HTML).
          .param("email", "[email protected]")
          .param("verifyEmail", "[email protected]")
          .param("password", "pass")
          .param("verifyPassword", "pass"))
          .andExpect(model().errorCount(0))
          .andExpect(status().isOk());
    }
}

次に、一致しない情報を/userエンドポイントに送信するJUnitテストを追加し、結果に2つのエラーが含まれることを表明しましょう。

@Test
public void givenNotMatchingEmailPassword_whenPostNewUserForm_thenOk()
  throws Exception {
    this.mockMvc.perform(MockMvcRequestBuilders
      .post("/user")
      .accept(MediaType.TEXT_HTML)
      .param("email", "[email protected]")
      .param("verifyEmail", "[email protected]")
      .param("password", "pass")
      .param("verifyPassword", "passsss"))
      .andExpect(model().errorCount(2))
      .andExpect(status().isOk());
    }

10. 概要

この簡単な記事では、カスタムバリデータを作成してフィールドまたはクラスを検証し、それらをSpring MVCに配線する方法を示しました。

いつものように、あなたは記事over on Githubからコードを見つけることができます。