所有分类
  • 所有分类
  • 未分类

SpringBoot–解决@Valid放在接口的List上时无效的问题

简介

本文介绍如何解决@Valid放在Controller的List类型的参数上时校验无效的问题。

问题复现

代码

Controller

package com.knife.example.business.user.controller;

import com.knife.example.business.user.bo.UserBO;
import com.knife.example.common.util.ValidateUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;

@Api(tags = "测试list")
@RestController
@RequestMapping("list")
public class ValidListController {

    /**
     * 如果入参是List类型,则必须是@RequestBody,否则会报错
     */
    @ApiOperation("valid测试基础列表")
    @PostMapping("validBaseList")
    public List<String> validBaseList(@Valid @RequestBody @NotEmpty List<String> nameList) {
        return nameList;
    }

    @ApiOperation("valid测试对象列表")
    @PostMapping("validObjectList")
    public List<UserBO> validObjectList(@Valid @RequestBody @NotEmpty List<UserBO> userBOList) {
        return userBOList;
    }

    @ApiOperation("valid测试手动校验工具")
    @PostMapping("validManualObjectList")
    public List<UserBO> validManualObjectList(@RequestBody List<UserBO> userBOList) {
        ValidateUtil.validate(userBOList);
        return userBOList;
    }
}

BO

package com.knife.example.business.user.bo;
 
import lombok.Data;
 
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
 
@Data
public class UserBO {
    @NotBlank(message = "名字不能为空")
    private String name;

    private Integer age;

    @NotBlank(message = "密码不能为空")
    private String password;

    @NotEmpty(message = "分数不能为空")
    private List<Integer> scoreArray;

    @Valid
    @NotNull(message = "账户不能为空")
    private AccountBO accountBO;

    @Valid
    @NotEmpty(message = "账户列表不能为空")
    private List<AccountBO> accountBOList;
}
package com.knife.example.business.user.bo;
 
import lombok.Data;
 
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
public class AccountBO {
    @NotNull(message = "账户ID不能为空")
    private Long id;

    @NotBlank(message = "电话号码不能为空")
    private String phoneNumber;

    private String[] emails;
}

测试

从下图可以看到,所有的校验都没有生效。 

测试1:空列表

测试2:valid校验对象列表

入参:

[
  {
    "accountBO": {
      "emails": [],
      "id": 2,
      "phoneNumber": ""
    },
    "accountBOList": [
      {
        "emails": [],
        "id": null,
        "phoneNumber": "12345"
      }
    ],
    "age": 0,
    "name": "",
    "password": "",
    "scoreArray": []
  }
]

测试3:手动校验对象列表

入参:

[
  {
    "accountBO": {
      "emails": [],
      "id": 2,
      "phoneNumber": ""
    },
    "accountBOList": [
      {
        "emails": [],
        "id": null,
        "phoneNumber": "12345"
      }
    ],
    "age": 0,
    "name": "",
    "password": "",
    "scoreArray": []
  }
]

原因分析

@Valid只能校验Java Bean(单个对象),而List<E>不是JavaBean所以校验会失败。

解决方案

方案1:@Validated + @Valid(推荐)

很简单,在接口类上加@Validated即可。

代码

package com.knife.example.business.user.controller;

import com.knife.example.business.user.bo.UserBO;
import com.knife.example.common.util.ValidateUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;

@Validated
@Api(tags = "测试list")
@RestController
@RequestMapping("list")
public class ValidListController {

    /**
     * 如果入参是List类型,则必须是@RequestBody,否则会报错
     */
    @ApiOperation("valid测试基础列表")
    @PostMapping("validBaseList")
    public List<String> validBaseList(@Valid @RequestBody @NotEmpty List<String> nameList) {
        return nameList;
    }

    @ApiOperation("valid测试对象列表")
    @PostMapping("validObjectList")
    public List<UserBO> validObjectList(@Valid @RequestBody @NotEmpty List<UserBO> userBOList) {
        return userBOList;
    }

