首页 > 解决方案 > @Valid 不适用于嵌套对象(Java / Spring Boot)

问题描述

几天来我一直在尝试在网上找到类似的问题,但似乎找不到任何东西,所以我在这里问我的问题。

我有一个控制器:

import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Validated
@RestController
@RequestMapping("/data")
public class TheController {

    private final TheService theService;

    @Autowired
    public TheController(TheService theService) {
        this.theService = theService;
    }

    @PostMapping(path = "/data", consumes = {MediaType.APPLICATION_JSON_VALUE}, produces = {MediaType.TEXT_PLAIN_VALUE})
    public ResponseEntity<String> saveData(@Valid @RequestBody Data data) {
        subscriptionDataFeedService.sendData(data.getDataList());
        return ResponseEntity.ok()
                             .body("Data successful.");
    }
}

我有请求正文类:

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Data {
    @NotEmpty(message = "Data list cannot be empty.")
    @JsonProperty(value = "dataArray")
    List<@Valid DataOne> dataList;
}

我有 DataOne 类:

import com.fasterxml.jackson.annotation.JsonProperty;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DataOne {
    private @NotBlank String currency;
    private @NotBlank String accountNumber;
    private @NotBlank String finCode;
    private String poNumber;
    private @NotBlank String invoiceNumber;
    private @NotNull Address billTo;
    private @NotNull Address soldTo;
    private @NotNull LocalDate invoiceDate;
    private @NotBlank String billingPeriod;
    private @NotNull LocalDate paymentDueDate;
    private @NotNull BigDecimal amountDue;

    @JsonProperty(value = "activitySummary")
    private @NotNull List<@Valid ProductSummary> productSummaryList;

    @JsonProperty(value = "accountSummary")
    private @NotNull List<@Valid AccountSummary> accountSummaryList;

    @JsonProperty(value = "transactions")
    private @NotNull List<@Valid Transaction> transactionList;

    private @NotNull PaymentByACH paymentByACH;
    private @NotNull Address paymentByCheck;
    private @NotNull CustomerServiceContact customerServiceContact;
}

我将包括地址类:

import javax.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Address {
    private @NotBlank String name;
    private @NotBlank String address1;
    private String address2;
    private @NotBlank String city;
    private @NotBlank String state;
    private @NotBlank String postalCode;
}

我省略了其他一些类,因为我的问题不需要它们。

所以我遇到的问题是 @Valid 注释能够验证除了DataOne 中不是列表的嵌套类之外的所有内容。换句话说,它无法验证 Address、PaymentByACH 等内部的字段。但是,它能够验证这些对象是否为 @NotNull,但无法验证这些类中的字段。

@Valid 无法验证地址内的名称、地址 1、城市等字段。每当我在 DataOne 内的地址字段前面添加一个@Valid 标记时,我都会得到一个 HV000028:isValid 调用异常期间的意外异常。

如何验证 Address 对象或任何嵌套对象内的嵌套字段?

TL;DR:作为列表的对象,例如List<@Valid Transaction> transactionList;确实验证 Transaction 内部的字段,但代码不验证 Address 内部的字段。

标签: javaspringspring-bootvalidationspring-mvc

解决方案


好问题。

我认为您稍微滥用了@Valid注释。

如何验证 Address 对象或任何嵌套对象内的嵌套字段?

@Valid不应作为要验证的字段的前缀。该工具专门用于验证@Controller端点方法(有时是@Service方法)中的参数。根据docs.spring.io

“Spring MVC 能够自动验证 @Controller 输入。”

它提供了以下示例,

@Controller
public class MyController {
    @RequestMapping("/foo", method=RequestMethod.POST)
    public void processFoo(@Valid Foo foo) { /* ... */ }
}

@Valid除了控制器(或服务)方法的参数之外,您应该在任何地方使用的唯一原因是注释复杂类型,例如对象列表(即:DataOne:productSummaryList、accountSummaryList、transactionList)。如果您愿意,这些文档包含实施您自己的验证策略的详细信息。

对于您的实际需求,您可能应该只使用@Valid控制器级别的方法和该方法引用的模型的复杂类型。然后使用字段级别的约束来确保您不会得到诸如负年龄之类的东西。例如:

@Data
...
public class Person {
    ...
    @Positive
    @Max(value = 117)
    private int age;
    ... 
}

从 spring 文档中查看可以使用的约束列表。您已经在使用@NotNull约束,所以这不应该太陌生。您可以验证电子邮件、信用卡、日期、小数、范围、负值或正值以及许多其他约束。


推荐阅读