SpringBoot validator parameter validation restful custom error code response mode

  • 2021-11-29 07:21:32
  • OfStack

Directory validator parameter validation restful custom error code response 1. Define restful system 1 result return 2. Define 1 error code enumeration 3. Statically encapsulate CommonResult 4. Define BaseController to deal with validation error custom error code return 5. Verify example summary 1. Use validator-api to validate springboot parameter 1. This method can be found on the Internet. 2. For the above example 3. Estimate that someone will ask here again

validator parameter validation restful custom error code response

There have been many articles about how to use Bean Validation API and hibernate-validator in spring web applications, so we will not repeat them in this article. Today, we will focus on how to respond to different custom error codes according to different validation errors in SpringBoot restful services. The code goes directly below.

1. Define restful 1 Result Return

"Error code" must be used for http/api open interfaces outside the company, while internal recommended exception throwing is applied; Result mode is preferred for cross-application RPC calls, encapsulating isSuccess () method, "error code" and "error brief message". Therefore, a return structure is also defined here.


public class CommonResult<T> implements Serializable {
	
	/**
	 * serialVersionUID:.
	 */
	private static final long serialVersionUID = -7268040542410707954L;
 
	/**
	 *  Whether it is successful or not 
	 */
	private boolean success = false;
 
	/**
	 *  Return information 
	 */
	private String message;
 
	/**
	 *  Installed in data 
	 */
	private T data;
 
	/**
	 *  Error code 
	 */
	private String code;
 
	/**
	 *  Default constructor 
	 */
	public CommonResult(){
		
	}
	/**
	 * 
	 * @param success
	 * 			 Whether it is successful or not 
	 * @param message
	 * 			 Message returned 
	 */
	public CommonResult(boolean success, String message){
		this.success = success;
		this.message = message;
	}
	/**
	 * 
	 * @param success
	 * 			 Whether it is successful or not 
	 */
	public CommonResult(boolean success){
		this.success = success;
	}
 
	/**
	 *
	 * @param code error code
	 * @param message success or error messages
	 */
	public CommonResult(String code,String message){
		this.code = code;
		this.message = message;
	}
	/**
	 * 
	 * @param success
	 * 			 Whether it is successful or not 
	 * @param message
	 * 			 Message 
	 * @param data
	 * 			 Data 
	 */
	public CommonResult(boolean success, String message, T data){
		this.success = success;
		this.message = message;
		this.data = data;
	}
    // Omission get set
}

2. Define an error code enumeration

For projects that need to be internationalized, it is better to configure them through i18n. Here, enumeration definitions are directly adopted for simplicity. The errors defined here are for reference only, and each application in different companies may not be 1 in actual situation.


/**
 *  Error code enumeration class 
 *
 */
public enum ErrorCodeEnum { 
    SUCCESS("0000", "success"), 
    PARAM_EMPTY("1001", " Required parameter is blank "), 
    PARAM_ERROR("1002", " Wrong parameter format "), 
    UNKNOWN_ERROR("9999", " The system is busy, please try again later ...."); 
    private String code; 
    private String desc; 
    ErrorCodeEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }
 
    public String getCode() {
        return this.code;
    } 
 
    public String getDesc() {
        return desc;
    }
 
    @Override
    public String toString() {
        return "[" + this.code + "]" + this.desc;
    }
}

3. Statically encapsulated CommonResult

Static encapsulation of CommonResult is mainly convenient to quickly write the return result code according to logic in the project.


/**
 *  Static method calls with public response results successful and failed 
 *
 */
public class ResultUtil {
 
 
    /**
     * return success
     *
     * @param data
     * @return
     */
    public static <T> CommonResult<T> returnSuccess(T data) {
        CommonResult<T> result = new CommonResult();
        result.setCode(ErrorCodeEnum.SUCCESS.getCode());
        result.setSuccess(true);
        result.setData(data);
        result.setMessage(ErrorCodeEnum.SUCCESS.getDesc());
        return result;
    }
 
    /**
     * return error
     *
     * @param code error code
     * @param msg  error message
     * @return
     */
    public static CommonResult returnError(String code, String msg) {
        CommonResult result = new CommonResult();
        result.setCode(code);
        result.setData("");
        result.setMessage(msg);
        return result; 
    }
 
    /**
     * use enum
     *
     * @param status
     * @return
     */
    public static CommonResult returnError(ErrorCodeEnum status) {
        return returnError(status.getCode(), status.getDesc());
    }
}

4. Define BaseController to handle validation errors. Custom error code return


/**
 * BaseController
 *
 */
