In this point

JDK1.8, SpringBoot2.3.4 release

  • This describes the necessity of verifying back-end parameters.
  • This section describes how to verify parameters using the Validator.
  • This section describes the difference between @Valid and @Validated.
  • Describes how to customize constraint annotations.
  • Bean Validation’s past life

The necessity of verifying back-end parameters

In development, data validation is a similarly logical but error-prone task from the presentation layer to the persistence layer,

Front-end frameworks often take some means to check parameters, such as verification and prompt messages, so, since the front-end has verification means, is it still necessary to back-end verification, is it unnecessary?

No, under normal circumstances, parameters do pass the front end verification to the back end, but if the back end does not do verification, once through special means to pass the detection of the front end, the system will appear security holes.

Do not use the argument handling logic of the Validator

If /else: if/else:

@PostMapping("/form") public String form(@RequestBody Person person) { if (person.getName() == null) { return "Name cannot be null"; } the if (person getName (). The length () < 6 | | person. The getName (). The length () > 12) {return "name length must be between 6 and 12". } if (person.getage () == null) {return "age cannot be null"; } if (person.getage () < 20) {return "minimum age 20"; } // service .. Return "Registration successful!" ; }Copy the code

It is easy to write, but there are too many if/else parameters, too bloated, and this is just two parameters of an interface, if you need more parameters verification, or even more methods need this verification, the amount of code can be expected. Therefore, this is obviously not desirable, we can use the following more elegant parameter handling.

Convenience provided by the Validator framework

Validating data is a common task that occurs throughout all application layers, from the presentation to the persistence layer. Often the same validation logic is implemented in each layer which is time consuming and error-prone.

It would be too cumbersome to perform similar checks on each level, as shown in the diagram below.

Jakarta Bean Validation 2.0 – defines a metadata model and API for entity and method Validation source are annotations, with the ability to override and extend the meta-data through the use of XML.

The API is not tied to a specific application tier nor programming model. It is specifically not tied to either web or persistence tier, and is available for both server-side application programming, as well as rich client Swing application developers.

Jakarta Bean Validation2.0 defines a metadata model and provides apis for data validation for entities and methods, with annotations as sources by default, which can be extended with XML.

ValidationAutoConfiguration SpringBoot automatic configuration

Hibernate Validator is a reference implementation of Jakarta Bean Validation.

In SpringBoot, Bean Validation is automatically enabled whenever an implementation of JSR-303, such as Hibernate Validator, exists on the classpath. We just need to import the spring-boot-starter-validation dependency to do what we need.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
Copy the code

The purpose is to introduce the following dependencies:

<! Glassfish </groupId> <artifactId> Jakarta. EL </artifactId> <version>3.0.3</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.hibernate.validator</groupId> < artifactId > hibernate validator - < / artifactId > < version > 6.1.5. The Final < / version > < scope > compile < / scope > < / dependency >Copy the code

SpringBoot support for BeanValidation automatic assembly defined in org. Springframework. Boot. Autoconfigure. Validation. ValidationAutoConfiguration class, Provides a default LocalValidatorFactoryBean and support method level MethodValidationPostProcessor interceptor.

@Configuration(proxyBeanMethods = false) @ConditionalOnClass(ExecutableValidator.class) @ConditionalOnResource(resources  = "classpath:META-INF/services/javax.validation.spi.ValidationProvider") @Import(PrimaryDefaultValidatorPostProcessor.class) public class ValidationAutoConfiguration { @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) @ConditionalOnMissingBean(Validator.class) public static LocalValidatorFactoryBean defaultValidator() { //ValidatorFactory LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean(); MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory(); factoryBean.setMessageInterpolator(interpolatorFactory.getObject()); return factoryBean; } // Support Aop, MethodValidationInterceptor method level of interceptor @ Bean @ ConditionalOnMissingBean public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment, @Lazy Validator validator) { MethodValidationPostProcessor processor = new MethodValidationPostProcessor(); boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true); processor.setProxyTargetClass(proxyTargetClass); // factory.getValidator(); The factoryBean obtains the Validator instance and sets the processor.setValidator(Validator). return processor; }}Copy the code

Validator+BindingResult gracefully handled

Dependencies are already introduced by default.

Define constraint annotations for the entity class

/ * * * plus javax.mail entity class field. The validation of the definition of constraints annotate * @ author Summerday * / @ Data @ ToString public class Person {private Integer id; @NotNull @Size(min = 6,max = 12) private String name; @NotNull @Min(20) private Integer age; }Copy the code

Use an @Valid or @Validated annotation

The functions of @Valid and @Validated method parameter verification at the Controller layer are similar. For details, see later.

@RestController public class ValidateController { @PostMapping("/person") public Map<String, Object> validatePerson(@Validated @RequestBody Person person, BindingResult result) { Map<String, Object> map = new HashMap<>(); If (result.haserrors ()) {List<String> res = new ArrayList<>(); result.getFieldErrors().forEach(error -> { String field = error.getField(); Object value = error.getRejectedValue(); String msg = error.getDefaultMessage(); Res.add (string. format(" error -> %s error -> %s cause -> %s", field, value, MSG)); }); map.put("msg", res); return map; } map.put("msg", "success"); System.out.println(person); return map; }}Copy the code

Send a Post request to forge invalid data

The HTTP Client tool provided by IDEA is used to send the request.

POST http://localhost:8081/person
Content-Type: application/json

{
  "name": "hyh",
  "age": 10
}
Copy the code

The response information is as follows:

HTTP/1.1 200 Content-Type: application/json Transfer-Encoding: chunked Date: Sat, 14 Nov 2020 15:58:17 GMT keep-alive: HTTP/1.1 200 Content-Type: application/json Transfer-Encoding: Chunked Date: Sat, 14 Nov 2020 15:58:17 GMT Keep-alive: timeout=60 Connection: keep-alive { "msg": [" error -> name error -> hyH cause -> Number must be between 6 and 12 ", "error -> age error -> 10 cause -> min < 20"]} Response code: 200; Time: 393ms; Content length: 92 bytesCopy the code

Validator + global exception handling

By BindingResult check and processing data in the interface methods in the process of information is a feasible scheme, but under the condition of the interface is numerous, is somewhat redundant, we can use the global exception handling, capture thrown MethodArgumentNotValidException abnormalities, And handle it accordingly.

Define global exception handling

@RestControllerAdvice public class GlobalExceptionHandler { /** * If the bean validation is failed, it will trigger a MethodArgumentNotValidException. */ @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Object> handleMethodArgumentNotValid( MethodArgumentNotValidException ex, HttpStatus status) { BindingResult result = ex.getBindingResult(); Map<String, Object> map = new HashMap<>(); List<String> list = new LinkedList<>(); result.getFieldErrors().forEach(error -> { String field = error.getField(); Object value = error.getRejectedValue(); String msg = error.getDefaultMessage(); List. add(string. format(" error -> %s error -> %s cause -> %s", field, value, MSG)); }); map.put("msg", list); return new ResponseEntity<>(map, status); }}Copy the code

Defines the interface

@RestController public class ValidateController { @PostMapping("/person") public Map<String, Object> validatePerson(@Valid @RequestBody Person person) { Map<String, Object> map = new HashMap<>(); map.put("msg", "success"); System.out.println(person); return map; }}Copy the code

@Validated Verifies the parameter field accurately

Sometimes, we only want to verify a parameter field, not the entire POJO object. We can use @Validated to verify exactly a field.

Defines the interface

@RestController @Validated public class OnlyParamsController { @GetMapping("/{id}/{name}") public String test(@PathVariable("id") @Min(1) Long id, @PathVariable("name") @Size(min = 5, max = 10) String name) { return "success"; }}Copy the code

Send a GET request to forge invalid information

GET http://localhost:8081/0/hyh
Content-Type: application/json
Copy the code

No processing is done, and the response results are as follows:

{"timestamp": "2020-11-15T15:23:29.734+00:00", "status": 500, "error": "Internal Server error", "trace": "Javax.mail. Validation. ConstraintViolationException: test id: minimum not less than 1, the test. The name: number must be between 5 and 10... Omit ", "message": "test.id: must be at least 1, test.name: must be between 5 and 10 ", "path": "/0/hyh"}Copy the code

As you can see, the check has been effective, but state and response error message is not quite right, we can capture ConstraintViolationException revision status.

Catch the exception and process the result

@ControllerAdvice public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler { private static final Logger log = LoggerFactory.getLogger(CustomGlobalExceptionHandler.class); /** * If the @Validated is failed, it will trigger a ConstraintViolationException */ @ExceptionHandler(ConstraintViolationException.class) public void constraintViolationException(ConstraintViolationException ex, HttpServletResponse response) throws IOException { ex.getConstraintViolations().forEach(x -> { String message = x.getMessage(); Path propertyPath = x.getPropertyPath(); Object invalidValue = x.getInvalidValue(); Log. error(" Error field -> {} error value -> {} cause -> {}", propertyPath, invalidValue, message); }); response.sendError(HttpStatus.BAD_REQUEST.value()); }}Copy the code

“@”, “Validated” and “Valid”

What is the difference between @Validated and @Valid? Teach you to use it to complete the Controller parameter verification (including cascading property verification) and principle analysis

  • The @VALID is a tag annotation of the standard JSR-303 specification. It is used to mark the return values of properties and methods for cascading and recursive verification.
  • @Validated: a annotation provided by Spring. It is a variant (supplement) of standard JSR-303 and provides a grouping function. Different authentication mechanisms can be used for input parameter verification according to different groupings.
  • There is no special difference between using @VALID and @validated when verifying method parameters in Controller (if no group verification is required).
  • The @validated annotation can be used at the class level to support Spring for method-level parameter verification. Valid can be used with attribute level constraints to represent cascading validation.
  • @Validated can only be used for classes, methods, and parameters, whereas @Valid can be used for methods, fields, constructors, and parameters.

How do I customize annotations

The Jakarta Bean Validation API defines a standard set of constraint annotations, such as @notnull, @size, etc. However, these built-in constraint annotations do not meet our needs. In this case, we can create a custom annotation.

  1. Create a Constraint annotation.
  2. Implement a Validator.
  3. Define a default error message.

Create a Constraint annotation

/** * @author Summerday */ @target ({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE}) @Retention(RUNTIME) @constraint (validatedBy = checkCasevalidator.class) // CheckCaseValidator @documented @Repeatable(CheckCase.List.class) public @interface CheckCase { String message() default "{CheckCase.message}"; Class<? >[] groups() default {}; Class<? extends Payload>[] payload() default {}; CaseMode value(); @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @interface List { CheckCase[] value(); }}Copy the code

Implement a Validator

/** * Implement ConstraintValidator ** @author Summerday */ public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> { private CaseMode caseMode; / / @override public void initialize(CheckCase constraintAnnotation) {this.casemode = constraintAnnotation.value(); } /** * Override public Boolean isValid(String object, ConstraintValidatorContext constraintContext) { if (object == null) { return true; } boolean isValid; if (caseMode == CaseMode.UPPER) { isValid = object.equals(object.toUpperCase()); } else { isValid = object.equals(object.toLowerCase()); } if (! IsValid) {/ / if the message is defined, by definition, not to / / ValidationMessages find CheckCase properties. The value of the message if(constraintContext.getDefaultConstraintMessageTemplate().isEmpty()){ constraintContext.disableDefaultConstraintViolation(); constraintContext.buildConstraintViolationWithTemplate( "{CheckCase.message}" ).addConstraintViolation(); } } return isValid; }}Copy the code

Define a default error message

In ValidationMessages. Defined in the properties file:

CheckCase.message=Case mode must be {value}.
Copy the code

This completes the custom annotation. If you are interested in testing it yourself, annotate a field: @checkCase (value = casemode.upper).

Download the source code

This article is a summary of the best blogs and official documents, and the original address is indicated in the references section. Finally, all the code samples have been uploaded to Gitee: gitee.com/tqbx/spring…

Three things to watch ❤️

If you find this article helpful, I’d like to invite you to do three small favors for me:

  1. Like, forward, have your “like and comment”, is the motivation of my creation.

  2. Follow the public account “Java rotten pigskin” and share original knowledge from time to time.

  3. Also look forward to the follow-up article ing🚀

  4. [666] Scan the code to obtain the learning materials package