Spring MVC Custom Validation

1. Overzicht

Over het algemeen biedt Spring MVC standaard voorgedefinieerde validators als we gebruikersinvoer moeten valideren.

Als we echter een meer bepaald type invoer moeten valideren, we hebben de mogelijkheid om onze eigen, aangepaste validatielogica te creëren.

In dit artikel doen we precies dat: we maken een aangepaste validator om een ​​formulier met een telefoonnummerveld te valideren en tonen vervolgens een aangepaste validator voor meerdere velden.

Dit artikel richt zich op Spring MVC. In ons artikel Validatie in Spring Boot wordt beschreven hoe u aangepaste validaties kunt uitvoeren in Spring Boot.

2. Installatie

Om te profiteren van de API, voegt u de afhankelijkheid toe aan uw pom.xml het dossier:

 org.hibernate hibernate-validator 6.0.10.Final 

De laatste versie van de afhankelijkheid kan hier worden gecontroleerd.

Als we Spring Boot gebruiken, kunnen we alleen het spring-boot-starter-web, die de slaapstand-validator afhankelijkheid ook.

3. Aangepaste validatie

Het maken van een aangepaste validator houdt in dat we onze eigen annotatie uitrollen en deze in ons model gebruiken om de validatieregels af te dwingen.

Dus laten we onze aangepaste validator - die telefoonnummers controleert. Het telefoonnummer moet een nummer zijn van meer dan acht cijfers maar niet meer dan 11 cijfers.

4. De nieuwe aantekening

Laten we een nieuw maken @koppel om onze annotatie te definiëren:

@Documented @Constraint (validatedBy = ContactNumberValidator.class) @Target ({ElementType.METHOD, ElementType.FIELD}) @Retention (RetentionPolicy.RUNTIME) openbaar @interface ContactNumberConstraint {String message () standaard "Ongeldig telefoonnummer"; Klasse [] groepen () standaard {}; Klasse [] payload () standaard {}; }

Met de @Beperking annotatie hebben we de klasse gedefinieerd die ons veld gaat valideren, de bericht() is de foutmelding die wordt weergegeven in de gebruikersinterface en de aanvullende code is de meest standaardcode die voldoet aan de Spring-normen.

5. Een validator maken

Laten we nu een validatorklasse maken die de regels van onze validatie afdwingt:

public class ContactNumberValidator implementeert ConstraintValidator {@Override public void initialize (ContactNumberConstraint contactNumber) {} @Override public boolean isValid (String contactField, ConstraintValidatorContext cxt) {return contactField! = null && contactField.matches ("[0&] +") (contactField.length ()> 8) && (contactField.length () <14); }}

De validatieklasse implementeert het ConstraintValidator interface en moet het is geldig methode; het is in deze methode dat we onze validatieregels hebben gedefinieerd.

Natuurlijk gaan we hier met een eenvoudige validatieregel, om te laten zien hoe de validator werkt.

ConstraintValidator ddefinieert de logica om een ​​bepaalde beperking voor een bepaald object te valideren. Implementaties moeten voldoen aan de volgende beperking:

  • het object moet worden omgezet naar een niet-geparametriseerd type
  • generieke parameters van het object moeten onbegrensde jokertekens zijn

6. Validatie-annotatie toepassen

In ons geval hebben we een eenvoudige klasse gemaakt met één veld om de validatieregels toe te passen. Hier stellen we ons geannoteerde veld in om te worden gevalideerd:

@ContactNumberConstraint privé String-telefoon;

We hebben een stringveld gedefinieerd en het geannoteerd met onze aangepaste annotatie @ContactNumberConstraint. In onze controller hebben we onze toewijzingen gemaakt en de eventuele fout afgehandeld:

@Controller openbare klasse ValidatedPhoneController {@GetMapping ("/ validatePhone") openbare String loadFormPage (Model m) {m.addAttribute ("validatedPhone", nieuwe ValidatedPhone ()); retourneer "phoneHome"; } @PostMapping ("/ addValidatePhone") public String submitForm (@Valid ValidatedPhone validatedPhone, BindingResult resultaat, Model m) {if (result.hasErrors ()) {return "phoneHome"; } m.addAttribute ("bericht", "Telefoon succesvol opgeslagen:" + validatedPhone.toString ()); retourneer "phoneHome"; }}

We hebben deze eenvoudige controller gedefinieerd met een enkele JSP pagina en gebruik de submitForm methode om de validatie van ons telefoonnummer af te dwingen.

7. Het uitzicht

Onze weergave is een eenvoudige JSP-pagina met een formulier dat een enkel veld heeft. Wanneer de gebruiker het formulier verzendt, wordt het veld gevalideerd door onze aangepaste validator en wordt het omgeleid naar dezelfde pagina met het bericht van succesvolle of mislukte validatie:

 Telefoon: 

8. Tests

Laten we nu onze controller testen en controleren of deze ons het juiste antwoord en beeld geeft:

@Test openbare ongeldig gegevenPhonePageUri_whenMockMvc_thenReturnsPhonePage () {this.mockMvc. perform (get ("/ validatePhone")). andExpect (view (). name ("phoneHome")); }

Laten we ook testen of ons veld is gevalideerd, op basis van gebruikersinvoer:

