Spring Boot automatically encapsulates and validates form entities with REST API requests

Keywords: Spring Java Maven Apache

Read Spring.io valid form validation demo and tutorials on the official website. Let me start with the official spring book tutorial, and then briefly talk about some of the ways in which unified validation management is written to facilitate lazy people or code optimization.

Form annotations require that the project rely on the hibernate-validtor component, which has been brought with it in spring-boot-starter-web and no longer relies on maven.

Create a Spring boot project. The pom file is as follows:

<?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>org.springframework</groupId>
    <artifactId>gs-validating-form-input</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
     </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

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

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Definition entity

Define a PersonForm entity and fill in annotations

package hello;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class PersonForm {
    @NotNull
    @Size(min=2, max=30)
    private String name;

    @NotNull
    @Min(18)
    private Integer age;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String toString() {
        return "Person(Name: " + this.name + ", Age: " + this.age + ")";
    }
}

The PersonForm class validates two pieces of information, one is name, which limits the length between 2 and 30 bits, and the other is age, which limits the minimum value of 18.

Create a Controller

@RestController
@RequestMapping("/test")
public class TestController {
    @PostMapping("test1")
    public AjaxResponse test(@Valid PersonForm personForm,BindingResult bindingResult){//[1]
   if (bindingResult.hasErrors()) {//[2]
            List<FieldError> errorList = bindingResult.getFieldErrors();
            String errorMsg = errorList.get(0).getField() + " Field error, cause of error:" +       errorList.get(0).getDefaultMessage();
            return error(errorMsg, FIELD_VALIDATE_ERROR.getKey());
        } else {
            return error(GLOBAL_UNKNOWN_ERROR);
        }    
    return ok("suc");
    }
}
  1. Define a Rest Controller, add a Valid annotation at 1
  2. The second parameter defines the BindingResult object, which Spring will automatically inject into us.
  3. The Controller method is used to verify the success of this request. If the BindingResult object is not passed here, Spring will throw a BindException exception upward. If we do not handle it, Spring will do an html rendering exception to the exception internally.

Start the project, make a post request http://localhost:8080/test/test1 using postman or other HTTP request simulation plug-ins, and return different information by filling in the parameters that are in accordance with the specifications.

Through examples, I think you have learned to use Valid annotations, but the above explanations are mainly for each specific method of controller verification, not to do unified verification processing. It illustrates a problem that if every Controller writes like this and wants to kill people, there may be many requests that will use this object, then whether it has written a lot of scrap code to deal with it in this way, it is not very clear that Spring's official tutorial is so simple and has not done in-depth exploration. Here, the kid explores the upper exception handler to handle the unified BindException.

Spring Boot defines uniform upper-level exception interceptors

stay Spring mvc 3.2 introduces a Controller Advice annotation This component can be used to handle exceptions thrown up by the Controller layer through the following steps

1. Create a GlobalExceptionHandler class and add Controller Advice annotations to it

package com.iflytek.adsring.rosp.config;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

import static com.iflytek.adsring.rosp.api.AjaxResponse.error;
import static com.iflytek.adsring.rosp.api.message.ServiceCode.FIELD_VALIDATE_ERROR;
import static com.iflytek.adsring.rosp.api.message.ServiceCode.GLOBAL_UNKNOWN_ERROR;

/**
 * @author yoqu
 * @date 2017 June 30, 2000
 * @time 2:53 p.m.
 * @email wcjiang2@iflytek.com
 */
@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public AjaxResponse errorHandler(HttpServletRequest request, Exception e) {
        logger.error("request page url{}, params:{}, error:{}", request.getRequestURI(), request.getParameterMap(), e);
        return error(e.getMessage(), ServiceCode.GLOBAL_EXCEPTION_ERROR.getKey());
    }

    @ExceptionHandler(BindException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public AjaxResponse validateErrorHandler(BindException e) {
        BindingResult bindingResult = e.getBindingResult();
        if (bindingResult.hasErrors()) {
            List<FieldError> errorList = bindingResult.getFieldErrors();
            String errorMsg = errorList.get(0).getField() + " Field error, cause of error:" + errorList.get(0).getDefaultMessage();
            return error(errorMsg, FIELD_VALIDATE_ERROR.getKey());
        } else {
            return error(GLOBAL_UNKNOWN_ERROR);
        }
    }
}
  1. Write an @ExceptionHandler annotation for the method, value for intercepted exceptions, Exception for the maximum level of exceptions, if a subclass exception is not defined, all exceptions will be accessed to the method.
  2. ResponseStatus, if an exception occurs, a status code needs to be returned to the foreground for processing.
  3. ResponseBody, return json string

