The good ones are not as good as the ones I wrote. Hello, everyone, I’m Java class representative. A JavaWeb developer on the front line. If you want to see more wonderful articles, welcome to follow my official account, Java class representative.
The introduction
I don’t know how to write the parameter verification of controller layer in the normal business development process. Is there a direct judgment like the following?
public String add(UserVO userVO) {
if(userVO.getAge() == null) {return "Age cannot be empty.";
}
if(userVO.getAge() > 120) {return "No more than 120.";
}
if(userVO.getName().isEmpty()){
return "User name cannot be empty";
}
// omit a bunch of parameter verification...
return "OK";
}
Copy the code
Business code has not started to write, optical parameter verification to write a bunch of judgment. There’s nothing wrong with writing this, but it comes across as unpolished and unprofessional.
The Spring framework already wraps a set of validation components for us: Validation. It is characterized by ease of use and high degree of freedom. Next, the class representative used Springboot-2.3.1. RELEASE to build a simple Web project and explained step by step how to do parameter verification gracefully in the development process.
1. Environment construction
Starting from Springboot-2.3, the verification package is independent as a starter component, so the following dependencies need to be introduced:
<! <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <! -- Web component --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId> Spring-boot-starter -web</artifactId> </dependency>Copy the code
Versions prior to SpringBoot-2.3 only needed to introduce Web dependencies.
2. Test the cat
Parameter verification is very simple, first add verification rule annotations on the field to be verified
public class UserVO {
@notnull (message = "age cannot be empty ")
private Integer age;
}
Copy the code
Then add @validated and BindingResult for receiving error information to the Controller method, and you have the first version:
public String add1(@Validated UserVO userVO, BindingResult result) {
List<FieldError> fieldErrors = result.getFieldErrors();
if(! fieldErrors.isEmpty()){return fieldErrors.get(0).getDefaultMessage();
}
return "OK";
}
Copy the code
If the parameter does not meet the rule, the corresponding message will be returned:
Age cannot be emptyCopy the code
There are many built-in validation annotations, listed as follows:
annotations | Check the function |
---|---|
@AssertFalse | Must be false |
@AssertTrue | It must be true |
@DecimalMax | Less than or equal to a given value |
@DecimalMin | Greater than or equal to a given value |
@Digits | Maximum number of integer and decimal digits can be set |
Verify that the Email format is correct | |
@Future | It has to be in the future |
@FutureOrPresent | Present or future time |
@Max | The maximum |
@Min | The minimum value |
@Negative | Negative numbers (excluding 0) |
@NegativeOrZero | Negative or zero |
@NotBlank | Is not null and contains at least one non-whitespace character |
@NotEmpty | Not null and not null |
@NotNull | Not null |
@Null | null |
@Past | It has to be in the past |
@PastOrPresent | It has to be the past, including the present |
@PositiveOrZero | Positive or zero |
@Size | Validates the number of elements in the container |
3. Normalize return values
When there are too many verification parameters, we hope to return all verification failure information at one time, which is convenient for interface callers to adjust. In this case, we need to unify the return format, and the common one is to encapsulate a result class.
public class ResultInfo<T>{
private Integer status;
private String message;
private T response;
// omit other code...
}
Copy the code
Modify the Controller method, version 2:
public ResultInfo add2(@Validated UserVO userVO, BindingResult result) {
List<FieldError> fieldErrors = result.getFieldErrors();
List<String> collect = fieldErrors.stream()
.map(o -> o.getDefaultMessage())
.collect(Collectors.toList());
return new ResultInfo<>().success(400."Request parameter error",collect);
}
Copy the code
When this method is requested, all error arguments are returned:
{
"status": 400."message": "Request parameter error"."response": [
"Must be between [1,120] years of age"."Bg field has a maximum of 3 integer bits and a maximum of 1 decimal place."."Name cannot be empty"."Email format error"]}Copy the code
4. Global exception handling
It would still be tedious to write the BindingResult information in each Controller method. Check exceptions can be handled uniformly through global exception handling.
Spring will throw an exception when we have an @validated annotation and no BindingResult. As a result, you can write a global exception handling class to handle this validation exception uniformly, eliminating the need to repeatedly organize the exception information.
The global exception handling class simply needs to annotate @RestControllerAdvice on the class and specify which exception to handle with the @ExceptionHandler annotation on the method that handles the corresponding exception.
@RestControllerAdvice
public class GlobalControllerAdvice {
private static final String BAD_REQUEST_MSG = "Client request parameter error";
// <1> Handle the exception thrown when the form data call fails to validate the interface
@ExceptionHandler(BindException.class)
public ResultInfo bindExceptionHandler(BindException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> collect = fieldErrors.stream()
.map(o -> o.getDefaultMessage())
.collect(Collectors.toList());
return new ResultInfo().success(HttpStatus.BAD_REQUEST.value(), BAD_REQUEST_MSG, collect);
}
// <2> Handle the exception thrown when the JSON request body invocation interface fails to validate
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultInfo methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> collect = fieldErrors.stream()
.map(o -> o.getDefaultMessage())
.collect(Collectors.toList());
return new ResultInfo().success(HttpStatus.BAD_REQUEST.value(), BAD_REQUEST_MSG, collect);
}
// <3> Handle exceptions thrown when a single parameter fails to validate
@ExceptionHandler(ConstraintViolationException.class)
public ResultInfo constraintViolationExceptionHandler(ConstraintViolationException e) { Set<ConstraintViolation<? >> constraintViolations = e.getConstraintViolations(); List<String> collect = constraintViolations.stream() .map(o -> o.getMessage()) .collect(Collectors.toList());return newResultInfo().success(HttpStatus.BAD_REQUEST.value(), BAD_REQUEST_MSG, collect); }}Copy the code
In fact, in the global exception handling class, we can write more than one exception handling method. The class representative summarized three kinds of exceptions that can be thrown when checking parameters:
- Call the interface using form Data to verify that the exception throws a BindException
- Using json request body call interface, check exception thrown MethodArgumentNotValidException
- A single parameter validation exception thrown ConstraintViolationException
Note: To verify a single parameter, add a verification comment on the parameter and mark @Validated on the class.
The global Exception handling class can add a variety of exceptions that need to be handled, such as adding an Exception. Class Exception. When all ExceptionHandler cannot handle the Exception, it will record the Exception information and return a friendly prompt.
5. Group verification
If different verification rules need to be applied to the same parameter in different scenarios, group verification is required. For example, if the newly registered user does not have a name, we allow the name field to be empty, but do not allow the name to be updated to a null character.
There are three steps for packet verification:
- Define a grouping class (or interface)
- Add to the validation annotation
groups
Attribute specifies grouping Controller
methods@Validated
Annotations add grouping classes
public interface Update extends Default{}Copy the code
public class UserVO {
@notblank (message = "name cannot be blank ",groups = update.class)
private String name;
// omit other code...
}
Copy the code
@PostMapping("update")
public ResultInfo update(@Validated({Update.class}) UserVO userVO) {
return new ResultInfo().success(userVO);
}
Copy the code
Careful students may have noticed that the custom Update grouping interface inherits the Default interface. Check annotations (such as: @ NotBlank) and @ validated by Default are Default. The class groups, this point in the javax.mail. Validation. Groups. The Default comments
/**
* Default Jakarta Bean Validation group.
* <p>
* Unless a list of groups is explicitly defined:
* <ul>
* <li>constraints belong to the {@code Default} group</li>
* <li>validation applies to the {@code Default} group</li>
* </ul>
* Most structural constraints should belong to the default group.
*
* @author Emmanuel Bernard
*/
public interface Default {}Copy the code
When writing the Update grouping interface, if you inherit from Default, the following two statements are equivalent:
@Validated({Update.class})
@Validated({Update.class,Default.class})
The /update interface verifies not only the name field but also other fields that belong to the default. class group by Default
{
"status": 400."message": "Client request parameter error"."response": [
"Name cannot be empty"."Age cannot be empty."."Email cannot be empty"]}Copy the code
If Update does not inherit Default, @Validated({update. class}) verifies only the parameter fields in the update. class group.
{
"status": 400."message": "Client request parameter error"."response": [
"Name cannot be empty"]}Copy the code
6. Recursive check
If you add an OrderVO property to the UserVO class, and the property in OrderVO also needs to be validated, you can use recursive validation by adding the @VALID annotation to the property (also for collections).
OrderVO classes as follows
public class OrderVO {
@NotNull
private Long id;
@notblank (message = "itemName cannot be empty ")
private String itemName;
// omit other code...
}
Copy the code
Add an OrderVO type property to the UserVO class
public class UserVO {
@notblank (message = "name cannot be blank ",groups = update.class)
private String name;
// OrderVO needs recursive validation
@Valid
private OrderVO orderVO;
// omit other code...
}
Copy the code
Call request validation is as follows:
7. Customize the verification
Validation from Spring provides so many features that it can be used for most parameter validation scenarios in everyday development. However, a good framework is always easy to extend. With the ability to scale, you can deal with more complex business scenarios; after all, the only constant in development is change itself.
Spring Validation allows you to customize Validation. It is simple to implement in two steps:
- Custom validation annotations
- Write the validator class
The code is also very simple, combined with comments you can read it
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {HaveNoBlankValidator.class})// Specify which class performs the validation logic
public @interface HaveNoBlank {
// The message returned by default when a verification error occurs
String message(a) default"No Spaces in the string"; Class<? >[] groups()default{}; Class<? extends Payload>[] payload()default{};/** * use */ when multiple annotations are specified on the same element
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
public @interfaceList { NotBlank[] value(); }}Copy the code
public class HaveNoBlankValidator implements ConstraintValidator<HaveNoBlank.String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// null does not verify
if (value == null) {
return true;
}
if (value.contains("")) {
// Verification failed
return false;
}
// The verification succeeds
return true; }}Copy the code
Custom validation annotations are used in the same way as the built-in annotations. You can add the corresponding annotations to the required fields and verify them by yourself
review
That’s all about using Spring Validation to gracefully validate parameters. Let’s summarize the Validation features mentioned in this article
- Built-in a variety of common verification annotations
- Supports verification of a single parameter
- Automatic assembly of validation exceptions with global exception handling
- Packet check
- Recursive verification is supported
- Custom check
The code has been uploaded to GitHub
【 Previous recommendation 】
MySQL priority queue (order by limit)
Dubbo exception handling source code exploration and best practices
Freemarker Tutorial (I)- Template development Manual
RabbitMQ official tutorial translation
👇 code word is not easy, follow the Java class representative, get the latest Java dry goods 👇