Validation des données - Validation Java et Spring

La validation des données de classe (bean) en java n'est pas un nouveau sujet, mais il est également pertinent ici et je combinerai divers aspects: validation des données dans JSR-303, je montrerai comment le faire purement en Java et en utilisant Spring, comment le faire dans une application standard et sur le Web.

Contenu: Validation des données (JSR-303) dans

  • application java standard
  • en utilisant le printemps
  • Association Java + Spring
  • Spring mvc

Validation dans une application Java standard


Pour vérifier l'objet, des annotations sur les champs de la classe sont utilisées, c'est-à-dire modèle déclaratif. Les annotations sont déjà prêtes:
Null , @DecimalMin, @Digits, Pattern , Email , etc.
, et vous pouvez également créer le vôtre. Et donc il y a une classe (bean)

import javax.validation.constraints.Digits; import javax.validation.constraints.Size; public class Person { @Size(min=2, max=50) private String Name; @Digits(integer=3, fraction=0, message = "  3- ") @PersonAgeConstraint private Integer age; public Person(String name, Integer age) { Name = name; this.age = age; } } 

Ici, dans l'exemple, les annotations prêtes de taille et @ chiffres, et @PersonAgeConstraint propre. Comment faire votre propre:

- préparer l'annotation

 @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy=PersonAgeConstraintValidator.class) public @interface PersonAgeConstraint { String message() default "{value.negative}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } 

Dans message (), spécifiez la clé (value.negative) du fichier de ressources (ValidationMessages.properties) pour le message
value.negative = valeur négative
et implémentation de la classe de vérification - PersonAgeConstraintValidator.class

 public class PersonAgeConstraintValidator implements ConstraintValidator<PersonAgeConstraint, Integer> { @Override public boolean isValid(Integer age, ConstraintValidatorContext constraintValidatorContext) { return age > 0; } } 

Ma propre annotation est prête, nous l'ajoutons au champ et il est déjà possible de vérifier, tous les champs sur lesquels il y a des annotations seront vérifiés par les règles correspondantes.

 import javax.validation.Validator; /** * Test Validation */ public class DemoJValidationApplicationTests { //  Validator private static Validator validator; static { ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); validator = validatorFactory.usingContext().getValidator(); } @Test public void testValidators() { final Person person = new Person(" ", -4500); Set<ConstraintViolation<Person>> validates = validator.validate(person); Assert.assertTrue(validates.size() > 0); validates.stream().map(v -> v.getMessage()) .forEach(System.out::println); } } 

Résultat dans la console

3-



Les messages pour les annotations standard peuvent être spécifiés dans le fichier de messages, en règle générale:

AnnotationName.entity.fieldname=

Structure du projet

image

fichier pom
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>DemoJSRvalidation</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>DemoJSRvalidation</name> <description>Demo project for Spring Boot JSR-303 validation</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 


Vérification progressive. Pour Class <?> [] Groups (), vous pouvez spécifier les types de classes par lesquels vous pouvez ensuite les regrouper, limiter la liste des vérifications, c.-à-d. utiliser comme filtre. Ainsi, la vérification peut se faire par étapes, 1) Par exemple, nous divisons l'examen d'une personne pour des raisons de santé, 2) puis les données professionnelles. Nous préparerons deux annotations
HealthConstraint et ProfessionalConstraint et leurs implémentations. Nous vérifions d'abord le respect de la santé, puis si cela passe par la santé, nous vérifions les données professionnelles.

