Skip to content

Commit a7b3778

Browse files
CodeCasterXclaude
andcommitted
fix(fit): 修复 validation 守卫逻辑漏检 PARAMETER 目标约束注解
hasConstraintAnnotationsInParameter 仅检查 TYPE_USE 注解,遗漏仅标注 PARAMETER 的约束注解(如 @notblank 用于 @RequestParam),导致守卫未 拦截校验请求,最终引发 ClassCastException。同时修复 javax 版本 hasConstraintAnnotationsInType 逻辑短路问题,并为两个插件添加 jakarta.validation-api 的 sharedDependencies 配置。 Closes #412 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ef7fe64 commit a7b3778

File tree

10 files changed

+213
-17
lines changed

10 files changed

+213
-17
lines changed

framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@
8787
<configuration>
8888
<category>system</category>
8989
<level>4</level>
90+
<sharedDependencies>
91+
<sharedDependency>
92+
<groupId>jakarta.validation</groupId>
93+
<artifactId>jakarta.validation-api</artifactId>
94+
</sharedDependency>
95+
</sharedDependencies>
9096
</configuration>
9197
</plugin>
9298
<plugin>

framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/main/java/modelengine/fitframework/validation/ValidationHandler.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*---------------------------------------------------------------------------------------------
2-
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
2+
* Copyright (c) 2025-2026 Huawei Technologies Co., Ltd. All rights reserved.
33
* This file is a part of the ModelEngine Project.
44
* Licensed under the MIT License. See License.txt in the project root for license information.
55
*--------------------------------------------------------------------------------------------*/
@@ -108,6 +108,9 @@ private boolean hasJakartaConstraintAnnotations(Parameter[] parameters) {
108108
* @return 如果包含 {@code jakarta.validation} 标注的校验注解则返回 {@code true},否则返回 {@code false}。
109109
*/
110110
private boolean hasConstraintAnnotationsInParameter(Parameter parameter) {
111+
if (Arrays.stream(parameter.getAnnotations()).anyMatch(this::isJakartaConstraintAnnotation)) {
112+
return true;
113+
}
111114
return hasConstraintAnnotationsInType(parameter.getAnnotatedType());
112115
}
113116

@@ -163,4 +166,4 @@ private boolean isJakartaConstraintAnnotation(Annotation annotation) {
163166
return "jakarta.validation".equals(packageName) && "Constraint".equals(className);
164167
});
165168
}
166-
}
169+
}

framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/ValidationDataControllerTest.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*---------------------------------------------------------------------------------------------
2-
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
2+
* Copyright (c) 2025-2026 Huawei Technologies Co., Ltd. All rights reserved.
33
* This file is a part of the ModelEngine Project.
44
* Licensed under the MIT License. See License.txt in the project root for license information.
55
*--------------------------------------------------------------------------------------------*/
@@ -92,4 +92,24 @@ void shouldFailedWhenCreateInvalidCompanyWithGroup() {
9292
this.response = this.mockMvc.perform(requestBuilder);
9393
assertThat(this.response.statusCode()).isEqualTo(500);
9494
}
95-
}
95+
96+
@Test
97+
@DisplayName("RequestParam 参数 NotBlank 校验 - 空白值应返回 500")
98+
void shouldFailWhenRequestParamIsBlank() {
99+
MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/param/notblank")
100+
.param("name", " ")
101+
.responseType(Void.class);
102+
this.response = this.mockMvc.perform(requestBuilder);
103+
assertThat(this.response.statusCode()).isEqualTo(500);
104+
}
105+
106+
@Test
107+
@DisplayName("RequestParam 参数 NotBlank 校验 - 合法值应返回 200")
108+
void shouldOkWhenRequestParamIsNotBlank() {
109+
MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/param/notblank")
110+
.param("name", "validName")
111+
.responseType(Void.class);
112+
this.response = this.mockMvc.perform(requestBuilder);
113+
assertThat(this.response.statusCode()).isEqualTo(200);
114+
}
115+
}

framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/ValidationHandlerTest.java

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*---------------------------------------------------------------------------------------------
2-
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
2+
* Copyright (c) 2025-2026 Huawei Technologies Co., Ltd. All rights reserved.
33
* This file is a part of the ModelEngine Project.
44
* Licensed under the MIT License. See License.txt in the project root for license information.
55
*--------------------------------------------------------------------------------------------*/
@@ -11,7 +11,10 @@
1111
import static org.mockito.Mockito.mock;
1212
import static org.mockito.Mockito.when;
1313

