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

Posted wangfenglei123456

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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<FileInfoVO2> fileInfoList;

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

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

        return "applyInfoDTO";
    

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

@Valid

private List<E> 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<FileInfoVO> 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<Void> 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<Void> 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<Void> 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<Void> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) 
        return R.fail(E.of(SystemExceptionEnum.METHOD_NOT_ALLOWED));
    

    @ExceptionHandler(BusinessException.class)
    public R<Void> 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<Void> 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<E> implements List<E> 

    @Valid
    private List<E> 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<E> iterator() 
        return list.iterator();
    

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

    @Override
    public <T> 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<? extends E> c) 
        return list.addAll(c);
    

    @Override
    public boolean addAll(int index, Collection<? extends E> 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<E> listIterator() 
        return list.listIterator();
    

    @Override
    public ListIterator<E> listIterator(int index) 
        return list.listIterator(index);
    

    @Override
    public List<E> 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<com.ccreate.cnpc.common.vo.FileInfoVO>): [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<ConstraintViolation<Object>> 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层校验需要自己写校验工具类。写校验方法。

Validated校验在springboot框架中的应用(教程版)

参考技术A

各位朋友大家好,我是 奋斗的小强001 ,本期更新的内容是: Validated校验在springboot框架中的应用。

前言

4.1网上业务处理

4.2系统业务处理

当输入不能满足条件时,就会抛出异常,而后统一由异常中心处理, 推荐方式2

如果一个类中包含了另外一个实体类,那么在上面加上@Validated即可,比如下面的

优缺点

优点: 统一接口校验方法,并且可以指定相应的异常信息,避免手动写if else判断参数是否合法

缺点: 在公用一个打开校验的接口时,校验有问题.需要在使用的时候考虑

注意事项

1、修改时如果不传此校验的参数会报错,所以修改时也应该传全参。

2、使用时需在接口参数前添加注解开启此校验。

3、使用注解时应抛出异常信息。默认的异常信息是英文。

4、建议大家使用的时候先结合大家的业务使用.

以上是关于spring参数校验@Validated及嵌套校验的主要内容,如果未能解决你的问题,请参考以下文章

Spring Validation最佳实践及其实现原理,参数校验没那么简单!

优雅的参数验证@Validated@Validated参数校验的使用及注解详解——你还在用if做条件验证?

Validated校验在springboot框架中的应用(教程版)

SpringBoot Validation参数校验 详解自定义注解规则和分组校验

@Validated注解和@Valid注解区别

java注解@Valid@Validated表单验证