验证Spring中的对象列表

The*_*uck 42 java validation spring list

我有以下控制器方法:

@RequestMapping(value="/map/update", method=RequestMethod.POST, produces = "application/json; charset=utf-8")
@ResponseBody
public ResponseEntityWrapper updateMapTheme(
        HttpServletRequest request, 
        @RequestBody @Valid List<CompanyTag> categories,
        HttpServletResponse response
        ) throws ResourceNotFoundException, AuthorizationException {
...
}
Run Code Online (Sandbox Code Playgroud)

CompanyTag以这种方式定义:

public class CompanyTag {
    @StringUUIDValidation String key;
    String value;
    String color;
    String icon;
    Icon iconObj;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
   ...
}
Run Code Online (Sandbox Code Playgroud)

问题是未触发验证,未验证CompanyTag列表,从不调用"StringUUIDValidation"验证器.

如果我删除了List并且只尝试发送一个CompanyTag,即代替:

@RequestBody @Valid List<CompanyTag> categories,
Run Code Online (Sandbox Code Playgroud)

使用:

@RequestBody @Valid CompanyTag category,
Run Code Online (Sandbox Code Playgroud)

它按预期工作,所以显然Spring不喜欢验证事物列表(尝试使用数组,但也没有用).

任何人都知道缺少什么?

小智 47

我找到了另一种方法.基本问题是您希望将列表作为服务的输入有效负载,但javax.validation不会验证列表,只验证JavaBean.诀窍是使用一个自定义列表类,它既作为List 作为JavaBean:

@RequestBody @Valid List<CompanyTag> categories
Run Code Online (Sandbox Code Playgroud)

改成:

@RequestBody @Valid ValidList<CompanyTag> categories
Run Code Online (Sandbox Code Playgroud)

您的列表子类看起来像这样:

public class ValidList<E> implements List<E> {

    @Valid
    private List<E> list;

    public ValidList() {
        this.list = new ArrayList<E>();
    }

    public ValidList(List<E> list) {
        this.list = list;
    }

    // Bean-like methods, used by javax.validation but ignored by JSON parsing

    public List<E> getList() {
        return list;
    }

    public void setList(List<E> list) {
        this.list = list;
    }

    // List-like methods, used by JSON parsing but ignored by javax.validation

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

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