14+
import jakarta.validation.Constraint;
1415
import jakarta.validation.ConstraintViolationException;
16+
import jakarta.validation.Payload;
17+
import jakarta.validation.Valid;
1518
import modelengine.fitframework.aop.JoinPoint;
1619
import modelengine.fitframework.ioc.BeanContainer;
1720
import modelengine.fitframework.ioc.annotation.AnnotationMetadataResolver;
@@ -31,6 +34,10 @@
3134
import org.junit.jupiter.api.Test;
3235
import org.mockito.Mockito;
3336

37+
import java.lang.annotation.ElementType;
38+
import java.lang.annotation.Retention;
39+
import java.lang.annotation.RetentionPolicy;
40+
import java.lang.annotation.Target;
3441
import java.lang.reflect.InvocationTargetException;
3542
import java.lang.reflect.Method;
3643
import java.math.BigDecimal;
@@ -79,6 +86,37 @@ private ConstraintViolationException invokeHandleMethod(Method targetMethod, Obj
7986
return ObjectUtils.cast(invocationTargetException.getTargetException());
8087
}
8188

89+
private boolean invokeHasJakartaConstraintAnnotations(Method targetMethod) {
90+
Method hasJakartaConstraintAnnotationsMethod =
91+
ReflectionUtils.getDeclaredMethod(ValidationHandler.class, "hasJakartaConstraintAnnotations",
92+
java.lang.reflect.Parameter[].class);
93+
hasJakartaConstraintAnnotationsMethod.setAccessible(true);
94+
try {
95+
return ObjectUtils.cast(hasJakartaConstraintAnnotationsMethod.invoke(this.handler,
96+
new Object[] {targetMethod.getParameters()}));
97+
} catch (IllegalAccessException | InvocationTargetException exception) {
98+
throw new IllegalStateException("调用 hasJakartaConstraintAnnotations 失败", exception);
99+
}
100+
}
101+
102+
@Test
103+
@DisplayName("仅 PARAMETER 目标的约束注解应被检测")
104+
void shouldDetectParameterConstraintWithoutTypeUse() {
105+
Method method =
106+
ReflectionUtils.getDeclaredMethod(GuardValidationService.class, "validateParameterOnly", String.class);
107+
boolean hasConstraintAnnotations = invokeHasJakartaConstraintAnnotations(method);
108+
assertThat(hasConstraintAnnotations).isTrue();
109+
}
110+
111+
@Test
112+
@DisplayName("存在非约束类型注解时仍应检测泛型参数中的约束注解")
113+
void shouldDetectConstraintInGenericTypeWhenTypeHasNonConstraintAnnotation() {
114+
Method method = ReflectionUtils.getDeclaredMethod(GuardValidationService.class, "validateEmployeeList",
115+
List.class);
116+
boolean hasConstraintAnnotations = invokeHasJakartaConstraintAnnotations(method);
117+
assertThat(hasConstraintAnnotations).isTrue();
118+
}
119+
82120
@Test
83121
@DisplayName("测试校验原始类型成功")
84122
void givePrimitiveThenValidateOk() {
@@ -773,4 +811,25 @@ void testNullValidation() {
773811
assertThat(exception.getMessage()).isNotNull();
774812
}
775813
}
776-
}
814+
815+
private static class GuardValidationService {
816+
public void validateParameterOnly(@ParameterOnlyConstraint String name) {}
817+
818+
public void validateEmployeeList(@NoConstraintTypeUse List<@Valid Employee> employees) {}
819+
}
820+
821+
@Target(ElementType.PARAMETER)
822+
@Retention(RetentionPolicy.RUNTIME)
823+
@Constraint(validatedBy = {})
824+
private @interface ParameterOnlyConstraint {
825+
String message() default "参数不合法";
826+
827+
Class<?>[] groups() default {};
828+
829+
Class<? extends Payload>[] payload() default {};
830+
}
831+
832+
@Target(ElementType.TYPE_USE)
833+
@Retention(RetentionPolicy.RUNTIME)
834+
private @interface NoConstraintTypeUse {}
835+
}

framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-jakarta/src/test/java/modelengine/fitframework/validation/data/ValidationDataController.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
/*---------------------------------------------------------------------------------------------
2-
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
2+
* Copyright (c) 2025-2026 Huawei Technologies Co., Ltd. All rights reserved.
33
* This file is a part of the ModelEngine Project.
44
* Licensed under the MIT License. See License.txt in the project root for license information.
55
*--------------------------------------------------------------------------------------------*/
66

77
package modelengine.fitframework.validation.data;
88

