从Springboot 2.6.6升级到2.6.7使用验证(Javax.Validation.constraintDeclarationException:hv000151)的行为更改行为

发布于 2025-01-25 03:14:39 字数 7594 浏览 2 评论 0 原文

刚刚最近将Springboot升级为2.6.6至2.6.7,但是尽管Hibernate-Validator的GAV尚未更改(两个版本都使用 org.hibernate.Validator.Validator:Hibernate-validator:6.2.2.3.3.final jakarta.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接口和类:
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


注:如上所述,我了解了错误的原因,以便类和界面必须服从Liskov替代原则。

要么消失的错误的修复程序是:

  • withNameOncard 接口中删除setter方法 setNameOncard()。或
  • 将验证注释从实现类的字段移动到接口的GETTER方法。或
  • 仅将 @notnull nameOncard sigral>成员级 class> class> class> getNameOncard()方法方法 nameOncard 接口,在 @notblank nameOncard 字段中留下 @notblank (我实际上不明白的最后一个修复程序。 )

我不明白为什么此错误不是Springboot 2.6.6生成的当Hibernate验证器伪像保持在2.6.6和2.6.7之间。

因此,它一定是导致行为变化的其他事情,但我无法确定它是什么。

Just recently upgraded from SpringBoot 2.6.6 to 2.6.7, but despite the GAV for hibernate-validator has not changed ( both releases use org.hibernate.validator:hibernate-validator:6.2.3.Final and jakarta.validation:jakarta.validation-api:2.0.2 ), I noticed a change in behaviour using the validation API.

I was able to reduce it to simple test case to show the difference between 2.6.6 and 2.6.7.

NOTE: I understand the reason why it fails and know the fix, but what I don't understand is why it only started to fail with SpringBoot 2.6.7 when Hibernate validator artifact did not change between releases.

To demonstrate, here are the code and test cases:

  1. First the POM file ( using 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. Then the java interface and classes :
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. Finally, the test case :
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. Run the test, and you get :
[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. Now change the POM file so that it uses SpringBoot 2.6.7 :
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.7</version>
        <relativePath/>
    </parent>

  1. Run the test, and you get :
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


NOTE: Like I mentioned above, I understand the reason for the error so that the class and interface must obey the Liskov substitution principle.

The fixes for the error to go away are, either :

  • Removing the setter method setNameOnCard() from the WithNameOnCard interface. OR
  • Moving the validation annotations from the fields of the implementing class to the getter method of the interface. OR
  • Moving only the @NotNull annotation from the nameOnCard field of the MembershipCard class to the getNameOnCard() method of the NameOnCard interface, while leaving the @NotBlank annotation on the nameOncard field in the class. ( This last fix I actually don't understand. What's unique about @NotNull vs @NotBlank that would cause this difference between 2.6.6 and 2.6.7 ? )

What I don't understand why was this error NOT generated with SpringBoot 2.6.6 when the Hibernate validator artifact remained the same between 2.6.6 and 2.6.7.

So it must be something else that is causing this change in behaviour, but I can't pinpoint to what it is.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

烟织青萝梦 2025-02-01 03:14:39

Spring Boot 2.6.7 升级了Lombok版本。较新的Lombok版本传播字段注释到生成的setter方法。作为解决的工作,您可以通过设置Maven属性来降低Lombok版本:

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

Spring Boot 2.6.7 upgraded the Lombok version. The newer Lombok version propagates the field annotations to the generated setter methods. As a work around, you can downgrade the Lombok version by setting the Maven property:

<lombok.version>1.18.22</lombok.version>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文