spring参数校验@Validated及嵌套校验

本文介绍项目中校验@Validated的使用,主要分参数对象属性校验,嵌套校验,集合在对象属性中校验,集合作为参数校验。

对象属性校验

controller层

@RestController
@Slf4j
@RequestMapping("/api/test")
public class TestController {
    
    @PostMapping(value = "/h9")
    public ApplyInfoDTO2 test9(@Validated @RequestBody ApplyInfoDTO2 applyInfoDTO) {

        System.out.println("kaidsd");
        return applyInfoDTO;
    }
}
package com.dto;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;

@Data
@EqualsAndHashCode(callSuper = false)
public class ApplyInfoDTO2 implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "主键")
    private Long id;

    @ApiModelProperty(value = "标题", required = true)
    @NotBlank(message = "标题不能为空")
    private String title;

    @Valid
    @NotEmpty(message = "集合1不能为空")
    private List fileInfoList;

// @NotEmpty会判断这个集合是不是空的,如果前端没传fileInfo这个参数,就不会校验里面的对象属性
// 只有加了这个注解,才能保证这个对象不能为空。
    @Valid
    @NotEmpty(message = "集合2不能为空")
    private List fileInfo;


}
package com.common.vo;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotNull;
import java.io.Serializable;


@Data
public class FileInfoVO implements Serializable {

    @NotNull(message = "文件名称不能为空")
    @ApiModelProperty("原文件名")
    private String name;

}

测试输入

{
    "titles": "44",
    "fileInfoList": [
        {
            "name": "sss"
        }
    ],
    "fileInfo": [
        {
            "names": "sss"
        }
    ]
}

输出

{
    "code": 400,
    "message": "标题不能为空,文件名称不能为空",
    "data": null,
    "timestamp": 1679912555257
}

集合作为参数校验

 @PostMapping(value = "/h10")
    public String test10(@Validated @RequestBody ValidList fileInfo) {

        System.out.println("kaidsd");
        System.out.println(fileInfo);

        return "applyInfoDTO";
    }

这里作为参数,如果使用List接收是不起作用的,必须用ValidList,这个类中有标记@Valid

@Valid

private List list = new ArrayList<>();

如果在对象参数中使用ValidList,就会出现2次错误提示消息。

对象改为

package com.dto;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;

@Data
@EqualsAndHashCode(callSuper = false)
public class ApplyInfoDTO2 implements Serializable {

// @NotEmpty会判断这个集合是不是空的,如果前端没传fileInfo这个参数,就不会校验里面的对象属性
// 只有加了这个注解,才能保证这个对象不能为空。
    @Valid
    @NotEmpty(message = "集合2不能为空")
    private ValidList fileInfo;


}

输入

还是上边输入

输出

{
    "code": 400,
    "message": "文件名称不能为空,文件名称不能为空",
    "data": null,
    "timestamp": 1679913439751
}

Get请求参数校验

需要在controller层上边加@Validated校验注解

正确形式

@RestController
@Slf4j
@RequestMapping("/api/test")
@Validated
public class TestController {
    @GetMapping(value = "/h11")
    public String test11( @NotEmpty(message = "姓名不能为空") String name) {

        System.out.println("kaidsd");
        System.out.println(name);

        return "applyInfoDTO";
    }
}

输入路径

http://localhost:9004/api/test/h11

后台报错

javax.validation.ConstraintViolationException: test11.name: 姓名不能为空
    at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:116)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)

错误写法,不生效

@RestController
@Slf4j
@RequestMapping("/api/test")
// 这里没有校验注解,在参数前面加不生效
public class TestController {
    @GetMapping(value = "/h11")

    public String test11(// 这里校验不生效   @Validated @NotEmpty(message = "姓名不能为空") String name) {

        System.out.println("kaidsd");
        System.out.println(name);

        return "applyInfoDTO";
    }
}

报错信息捕捉

之所以上面的请求,校验不通过能返回错误信息,是因为设置了全局异常信息捕捉