    @ApiOperation("valid测试手动校验工具")
    @PostMapping("validManualObjectList")
    public List<UserBO> validManualObjectList(@RequestBody List<UserBO> userBOList) {
        ValidateUtil.validate(userBOList);
        return userBOList;
    }
}

测试

可以看到:自动校验都成功了。(手动校验仍然失效)。

测试1:空列表(有效)

测试2:valid校验对象列表(有效)

入参:

[
  {
    "accountBO": {
      "emails": [],
      "id": 2,
      "phoneNumber": ""
    },
    "accountBOList": [
      {
        "emails": [],
        "id": null,
        "phoneNumber": "12345"
      }
    ],
    "age": 0,
    "name": "",
    "password": "",
    "scoreArray": []
  }
]

测试3:手动校验对象列表(无效)

入参:

[
  {
    "accountBO": {
      "emails": [],
      "id": 2,
      "phoneNumber": ""
    },
    "accountBOList": [
      {
        "emails": [],
        "id": null,
        "phoneNumber": "12345"
      }
    ],
    "age": 0,
    "name": "",
    "password": "",
    "scoreArray": []
  }
]

如上所示,校验无效,解决方法见:本文最后。

方案2:自定义List

新建一个类,实现List接口,使这个类即具有了JavaBean的特性,又具有了List的特性。

这样写有些麻烦,要实现几十个方法。

package com.example.demo.common;
 
 
import javax.validation.Valid;
import java.util.*;
 
public class ValidList<E> implements List<E> {
 
    @Valid
    private List<E> list;
 
    public ValidList() {
        this.list = new ArrayList<>();
    }
 
    public ValidList(List<E> list) {
        this.list = list;
    }
 
    public List<E> getList() {
        return list;
    }
 
    public void setList(List<E> list) {
        this.list = list;
    }
 
    @Override
    public int size() {
        return list.size();
    }
 
    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }
 
    // 实现其他方法
 
}

用法

Controller将原来的List改为ValidList即可:

package com.knife.example.business.user.controller;

import com.knife.example.business.user.bo.UserBO;
import com.knife.example.common.util.ValidateUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;

@Api(tags = "测试list")
@RestController
@RequestMapping("list")
public class ValidListController {

    /**
     * 如果入参是List类型,则必须是@RequestBody,否则会报错
     */
    @ApiOperation("valid测试基础列表")
    @PostMapping("validBaseList")
    public List<String> validBaseList(@Valid @RequestBody @NotEmpty ValidList<String> nameList) {
        return nameList;
    }

    @ApiOperation("valid测试对象列表")
    @PostMapping("validObjectList")
    public List<UserBO> validObjectList(@Valid @RequestBody @NotEmpty ValidList<UserBO> userBOList) {
        return userBOList;
    }

    @ApiOperation("valid测试手动校验工具")
    @PostMapping("validManualObjectList")
    public List<UserBO> validManualObjectList(@RequestBody ValidList<UserBO> userBOList) {
        ValidateUtil.validate(userBOList);
        return userBOList;
    }
}

方案3:包装List

把List封装成Java Bean,定义一个ListWrapper类。

包装List

package com.example.demo.business.validAndValidated.common;
 
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
 
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
 
@Data
public class ListWrapper<E> {
    @Valid
    private List<E> list;
 
    public ListWrapper() {
        list = new ArrayList<>();
    }
 
    public  ListWrapper(List<E> list) {
        this.list = list;
    }
 
}

用法

  1. 将Controller的List换成ListWrapper;
  2. 传参的时候[{},{}..]要改为{“list”: [{},{}..]}
package com.knife.example.business.user.controller;

import com.knife.example.business.user.bo.UserBO;
import com.knife.example.common.util.ValidateUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;

@Api(tags = "测试list")
@RestController
@RequestMapping("list")
public class ValidListController {

    /**
     * 如果入参是List类型,则必须是@RequestBody,否则会报错
     */
    @ApiOperation("valid测试基础列表")
    @PostMapping("validBaseList")
    public List<String> validBaseList(@Valid @RequestBody @NotEmpty ListWrapper<String> nameList) {
        return nameList;
    }