Un exemple:

 public class Person { @HealthConstraint(groups = Health.class) private Documents healthDocuments; @ProfessionalConstraint(groups = Professional.class) private Documents ProfessionalDocuments; //... } 

Exemple d'annotation HealthConstraint

 @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy=HealthConstraintValidator.class) public @interface HealthConstraint { String message() default "{health.documents}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } 

Exemple d'implémentation de HealthConstraintValidator

 public class HealthConstraintValidator implements ConstraintValidator<HealthConstraint, Documents> { @Override public boolean isValid(Documents documents, ConstraintValidatorContext constraintValidatorContext) { return documents.contains(" 1"); } } 

pour ProfessionalConstraint, tout est pareil

Vérifiez ensuite comme ceci:

  @Test public void healthAndProfessionalValidators() { final Person person = new Person(" ", 45); person.setHealthDocuments(new Documents(Arrays.asList(" 1", " 3"))); person.setProfessionalDocuments(new Documents(Arrays.asList(" 1", " 4"))); //    Set<ConstraintViolation<Person>> validates = validator.validate(person, Health.class); Assert.assertTrue(validates.size() == 0); //    ,  .  validates = validator.validate(person, Professional.class); Assert.assertTrue(validates.size() == 0); } 

Ces vérifications, par exemple, sont nécessaires lorsque nous chargeons des données à partir d'un fichier, d'un service Web et d'autres sources.

Documents de classe
 public class Documents { private List<String> tests = new ArrayList(); public Documents(List<String> tests) { this.tests.addAll(tests); } public boolean contains(String test) { return this.tests.contains(test); } } 


Validation à l'aide de Spring


Spring a également sa propre interface Validator.
(org.springframework.validation.Validator)
comme en java
(javax.validation.Validator)
et c'est précisément sa mise en œuvre qui valide les données. Ce n'est plus une approche déclarative, mais elle a sa propre flexibilité et extensibilité. Pour le même bac, je ferai la même vérification d'âge.

En remplaçant les deux méthodes, nous validons

 @Service public class PersonValidator implements Validator { @Override public boolean supports(Class<?> aClass) { return Person.class.equals(aClass); } @Override public void validate(Object obj, Errors errors) { Person p = (Person) obj; if (p.getAge() < 0) { errors.rejectValue("age", "value.negative"); } } } 

value.negative - est également la clé du fichier de messages, public boolean supports déterminent le type de classe pris en charge.

La vérification est lancée via DataBinder

Un exemple:

 @RunWith(SpringRunner.class) @SpringBootTest public class DemoJValidationApplicationTests { //    private static final ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); static { messageSource.setBasename("message"); } @Autowired private PersonValidator personValidator; @Test public void testValidators() { final Person person = new Person(" ", -4500); final DataBinder dataBinder = new DataBinder(person); dataBinder.addValidators(personValidator); dataBinder.validate(); Assert.assertTrue(dataBinder.getBindingResult().hasErrors()); if (dataBinder.getBindingResult().hasErrors()) { dataBinder.getBindingResult().getAllErrors().stream(). forEach(e -> System.out.println(messageSource .getMessage(e, Locale.getDefault()))); } } } 

Toutes les vérifications qui ont implémenté org.springframework.validation.Validator pour la classe Person seront effectuées.

Vous pouvez également ajouter plusieurs validateurs, dataBinder.addValidators, vous pouvez faire une composition de règles (un appel d'une règle, une autre), par exemple:

 public class OtherValidator implements Validator { @Override public boolean supports(Class<?> aClass) { return Person.class.equals(aClass); } @Override public void validate(Object obj, Errors errors) { // ... } } //--------- @Service public class PersonValidator implements Validator { /** *   */ @Autowired private OtherValidator otherValidator; @Override public void validate(Object obj, Errors errors) { Person p = (Person) obj; if (p.getAge() < 0) { errors.rejectValue("age", "value.negative"); } //   ,   otherValidator.validate(obj, errors); } } 

Pour une raison quelconque, je m'attendais à ce que Spring effectue également les vérifications indiquées dans les annotations, mais non, cet appel doit être effectué indépendamment.

Structure du projet

image

fichier pom
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>DemoJSRvalidation</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>DemoJSRvalidation</name> <description>Demo project for Spring Boot JSR-303 validation</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 


Java & Spring


Évidemment, je veux utiliser deux approches dans la vérification des données - Java et Spring, vous pouvez les combiner, à savoir ajouter l'appel javax.validation.Validator au validateur Spring.

Exemple

 import javax.validation.Validator; @Service public class PersonValidator implements org.springframework.validation.Validator { // javax.validation.Validator @Autowired private Validator validator; @Override public boolean supports(Class<?> aClass) { return Person.class.equals(aClass); } @Override public void validate(Object obj, Errors errors) { Set<ConstraintViolation<Object>> validates = validator.validate(obj); for (ConstraintViolation<Object> constraintViolation : validates) { String propertyPath = constraintViolation.getPropertyPath().toString(); String message = constraintViolation.getMessage(); errors.rejectValue(propertyPath, "", message); } Person p = (Person) obj; if (p.getAge() < 0) { errors.rejectValue("age", "only.positive.numbers"); } } } 

En utilisant le ressort, nous faisons l'injection javax.validation.Validator

@Autowired
private Validator validator;


plus loin sur la méthode public void validate(Object obj, Errors errors)
effectuer des vérifications déclaratives sur java, puis effectuer toutes les vérifications de la classe Person sur spring org.springframework.validation.Validator.

Nous effectuons également une vérification au printemps

  @Test public void testValidators() { final Person person = new Person("", -4500); final DataBinder dataBinder = new DataBinder(person); dataBinder.addValidators(personValidator); dataBinder.validate(); if (dataBinder.getBindingResult().hasErrors()) { dataBinder.getBindingResult().getAllErrors() // .... 

Maintenant, dans la collection, il y aura des vérifications par rapport aux annotations Java et Spring (org.springframework.validation.Validator) pour Person

Sortie console

()
3- ()
(spring)


Structure du projet

image

Spring mvc


Bien sûr, maintenant tout cela peut être appliqué dans une application Web.

Nous ajoutons le contrôleur, la page jsp au projet (ici, en passant, il peut y avoir d'autres options, par exemple, générer des pages en utilisant freeMarker, etc.), le style css, la dépendance pom. Et donc pour

1) Contrôleur MVC

 import org.springframework.validation.Validator; @Controller public class DemoJValidationController { @Autowired @Qualifier("personValidator") // spring validator private Validator personValidator; @InitBinder protected void initBinder(WebDataBinder binder) { binder.setValidator(personValidator); } @GetMapping("/") public String savePersonAction(ModelMap model) { model.addAttribute("person", new Person(null, null)); return "personEdit"; } @RequestMapping(value = "/save", method = RequestMethod.POST) public String savePersonAction( @Valid @ModelAttribute("person") Person person, BindingResult bindingResult, Model model) { if (bindingResult.hasErrors()) { return "personEdit"; // to person.jsp page } model.addAttribute("name", person.getName()); model.addAttribute("age", person.getAge()); return "saveSuccess"; // to saveSuccess.jsp page } @RequestMapping(value = "/edit", method = RequestMethod.POST) public String editPersonAction(ModelMap model) { model.addAttribute("person", new Person(null, null)); return "personEdit"; // to personEdit.jsp page; } } 

Ici avec PersonValidator connecté par injection de ressort

@Autowired
@Qualifier("personValidator") // spring validator
private Validator personValidator;


définissez PersonValidator sur initBinder

@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(personValidator);
}


Validation déclenchée par l'annotation @Valid
Dans ce cas, seule la vérification du ressort sera effectuée, les vérifications déclaratives seront ignorées.

En cas de suppression du code

@InitBinder
protected void initBinder(WebDataBinder binder)


au contraire, tous les contrôles déclaratifs seront effectués et le ressort sera ignoré.
Pour effectuer tous les contrôles, déclaratifs et printaniers, vous pouvez procéder comme suit:

Supprimer @InitBinder, laisser l'injection

@Autowired
@Qualifier("personValidator") // spring validator
private Validator personValidator;


et ajouter manuellement un appel de vérification du printemps

// spring validate
personValidator.validate(person, bindingResult);


Voici le code:

 @Controller public class DemoJValidationController { @Autowired @Qualifier("personValidator") // spring validator private Validator personValidator; //... @RequestMapping(value = "/save", method = RequestMethod.POST) public String savePersonAction( @Valid @ModelAttribute("person") Person person, BindingResult bindingResult, Model model) { // spring validate personValidator.validate(person, bindingResult); if (bindingResult.hasErrors()) { return "personEdit"; // to person.jsp page } model.addAttribute("name", person.getName()); model.addAttribute("age", person.getAge()); return "saveSuccess"; // to saveSuccess.jsp page } } 

c'est-à-dire des vérifications supplémentaires du printemps seront ajoutées à bindingResult :-), c'est ce que je voulais!

La liaison de données dans jsp et le modèle est effectuée par l'attribut - modelAttribute="person" Dans l'exemple, la bibliothèque de balises de formulaire de SpringMVC est connectée.

Les ressources restantes de cet exemple sont:

DemoJValidationApplication
 @SpringBootApplication @ImportResource("classpath:configuration.xml") public class DemoJValidationApplication { public static void main(String[] args) { SpringApplication.run(DemoJValidationApplication.class, args); } } 


Configuration du ressort
configuration.xml
 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/c" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:message"/> <property name="defaultEncoding" value="UTF-8"/> </bean> <mvc:annotation-driven/> <mvc:resources mapping="/resources/**" location="classpath:/META-INF/resources/"/> </beans> 


personEdit.jsp
 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <link href="<c:url value="/resources/my.css" />" rel="stylesheet"> <title>Person</title> </head> <body> <h3> Enter Person. </h3> <form:form method="POST" modelAttribute="person" action="save"> <div> Name: <form:input path="name"/> <form:errors path="name" cssClass="error"/> </div> <div> Age: <form:input path="age"/> <form:errors path="age" cssClass="error"/> </div> <button type="submit">Registration</button> </form:form> </body> </html> 


saveSuccess.jsp
 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <link href="<c:url value="/resources/my.css" />" rel="stylesheet"> <title>Person Saved Successfully</title> </head> <body> <h3> Person Saved Successfully. </h3> <form:form method="POST" modelAttribute="person" action="edit"> <div> ${name} </div> <div> ${age} </div> <button type="submit">Edit</button> </form:form> </body> </html> 


my.css
 span.error { color: red; } form div{ margin: 5px; } 


fichier pom
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>DemoJSRvalidation</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>DemoJSRvalidation</name> <description>Demo project for Spring Boot JSR-303 validation</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>4.1.0.Final</version> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 


Structure du projet

image

Travail d'application

image

Matériaux

Spécification de validation du bean

Source: https://habr.com/ru/post/fr424819/


All Articles