【问题标题】:Custom Messages when Validating Object Graphs/Collections with Java Bean Validation使用 Java Bean 验证验证对象图/集合时的自定义消息
【发布时间】:2019-08-14 05:27:19
【问题描述】:

遵守以下代码。 DealerLot 有一个 carInventory,它由汽车组成,所有汽车都包含一个引擎。当前dealerlot 有两辆汽车在库存中,并且在验证时,汽车的一个引擎未能通过验证。但是,错误消息仅包含有关引擎的信息。在消息中包含 carName 的最简洁方式是什么?

public class main {

@Data
@AllArgsConstructor
static class Engine {
    @Pattern(regexp="V.*", message="Engine Name must start with a V! You provided ${validatedValue}")
    private String engineName;
}

@Data
@AllArgsConstructor
static class Car {
    private String carName;
    @Valid
    private Engine engine;
}

@Data
@AllArgsConstructor
static class DealerLot {
    @Valid
    private List<Car> carInventory;
}

public static void main(String args[]){
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    Validator validator = factory.getValidator();

    Engine engine1 = new Engine("VQ35DE");
    Car car1 = new Car("Nissan 350z", engine1);
    Engine engine2 = new Engine("2JZ-GTE");
    Car car2 = new Car("Toyota Supra", engine2);
    DealerLot dealerLot1 = new DealerLot(Arrays.asList(car1, car2));

    Set<ConstraintViolation<DealerLot>> i = validator.validate(dealerLot1);
    for (ConstraintViolation<DealerLot> v : i) {
        System.out.println(v.getMessage());
    };
}}

输出:

Engine Name must start with a V! You provided 2JZ-GTE

我希望输出看起来像下面这样:

Toyota Supra, Engine Name must start with a V! You provided 2JZ-GTE

如果没有这种程度的清晰度,确定哪些汽车存在问题可能会非常混乱。

仅供参考,这些是我在示例中使用的依赖项的版本:

compile group: 'org.hibernate.validator', name: 'hibernate-validator', version: '6.0.17.Final'
compile group: 'org.glassfish', name: 'javax.el', version: '3.0.1-b11'
implementation group: 'org.projectlombok', name: 'lombok', version: "1.18.4"
compile group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'

【问题讨论】:

  • 您可能会添加从 Engine dto 到 Car dto 的反向引用,然后在消息中使用表达式语言通过该引用获取名称。不过我还没有尝试过这样的事情。
  • 这是一个聚合问题。如果您希望父类 (Car) 报告其组件之一 (Engine) 的问题,那么您需要将验证移到那里。

标签: java validation hibernate-validator javax.validation


【解决方案1】:

就像 hfontanez 所说,您需要将验证移至Car。您仍然可以使用自定义验证注释(我们称之为ValidCarEngine)和验证器来获取引擎名称上报告的错误。

public class ValidCarEngineValidator implements ConstraintValidator<ValidCarEngine, Car> {

private static final Pattern ENGINE_NAME_PATTERN = Pattern.compile("V.*");

@Override
public boolean isValid(Car car, ConstraintValidatorContext context) {
    if (car == null || car.getEngine() == null || car.getEngine().getEngineName() == null) {
        return true; // use @NotNull if needed
    }
    String carName = car.getCarName();
    String engineName = car.getEngine().getName();

    if (Pattern.matcher(engineName).matches()) {
        return true;
    }
    // Disable the out-of-the-box error on the Car
    constraintValidatorContext.disableDefaultConstraintViolation();

    String message = carName + ", Engine Name must start with a V! You provided " + engineName;
    constraintValidatorContext.buildConstraintViolationWithTemplate(message)
            .addPropertyNode("engine")
            .addPropertyNode("engineName")
            .addConstraintViolation();
    return false;
}

这对所有汽车都使用硬编码模式,但您甚至可以添加从汽车类型/名称到模式的映射(只需确保使用默认值,如果没有映射,则返回 true)。

【讨论】:

  • 这就是我添加经销商手数概念的原因。所以无论属性树有多深,在这种情况下我们是 3 深(Engine -> Car -> DealerLot),你会编写一个自定义的 ConstraintValidators 来单独验证每个子属性吗?即使我们说30个属性深?在我看来,我们必须创建汽车级验证来验证引擎,这似乎有点离题。此外,在Engine 类本身和ValidCarEngineValidator 中,您将拥有用于验证引擎的重复代码。当然,你可以把它提取出来……但这些看起来很简单。
  • @CopyandPaste 如果您在引擎验证中需要汽车的任何内容,您只需要进行汽车级验证即可验证引擎。其中包括汽车的名称。
  • 为什么汽车级验证要处理引擎验证?正则表达式模式特定于引擎验证本身。以托盘引擎(尚未安装在汽车中)为例;我们可能想要验证这个引擎本身。使用上面的代码,我必须创建一个人造汽车,以验证引擎。除非您建议我们重复验证?
  • @CopyandPaste 因为您想在验证中使用汽车的一部分。这只是消息,但仍然如此。
猜你喜欢
  • 2011-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-29
  • 2012-12-12
  • 1970-01-01
相关资源
最近更新 更多