99
import jakarta.validation.Valid;
10+
import jakarta.validation.constraints.NotBlank;
1011
import modelengine.fit.http.annotation.PostMapping;
1112
import modelengine.fit.http.annotation.RequestBody;
1213
import modelengine.fit.http.annotation.RequestMapping;
14+
import modelengine.fit.http.annotation.RequestParam;
1315
import modelengine.fitframework.annotation.Component;
1416
import modelengine.fitframework.validation.Validated;
1517

@@ -38,4 +40,12 @@ public void validateCompanyDefaultGroup(@RequestBody @Valid Company company) {}
3840
*/
3941
@PostMapping(path = "/company/companyGroup", description = "验证 Company 类特定分组注解")
4042
public void validateCompanyGroup(@RequestBody @Valid Company company) {}
41-
}
43+
44+
/**
45+
* 验证 RequestParam 参数的 NotBlank 约束。
46+
*
47+
* @param name 表示待校验的名字参数的 {@link String}。
48+
*/
49+
@PostMapping(path = "/param/notblank", description = "验证 RequestParam 参数的 NotBlank 约束")
50+
public void validateRequestParamNotBlank(@RequestParam("name") @NotBlank String name) {}
51+
}

framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@
8787
<configuration>
8888
<category>system</category>
8989
<level>4</level>
90+
<sharedDependencies>
91+
<sharedDependency>
92+
<groupId>jakarta.validation</groupId>
93+
<artifactId>jakarta.validation-api</artifactId>
94+
</sharedDependency>
95+
</sharedDependencies>
9096
</configuration>
9197
</plugin>
9298
<plugin>

framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/main/java/modelengine/fitframework/validation/ValidationHandler.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*---------------------------------------------------------------------------------------------
2-
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
2+
* Copyright (c) 2025-2026 Huawei Technologies Co., Ltd. All rights reserved.
33
* This file is a part of the ModelEngine Project.
44
* Licensed under the MIT License. See License.txt in the project root for license information.
55
*--------------------------------------------------------------------------------------------*/
@@ -109,6 +109,9 @@ private boolean hasJavaxConstraintAnnotations(Parameter[] parameters) {
109109
* @return 如果包含 {@code javax.validation} 标注的校验注解则返回 {@code true},否则返回 {@code false}。
110110
*/
111111
private boolean hasConstraintAnnotationsInParameter(Parameter parameter) {
112+
if (Arrays.stream(parameter.getAnnotations()).anyMatch(this::isJavaxConstraintAnnotation)) {
113+
return true;
114+
}
112115
return hasConstraintAnnotationsInType(parameter.getAnnotatedType());
113116
}
114117

@@ -120,8 +123,8 @@ private boolean hasConstraintAnnotationsInParameter(Parameter parameter) {
120123
*/
121124
private boolean hasConstraintAnnotationsInType(AnnotatedType annotatedType) {
122125
// 检查当前类型上的注解。
123-
if (annotatedType.getAnnotations().length > 0) {
124-
return Arrays.stream(annotatedType.getAnnotations()).anyMatch(this::isJavaxConstraintAnnotation);
126+
if (Arrays.stream(annotatedType.getAnnotations()).anyMatch(this::isJavaxConstraintAnnotation)) {
127+
return true;
125128
}
126129
// 如果是参数化类型,递归检查类型参数。
127130
if (annotatedType instanceof AnnotatedParameterizedType parameterizedType) {
@@ -164,4 +167,4 @@ private boolean isJavaxConstraintAnnotation(Annotation annotation) {
164167
return "javax.validation".equals(packageName) && "Constraint".equals(className);
165168
});
166169
}
167-
}
170+
}

framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/ValidationDataControllerTest.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*---------------------------------------------------------------------------------------------
2-
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
2+
* Copyright (c) 2025-2026 Huawei Technologies Co., Ltd. All rights reserved.
33
* This file is a part of the ModelEngine Project.
44
* Licensed under the MIT License. See License.txt in the project root for license information.
55
*--------------------------------------------------------------------------------------------*/
@@ -92,4 +92,24 @@ void shouldFailedWhenCreateInvalidCompanyWithGroup() {
9292
this.response = this.mockMvc.perform(requestBuilder);
9393
assertThat(this.response.statusCode()).isEqualTo(500);
9494
}
95-
}
95+
96+
@Test
97+
@DisplayName("RequestParam 参数 NotBlank 校验 - 空白值应返回 500")
98+
void shouldFailWhenRequestParamIsBlank() {
99+
MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/param/notblank")
100+
.param("name", " ")
101+
.responseType(Void.class);
102+
this.response = this.mockMvc.perform(requestBuilder);
103+
assertThat(this.response.statusCode()).isEqualTo(500);
104+
}
105+
106+
@Test
107+
@DisplayName("RequestParam 参数 NotBlank 校验 - 合法值应返回 200")
108+
void shouldOkWhenRequestParamIsNotBlank() {
109+
MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/param/notblank")
110+
.param("name", "validName")
111+
.responseType(Void.class);
112+
this.response = this.mockMvc.perform(requestBuilder);
113+
assertThat(this.response.statusCode()).isEqualTo(200);
114+
}
115+
}

framework/fit/java/fit-builtin/plugins/fit-validation-hibernate-javax/src/test/java/modelengine/fitframework/validation/ValidationHandlerTest.java

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*---------------------------------------------------------------------------------------------
2-
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
2+
* Copyright (c) 2025-2026 Huawei Technologies Co., Ltd. All rights reserved.
33
* This file is a part of the ModelEngine Project.
44
* Licensed under the MIT License. See License.txt in the project root for license information.
55
*--------------------------------------------------------------------------------------------*/
@@ -40,8 +40,15 @@
4040
import java.util.List;
4141
import java.util.Locale;
4242
import java.util.Map;
43+
import java.lang.annotation.ElementType;
44+
import java.lang.annotation.Retention;
45+
import java.lang.annotation.RetentionPolicy;
46+
import java.lang.annotation.Target;
4347

48+
import javax.validation.Constraint;
4449
import javax.validation.ConstraintViolationException;
50+
import javax.validation.Payload;
51+
import javax.validation.Valid;
4552

4653
/**
4754
* {@link ValidationHandler} 的单元测试。
@@ -80,6 +87,37 @@ private ConstraintViolationException invokeHandleMethod(Method targetMethod, Obj
8087
return ObjectUtils.cast(invocationTargetException.getTargetException());
8188
}
8289

90+
private boolean invokeHasJavaxConstraintAnnotations(Method targetMethod) {
91+
Method hasJavaxConstraintAnnotationsMethod =
92+
ReflectionUtils.getDeclaredMethod(ValidationHandler.class, "hasJavaxConstraintAnnotations",
93+
java.lang.reflect.Parameter[].class);
94+
hasJavaxConstraintAnnotationsMethod.setAccessible(true);
95+
try {
96+
return ObjectUtils.cast(hasJavaxConstraintAnnotationsMethod.invoke(this.handler,
97+
new Object[] {targetMethod.getParameters()}));
98+
} catch (IllegalAccessException | InvocationTargetException exception) {
99+
throw new IllegalStateException("调用 hasJavaxConstraintAnnotations 失败", exception);
100+
}
101+
}
102+
103+
@Test
104+
@DisplayName("仅 PARAMETER 目标的约束注解应被检测")
105+
void shouldDetectParameterConstraintWithoutTypeUse() {
106+
Method method =
107+
ReflectionUtils.getDeclaredMethod(GuardValidationService.class, "validateParameterOnly", String.class);
108+
boolean hasConstraintAnnotations = invokeHasJavaxConstraintAnnotations(method);
109+
assertThat(hasConstraintAnnotations).isTrue();
110+
}
111+
112+
@Test
113+
@DisplayName("存在非约束类型注解时仍应检测泛型参数中的约束注解")
114+
void shouldDetectConstraintInGenericTypeWhenTypeHasNonConstraintAnnotation() {
115+
Method method = ReflectionUtils.getDeclaredMethod(GuardValidationService.class, "validateEmployeeList",
116+
List.class);
117+
boolean hasConstraintAnnotations = invokeHasJavaxConstraintAnnotations(method);
118+
assertThat(hasConstraintAnnotations).isTrue();
119+
}
120+
83121
@Test
84122
@DisplayName("测试校验原始类型成功")
85123
void givePrimitiveThenValidateOk() {
@@ -774,4 +812,25 @@ void testNullValidation() {
774812
assertThat(exception.getMessage()).isNotNull();
775813
}
776814
}
815+
816+
private static class GuardValidationService {
817+
public void validateParameterOnly(@ParameterOnlyConstraint String name) {}
818+
819+
public void validateEmployeeList(@NoConstraintTypeUse List<@Valid Employee> employees) {}
820+
}
821+
822+
@Target(ElementType.PARAMETER)
823+
@Retention(RetentionPolicy.RUNTIME)
824+
@Constraint(validatedBy = {})
825+
private @interface ParameterOnlyConstraint {
826+
String message() default "参数不合法";
827+
828+
Class<?>[] groups() default {};
829+
830+
Class<? extends Payload>[] payload() default {};
831+
}
832+
833+
@Target(ElementType.TYPE_USE)
834+
@Retention(RetentionPolicy.RUNTIME)
835+
private @interface NoConstraintTypeUse {}
777836
}

0 commit comments

Comments
 (0)