Datenvalidierung - Java- und Spring-Validierung

Das Validieren von Klassendaten (Bean) in Java ist kein neues Thema, aber es ist auch hier relevant und ich werde verschiedene Aspekte kombinieren: Datenvalidierung in JSR-303, ich werde zeigen, wie dies rein in Java und mit Spring gemacht wird, wie es in einer Standardanwendung und im Web gemacht wird.

Inhalt: Datenvalidierung (JSR-303) in

  • Standard Java-Anwendung
  • mit Spring
  • Java + Spring Association
  • Frühling mvc

Validierung in einer Standard-Java-Anwendung


Um das Objekt zu überprüfen, werden Anmerkungen zu den Feldern der Klasse verwendet, d.h. deklaratives Modell. Anmerkungen sind bereits fertig:
Null , @DecimalMin, @Digits, Pattern , Email usw.
, und Sie können auch Ihre eigenen machen. Und so gibt es eine Klasse (Bohne)

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

Hier im Beispiel sind Size und @Digits fertige Annotationen und @PersonAgeConstraint eigene. So machen Sie Ihre eigenen:

- Bereiten Sie die Anmerkung vor

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

Geben Sie in message () den Schlüssel (value.negative) aus der Ressourcendatei (ValidationMessages.properties) für die Nachricht an
value.negative = Negativer Wert
und Implementierung der Verifizierungsklasse - PersonAgeConstraintValidator.class

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

Meine eigene Anmerkung ist fertig, wir fügen sie dem Feld hinzu und Sie können bereits überprüfen, ob alle Felder, in denen Anmerkungen vorhanden sind, durch die entsprechenden Regeln überprüft werden.

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

Ergebnis in der Konsole

3-



Nachrichten für Standardanmerkungen können in der Regel in der Nachrichtendatei angegeben werden:

AnnotationName.entity.fieldname=

Projektstruktur

Bild

POM-Datei
 <?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> 


Stufenweise Überprüfung. Für Class <?> [] Groups () können Sie die Klassentypen angeben, nach denen Sie sie dann gruppieren können, und die Liste der Prüfungen einschränken, d. H. als Filter verwenden. So kann die Überprüfung schrittweise erfolgen: 1) Zum Beispiel teilen wir die Untersuchung einer Person aus gesundheitlichen Gründen auf, 2) und dann professionelle Daten. Wir werden zwei Anmerkungen vorbereiten
HealthConstraint und ProfessionalConstraint und Implementierungen für sie. Zuerst überprüfen wir die Einhaltung der Gesundheit und dann, wenn es durch die Gesundheit geht, überprüfen wir professionelle Daten.

Ein Beispiel:

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

Beispiel für eine HealthConstraint-Annotation

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

Implementierungsbeispiel für HealthConstraintValidator

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

Für ProfessionalConstraint ist alles gleich

Nächste Prüfung wie folgt:

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

Solche Überprüfungen sind beispielsweise erforderlich, wenn wir Daten aus einer Datei, einem Webdienst und anderen Quellen laden.

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


Validierung mit Spring


Spring hat auch eine eigene Validator-Oberfläche.
(org.springframework.validation.Validator)
wie in Java
(javax.validation.Validator)
und genau seine Implementierung validiert die Daten. Dies ist kein deklarativer Ansatz mehr, sondern hat seine eigene Flexibilität und Erweiterbarkeit. Für den gleichen Behälter mache ich die gleiche Altersüberprüfung.

Durch Überschreiben der beiden Methoden validieren wir

 @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 - ist auch der Schlüssel in der Nachrichtendatei. public boolean supports bestimmt den Typ der unterstützten Klasse.

Die Überprüfung wird über den DataBinder gestartet

Ein Beispiel:

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

Alle Überprüfungen, die org.springframework.validation.Validator für die Person-Klasse implementiert haben, werden durchgeführt.

Sie können auch mehrere Validatoren hinzufügen, dataBinder.addValidators. Sie können eine Zusammenstellung von Regeln erstellen (ein Aufruf von einer Regel, einer anderen), zum Beispiel:

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

Aus irgendeinem Grund habe ich erwartet, dass Spring auch die in den Anmerkungen angegebenen Überprüfungen durchführt, aber nein, dieser Aufruf muss unabhängig erfolgen.

Projektstruktur

Bild

POM-Datei
 <?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 & Frühling


Natürlich möchte ich bei der Datenüberprüfung zwei Ansätze verwenden - Java und Spring. Sie können sie kombinieren, nämlich den Aufruf javax.validation.Validator zu Spring validator hinzufügen.

Beispiel

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

Mit der Feder javax.validation.Validator wir javax.validation.Validator

@Autowired
private Validator validator;


weiter auf der Methode public void validate(Object obj, Errors errors)
Führen Sie deklarative Überprüfungen für Java durch und führen Sie dann alle Überprüfungen für die Person-Klasse für spring org.springframework.validation.Validator durch.

Wir führen die Überprüfung auch bis zum Frühjahr durch

  @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() // .... 

Jetzt in der Sammlung gibt es Überprüfungen gegen Java- und Spring-Annotationen (org.springframework.validation.Validator) für Person

Konsolenausgabe

()
3- ()
(spring)


Projektstruktur

Bild

Frühling mvc


Natürlich kann dies alles jetzt in einer Webanwendung angewendet werden.

Wir fügen dem Projekt den Controller, die JSP-Seite hinzu (hier gibt es übrigens möglicherweise andere Optionen, z. B. das Generieren von Seiten mit freeMarker usw.), den CSS-Stil und die POM-Abhängigkeit. Und so in Ordnung

1) MVC-Controller

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

Hier mit Federeinspritzung PersonValidator angeschlossen

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


Setzen Sie PersonValidator auf initBinder

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


Validierung ausgelöst durch @Valid Annotation
In diesem Fall wird nur die Federprüfung durchgeführt, deklarative Prüfungen werden ignoriert.

Wenn aus dem Code entfernt

@InitBinder
protected void initBinder(WebDataBinder binder)


Im Gegenteil, alle deklarativen Prüfungen werden durchgeführt und die Feder wird ignoriert.
Um alle deklarativen und Frühjahrsprüfungen abzuschließen, können Sie Folgendes tun:

@InitBinder entfernen, Injektion lassen

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


und fügen Sie manuell einen Federprüfungsaufruf hinzu

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


Hier ist der 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 } } 

d.h. zusätzliche Schecks vom Frühling werden zu bindingResult hinzugefügt :-), was ich wollte!

Die modelAttribute="person" in jsp und model erfolgt über das Attribut - modelAttribute="person" Im Beispiel ist die Form-Tag-Bibliothek von SpringMVC verbunden.

Die verbleibenden Ressourcen dieses Beispiels sind:

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


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


POM-Datei
 <?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> 


Projektstruktur

Bild

Bewerbungsarbeit

Bild

Material

Bean-Validierungsspezifikation

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


All Articles