Spring Boot elegantly implements interface parameter validation

Today, I will continue to share with you how to elegantly verify the validity of the parameters of the interface at work and how to uniformly handle the json format returned by the interface. Every word is dry goods, originality is not easy, sharing is not easy.

Spring Boot elegantly implements interface parameter validation

Validation is mainly to verify the legitimacy of the data submitted by the user, such as whether it is empty, whether the password conforms to the rules, whether the email format is correct, etc. There are many verification frameworks, and the most used is hibernate-validator, which also supports internationalization , you can also customize the annotation of the validation type. Here is just a simple demonstration of the simple integration of the validation framework in Spring Boot. For more information, please refer to hibernate-validator.

1. pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
复制代码

2. dto

public class UserInfoIDto {

    private Long id;

    @NotBlank
    @Length(min=3, max=10)
    private String username;

    @NotBlank
    @Email
    private String email;

    @NotBlank
    @Pattern(regexp="^((13[0-9])|(15[^4,\\D])|(18[0,3-9]))\\d{8}$", message="手机号格式不正确")
    private String phone;

    @Min(value=18)
    @Max(value = 200)
    private int age;

    @NotBlank
    @Length(min=6, max=12, message="昵称长度为6到12位")
    private String nickname;

     // Getter & Setter
}
复制代码

3. controller

import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;

@RestController
public class SimpleController {

    @PostMapping("/users")
    public String register(@Valid @RequestBody UserInfoIDto userInfoIDto, BindingResult result){
        if (result.hasErrors()) {
            FieldError fieldError = result.getFieldError();
            String field = fieldError.getField();
            String msg = fieldError.getDefaultMessage();

            return field + ":" + msg;
        }
        System.out.println("开始注册用户...");

        return "success";
    }
}
复制代码

Spring Boot elegantly implements interface parameter validation

4. Remove the BindingResult parameter

Each interface requires a BindingResult parameter, and each interface needs to handle error information, so adding a parameter is not elegant, and the amount of code for handling error information is also very repetitive. If the BindingResult parameter is removed, the system will report an error MethodArgumentNotValidException. We only need to use the global exception to catch the error, and the BindingResult parameter can be omitted after processing.

@RestController
public class SimpleController {

    @PostMapping("/users")
    public String register(@Valid @RequestBody UserInfoIDto userInfoIDto){
        System.out.println("开始注册用户...");
        return "success";
    }
}
复制代码

@RestControllerAdvice is used to intercept all @RestController

@RestControllerAdvice
public class GlobalExceptionHandlerAdvice {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public String methodArgumentNotValidException(MethodArgumentNotValidException e) {
        // 从异常对象中拿到ObjectError对象
        ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
        // 然后提取错误提示信息进行返回
        return objectError.getDefaultMessage();
    }
}
复制代码

Spring Boot elegantly implements interface parameter validation

5. Unified return format

Error code enumeration

@Getter
public enum ErrorCodeEnum {
    SUCCESS(1000, "成功"),
    FAILED(1001, "响应失败"),
    VALIDATE_FAILED(1002, "参数校验失败"),
    ERROR(5000, "未知错误");

    private Integer code;
    private String msg;

    ErrorCodeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}
复制代码

Custom exception.

@Getter
public class APIException extends RuntimeException {
    private int code;
    private String msg;

    public APIException(ErrorCodeEnum errorCodeEnum) {
        super(errorCodeEnum.getMsg());
        this.code = errorCodeEnum.getCode();
        this.msg = errorCodeEnum.getMsg();
    }
}
复制代码

Define the return format.

@Getter
public class Response<T> {
    /**
     * 状态码,比如1000代表响应成功
     */
    private int code;

    /**
     * 响应信息,用来说明响应情况
     */
    private String msg;

    /**
     * 响应的具体数据
     */
    private T data;


    public Response(T data) {
        this.code = ErrorCodeEnum.SUCCESS.getCode();
        this.msg = ErrorCodeEnum.SUCCESS.getMsg();
        this.data = data;
    }

    public Response(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}
复制代码

The global exception handler increases the interception of APIException and modifies the data format returned when the exception occurs.

@RestControllerAdvice
public class GlobalExceptionHandlerAdvice {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Response<String> methodArgumentNotValidException(MethodArgumentNotValidException e) {
        // 从异常对象中拿到ObjectError对象
        ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
        // 然后提取错误提示信息进行返回
        return new Response<>(ErrorCodeEnum.VALIDATE_FAILED.getCode(), objectError.getDefaultMessage());
    }


    @ExceptionHandler(APIException.class)
    public Response<String> APIExceptionHandler(APIException e) {
        return new Response<>(e.getCode(), e.getMsg());
    }
}
复制代码

SimpleController adds a method that throws an exception.

@RestController
public class SimpleController {

    @PostMapping("/users")
    public String register(@Valid @RequestBody UserInfoIDto userInfoIDto){
        System.out.println("开始注册用户...");
        return "success";
    }