    @ApiOperation("valid测试对象列表")
    @PostMapping("validObjectList")
    public List<UserBO> validObjectList(@Valid @RequestBody @NotEmpty ListWrapper<UserBO> userBOList) {
        return userBOList;
    }

    @ApiOperation("valid测试手动校验工具")
    @PostMapping("validManualObjectList")
    public List<UserBO> validManualObjectList(@RequestBody ListWrapper<UserBO> userBOList) {
        ValidateUtil.validate(userBOList);
        return userBOList;
    }
}

解决手动校验List无效的问题

只能手动遍历列表,挨个去校验单个对象。

代码

原来的ValidateUtil

package com.knife.example.common.util;
 
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidationException;
import javax.validation.Validator;
import java.util.Set;

/**
 * hibernate validator的校验工具
 */
public class ValidateUtil {
    private static final Validator validator =
            Validation.buildDefaultValidatorFactory().getValidator();
 
    /**
     * 校验实体类
     */
    public static <T> void validate(T t) {
        Set<ConstraintViolation<T>> constraintViolations = validator.validate(t);
        if (constraintViolations.size() > 0) {
            StringBuilder validateError = new StringBuilder();
            for (ConstraintViolation<T> constraintViolation : constraintViolations) {
                validateError.append(constraintViolation.getMessage()).append(";");
            }
 
            throw new ValidationException(validateError.toString());
        }
    }
 
    /**
     * 通过组来校验实体类
     */
    public static <T> void validate(T t, Class<?>... groups) {
        Set<ConstraintViolation<T>> constraintViolations = validator.validate(t, groups);
        if (constraintViolations.size() > 0) {
            StringBuilder validateError = new StringBuilder();
            for (ConstraintViolation<T> constraintViolation : constraintViolations) {
                validateError.append(constraintViolation.getMessage()).append(";");
            }
 
            throw new ValidationException(validateError.toString());
        }
    }
}

修改后的ValidateUtil(加了个重载)

package com.knife.example.common.util;
 
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidationException;
import javax.validation.Validator;
import java.util.Collection;
import java.util.Set;

/**
 * hibernate validator的校验工具
 */
public class ValidateUtil {
    private static final Validator validator =
            Validation.buildDefaultValidatorFactory().getValidator();
 
    /**
     * 校验实体类
     */
    public static <T> void validate(T t) {
        Set<ConstraintViolation<T>> constraintViolations = validator.validate(t);
        if (constraintViolations.size() > 0) {
            StringBuilder validateError = new StringBuilder();
            for (ConstraintViolation<T> constraintViolation : constraintViolations) {
                validateError.append(constraintViolation.getMessage()).append(";");
            }
 
            throw new ValidationException(validateError.toString());
        }
    }

    /**
     * 校验实体类集合
     */
    public static <T> void validate(Collection<T> tCollection) {
        for (T t : tCollection) {
            validate(t);
        }
    }
 
    /**
     * 通过组来校验实体类
     */
    public static <T> void validate(T t, Class<?>... groups) {
        Set<ConstraintViolation<T>> constraintViolations = validator.validate(t, groups);
        if (constraintViolations.size() > 0) {
            StringBuilder validateError = new StringBuilder();
            for (ConstraintViolation<T> constraintViolation : constraintViolations) {
                validateError.append(constraintViolation.getMessage()).append(";");
            }
 
            throw new ValidationException(validateError.toString());
        }
    }

    /**
     * 通过组来校验实体类列表
     */
    public static <T> void validate(Collection<T> tCollection, Class<?>... groups) {
        for (T t : tCollection) {
            validate(t, groups);
        }
    }
}

测试

如下图所示:校验生效了!

入参:

[
  {
    "accountBO": {
      "emails": [],
      "id": 2,
      "phoneNumber": ""
    },
    "accountBOList": [
      {
        "emails": [],
        "id": null,
        "phoneNumber": "12345"
      }
    ],
    "age": 0,
    "name": "",
    "password": "",
    "scoreArray": []
  }
]

下载源码

此隐藏内容仅限VIP查看升级VIP
0

评论0

请先

显示验证码
没有账号?注册  忘记密码?

社交账号快速登录