Spring MVC Custom Validation

Spring MVC Custom Validation

1. Überblick

Wenn Benutzereingaben überprüft werden müssen, bietet Spring MVC im Allgemeinen vordefinierte Standardüberprüfungen an.

Wenn wir jedoch eine bestimmte Typeneingabe validieren müssen,we have the possibility of creating our own, custom validation logic.

In diesem Artikel tun wir genau das. Wir erstellen einen benutzerdefinierten Validator, um ein Formular mit einem Telefonnummernfeld zu validieren, und zeigen dann einen benutzerdefinierten Validator für mehrere Felder an.

2. Konfiguration

Fügen Sie die Abhängigkeit zu Ihrerpom.xml-Datei hinzu, um von der API zu profitieren:


    org.hibernate
    hibernate-validator
    6.0.10.Final

Die neueste Version der Abhängigkeit kann inhere überprüft werden.

Wenn wir Spring Boot verwenden, können wir nur diespring-boot-starter-web, hinzufügen, wodurch auch die Abhängigkeit vonhibernate-validatorentsteht.

3. Benutzerdefinierte Validierung

Um einen benutzerdefinierten Validator zu erstellen, müssen wir unsere eigenen Anmerkungen einführen und in unserem Modell verwenden, um die Validierungsregeln durchzusetzen.

Erstellen wir also unserecustom validator – which checks phone numbers. Die Telefonnummer muss aus mehr als acht Ziffern bestehen, darf jedoch nicht länger als 11 Ziffern sein.

4. Die neue Anmerkung

Erstellen wir ein neues@interface, um unsere Anmerkung zu definieren:

@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 {};
}

Mit der Annotation@Constraint haben wir die Klasse definiert, die unser Feld validieren soll,message() ist die Fehlermeldung, die in der Benutzeroberfläche angezeigt wird, und der zusätzliche Code ist der Code, der am besten mit dem Code übereinstimmt Federstandards.

5. Validator erstellen

Erstellen wir jetzt eine Validator-Klasse, die die Regeln unserer Validierung erzwingt:

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);
    }

}

Die Validierungsklasse implementiert die SchnittstelleConstraintValidatorund muss die MethodeisValidimplementieren. In dieser Methode haben wir unsere Validierungsregeln definiert.

Natürlich verwenden wir hier eine einfache Validierungsregel, um zu zeigen, wie der Validator funktioniert.

ConstraintValidator ddefiniert die Logik zum Validieren einer bestimmten Einschränkung für ein bestimmtes Objekt. Implementierungen müssen die folgenden Einschränkungen erfüllen:

  • Das Objekt muss in einen nicht parametrisierten Typ aufgelöst werden

  • Generische Parameter des Objekts müssen unbegrenzte Platzhaltertypen sein

6. Anwenden der Validierungsanmerkung

In unserem Fall haben wir eine einfache Klasse mit einem Feld erstellt, um die Validierungsregeln anzuwenden. Hier richten wir unser mit Anmerkungen versehenes Feld für die Validierung ein:

@ContactNumberConstraint
private String phone;

Wir haben ein Zeichenfolgenfeld definiert und es mit unserer benutzerdefinierten Anmerkung@ContactNumberConstraint. kommentiert. In unserem Controller haben wir unsere Zuordnungen erstellt und den Fehler ggf. behandelt:

@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";
    }
}

Wir haben diesen einfachen Controller definiert, der eine einzelneJSP-Seite hat, und verwenden diesubmitForm-S-Methode, um die Validierung unserer Telefonnummer zu erzwingen.

7. Die Aussicht

Unsere Ansicht ist eine einfache JSP-Seite mit einem Formular, das ein einzelnes Feld enthält. Wenn der Benutzer das Formular absendet, wird das Feld von unserem benutzerdefinierten Validierer validiert und mit der Meldung über die erfolgreiche oder fehlgeschlagene Validierung auf dieselbe Seite weitergeleitet:


    
    
    
    

8. Tests

Lassen Sie uns nun unseren Controller testen und prüfen, ob er uns die richtige Antwort und Ansicht gibt:

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

Testen wir außerdem anhand von Benutzereingaben, ob unser Feld validiert ist:

@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());
}

Im Test geben wir einem Benutzer die Eingabe von "123" und - wie erwartet - funktioniert alles undwe’re seeing the error on the client side.

9. Validierung der benutzerdefinierten Klassenebene

Eine benutzerdefinierte Validierungsanmerkung kann auch auf Klassenebene definiert werden, um mehr als ein Attribut der Klasse zu validieren.

In diesem Szenario wird häufig überprüft, ob zwei Felder einer Klasse übereinstimmende Werte aufweisen.

9.1. Anmerkung erstellen

Fügen wir eine neue Anmerkung mit dem NamenFieldsValueMatch hinzu, die später auf eine Klasse angewendet werden kann. Die Anmerkung enthält zwei Parameterfield undfieldMatch, die die Namen der zu vergleichenden Felder darstellen:

@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();
    }
}

Wir können sehen, dass unsere benutzerdefinierte Annotation auch eineList-Unterschnittstelle zum Definieren mehrererFieldsValueMatch-Annotationen für eine Klasse enthält.

9.2. Validator erstellen

Als nächstes müssen wir dieFieldsValueMatchValidator-Klasse hinzufügen, die die eigentliche Validierungslogik enthält:

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;
        }
    }
}

Die MethodeisValid() ruft die Werte der beiden Felder ab und prüft, ob sie gleich sind.

9.3. Anwenden der Anmerkung

Erstellen wir eineNewUserForm-Modellklasse für Daten, die für eine Benutzerregistrierung erforderlich sind. Sie enthält zweiemail- undpassword-Attribute sowie zweiverifyEmail- undverifyPassword-Attribute um die beiden Werte erneut einzugeben.

Da wir zwei Felder haben, um sie mit den entsprechenden übereinstimmenden Feldern zu vergleichen, fügen wir zwei@FieldsValueMatch-Anmerkungen zur KlasseNewUserFormhinzu, eines füremail-Werte und eines fürpassword-Werte:

@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
}

Um das Modell in Spring MVC zu validieren, erstellen wir einen Controller mit einer POST-Zuordnung von/user, der ein mit@Valid annotiertesNewUserForm-Objekt empfängt und überprüft, ob Validierungsfehler vorliegen:

@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. Testen der Anmerkung

Um unsere Annotation auf benutzerdefinierter Klassenebene zu überprüfen, schreiben wir einenJUnit-Test, der übereinstimmende Informationen an den Endpunkt von/userendet, und überprüfen dann, ob die Antwort keine Fehler enthält:

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());
    }
}

Als Nächstes fügen wir außerdem einenJUnit-Test hinzu, der nicht übereinstimmende Informationen an den Endpunkt von/userendet, und bestätigen, dass das Ergebnis zwei Fehler enthält:

@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. Zusammenfassung

In diesem kurzen Artikel haben wir gezeigt, wie benutzerdefinierte Validatoren erstellt werden, um ein Feld oder eine Klasse zu überprüfen und sie mit Spring MVC zu verbinden.

Wie immer finden Sie den Code aus dem Artikelover on Github.