从 Spring Boot 2.6.6 升级到 2.6.7 通过验证更改了行为 (javax.validation.ConstraintDeclarationException: HV000151)
Upgrading from SpringBoot 2.6.6 to 2.6.7 changed behaviour with validation ( javax.validation.ConstraintDeclarationException: HV000151 )
最近刚刚从 SpringBoot 2.6.6 升级到 2.6.7,但是尽管 hibernate-validator 的 GAV 没有改变(两个版本都使用 org.hibernate.validator:hibernate-validator:6.2.3.Final
和 jakarta.validation:jakarta.validation-api:2.0.2
) ,我注意到使用验证 API.
的行为发生了变化
我能够将其简化为简单的测试用例以显示 2.6.6 和 2.6.7 之间的区别。
注意: 我理解它失败的原因并知道修复方法,但我不明白的是为什么它只是在使用 SpringBoot 时才开始失败2.6.7 当 Hibernate 验证器工件在版本之间没有变化时。
为了演示,这里是代码和测试用例:
- 首先是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>
- 然后是 java 界面和 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);
}
}
- 最后,测试用例:
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());
}
}
- 运行 测试,你得到:
[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
- 现在更改 POM 文件,使其使用 SpringBoot 2.6.7 :
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
<relativePath/>
</parent>
- 运行 测试,你得到:
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
注意: 就像我上面提到的,我明白错误的原因所以class和接口必须遵守Liskov替换原则。
消除错误的修复方法是:
- 正在从
WithNameOnCard
界面中删除 setter 方法 setNameOnCard()
。或者
- 将验证注释从实现 class 的字段移动到接口的 getter 方法。或者
- 仅将
@NotNull
注释从 MembershipCard
class 的 nameOnCard
字段移动到 NameOnCard
接口的 getNameOnCard()
方法, 同时在 class 的 nameOncard
字段上留下 @NotBlank
注释。 (最后一个修复我其实不明白。@NotNull
和 @NotBlank
有什么独特之处会导致 2.6.6 和 2.6.7 之间的这种差异?)
我不明白为什么当 Hibernate 验证器工件在 2.6.6 和 2.6.7 之间保持不变时,SpringBoot 2.6.6 不会生成此错误 NOT。
所以一定是其他原因导致了这种行为变化,但我无法确定是什么原因。
Spring 引导 2.6.7 upgraded the Lombok version. The newer Lombok version propagates the field annotations 到生成的 setter 方法。作为解决方法,您可以通过设置 Maven 属性:
来降级 Lombok 版本
<lombok.version>1.18.22</lombok.version>
最近刚刚从 SpringBoot 2.6.6 升级到 2.6.7,但是尽管 hibernate-validator 的 GAV 没有改变(两个版本都使用 org.hibernate.validator:hibernate-validator:6.2.3.Final
和 jakarta.validation:jakarta.validation-api:2.0.2
) ,我注意到使用验证 API.
我能够将其简化为简单的测试用例以显示 2.6.6 和 2.6.7 之间的区别。
注意: 我理解它失败的原因并知道修复方法,但我不明白的是为什么它只是在使用 SpringBoot 时才开始失败2.6.7 当 Hibernate 验证器工件在版本之间没有变化时。
为了演示,这里是代码和测试用例:
- 首先是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>
- 然后是 java 界面和 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);
}
}
- 最后,测试用例:
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());
}
}
- 运行 测试,你得到:
[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
- 现在更改 POM 文件,使其使用 SpringBoot 2.6.7 :
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
<relativePath/>
</parent>
- 运行 测试,你得到:
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
注意: 就像我上面提到的,我明白错误的原因所以class和接口必须遵守Liskov替换原则。
消除错误的修复方法是:
- 正在从
WithNameOnCard
界面中删除 setter 方法setNameOnCard()
。或者 - 将验证注释从实现 class 的字段移动到接口的 getter 方法。或者
- 仅将
@NotNull
注释从MembershipCard
class 的nameOnCard
字段移动到NameOnCard
接口的getNameOnCard()
方法, 同时在 class 的nameOncard
字段上留下@NotBlank
注释。 (最后一个修复我其实不明白。@NotNull
和@NotBlank
有什么独特之处会导致 2.6.6 和 2.6.7 之间的这种差异?)
我不明白为什么当 Hibernate 验证器工件在 2.6.6 和 2.6.7 之间保持不变时,SpringBoot 2.6.6 不会生成此错误 NOT。
所以一定是其他原因导致了这种行为变化,但我无法确定是什么原因。
Spring 引导 2.6.7 upgraded the Lombok version. The newer Lombok version propagates the field annotations 到生成的 setter 方法。作为解决方法,您可以通过设置 Maven 属性:
来降级 Lombok 版本<lombok.version>1.18.22</lombok.version>