    // Other list methods ...
}
Run Code Online (Sandbox Code Playgroud)

  • 这是一个可行的解决方案,但不是违反Spring明确表示应该验证列表的JSR-303吗?集合值,数组值和通常可迭代的字段和属性也可以用@Valid注释修饰。这将使迭代器的内容得到验证。支持任何实现java.lang.Iterable的对象。具体包括:•对象数组•java.util.Collection•java.util.Set•java.util.List•java.util.Map(特殊处理,请参见下文)`https://beanvalidation.org/1.0/spec /#constraintdeclarationvalidationprocess (4认同)
  • 这是我见过的最优雅的方式,谢谢! (3认同)
  • 这确实是一个简洁的解决方案,但有一个问题 - 在这种情况下如何处理验证消息? (2认同)
  • ValidList应该@Override所有List方法吗?谈论样板代码... (2认同)

Leb*_*cca 17

起源答案

我尝试在项目中使用Paul的方法,但有人说这太复杂了。不久之后,我发现了另一种简单的方法,类似于下面的代码:

@Validated
@RestController
@RequestMapping("/parent")
public class ParentController {

  private FatherRepository fatherRepository;

  /**
   * DI
   */
  public ParentController(FatherRepository fatherRepository) {
    this.fatherRepository = fatherRepository;
  }

  @PostMapping("/test")
  public void test(@RequestBody @Valid List<Father> fathers) {

  }
}
Run Code Online (Sandbox Code Playgroud)

它有效且易于使用。关键点是类上的@Valiated批注。顺便说一句,我使用的是springBootVersion ='2.0.4.RELEASE'。

更新异常处理部分

如评论中所述,我们必须在此处处理异常,例如以下代码:

@RestControllerAdvice
@Component
public class ControllerExceptionHandler {

  /**
   * handle controller methods parameter validation exceptions
   *
   * @param exception ex
   * @return wrapped result
   */
  @ExceptionHandler
  @ResponseBody
  @ResponseStatus(HttpStatus.OK)
  public DataContainer handle(ConstraintViolationException exception) {

    Set<ConstraintViolation<?>> violations = exception.getConstraintViolations();
    StringBuilder builder = new StringBuilder();
    for (ConstraintViolation<?> violation : violations) {
      builder.append(violation.getMessage());
      break;
    }
    DataContainer container = new DataContainer(CommonCode.PARAMETER_ERROR_CODE, builder.toString());
    return container;
  }
}
Run Code Online (Sandbox Code Playgroud)

以http状态代码表示网络是可以的,这里仅返回第一个违规消息。您可以更改它以满足定制要求。

更多详情

在类级别使用@Validated时,将在Spring Boot中通过所谓的方法级验证来验证方法的参数,这不仅适用于控制器,而且适用于IOC容器管理的任何bean 。

顺便说一下,通过以下方式增强了方法级别验证中的方法:

  • org.springframework.validation.beanvalidation.MethodValidationInterceptor

而典型的Spring Boot控制器方法验证则在

  • org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor

两者org.hibernate.validator.internal.engine.ValidatorImpl默认都会导致实际的验证操作,但是它们调用的方法不同,这导致验证逻辑上的差异。

  • MethodValidationInterceptor调用validateParameters方法ValidatorImpl
  • RequestResponseBodyMethodProcessor调用validate方法ValidatorImpl

您可以深入了解更多细节。

  • 这将意味着您的休息控制器本身将抛出异常,而不是返回适当的 400 响应。 (2认同)

Bab*_*abl 9

我建议将List类别包装到一些DTO bean中并验证它.除了工作验证之外,您还将受益于更灵活的API.

@RequestMapping(value="/map/update", method=RequestMethod.POST, produces = "application/json; charset=utf-8")
@ResponseBody
public ResponseEntityWrapper updateMapTheme(
    HttpServletRequest request, 
    @RequestBody @Valid TagRequest tagRequest,
    HttpServletResponse response
    ) throws ResourceNotFoundException, AuthorizationException {
...
}

public static class TagRequest {
    @Valid
    List<CompanyTag> categories;    
    // Gettes setters
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,您必须将JSON格式从[{},{}]更改为{categories:[{},{}]} (4认同)
  • 那就是我所做的(使用泛型,好吧,泛型),但这是一种解决方法,我想知道Spring是否是一个相对活跃的,不断开发和改进的框架,尚未在一个我不熟悉的方式。 (2认同)

aym*_*afa 9

我执行了以下步骤来对列表进行验证:

  1. @Validated在类级别注释其余控制器
  2. 在列表中泛型类型前添加@Valid,即List<@Valid MyClass>

另外,发现如果验证失败,我会得到 javax.validation.ConstraintViolationException


eru*_*uiz 5

我认为最好的解决方法是创建集合的自定义验证,并且注册了验证在WebDataBinders一个@ControllerAdvice,看一看,以弹簧在控制器方法势必收藏RequestBody参数的验证


laf*_*ste 5

@Paul Strack的出色解决方案与Lombok魔术混合在一起:

@Data
public class ValidList<E> implements List<E> {
    @Valid
    @Delegate
    private List<E> list = new ArrayList<>();
}
Run Code Online (Sandbox Code Playgroud)

用法(ValidList的交换列表):

public ResponseEntityWrapper updateMapTheme(
        @RequestBody @Valid ValidList<CompanyTag> categories, ...)
Run Code Online (Sandbox Code Playgroud)

(需要Lombok,但是如果您还不使用它,那么您真的想尝试一下)

  • 龙目岛(Lombok)的添加效果很好,效果很好。甚至都不知道`@ Delegate`。我称之为Spring Bug的最简单的解决方法答案仍然存在于SpringBoot 1.5.x发行版中。 (2认同)

Ham*_*eji 5

@Valid注释可以在菱形运算符内部使用:

private List<@Valid MyType> types;
Run Code Online (Sandbox Code Playgroud)

或者

@Valid
private List<MyType> types;
Run Code Online (Sandbox Code Playgroud)

现在,每个列表项都将被验证。


Mar*_*vic 5

使用Spring Boot 2.4.1

  1. @Validated给类添加注解

  2. 移动@Valid菱形运算符内的注释:

    @RestController
    @Validated          // <-- This activates the Spring Validation AOP interceptor
    public class MyController {
    
      ...
          @RequestBody List<@Valid CompanyTag> categories
                           // ^^^ - NOTE: the @Valid annotation is inside <> brackets
    
    Run Code Online (Sandbox Code Playgroud)

  • 这将返回 500 而不是预期的 400 Http 代码 (4认同)

Bor*_*lov 5

使用 spring 的更高版本,您现在可以执行此操作。

@RequestMapping(value="/map/update", method=RequestMethod.POST, produces = "application/json; charset=utf-8")
@ResponseBody
public ResponseEntityWrapper updateMapTheme(
        HttpServletRequest request, 
        @RequestBody List<@Valid CompanyTag> categories,
        HttpServletResponse response
        ) throws ResourceNotFoundException, AuthorizationException {
...
}
Run Code Online (Sandbox Code Playgroud)

@Valid 注释位于通用参数中。

如果您使用自定义 javax 验证注释,请确保将 TYPE_USE 添加到注释目标

@Target({ ElementType.TYPE_USE})
public @interface ValidationAnnotation {.. }
Run Code Online (Sandbox Code Playgroud)


Cri*_*scu 4

验证集合不能直接进行。

例如:如果多个元素验证失败该怎么办?第一次验证后停止?验证所有(如果是的话,如何处理消息集合)?

如果在您的配置中 Spring 委托给 Bean Validator 提供者(例如 Hibernate Validator),您应该在那里查找实现集合验证器的方法。

对于 Hibernate,这里讨论类似的问题