📜 ⬆️ ⬇️

Data Verification - Java & Spring Validation

Checking class data (bean) in java is not a new topic, but it’s relevant here, and I’ll combine various aspects: validation of data in JSR-303, show you how to do it purely in Java and using Spring, how to do it in a standard application and on the Web.

Content: Data Validation (JSR-303) in


Validation in a standard Java application


To verify the object, annotations are used in the class fields, i.e. declarative model. Annotations are already ready:
Null , @DecimalMin, @Digits, Pattern , Email , etc.
and you can also make your own. And so there is a class (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; } } 

Here, in the Size and @Digits example, there are ready annotations, and @PersonAgeConstraint is own. How to make your own:

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

In message () we specify the key (value.negative) from the resource file (ValidationMessages.properties) for the message
value.negative = Negative \ u0020 value
and implementation of the validation class - PersonAgeConstraintValidator.class

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

My own annotation is ready, we add it to the field and it is already possible to check, all the fields on which there are annotations will be checked by the relevant rules.

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

Result in the console

3-



Messages for standard annotations can be specified in the message file, by the rule:

AnnotationName.entity.fieldname=

Project structure

image

pom file
 <?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> 


Phased check. For Class <?> [] Groups (), you can specify the types of classes by which you can then group, limit the list of checks, i.e. use as a filter. Thus, the check can be done in stages, 1) For example, we divide the face check for health reasons, 2) and then professional data. Prepare two annotations
HealthConstraint and ProfessionalConstraint and implementations for them. First, we will verify compliance with health and then if we pass on health, we will check for professional data.

Example:

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

HealthConstraint annotation example

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

Implementation example HealthConstraintValidator

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

for ProfessionalConstraint everything is similar

Next, check this:

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

Such checks, for example, are needed when we load data from a file, web service, and other sources.

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


Spring Validation


Spring also has its own Validator interface.
(org.springframework.validation.Validator)
like in java
(javax.validation.Validator)
and its implementation performs data validation. This is no longer a declarative approach, but it has its own flexibility and extensibility. For the same Bina, I’ll do a tighter age check.

Having redefined two methods, we do validation

 @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 is also a key in the message file, public boolean supports determine the type of class supported.

Check is started through DataBinder

Example:

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

All checks that implement org.springframework.validation.Validator for the Person class will be performed.

You can also add several validators, dataBinder.addValidators, you can make a composition of rules (call from one rule, another), for example:

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

For some reason I expected, Spring will also perform the checks specified in the annotations, but no, this call should be done independently.

Project structure

image

pom file
 <?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


Obviously, I want to use two approaches in data validation - Java and Spring, you can combine them, namely add a call to javax.validation.Validator in Spring validator .

Example

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

With the help of spring we make injection javax.validation.Validator

@Autowired
private Validator validator;


further on the public void validate(Object obj, Errors errors) method public void validate(Object obj, Errors errors)
perform declarative java checks, and then perform all checks for the Person class at spring org.springframework.validation.Validator.

We start the test also in spring

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

Now the collection will have checks from java and spring annotations (org.springframework.validation.Validator) for Person

Console output

()
3- ()
(spring)


Project structure

image

Spring MVC


Of course now all this can be applied in a web application.

Add to the project Controller, jsp page (here, by the way, there may be other options, for example, page generation using freeMarker, etc.), css style, pom dependency. And so in order

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

PersonValidator is connected via spring injection here.

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


install PersonValidator in initBinder

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


The check is initiated using the @Valid annotation @Valid
In this case, only spring check will be executed, declarative checks will be ignored.

If you remove from the code

@InitBinder
protected void initBinder(WebDataBinder binder)


then all declarative checks will be executed on the contrary, and spring will be ignored.
To perform all the checks, both declarative and spring, you can do this:

Remove @InitBinder, leave injection

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


and add call spring checks manually

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


Here is the 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 } } 

those. In the bindingResult, more checks from spring will be added :-), as we wanted!

Data binding in jsp and models is performed by the attribute - modelAttribute="person" In the example SpringMVC's Form Tag Library is connected.

The remaining resources of this example are:

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


Spring configuration
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 file
 <?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> 


Project structure

image

Application operation

image

Materials

Bean Validation specification

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


All Articles