In ValidateErrorHandler, we know that the failure of validation results in the occurrence of BindException exceptions. Then we do an exception handling at this level to generate the final results by logging and returning entity objects.

2. Method of rewriting the previous controller layer

@RestController
@RequestMapping("/test")
public class TestController {
    @PostMapping("test1")
    public AjaxResponse test(@Valid PersonForm personForm){//[1]
    return ok("suc");
    }
}

At one point, we delete the second parameter, BindingResult, passed before, so that when the validation fails, the exception will be thrown upward, and our global interceptor will intercept the processing.

Some classes used:
AjaxResponse Entities for Printing Messages to the Front Desk

package com.iflytek.adsring.rosp.api;
import com.alibaba.fastjson.JSON;
import java.io.Serializable;
import java.util.Map;
/**
 * @author yoqu
 * @date 2017 June 29, 2000
 * @time 2:29 p.m.
 * @email wcjiang2@iflytek.com
 */
public class AjaxResponse implements Serializable {

    /**
     * Success or not
     */
    private boolean flag;

    /**
     * Response?
     */
    private int code;

    /**
     * Business Status Code
     */
    private String stateCode;

    /**
     * data
     */
    private Object data;

    /**
     * news
     */
    private String msg;

    public AjaxResponse() {

    }

    public AjaxResponse(boolean flag, String msg) {
        this.flag = flag;
        this.msg = msg;
    }

    public AjaxResponse(boolean flag, String msg, Object data) {
        this(flag, msg);
        this.data = data;
    }

    public AjaxResponse(boolean flag, String msg, String stateCode, Object data) {
        this(flag, msg, data);
        this.stateCode = stateCode;
    }

    public static AjaxResponse ok(Object data) {
        return ok("success", data);
    }

    public static AjaxResponse ok(String msg){
        return ok(msg,null);
    }

    public static AjaxResponse ok(String msg, Object data) {
        return new AjaxResponse(true, msg, data);
    }

    public static AjaxResponse error(String msg, String stateCode, Object data) {
        return new AjaxResponse(false, msg, stateCode, data);
    }

    public static AjaxResponse error(Map.Entry<String,String> code) {
        return new AjaxResponse(false,code.getValue(),code.getKey(),null);
    }

    public static AjaxResponse error(Map.Entry<String,String> code,Object data) {
        return new AjaxResponse(false,code.getValue(),code.getKey(),data);
    }

    public static AjaxResponse error(String msg, String stateCode) {
        return error(msg, stateCode, null);
    }

    public static AjaxResponse error(String msg, Object data) {
        return error(msg, "", data);
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getStateCode() {
        return stateCode;
    }

    public void setStateCode(String stateCode) {
        this.stateCode = stateCode;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public String toJSON() {
        return JSON.toJSONString(this);
    }
}

System Error Code Service Code

package com.iflytek.adsring.rosp.api.message;

import java.util.AbstractMap;
import java.util.Map;

/**
 * @author yoqu
 * @date 2017 June 30, 2000
 * @time 2:48 p.m.
 * @email wcjiang2@iflytek.com
 * System Error Code Correspondence Library
 */
public class ServiceCode {
    public static Map.Entry<String, String> FIELD_VALIDATE_ERROR = new AbstractMap.SimpleEntry<String, String>("100000000", "Parameter error");
    public static Map.Entry<String, String> GLOBAL_EXCEPTION_ERROR = new AbstractMap.SimpleEntry<String, String>("100000001", "Systematic anomaly");
    public static Map.Entry<String, String> GLOBAL_UNKNOWN_ERROR = new AbstractMap.SimpleEntry<String, String>("100000002", "unknown error");
}

Posted by fusioneko on Mon, 17 Jun 2019 14:26:25 -0700