spring-boot - 从 SpringBoot 2.6.6 升级到 2.6.7 更改了验证行为(javax.validation.ConstraintDeclarationException : HV000151 )

标签 spring-boot hibernate-validator

最近刚刚从 SpringBoot 2.6.6 升级到 2.6.7,但是尽管 hibernate-validator 的 GAV 没有改变 (两个版本都使用 org.hibernate.validator:hibernate-validator:6.2.3 .Finaljakarta.validation:jakarta.validation-api:2.0.2 ),我注意到使用验证 API 的行为发生了变化。

我能够将其简化为简单的测试用例以显示 2.6.6 和 2.6.7 之间的区别。

注意: 我理解它失败的原因并知道修复方法,但我不明白的是为什么它只在 Hibernate 验证器工件时才开始使用 SpringBoot 2.6.7 失败版本之间没有变化。

为了演示,这里是代码和测试用例:

  1. 首先是 POM 文件(使用 SpringBoot 2.6.6):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.6</version>
        <relativePath/>
    </parent>
    <groupId>org.example</groupId>
    <artifactId>validator</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  1. 然后是 java 接口(interface)和类:
package org.example.demo;

public interface WithNameOnCard {

    String getNameOnCard();
    void setNameOnCard(String nameOnCard);

}
package org.example.demo;

import lombok.Getter;
import lombok.Setter;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Getter
@Setter
public class MembershipCard implements WithNameOnCard {

    @NotNull
    @NotBlank
    private String nameOnCard;

    @NotNull
    private String membershipNumber;

}
package org.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}
  1. 最后,测试用例:
package org.example.demo;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;

public class ValidatorTest {

    public static Validator validator;

    @BeforeAll
    public static void setUp() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }

    @Test
    public void whenPropertiesEmptyThenFailValidation() {
        MembershipCard membershipCard = new MembershipCard();

        Set<ConstraintViolation<MembershipCard>> violations = validator.validate(membershipCard);
        Assertions.assertTrue(violations.size() > 0);
    }

    @Test
    public void whenPropertiesNotEmptyThenFailPasses() {
        MembershipCard membershipCard = new MembershipCard();
        membershipCard.setMembershipNumber("123456");
        membershipCard.setNameOnCard("JOHN SMITH");

        Set<ConstraintViolation<MembershipCard>> violations = validator.validate(membershipCard);
        Assertions.assertEquals(0, violations.size());
    }

}

  1. 运行测试,你会得到:
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running org.example.demo.ValidatorTest
< ... snipped ... >
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.196 s - in org.example.demo.ValidatorTest

  1. 现在更改 POM 文件,使其使用 SpringBoot 2.6.7:
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.7</version>
        <relativePath/>
    </parent>

  1. 运行测试,你会得到:
INFO] Running org.example.demo.ValidatorTest
<... snipped ...>
[ERROR] Tests run: 2, Failures: 0, Errors: 2, Skipped: 0, Time elapsed: 0.17 s <<< FAILURE! - in org.example.demo.ValidatorTest
[ERROR] whenPropertiesEmptyThenFailValidation  Time elapsed: 0.026 s  <<< ERROR!
javax.validation.ConstraintDeclarationException: HV000151: A method overriding another method must not redefine the parameter constraint configuration, but method MembershipCard#setNameOnCard(String) redefines the configuration of WithNameOnCard#setNameOnCard(String).
    at org.example.demo.ValidatorTest.whenPropertiesEmptyThenFailValidation(ValidatorTest.java:27)

[ERROR] whenPropertiesNotEmptyThenFailPasses  Time elapsed: 0.003 s  <<< ERROR!
javax.validation.ConstraintDeclarationException: HV000151: A method overriding another method must not redefine the parameter constraint configuration, but method MembershipCard#setNameOnCard(String) redefines the configuration of WithNameOnCard#setNameOnCard(String).
    at org.example.demo.ValidatorTest.whenPropertiesNotEmptyThenFailPasses(ValidatorTest.java:37)

[ERROR] Errors: 
[ERROR]   ValidatorTest.whenPropertiesEmptyThenFailValidation:27 » ConstraintDeclaration
[ERROR]   ValidatorTest.whenPropertiesNotEmptyThenFailPasses:37 » ConstraintDeclaration ...
[INFO] 
[ERROR] Tests run: 3, Failures: 0, Errors: 2, Skipped: 0


注意: 就像我上面提到的,我理解错误的原因,所以类和接口(interface)必须遵守 Liskov 替换原则。

错误消失的修复方法是:

  • WithNameOnCard 接口(interface)中删除 setter 方法 setNameOnCard()。或者
  • 将验证注释从实现类的字段移至接口(interface)的 getter 方法。或者
  • 仅将 @NotNull 注释从 MembershipCard 类的 nameOnCard 字段移动到 getNameOnCard() NameOnCard 接口(interface)的方法,同时在类中的 nameOncard 字段上保留 @NotBlank 注释。 ( 我实际上不明白最后一个修复。@NotNull@NotBlank 有什么独特之处会导致 2.6.6 和 2.6.7 之间出现这种差异? )

当 Hibernate 验证器工件在 2.6.6 和 2.6.7 之间保持不变时,我不明白为什么 SpringBoot 2.6.6 NOT 会生成此错误。

所以一定是其他原因导致了这种行为变化,但我无法确定是什么。

最佳答案

Spring Boot 2.6.7 upgraded the Lombok version .较新的 Lombok 版本 propagates the field annotations到生成的setter方法。作为解决方法,您可以通过设置 Maven 属性来降级 Lombok 版本:

<lombok.version>1.18.22</lombok.version>

关于spring-boot - 从 SpringBoot 2.6.6 升级到 2.6.7 更改了验证行为(javax.validation.ConstraintDeclarationException : HV000151 ),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72053338/

相关文章:

spring - 如何匹配 Spring MVC 中的 URL 部分?

Spring MVC 与 Hibernate Validator 对于数据库字段是必需的,但在应用程序中不是必需的

java - com.springsource.org.springmodules.validation.validator 0.9.0 有更新的替代品吗?

java - 如何在 hibernate validator 中对所有属性设置默认约束

java - 读取以 utf-8 编码的 ValidationMessages

java - 如何使用 hibernate validator 检查长度为 5 或 9?

excel - 在 XSSFWorkbook 中保存时日期字段未以正确格式保存

java - 如何动态更新信任库?

tomcat - 带端点的 Spring Boot 创建两个 Tomcat 容器

java - GraphQL-Java 将 HTTP header 发送到 REST 端点