    @GetMapping("/users")
    public Response<UserInfoIDto> list() {
        UserInfoIDto userInfoIDto = new UserInfoIDto();
        userInfoIDto.setUsername("monday");
        userInfoIDto.setAge(30);
        userInfoIDto.setPhone("123456789");
        if (true) {
            throw new APIException(ErrorCodeEnum.ERROR);
        }
        // 为了保持数据格式统一,必须使用Response包装一下
        return new Response<>(userInfoIDto);
    }
}
复制代码

Spring Boot elegantly implements interface parameter validation

Error return format.

Spring Boot elegantly implements interface parameter validation

No error is reported, the format returned.

Spring Boot elegantly implements interface parameter validation

6. Remove the Response wrapper in the interface

@RestControllerAdvice can intercept both exceptions globally and normal return values ​​under the specified package, and can modify the return value.

@RestControllerAdvice(basePackages = {"com.example.validator.controller"})
public class ResponseControllerAdvice implements ResponseBodyAdvice<Object> {

    /**
     * 对那些方法需要包装,如果接口直接返回Response就没有必要再包装了
     *
     * @param returnType
     * @param aClass
     * @return 如果为true才会执行beforeBodyWrite
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> aClass) {
        return !returnType.getParameterType().equals(Response.class);
    }


    @Override
    public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) {
        // String类型不能直接包装,所以要进行些特别的处理
        if (returnType.getGenericParameterType().equals(String.class)) {
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                // 将数据包装在Response里后,再转换为json字符串响应给前端
                return objectMapper.writeValueAsString(new Response<>(data));
            } catch (JsonProcessingException e) {
                throw new APIException(ErrorCodeEnum.ERROR);
            }
        }
        // 这里统一包装
        return new Response<>(data);
    }
}


@RestController
public class SimpleController {

    @GetMapping("/users")
    public UserInfoIDto list() {
        UserInfoIDto userInfoIDto = new UserInfoIDto();
        userInfoIDto.setUsername("monday");
        userInfoIDto.setAge(30);
        userInfoIDto.setPhone("123456789");
        // 直接返回值,不需要再使用Response包装
        return userInfoIDto;
    }
}
复制代码

Spring Boot elegantly implements interface parameter validation

7. Each check error corresponds to a different error code

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface ValidateErrorCode {
    /** 校验错误码 code */
    int value() default 100000;
}


@Data
public class UserInfoIDto {
    @NotBlank
    @Email
    @ValidateErrorCode(value = 20000)
    private String email;

    @NotBlank
    @Pattern(regexp="^((13[0-9])|(15[^4,\\D])|(18[0,3-9]))\\d{8}$", message="手机号格式不正确")
    @ValidateErrorCode(value = 30000)
    private String phone;
}
复制代码

Check exception to get the error code in the annotation.

@ExceptionHandler(MethodArgumentNotValidException.class)
    public Response<String> methodArgumentNotValidException(MethodArgumentNotValidException e) throws NoSuchFieldException {
        // 从异常对象中拿到ObjectError对象
        ObjectError objectError = e.getBindingResult().getAllErrors().get(0);

        // 参数的Class对象,等下好通过字段名称获取Field对象
        Class<?> parameterType = e.getParameter().getParameterType();
        // 拿到错误的字段名称
        String fieldName = e.getBindingResult().getFieldError().getField();
        Field field = parameterType.getDeclaredField(fieldName);
        // 获取Field对象上的自定义注解
        ValidateErrorCode annotation = field.getAnnotation(ValidateErrorCode.class);
        if (annotation != null) {
            return new Response<>(annotation.value(),objectError.getDefaultMessage());
        }

        // 然后提取错误提示信息进行返回
        return new Response<>(ErrorCodeEnum.VALIDATE_FAILED.getCode(), objectError.getDefaultMessage());
    }
复制代码

Spring Boot elegantly implements interface parameter validation

Spring Boot elegantly implements interface parameter validation

8. Individual interfaces do not uniformly package responses

Sometimes the third-party interface calls back our interface, and our interface must follow the return format defined by the third party. At this time, the third party is not necessarily the same as our own return format, so we need to provide a way to bypass the unified packaging.

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface NotResponseWrap {
}


@RestController
public class SimpleController {

    @NotResponseWrap
    @PostMapping("/users")
    public String register(@Valid @RequestBody UserInfoIDto userInfoIDto){
        System.out.println("开始注册用户...");
        return "success";
    }
}
复制代码

ResponseControllerAdvice adds a condition of no wrapping, and the wrapping is skipped if the @NotResponseWrap annotation is configured.

@Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> aClass) {
        return !(returnType.getParameterType().equals(Response.class) || returnType.hasMethodAnnotation(NotResponseWrap.class));
    }
复制代码

Spring Boot elegantly implements interface parameter validation

Guess you like

Origin juejin.im/post/7078258211299541029