[most complete in the whole network] JSR303 parameter verification and global exception handling (don't use if to judge parameters from theory to practice)

Posted by JJohnsenDK on Wed, 02 Mar 2022 04:11:35 +0100

1, Foreword

In our daily development, we cannot avoid parameter verification. Some people say that the front end will verify in the form? In the back-end, we can directly judge the filtering regardless of how the front-end judges it. Our back-end needs to judge again for security. Because the front end is easy to request. When the test uses PostMan to test, if the back end is not verified, it will be messy? There must be a lot of exceptions. Today, Xiaobian will learn from you that JSR303 is specially used for parameter verification. It can be regarded as a tool!

2, Introduction to JSR303

Bean validator 6 is a sub reference of the official JAVA implementation of the java-validator.
Hibernate Validator provides the implementation of all built-in constraints in JSR 303 specification. In addition, there are some additional constraints.

Hibernate official website

Introduction to the official website:

Validating data is a common task that occurs in all application layers, from the presentation layer to the persistence layer. Usually, the same verification logic is implemented at each layer, which is time-consuming and error prone. In order to avoid repeating these verifications, developers often bundle the verification logic directly into the domain model and mix the domain class with the verification code, which is actually the metadata about the class itself.

Jakarta Bean Validation 2.0 - defines metadata models and APIs for entity and method validation. The default metadata source is annotations, which can overwrite and extend metadata by using XML. The API does not depend on a specific application layer or programming model. In particular, it does not rely on the Web or persistence layer, and can be used for server-side application programming and rich client Swing application developers.

3, Import dependency

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

4, Common notes

Constraint annotation nameConstraint annotation description
@NullThe object used to verify is null
@NotNullThe object used cannot be null, and the string with length 0 cannot be checked
@NotBlankIt is only used on String type, cannot be null, and the size after trim() is > 0
@NotEmptyUsed for collection class, String class cannot be null, and size > 0. But the String with spaces cannot be verified
@SizeUsed to check whether the length of the object (Array,Collection,Map,String) is within the given range
@LengthThe size used for the String object must be within the specified range
@PatternRules for whether String objects conform to regular expressions
@EmailUsed to check whether the String object conforms to the mailbox format
@MinUsed to determine whether the Number and String objects are greater than or equal to the specified value
@MaxUsed to determine whether the Number and String objects are less than or equal to the specified value
@AssertTrueUsed to check whether the Boolean object is true
@AssertFalseUsed to determine whether the Boolean object is false

All of you refer to the jar package

5, The difference between @ Validated and @ Valid

@Validated:

  • Provided by Spring
  • Support group verification
  • Can be used on types, methods, and method parameters. However, it cannot be used on member properties (fields)
  • Because it cannot be added to the member attribute (field), the cascade verification cannot be completed alone. It needs to be combined with @ Valid

@Valid:

  • Provided by JDK (standard JSR-303 specification)
  • Group verification is not supported
  • Can be used on methods, constructors, method parameters, and member properties (fields)
  • It can be added to member attributes (fields) and can complete cascading verification alone

Summary: @ Validated is used when grouping. There are many student objects in a school object. You need to add @ Validated before the Controller method parameter, @ Valid is added to the student attribute in the school. If it is not added, the attribute in the student object cannot be verified!

Differential reference blog address

example:

@Data
public class School{

    @NotBlank
    private String id;
    private String name;
    @Valid                // It needs to be added, otherwise the verification annotation in the student class will not be verified
    @NotNull 			  // And the nested validation can only be performed when the validation of this field is triggered.
    private List<Student> list;
}

@Data
public class Student {

    @NotBlank
    private String id;
    private String name;
    private int age;
    
}

@PostMapping("/test")
public Result test(@Validated @RequestBody School school){

}

6, Common use test

1. Add verification for entity class

import lombok.Data;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.io.Serializable;

@Data
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * Brand id
	 */
	@NotNull(message = "Modification must have brand id")
	private Long brandId;
	/**
	 * Brand name F
	 */
	@NotBlank(message = "Brand name must be submitted")
	private String name;
	/**
	 * Brand logo address
	 */
	@NotBlank(message = "Address must not be empty")
	private String logo;
	/**
	 * introduce
	 */
	private String descript;
	
	/**
	 * Retrieve initials
	 */
	//regular expression 
	@Pattern(regexp = "^[a-zA-Z]$",message = "The first letter of the search must be a letter")
	private String firstLetter;
	/**
	 * sort
	 */
	@Min(value = 0,message = "Sort must be greater than or equal to 0")
	private Integer sort;

}

2. Unified return type

import com.alibaba.druid.util.StringUtils;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

//Unified return results
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel
public class Result<T> {
    @ApiModelProperty("Response code")
    private Integer code;
    @ApiModelProperty("Corresponding information")
    private String msg;
    @ApiModelProperty("Returns an object or collection")
    private T data;

    //Success code
    public static final Integer SUCCESS_CODE = 200;
    //Success message
    public static final String SUCCESS_MSG = "SUCCESS";

    //fail
    public static final Integer ERROR_CODE = 201;
    public static final String ERROR_MSG = "System abnormality,Please contact the administrator";
    //Response code without permission
    public static final Integer NO_AUTH_COOD = 999;

    //Successful execution
    public static <T> Result<T> success(T data){
        return new Result<>(SUCCESS_CODE,SUCCESS_MSG,data);
    }
    //Execution failed
    public static <T> Result failed(String msg){
        msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
        return new Result(ERROR_CODE,msg,"");
    }
    //Method of passing in error code
    public static <T> Result failed(int code,String msg){
        msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
        return new Result(code,msg,"");
    }
    //Data of incoming error code
    public static <T> Result failed(int code,String msg,T data){
        msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg;
        return new Result(code,msg,data);
    }
}

3. Testing

@PostMapping("/add")
public Result add(@Valid @RequestBody BrandEntity brandEntity)  {

    return Result.success("success");
}

What's wrong: Xiaobian has no problem adding it to the company's project, but it just can't trigger the verification. What we see is that the Springboot version is too high, so we need to add the following dependencies to trigger it.

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.18.Final</version>
</dependency>

4. General test results

5. We return the exception to the page

@PostMapping("/add")
public Result add(@Valid @RequestBody BrandEntity brandEntity, BindingResult bindingResult){

    if (bindingResult.hasErrors()){
        Map<String,String> map = new HashMap<>();
        bindingResult.getFieldErrors().forEach(item ->{
            map.put(item.getField(),item.getDefaultMessage());
        });
        return Result.failed(400,"The data submitted is not standard",map);
    }
    
    return Result.success("success");
}

6. Exception handling results

{
    "code": 400,
    "data": {
        "name": "Brand name must be submitted",
        "logo": "Address must not be empty"
    },
    "msg": "The data submitted is not standard"
}

7, Extract global exception handling

1. Experience

Above, we need to write on each verification interface, so we need to pull it out and make a global exception. And it needs to be improved. The original is to put the error information into data, but under normal circumstances, data is the data returned to the front end. If we put the abnormal data in this way, the data of data will be ambiguous. In this way, the front-end does not know whether it is data or error information. In this way, you can directly display the tips in msg on the front-end!

2. Write ExceptionControllerAdvice

import com.wang.test.demo.response.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice(basePackages = "com.wang.test.demo.controller")
public class ExceptionControllerAdvice {

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Result handleVaildException(MethodArgumentNotValidException e){

        log.error("Problem in data verification:{},Exception type:{}",e.getMessage(),e.getClass());
        BindingResult bindingResult = e.getBindingResult();
        StringBuffer stringBuffer = new StringBuffer();
        bindingResult.getFieldErrors().forEach(item ->{
            //Get error information
            String message = item.getDefaultMessage();
            //Get wrong property name
            String field = item.getField();
            stringBuffer.append(field + ":" + message + " ");
        });
        return Result.failed(400, stringBuffer + "");

    }

    @ExceptionHandler(value = Throwable.class)
    public Result handleException(Throwable throwable){

        log.error("error",throwable);
        return Result.failed(400, "System abnormality");
    }
}

3. Test results

{
    "code": 400,
    "data": "",
    "msg": "logo:Address must not be empty name:Brand name must be submitted "
}

8, Group check

1. Demand

When doing verification, we usually encounter the addition and modification of an entity class. Their verification rules are different, so grouping is particularly important. It can help us build less redundant entity classes, so we must be able to.

2. Create a grouping interface (no need to write anything)

public interface EditGroup {
}
public interface AddGroup {
}

3. Add a group to the field that needs ambiguity

/**
 * Brand id
 */
@NotNull(message = "Modification must have brand id",groups = {EditGroup.class})
@Null(message = "New cannot be specified id",groups = {AddGroup.class})
private Long brandId;
// Other properties remain unchanged

4. Add verification rules for different controllers

Note: we need to group, so @ Valid cannot be used. Use @ Validated instead. I believe you have seen the difference between them!

@PostMapping("/add")
public Result add(@Validated({AddGroup.class}) @RequestBody BrandEntity brandEntity){

    return Result.success("success");
}

@PostMapping("/edit")
public Result edit(@Validated({EditGroup.class}) @RequestBody BrandEntity brandEntity){

    return Result.success("success");
}

5. Test


9, Custom verification

1. Define a user-defined verifier

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;

//Write a custom validator
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {

    private Set<Integer> set=new HashSet<Integer>();

    //Initialization method
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] value = constraintAnnotation.vals();
        for (int i : value) {
            set.add(i);
        }
    }
    /**
     * Judge whether the verification is successful
     * @param value  Value to be verified
     * @param context
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return  set.contains(value);
    }
}

2. Define an annotation for use with the verifier

@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
    // Use this attribute to validate Properties
    String message() default "{com.atguigu.common.valid.ListValue.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    int[] vals() default {};

}

3. Add a new verification attribute to the entity class

Note: we have made grouping above. If the attribute does not specify grouping, it will not take effect. Now some of our attribute verification has not worked. Now only brandId and showStatus work.

/**
 * Display status [0-not displayed; 1-displayed]
 */
@NotNull(groups = {AddGroup.class, EditGroup.class})
@ListValue(vals = {0,1},groups = {AddGroup.class, EditGroup.class},message = "Must be 0 or 1")
private Integer showStatus;

4. Test


10, Summary

In this way, I almost have a basic understanding of JSR303, and there is no problem to meet the basic development! See here, collect and praise it. It's been sorted out for nearly a day!! Thank you!!

Only those who are destined to see their own website, welcome to visit!!!

Click to visit! Welcome to visit, there are many good articles in it!

Topics: Java Back-end