Skip to content

Commit cf9f74b

Browse files
committed
Merge branch '3.6.x'
2 parents 175a48a + 5e9159e commit cf9f74b

File tree

8 files changed

+323
-29
lines changed

8 files changed

+323
-29
lines changed

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

Lines changed: 33 additions & 7 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
*--------------------------------------------------------------------------------------------*/
@@ -24,6 +24,7 @@
2424
import java.lang.annotation.Annotation;
2525
import java.lang.reflect.AnnotatedParameterizedType;
2626
import java.lang.reflect.AnnotatedType;
27+
import java.lang.reflect.Method;
2728
import java.lang.reflect.Parameter;
2829
import java.util.Arrays;
2930
import java.util.Locale;
@@ -72,11 +73,12 @@ public void setLocale(Locale locale) {
7273
*/
7374
@Before(value = "@target(validated) && execution(public * *(..))", argNames = "joinPoint, validated")
7475
private void handle(JoinPoint joinPoint, Validated validated) {
76+
Method method = joinPoint.getMethod();
7577
// 检查方法参数是否包含被 jakarta.validation.Constraint 标注的校验注解。
76-
if (hasJakartaConstraintAnnotations(joinPoint.getMethod().getParameters())) {
78+
if (hasJakartaConstraintAnnotations(method)) {
7779
ExecutableValidator execVal = this.validator.forExecutables();
7880
Set<ConstraintViolation<Object>> result = execVal.validateParameters(joinPoint.getTarget(),
79-
joinPoint.getMethod(),
81+
method,
8082
joinPoint.getArgs(),
8183
validated.value());
8284
if (!result.isEmpty()) {
@@ -94,11 +96,32 @@ public void close() {
9496
/**
9597
* 检查方法参数是否包含 {@code jakarta.validation} 校验注解。
9698
*
97-
* @param parameters 表示可能携带校验注解的方法参数数组 {@link Parameter}{@code []}。
99+
* @param method 表示待检查的方法 {@link Method}。
98100
* @return 如果包含 {@code jakarta.validation} 标注的校验注解则返回 {@code true},否则返回 {@code false}。
99101
*/
100-
private boolean hasJakartaConstraintAnnotations(Parameter[] parameters) {
101-
return Arrays.stream(parameters).anyMatch(this::hasConstraintAnnotationsInParameter);
102+
private boolean hasJakartaConstraintAnnotations(Method method) {
103+
if (Arrays.stream(method.getParameters()).anyMatch(this::hasConstraintAnnotationsInParameter)) {
104+
return true;
105+
}
106+
return this.hasConstraintAnnotationsInInterfaces(method);
107+
}
108+
109+
private boolean hasConstraintAnnotationsInInterfaces(Method method) {
110+
Class<?> clazz = method.getDeclaringClass();
111+
while (clazz != null) {
112+
for (Class<?> iface : clazz.getInterfaces()) {
113+
try {
114+
Method interfaceMethod = iface.getMethod(method.getName(), method.getParameterTypes());
115+
if (Arrays.stream(interfaceMethod.getParameters()).anyMatch(this::hasConstraintAnnotationsInParameter)) {
116+
return true;
117+
}
118+
} catch (NoSuchMethodException ignored) {
119+
// 当前接口未声明该方法,继续检查其他接口。
120+
}
121+
}
122+
clazz = clazz.getSuperclass();
123+
}
124+
return false;
102125
}
103126

104127
/**
@@ -108,6 +131,9 @@ private boolean hasJakartaConstraintAnnotations(Parameter[] parameters) {
108131
* @return 如果包含 {@code jakarta.validation} 标注的校验注解则返回 {@code true},否则返回 {@code false}。
109132
*/
110133
private boolean hasConstraintAnnotationsInParameter(Parameter parameter) {
134+
if (Arrays.stream(parameter.getAnnotations()).anyMatch(this::isJakartaConstraintAnnotation)) {
135+
return true;
136+
}
111137
return hasConstraintAnnotationsInType(parameter.getAnnotatedType());
112138
}
113139

@@ -163,4 +189,4 @@ private boolean isJakartaConstraintAnnotation(Annotation annotation) {
163189
return "jakarta.validation".equals(packageName) && "Constraint".equals(className);
164190
});
165191
}
166-
}
192+
}

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: 94 additions & 3 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,11 @@
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;
18+
import jakarta.validation.constraints.NotNull;
1519
import modelengine.fitframework.aop.JoinPoint;
1620
import modelengine.fitframework.ioc.BeanContainer;
1721
import modelengine.fitframework.ioc.annotation.AnnotationMetadataResolver;
@@ -31,6 +35,10 @@
3135
import org.junit.jupiter.api.Test;
3236
import org.mockito.Mockito;
3337

38+
import java.lang.annotation.ElementType;
39+
import java.lang.annotation.Retention;
40+
import java.lang.annotation.RetentionPolicy;
41+
import java.lang.annotation.Target;
3442
import java.lang.reflect.InvocationTargetException;
3543
import java.lang.reflect.Method;
3644
import java.math.BigDecimal;
@@ -65,20 +73,73 @@ void setUp() {
6573
}
6674

6775
private ConstraintViolationException invokeHandleMethod(Method targetMethod, Object[] args) {
76+
return this.invokeHandleMethod(targetMethod, this.validateService, args);
77+
}
78+
79+
private ConstraintViolationException invokeHandleMethod(Method targetMethod, Object target, Object[] args) {
6880
Method handleValidatedMethod =
6981
ReflectionUtils.getDeclaredMethod(ValidationHandler.class, "handle", JoinPoint.class, Validated.class);
7082
handleValidatedMethod.setAccessible(true);
7183
JoinPoint joinPoint = mock(JoinPoint.class);
7284
when(joinPoint.getMethod()).thenReturn(targetMethod);
7385
when(joinPoint.getArgs()).thenReturn(args);
74-
when(joinPoint.getTarget()).thenReturn(this.validateService);
86+
when(joinPoint.getTarget()).thenReturn(target);
7587

7688
InvocationTargetException invocationTargetException = catchThrowableOfType(InvocationTargetException.class,
7789
() -> handleValidatedMethod.invoke(this.handler, joinPoint, this.validated));
7890

7991
return ObjectUtils.cast(invocationTargetException.getTargetException());
8092
}
8193

94+
private boolean invokeHasJakartaConstraintAnnotations(Method targetMethod) {
95+
Method hasJakartaConstraintAnnotationsMethod =
96+
ReflectionUtils.getDeclaredMethod(ValidationHandler.class, "hasJakartaConstraintAnnotations",
97+
Method.class);
98+
hasJakartaConstraintAnnotationsMethod.setAccessible(true);
99+
try {
100+
return ObjectUtils.cast(hasJakartaConstraintAnnotationsMethod.invoke(this.handler, targetMethod));
101+
} catch (IllegalAccessException | InvocationTargetException exception) {
102+
throw new IllegalStateException("调用 hasJakartaConstraintAnnotations 失败", exception);
103+
}
104+
}
105+
106+
@Test
107+
@DisplayName("仅 PARAMETER 目标的约束注解应被检测")
108+
void shouldDetectParameterConstraintWithoutTypeUse() {
109+
Method method =
110+
ReflectionUtils.getDeclaredMethod(GuardValidationService.class, "validateParameterOnly", String.class);
111+
boolean hasConstraintAnnotations = invokeHasJakartaConstraintAnnotations(method);
112+
assertThat(hasConstraintAnnotations).isTrue();
113+
}
114+
115+
@Test
116+
@DisplayName("存在非约束类型注解时仍应检测泛型参数中的约束注解")
117+
void shouldDetectConstraintInGenericTypeWhenTypeHasNonConstraintAnnotation() {
118+
Method method = ReflectionUtils.getDeclaredMethod(GuardValidationService.class, "validateEmployeeList",
119+
List.class);
120+
boolean hasConstraintAnnotations = invokeHasJakartaConstraintAnnotations(method);
121+
assertThat(hasConstraintAnnotations).isTrue();
122+
}
123+
124+
@Test
125+
@DisplayName("接口声明约束注解时实现类方法也应被检测")
126+
void shouldDetectConstraintAnnotationsFromInterface() {
127+
Method method =
128+
ReflectionUtils.getDeclaredMethod(ValidatedCommandHandlerImpl.class, "handle", Employee.class);
129+
boolean hasConstraintAnnotations = invokeHasJakartaConstraintAnnotations(method);
130+
assertThat(hasConstraintAnnotations).isTrue();
131+
}
132+
133+
@Test
134+
@DisplayName("接口声明约束注解时校验应生效")
135+
void shouldValidateWhenConstraintAnnotationsOnInterface() {
136+
Method method =
137+
ReflectionUtils.getDeclaredMethod(ValidatedCommandHandlerImpl.class, "handle", Employee.class);
138+
ConstraintViolationException exception =
139+
invokeHandleMethod(method, new ValidatedCommandHandlerImpl(), new Object[] {null});
140+
assertThat(exception.getMessage()).contains("Command cannot be null.");
141+
}
142+
82143
@Test
83144
@DisplayName("测试校验原始类型成功")
84145
void givePrimitiveThenValidateOk() {
@@ -773,4 +834,34 @@ void testNullValidation() {
773834
assertThat(exception.getMessage()).isNotNull();
774835
}
775836
}
776-
}
837+
838+
private static class GuardValidationService {
839+
public void validateParameterOnly(@ParameterOnlyConstraint String name) {}
840+
841+
public void validateEmployeeList(@NoConstraintTypeUse List<@Valid Employee> employees) {}
842+
}
843+
844+
private interface ValidatedCommandHandler {
845+
void handle(@Valid @NotNull(message = "Command cannot be null.") Employee employee);
846+
}
847+
848+
private static class ValidatedCommandHandlerImpl implements ValidatedCommandHandler {
849+
@Override
850+
public void handle(Employee employee) {}
851+
}
852+
853+
@Target(ElementType.PARAMETER)
854+
@Retention(RetentionPolicy.RUNTIME)
855+
@Constraint(validatedBy = {})
856+
private @interface ParameterOnlyConstraint {
857+
String message() default "参数不合法";
858+
859+
Class<?>[] groups() default {};
860+
861+
Class<? extends Payload>[] payload() default {};
862+
}
863+
864+
@Target(ElementType.TYPE_USE)
865+
@Retention(RetentionPolicy.RUNTIME)
866+
private @interface NoConstraintTypeUse {}
867+
}

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/src/main/java/modelengine/fitframework/validation/ValidationHandler.java

Lines changed: 35 additions & 9 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
*--------------------------------------------------------------------------------------------*/
@@ -18,6 +18,7 @@
1818
import java.lang.annotation.Annotation;
1919
import java.lang.reflect.AnnotatedParameterizedType;
2020
import java.lang.reflect.AnnotatedType;
21+
import java.lang.reflect.Method;
2122
import java.lang.reflect.Parameter;
2223
import java.util.Arrays;
2324
import java.util.Locale;
@@ -73,11 +74,12 @@ public void setLocale(Locale locale) {
7374
*/
7475
@Before(value = "@target(validated) && execution(public * *(..))", argNames = "joinPoint, validated")
7576
private void handle(JoinPoint joinPoint, Validated validated) {
77+
Method method = joinPoint.getMethod();
7678
// 检查方法参数是否包含被 javax.validation.Constraint 标注的校验注解。
77-
if (hasJavaxConstraintAnnotations(joinPoint.getMethod().getParameters())) {
79+
if (hasJavaxConstraintAnnotations(method)) {
7880
ExecutableValidator execVal = this.validator.forExecutables();
7981
Set<ConstraintViolation<Object>> result = execVal.validateParameters(joinPoint.getTarget(),
80-
joinPoint.getMethod(),
82+
method,
8183
joinPoint.getArgs(),
8284
validated.value());
8385
if (!result.isEmpty()) {
@@ -95,11 +97,32 @@ public void close() {
9597
/**
9698
* 检查方法参数是否包含 {@code javax.validation} 校验注解。
9799
*
98-
* @param parameters 表示可能携带校验注解的方法参数数组 {@link Parameter}{@code []}。
100+
* @param method 表示待检查的方法 {@link Method}。
99101
* @return 如果包含 {@code javax.validation} 标注的校验注解则返回 {@code true},否则返回 {@code false}。
100102
*/
101-
private boolean hasJavaxConstraintAnnotations(Parameter[] parameters) {
102-
return Arrays.stream(parameters).anyMatch(this::hasConstraintAnnotationsInParameter);
103+
private boolean hasJavaxConstraintAnnotations(Method method) {
104+
if (Arrays.stream(method.getParameters()).anyMatch(this::hasConstraintAnnotationsInParameter)) {
105+
return true;
106+
}
107+
return this.hasConstraintAnnotationsInInterfaces(method);
108+
}
109+
110+
private boolean hasConstraintAnnotationsInInterfaces(Method method) {
111+
Class<?> clazz = method.getDeclaringClass();
112+
while (clazz != null) {
113+
for (Class<?> iface : clazz.getInterfaces()) {
114+
try {
115+
Method interfaceMethod = iface.getMethod(method.getName(), method.getParameterTypes());
116+
if (Arrays.stream(interfaceMethod.getParameters()).anyMatch(this::hasConstraintAnnotationsInParameter)) {
117+
return true;
118+
}
119+
} catch (NoSuchMethodException ignored) {
120+
// 当前接口未声明该方法,继续检查其他接口。
121+
}
122+
}
123+
clazz = clazz.getSuperclass();
124+
}
125+
return false;
103126
}
104127

105128
/**
@@ -109,6 +132,9 @@ private boolean hasJavaxConstraintAnnotations(Parameter[] parameters) {
109132
* @return 如果包含 {@code javax.validation} 标注的校验注解则返回 {@code true},否则返回 {@code false}。
110133
*/
111134
private boolean hasConstraintAnnotationsInParameter(Parameter parameter) {
135+
if (Arrays.stream(parameter.getAnnotations()).anyMatch(this::isJavaxConstraintAnnotation)) {
136+
return true;
137+
}
112138
return hasConstraintAnnotationsInType(parameter.getAnnotatedType());
113139
}
114140

@@ -120,8 +146,8 @@ private boolean hasConstraintAnnotationsInParameter(Parameter parameter) {
120146
*/
121147
private boolean hasConstraintAnnotationsInType(AnnotatedType annotatedType) {
122148
// 检查当前类型上的注解。
123-
if (annotatedType.getAnnotations().length > 0) {
124-
return Arrays.stream(annotatedType.getAnnotations()).anyMatch(this::isJavaxConstraintAnnotation);
149+
if (Arrays.stream(annotatedType.getAnnotations()).anyMatch(this::isJavaxConstraintAnnotation)) {
150+
return true;
125151
}
126152
// 如果是参数化类型,递归检查类型参数。
127153
if (annotatedType instanceof AnnotatedParameterizedType parameterizedType) {
@@ -164,4 +190,4 @@ private boolean isJavaxConstraintAnnotation(Annotation annotation) {
164190
return "javax.validation".equals(packageName) && "Constraint".equals(className);
165191
});
166192
}
167-
}
193+
}

0 commit comments

Comments
 (0)