@Test openbare leegte gegevenPhoneURIWithPostAndFormData_whenMockMVC_thenVerifyErrorResponse () {this.mockMvc.perform (MockMvcRequestBuilders.post ("/ addValidatePhone"). Accept ("phone"). Param ("phone"). Param ("phone"). andExpect (model (). attributeHasFieldErrorCode ("validatedPhone", "phone", "ContactNumberConstraint")). andExpect (view (). name ("phoneHome")). andExpect (status (). isOk ()). andDo (print ()); }

In de test geven we een gebruiker de invoer van "123" en - zoals we hadden verwacht - werkt alles en we zien de fout aan de kant van de klant.

9. Validatie op klasniveau

Een aangepaste validatie-annotatie kan ook op klasniveau worden gedefinieerd om meer dan één attribuut van de klas te valideren.

Een veelvoorkomend gebruiksscenario voor dit scenario is het verifiëren of twee velden van een klasse overeenkomende waarden hebben.

9.1. De annotatie maken

Laten we een nieuwe annotatie toevoegen met de naam FieldsValueMatch die later op een klas kan worden toegepast. De annotatie heeft twee parameters veld- en fieldMatch die de namen van de te vergelijken velden vertegenwoordigen:

@Constraint (validatedBy = FieldsValueMatchValidator.class) @Target ({ElementType.TYPE}) @Retention (RetentionPolicy.RUNTIME) openbaar @interface FieldsValueMatch {String message () standaard "Veldenwaarden komen niet overeen!"; Tekenreeksveld (); Tekenreeks fieldMatch (); @Target ({ElementType.TYPE}) @Retention (RetentionPolicy.RUNTIME) @interface List {FieldsValueMatch [] waarde (); }}

We kunnen zien dat onze aangepaste annotatie ook een Lijst subinterface voor het definiëren van meerdere FieldsValueMatch annotaties over een klas.

9.2. De validator maken

Vervolgens moeten we het FieldsValueMatchValidator klasse die de feitelijke validatielogica zal bevatten:

openbare klasse FieldsValueMatchValidator implementeert ConstraintValidator {privé String-veld; private String fieldMatch; public void initialiseren (FieldsValueMatch constraintAnnotation) {this.field = constraintAnnotation.field (); this.fieldMatch = constraintAnnotation.fieldMatch (); } openbare boolean isValid (Objectwaarde, ConstraintValidatorContext-context) {Object fieldValue = nieuwe BeanWrapperImpl (waarde) .getPropertyValue (veld); Object fieldMatchValue = nieuwe BeanWrapperImpl (waarde) .getPropertyValue (fieldMatch); if (fieldValue! = null) {return fieldValue.equals (fieldMatchValue); } else {return fieldMatchValue == null; }}}

De is geldig() methode haalt de waarden van de twee velden op en controleert of ze gelijk zijn.

9.3. De annotatie toepassen

Laten we een NewUserForm modelklasse bedoeld voor gegevens die nodig zijn voor gebruikersregistratie, dat heeft twee e-mail en wachtwoord attributen, samen met twee Verifieer Email en verifieerPassword attributen om de twee waarden opnieuw in te voeren.

Aangezien we twee velden hebben om te vergelijken met hun overeenkomstige overeenkomende velden, laten we er twee toevoegen @FieldsValueMatch annotaties op de NewUserForm klasse, een voor e-mail waarden, en een voor wachtwoord waarden:

@ FieldsValueMatch.List ({@FieldsValueMatch (field = "password", fieldMatch = "verifyPassword", message = "Wachtwoorden komen niet overeen!"), @FieldsValueMatch (field = "email", fieldMatch = "verifyEmail", message = " E-mailadressen komen niet overeen! ")}) Public class NewUserForm {private String email; private String verificEmail; privé String-wachtwoord; private String verificPassword; // standard constructor, getters, setters}

Om het model in Spring MVC te valideren, maken we een controller met een /gebruiker POST-toewijzing die een NewUserForm object geannoteerd met @Geldig en controleert of er validatiefouten zijn:

@Controller openbare klasse NewUserController {@GetMapping ("/ user") openbare String loadFormPage (Modelmodel) {model.addAttribute ("newUserForm", nieuwe NewUserForm ()); retourneer "userHome"; } @PostMapping ("/ user") public String submitForm (@Valid NewUserForm newUserForm, BindingResult-resultaat, Modelmodel) {if (result.hasErrors ()) {return "userHome"; } model.addAttribute ("bericht", "Geldige vorm"); retourneer "userHome"; }}

9.4. De aantekening testen

Om onze aangepaste annotatie op klasniveau te verifiëren, schrijven we een JUnit test die overeenkomende informatie naar het /gebruiker endpoint, controleert vervolgens of het antwoord geen fouten bevat:

openbare klasse ClassValidationMvcTest {privé MockMvc mockMvc; @Before public void setup () {this.mockMvc = MockMvcBuilders .standaloneSetup (nieuwe NewUserController ()). Build (); } @Test openbare leegte gegevenMatchingEmailPassword_whenPostNewUserForm_thenOk () gooit uitzondering {this.mockMvc.perform (MockMvcRequestBuilders .post ("/ user") .accept (MediaType.TEXT_HTML). .Param ("email", "[email protected]"). ("verifyEmail", "[email protected]") .param ("password", "pass") .param ("verifyPassword", "pass")) .andExpect (model (). errorCount (0)) .andExpect ( status (). isOk ()); }}

Laten we vervolgens ook een JUnit test die niet-overeenkomende informatie naar het /gebruiker endpoint en beweer dat het resultaat twee fouten zal bevatten:

@Test openbare leegte gegevenNotMatchingEmailPassword_whenPostNewUserForm_thenOk () gooit uitzondering {this.mockMvc.perform (MockMvcRequestBuilders .post ("/ user") .accept (MediaType.TEXT_HTML) .param ("email", "[email protected]"). confirmEmail "," [email protected] ") .param (" password "," pass ") .param (" verifyPassword "," passsss ")) .andExpect (model (). errorCount (2)) .andExpect (status ( ) .isOk ()); }

10. Samenvatting

In dit korte artikel hebben we laten zien hoe u aangepaste validators kunt maken om een ​​veld of klasse te verifiëren en deze in Spring MVC kunt aansluiten.

Zoals altijd kun je de code uit het artikel op Github vinden.