import com.common.exception.BusinessException;
import com.common.exception.E;
import com.common.exception.SystemExceptionEnum;
import com.common.response.R;
import java.util.Iterator;
import java.util.Objects;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Order(0)
@RestControllerAdvice
public class SystemExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(SystemExceptionHandler.class);
    private static final String ENUM_TYPE_ERROR_KEYWORD = "枚举类型错误";

    public SystemExceptionHandler() {
    }

    @ResponseBody
    @ExceptionHandler({E.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public R handle(E e) {
        log.error("全局异常信息 -> {}", e.getMessage(), e);
        return e.isHideMessage() ? R.fail(e.getCode(), SystemExceptionEnum.UNKNOWN.getMessage()) : R.fail(e);
    }

    @ResponseBody
    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R validationBodyException(MethodArgumentNotValidException e) {
        log.error("全局参数校验异常信息 -> {}", e.getMessage(), e);
        StringBuilder builder = new StringBuilder();
        Iterator var3 = e.getBindingResult().getAllErrors().iterator();

        while(var3.hasNext()) {
            ObjectError error = (ObjectError)var3.next();
            String defaultMessage = error.getDefaultMessage();
            if (!Strings.isBlank(defaultMessage)) {
                if (builder.length() > 0) {
                    builder.append(",");
                }

                builder.append(defaultMessage);
            }
        }

        String message = builder.toString();
        message = Strings.isBlank(message) ? SystemExceptionEnum.ARGUMENT_ERROR.getMessage() : message;
        return R.fail(SystemExceptionEnum.ARGUMENT_ERROR.getCode(), message);
    }

    @ResponseBody
    @ExceptionHandler({HttpMessageNotReadableException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R httpMessageNotReadableException(HttpMessageNotReadableException e) {
        log.error("全局参数校验异常信息 -> {}", e.getMessage(), e);
        return ((String)Objects.requireNonNull(e.getMessage())).contains("枚举类型错误") ? R.fail(E.of(SystemExceptionEnum.ENUM_TYPE_ERROR)) : R.fail(E.of(SystemExceptionEnum.ARGUMENT_ERROR));
    }

    @ResponseBody
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public R handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        return R.fail(E.of(SystemExceptionEnum.METHOD_NOT_ALLOWED));
    }

    @ExceptionHandler({BusinessException.class})
    public R handleBusinessException(BusinessException e) {
        log.error("业务异常:{},{}", e.getMessage(), e);
        return R.fail(e.getCode(), e.getMessage());
    }

    @ResponseBody
    @ExceptionHandler({Exception.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public R handleUnknown(Exception e) {
        log.error("全局未知异常信息 -> {}", e.getMessage(), e);
        return R.fail(E.of(SystemExceptionEnum.UNKNOWN));
    }
}

自定义的ValidList类

package com.utils;

import lombok.Data;

import javax.validation.Valid;
import java.util.*;

/**
 * 文件描述: 校验List中字段属性
 *
 * @date 2020年03月16日 11:22
 */
@Data
public class ValidList implements List {

    @Valid
    private List list = new ArrayList<>();

    @Override
    public int size() {
        return list.size();
    }

    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return list.contains(o);
    }

    @Override
    public Iterator iterator() {
        return list.iterator();
    }

    @Override
    public Object[] toArray() {
        return list.toArray();
    }

    @Override
    public  T[] toArray(T[] a) {
        return list.toArray(a);
    }

    @Override
    public boolean add(E e) {
        return list.add(e);
    }

    @Override
    public boolean remove(Object o) {
        return list.remove(o);
    }

    @Override
    public boolean containsAll(Collection c) {
        return list.containsAll(c);
    }

    @Override
    public boolean addAll(Collection c) {
        return list.addAll(c);
    }

    @Override
    public boolean addAll(int index, Collection c) {
        return list.addAll(index,c);
    }

    @Override
    public boolean removeAll(Collection c) {
        return list.removeAll(c);
    }

    @Override
    public boolean retainAll(Collection c) {
        return list.retainAll(c);
    }

    @Override
    public void clear() {
        list.clear();
    }

    @Override
    public E get(int index) {
        return list.get(index);
    }

    @Override
    public E set(int index, E element) {
        return list.set(index,element);
    }

    @Override
    public void add(int index, E element) {
        list.add(index,element);
    }

    @Override
    public E remove(int index) {
        return list.remove(index);
    }

    @Override
    public int indexOf(Object o) {
        return list.indexOf(o);
    }

    @Override
    public int lastIndexOf(Object o) {
        return list.lastIndexOf(o);
    }

    @Override
    public ListIterator listIterator() {
        return list.listIterator();
    }

    @Override
    public ListIterator listIterator(int index) {
        return list.listIterator(index);
    }

    @Override
    public List subList(int fromIndex, int toIndex) {
        return list.subList(fromIndex,toIndex);
    }
}

post请求报错异常信息类是MethodArgumentNotValidException

org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.ccreate.cnpc.apply.controller.TestController.test10(com.ccreate.cnpc.apply.utils.ValidList): [Field error in object 'fileInfoVOList' on field 'list[0].name': rejected value [null]; codes [NotNull.fileInfoVOList.list[0].name,NotNull.fileInfoVOList.list.name,NotNull.list[0].name,NotNull.list.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [fileInfoVOList.list[0].name,list[0].name]; arguments []; default message [list[0].name]]; default message [文件名称不能为空]] 
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:139)
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)

get请求报错的是ConstraintViolationException

在service层校验对象参数

错误写法,在service层方法参数上加@Validated是不生效的


import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

@Service
// 加这不生效校验
@Validated
public class ApplyTestService {
    public Boolean add(// 加这也不生效校验  @Validated ApplyInfoDTO2 applyInfoDTO) {
  
        System.out.println("applyTestService");

        return true;
    }
}

正确写法

自定义一个校验工具类


import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;
import java.util.stream.Collectors;


public class ValidationUtils {

    private static final Validator validator;

    static {
        validator = Validation.buildDefaultValidatorFactory().getValidator();
    }

    /**
     * 校验对象
     *
     * @param object 待校验对象
     * @param groups 待校验的组
     */
    public static void validateEntity(Object object, Class... groups) throws IllegalArgumentException {
        Set> constraintViolations = validator.validate(object, groups);
        if (!constraintViolations.isEmpty()) {
            String msg = constraintViolations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining("||"));
            throw new E(msg);
        }
    }
}

正确service写法

import com.dto.ApplyInfoDTO2;
import com.ValidationUtils;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;


@Service
public class ApplyTestService {
    public Boolean add(@Validated ApplyInfoDTO2 applyInfoDTO) {
// 这里写自己的校验
        ValidationUtils.validateEntity(applyInfoDTO);
        System.out.println("applyTestService");

        return true;
    }
}

总结:

  1. 对象校验,用post传参,对象属性的校验注解导包使用正确

  1. 集合作为校验接收参数使用ValidList这个类接收

  1. 对象中集合校验使用List接收,不能使用ValidList,会出现2次错误信息

  1. get参数校验,需要在类上加@Validated

  1. service层校验需要自己写校验工具类。写校验方法。

你可能感兴趣的:(架构,spring,boot,java)