public abstract class BaseController { 
    private static final Logger LOGGER = LoggerFactory.getLogger(BaseController.class); 
    /**
     * validate params
     *
     * @param bindingResult
     * @return
     */
    protected CommonResult validParams(BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();
            return processBindingError(fieldError);
        }
        return ResultUtil.returnSuccess("");
    }
 
    /**
     *  According to spring binding  Error message customization returns error code and error message 
     *
     * @param fieldError
     * @return
     */
    private CommonResult processBindingError(FieldError fieldError) {
        String code = fieldError.getCode();
        LOGGER.debug("validator error code: {}", code);
        switch (code) {
            case "NotEmpty":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_EMPTY.getCode(), fieldError.getDefaultMessage());
            case "NotBlank":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_EMPTY.getCode(), fieldError.getDefaultMessage());
            case "NotNull":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_EMPTY.getCode(), fieldError.getDefaultMessage());
            case "Pattern":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Min":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Max":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Length":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Range":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Email":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "DecimalMin":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "DecimalMax":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Size":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Digits":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Past":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            case "Future":
                return ResultUtil.returnError(ErrorCodeEnum.PARAM_ERROR.getCode(), fieldError.getDefaultMessage());
            default:
                return ResultUtil.returnError(ErrorCodeEnum.UNKNOWN_ERROR);
        }
    }
}

5. Verify the instance

Here is a simple example of parameter verification.

Controller inherits BaseController written above


/**
 *  About Validator Use test 
 *
 */ 
@RestController
@RequestMapping("validator")
public class ValidatorTestController extends BaseController { 
    private static final Logger LOGGER = LoggerFactory.getLogger(ValidatorTestController.class); 
    /**
     * validate Verification test 
     *
     * @param leader
     * @param bindingResult
     * @return
     */
    @PostMapping("/test")
    public CommonResult testSimpleValidate(@Valid @RequestBody Leader leader, BindingResult bindingResult) {
        LOGGER.debug("ReqParams:{}", JSON.toJSONString(leader));
        CommonResult result = validParams(bindingResult);
        if (!result.isSuccess()) {
            return result;
        }
        return ResultUtil.returnSuccess("");
    }
}

Input object Leader code


public class Leader { 
    /**
     *  Name 
     */
    @NotEmpty
    private String name;
 
    /**
     *  Birthday 
     */
    @Pattern(regexp = "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", message = " Date of Birth Format Incorrect ")
    private String birthday;
 
    /**
     *  Age 
     */
    @Min(value = 0)
    private Integer age; 
    // Omission gettes and  setters 
}

The project is ready to return custom error codes and prompts based on validation errors.

The source code involved in this example: https://github.com/shalousun/api-doc-test

Summary 1

In some applications that provide restful to external services, it is inevitable to return according to different verification errors. Of course, there are ways to realize it, but the way adopted in this paper is relatively simple and easy to understand.

Validate the parameters of springboot using validator-api

As a server-side development, it is an essential step to verify the legitimacy of the parameters passed in from the front end, but verifying the parameters is basically a physical activity, and there are many redundant codes, which also affect the readability of the codes, so is there an elegant way to solve this problem?

Of course, such a simple problem has long been encountered and solved by great gods. This article mainly talks about a better method to solve the verification parameters based on spring-boot: using validator-api to verify parameters.

In the spring-boot-starter-web package, there is hibernate-validator package, which provides a series of methods to verify various parameters, so spring-boot has helped us figure out how to solve this problem.

This article introduces three ways to validate parameters for spring-mvc in spring-boot.

1. This method can be found on the Internet for the most part

Let's assume that our restful interface accepts an object of type GradeAndClassroomModel, and this class is defined as


@Data
public class GradeAndClassroomModel {  
@Range(min = 1, max = 9, message = " Grade can only be obtained from 1-9")  
private int grade;  
@Range(min = 1, max = 99, message = " Classes can only be started from 1-99")  
private int classroomNumber;
}

Using the 1 series of annotations provided by validator, such as @ Range in this example, you can express the range of parameters and the prompt information when an error occurs. There are many other annotations, which are not listed in 11 here

Then our Controller layer code is


@RequestMapping(value = "/paramErrorTest", method = RequestMethod.GET)
public String paramErrorTest(    
  @Valid    
  @ModelAttribute    
  GradeAndClassroomModel gradeAndClassroomModel, 
  BindingResult result) {  
  return classroomService.getTeacherName(gradeAndClassroomModel.getGrade(), gradeAndClassroomModel.getClassroomNumber());
}

If the validation error, result object will have an error message, and then you can handle it yourself.

2. For the above example

Some people will say, just two parameters, why should they be objects? Will it be too much trouble? Indeed, if there are only a few objects, just write the parameters directly to the Controller layer and then validate them at the Controller layer.


@RequestMapping(value = "/teacherName", method = RequestMethod.GET)
public String teacherName(
  @Range(min = 1, max = 9, message = " Grade can only be obtained from 1-9")        
  @RequestParam(name = "grade", required = true) 
  int grade,  
  @Min(value = 1, message = " The smallest class can only 1")    
  @Max(value = 99, message = " The maximum class can only 99")      
  @RequestParam(name = "classroom", required = true)    
  int classroom) {  
return classroomService.getTeacherName(grade, classroom);
}

Is it OK to remove the annotations provided by validator and write them to the request parameters? The answer is wrong. Why can't you validate the parameters successfully? For specific reasons, please refer to the official documents:

The above documentation is clear, so we need to create an Bean


@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {  
  return new MethodValidationPostProcessor();
}

Then add the annotation @ Validated to the class method


/**
 *  Error code enumeration class 
 *
 */
public enum ErrorCodeEnum { 
    SUCCESS("0000", "success"), 
    PARAM_EMPTY("1001", " Required parameter is blank "), 
    PARAM_ERROR("1002", " Wrong parameter format "), 
    UNKNOWN_ERROR("9999", " The system is busy, please try again later ...."); 
    private String code; 
    private String desc; 
    ErrorCodeEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }
 
    public String getCode() {
        return this.code;
    } 
 
    public String getDesc() {
        return desc;
    }
 
    @Override
    public String toString() {
        return "[" + this.code + "]" + this.desc;
    }
}
0

Then the annotations provided in validator packages, such as @ Range, @ Min, @ Max, which did not take effect before, can take effect.

3. It is estimated that someone will ask again when they get here

If the annotations in the validator package cannot meet our needs, can we define the logic of parameter validation by ourselves? The answer is yes, we can use


/**
 *  Error code enumeration class 
 *
 */
public enum ErrorCodeEnum { 
    SUCCESS("0000", "success"), 
    PARAM_EMPTY("1001", " Required parameter is blank "), 
    PARAM_ERROR("1002", " Wrong parameter format "), 
    UNKNOWN_ERROR("9999", " The system is busy, please try again later ...."); 
    private String code; 
    private String desc; 
    ErrorCodeEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }
 
    public String getCode() {
        return this.code;
    } 
 
    public String getDesc() {
        return desc;
    }
 
    @Override
    public String toString() {
        return "[" + this.code + "]" + this.desc;
    }
}
1

And


/**
 *  Error code enumeration class 
 *
 */
public enum ErrorCodeEnum { 
    SUCCESS("0000", "success"), 
    PARAM_EMPTY("1001", " Required parameter is blank "), 
    PARAM_ERROR("1002", " Wrong parameter format "), 
    UNKNOWN_ERROR("9999", " The system is busy, please try again later ...."); 
    private String code; 
    private String desc; 
    ErrorCodeEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }
 
    public String getCode() {
        return this.code;
    } 
 
    public String getDesc() {
        return desc;
    }
 
    @Override
    public String toString() {
        return "[" + this.code + "]" + this.desc;
    }
}
2

Combination of custom, specific examples of other articles on the Internet is a lot, here will not carry out detailed examples, but the final use of the time is


  @RequestMapping(value = "/paramValidator", method = RequestMethod.GET)
  public String paramValidator(
      @ParamValidator(isRequired = true, desc = " Grade ", range = "int:1~9", message = " Grade can only be obtained from 1-9")
      @RequestParam(name = "grade", required = true)
      int grade,
      @ParamValidator(isRequired = true, desc = " Class ", range = "int:1~99", message = " Classes can only be started from 1-99")
      @RequestParam(name = "classroom", required = true)
      int classroom) {
    return classroomService.getTeacherName(grade, classroom);
  }

Also, don't forget the MethodValidationPostProcessor mentioned in Method 2, bean. If this bean is not initialized, the custom authentication method will not be executed. Validation logic will fail.

Is it better to write annotations to verify the parameters of the request, and the code logic is clearer and more elegant? The meaning of expression will be better and clearer? And there is no large number of repeated similar verification codes.

Ps: The code here is based on spring-mvc framework to test. If someone uses jersey as rest framework instead of spring-mvc framework, some details may need to be adjusted, but these three schemes should be compatible.


Related articles: