This is an automated email from the ASF dual-hosted git repository.
jianbin pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git
The following commit(s) were added to refs/heads/2.x by this push:
new 07f56b52a3 optimize: add support for parsing @RequestParam annotation
in netty-http-server (#7430)
07f56b52a3 is described below
commit 07f56b52a327cab72f020a5e773aab2a8eb110bc
Author: xiaoyu <[email protected]>
AuthorDate: Sat Jun 14 16:00:42 2025 +0800
optimize: add support for parsing @RequestParam annotation in
netty-http-server (#7430)
---
changes/en-us/2.x.md | 3 +
changes/zh-cn/2.x.md | 4 +-
.../core/rpc/netty/http/HttpDispatchHandler.java | 3 +
.../seata/core/rpc/netty/http/ParamMetaData.java | 30 ++++
.../seata/core/rpc/netty/http/ParameterParser.java | 33 +++-
.../rpc/netty/http/HttpDispatchHandlerTest.java | 1 +
.../core/rpc/netty/http/ParameterParserTest.java | 152 +++++++++++++++++-
.../http/RestControllerBeanPostProcessor.java | 114 ++++++++++++--
.../http/RestControllerBeanPostProcessorTest.java | 172 +++++++++++++++++++--
.../seata/server/controller/ClusterController.java | 6 +-
10 files changed, 487 insertions(+), 31 deletions(-)
diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index d2749f0ab3..f32621d09f 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -56,6 +56,7 @@ Add changes here for all PR submitted to the 2.x branch.
- [[#7418](https://github.com/apache/incubator-seata/pull/7418)] add jackson
notice
- [[#7419](https://github.com/apache/incubator-seata/pull/7419)] Add maven
profile to support packaging source code
- [[#7428](https://github.com/apache/incubator-seata/pull/7428)] pmd-check log
as ERROR level
+- [[#7430](https://github.com/apache/incubator-seata/pull/7430)] Add support
for parsing @RequestParam annotation in netty-http-server
- [[#7432](https://github.com/apache/incubator-seata/pull/7432)] conditionally
include test modules using Maven profiles
@@ -120,6 +121,8 @@ Thanks to these contributors for their code commits. Please
report an unintended
- [PengningYang](https://github.com/PengningYang)
- [WangzJi](https://github.com/WangzJi)
- [maple525866](https://github.com/maple525866)
+- [YvCeung](https://github.com/YvCeung)
+
Also, we receive many valuable issues, questions and advices from our
community. Thanks for you all.
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 599cfcf1de..0e5350ad4d 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -56,6 +56,7 @@
- [[#7418](https://github.com/apache/incubator-seata/pull/7418)] 添加 jackson
notice
- [[#7419](https://github.com/apache/incubator-seata/pull/7419)] 添加 Maven
配置文件以支持源码打包
- [[#7428](https://github.com/apache/incubator-seata/pull/7428)] 修改 pmd-check
输出日志为 ERROR 级别
+- [[#7430](https://github.com/apache/incubator-seata/pull/7430)]
在netty-http-server中增加了对解析@RequestParam注释的支持
### security:
@@ -122,8 +123,7 @@
- [PengningYang](https://github.com/PengningYang)
- [WangzJi](https://github.com/WangzJi)
- [maple525866](https://github.com/maple525866)
-
-
+- [YvCeung](https://github.com/YvCeung)
同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandler.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandler.java
index 40dd67d2a5..782c7ebf6a 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandler.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandler.java
@@ -48,6 +48,9 @@ import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+/**
+ * A Netty HTTP request handler that dispatches incoming requests to
corresponding controller methods
+ */
public class HttpDispatchHandler extends
SimpleChannelInboundHandler<HttpRequest> {
private static final Logger LOGGER =
LoggerFactory.getLogger(HttpDispatchHandler.class);
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/ParamMetaData.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/ParamMetaData.java
index 4828e38cbf..41625ec3ff 100644
--- a/core/src/main/java/org/apache/seata/core/rpc/netty/http/ParamMetaData.java
+++ b/core/src/main/java/org/apache/seata/core/rpc/netty/http/ParamMetaData.java
@@ -20,6 +20,12 @@ public class ParamMetaData {
private ParamConvertType paramConvertType;
+ private String paramName;
+
+ private boolean required;
+
+ private String defaultValue;
+
public ParamConvertType getParamConvertType() {
return paramConvertType;
}
@@ -28,6 +34,30 @@ public class ParamMetaData {
this.paramConvertType = paramConvertType;
}
+ public String getParamName() {
+ return paramName;
+ }
+
+ public void setParamName(String paramName) {
+ this.paramName = paramName;
+ }
+
+ public boolean isRequired() {
+ return required;
+ }
+
+ public void setRequired(boolean required) {
+ this.required = required;
+ }
+
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ public void setDefaultValue(String defaultValue) {
+ this.defaultValue = defaultValue;
+ }
+
public enum ParamConvertType {
/**
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/ParameterParser.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/ParameterParser.java
index 149681a4ec..5ec665ea80 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/ParameterParser.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/ParameterParser.java
@@ -26,12 +26,18 @@ import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
+
import org.apache.seata.common.rpc.http.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static
com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS;
+/**
+ * A utility class for parsing HTTP request parameters and converting them
into Java objects.
+ * Supports various parameter types including request params, request body,
model attributes, etc.
+ */
public class ParameterParser {
private static final Logger LOGGER =
LoggerFactory.getLogger(ParameterParser.class);
@@ -39,6 +45,9 @@ public class ParameterParser {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
false).configure(FAIL_ON_EMPTY_BEANS, false);
+ private static final String DEFAULT_NONE =
"\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
+
+
public static ObjectNode convertParamMap(Map<String, List<String>>
paramMap) {
ObjectNode paramNode = OBJECT_MAPPER.createObjectNode();
for (Map.Entry<String, List<String>> entry : paramMap.entrySet()) {
@@ -89,7 +98,7 @@ public class ParameterParser {
private static Object getArgValue(Class<?> parameterType, String
parameterName, ParamMetaData paramMetaData,
ObjectNode paramMap, HttpContext httpContext) {
ParamMetaData.ParamConvertType paramConvertType =
paramMetaData.getParamConvertType();
- if (parameterType.equals(HttpContext.class)) {
+ if (HttpContext.class.equals(parameterType)) {
return httpContext;
} else if
(ParamMetaData.ParamConvertType.MODEL_ATTRIBUTE.equals(paramConvertType)) {
JsonNode param = paramMap.get("param");
@@ -97,6 +106,28 @@ public class ParameterParser {
} else if
(ParamMetaData.ParamConvertType.REQUEST_BODY.equals(paramConvertType)) {
JsonNode body = paramMap.get("body");
return OBJECT_MAPPER.convertValue(body, parameterType);
+ } else if
(ParamMetaData.ParamConvertType.REQUEST_PARAM.equals(paramConvertType)) {
+ String paramName = paramMetaData.getParamName();
+ JsonNode jsonNode = Optional.ofNullable(paramMap.get("param"))
+ .map(body -> body.get(paramName))
+ .orElse(null);
+
+ // Step 1: If body exists and contains paramName, use its value
first
+ if (jsonNode != null && !jsonNode.isNull()) {
+ return OBJECT_MAPPER.convertValue(jsonNode, parameterType);
+ }
+
+ // Step 2: If the parameter is missing but a defaultValue is set,
use the defaultValue
+ String defaultValue = paramMetaData.getDefaultValue();
+ if (defaultValue != null && !defaultValue.equals(DEFAULT_NONE)) {
+ return OBJECT_MAPPER.convertValue(defaultValue, parameterType);
+ }
+
+ // Step 3: If the parameter is required but no value or
defaultValue is provided, throw an exception
+ if (paramMetaData.isRequired()) {
+ throw new IllegalArgumentException("Required request parameter
'" + paramName + "' is missing");
+ }
+ return null;
} else {
JsonNode paramNode = paramMap.get("param");
if (paramNode != null) {
diff --git
a/core/src/test/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandlerTest.java
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandlerTest.java
index 4b55cc2af7..9763f9c18b 100644
---
a/core/src/test/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandlerTest.java
+++
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandlerTest.java
@@ -57,6 +57,7 @@ class HttpDispatchHandlerTest {
Method method = TestController.class.getMethod("handleRequest",
String.class);
ParamMetaData paramMetaData = new ParamMetaData();
paramMetaData.setParamConvertType(ParamMetaData.ParamConvertType.REQUEST_PARAM);
+ paramMetaData.setParamName("param");
ParamMetaData[] paramMetaDatas = new ParamMetaData[]{paramMetaData};
HttpInvocation invocation = new HttpInvocation();
diff --git
a/core/src/test/java/org/apache/seata/core/rpc/netty/http/ParameterParserTest.java
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/ParameterParserTest.java
index f9bfcf071c..1e4ee9b0e4 100644
---
a/core/src/test/java/org/apache/seata/core/rpc/netty/http/ParameterParserTest.java
+++
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/ParameterParserTest.java
@@ -30,11 +30,16 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
class ParameterParserTest {
private final ObjectMapper objectMapper = new ObjectMapper();
+ private static final String DEFAULT_NONE =
"\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
@Test
void testConvertParamMapWithSingleValue() throws JsonProcessingException {
@@ -95,9 +100,152 @@ class ParameterParserTest {
assertNotNull(args[0]);
}
- // 测试辅助类
+ @Test
+ void testGetArgValuesWithRequestParam() throws Exception {
+ Method method = TestClassA.class.getMethod("objectMethod",
String.class);
+
+ ParamMetaData paramMetaData = new ParamMetaData();
+
paramMetaData.setParamConvertType(ParamMetaData.ParamConvertType.REQUEST_PARAM);
+ paramMetaData.setParamName("userName");
+ paramMetaData.setDefaultValue("a");
+ paramMetaData.setRequired(false);
+
+ ObjectNode paramMap = objectMapper.createObjectNode();
+ ObjectNode bodyNode = paramMap.putObject("param");
+ bodyNode.put("userName", "LiHua");
+ HttpContext httpContext = new HttpContext(null,null,false);
+ Object[] args = ParameterParser.getArgValues(
+ new ParamMetaData[]{paramMetaData},
+ method,
+ paramMap, httpContext
+ );
+
+ assertEquals(1, args.length);
+ assertNotNull(args[0]);
+ assertEquals("LiHua", args[0]);
+ }
+
+ @Test
+ void testGetArgValuesWithRequestParamAndDefaultValue() throws Exception {
+ Method method = TestClassA.class.getMethod("objectMethod",
String.class);
+
+ ParamMetaData paramMetaData = new ParamMetaData();
+
paramMetaData.setParamConvertType(ParamMetaData.ParamConvertType.REQUEST_PARAM);
+ paramMetaData.setParamName("userName");
+ paramMetaData.setDefaultValue("XiaMing");
+ paramMetaData.setRequired(false);
+
+ ObjectNode paramMap = objectMapper.createObjectNode();
+ HttpContext httpContext = new HttpContext(null,null,false);
+ Object[] args = ParameterParser.getArgValues(
+ new ParamMetaData[]{paramMetaData},
+ method,
+ paramMap, httpContext
+ );
+
+ assertEquals(1, args.length);
+ assertNotNull(args[0]);
+ assertEquals("XiaMing", args[0]);
+ }
+
+ @Test
+ void testGetArgValuesWithRequestParamThrowException() throws Exception {
+ Method method = TestClassA.class.getMethod("objectMethod",
String.class);
+
+ ParamMetaData paramMetaData = new ParamMetaData();
+
paramMetaData.setParamConvertType(ParamMetaData.ParamConvertType.REQUEST_PARAM);
+ paramMetaData.setParamName("userName");
+ paramMetaData.setDefaultValue(DEFAULT_NONE);
+ paramMetaData.setRequired(true);
+ assertThrows(IllegalArgumentException.class, () ->{
+ ObjectNode paramMap = objectMapper.createObjectNode();
+ HttpContext httpContext = new HttpContext(null,null,false);
+ ParameterParser.getArgValues(
+ new ParamMetaData[]{paramMetaData},
+ method,
+ paramMap, httpContext
+ );
+ });
+ }
+
+ @Test
+ void testGetArgValuesWithRequestParamAndReturnNull() throws Exception {
+ Method method = TestClassA.class.getMethod("objectMethod",
String.class);
+
+ ParamMetaData paramMetaData = new ParamMetaData();
+
paramMetaData.setParamConvertType(ParamMetaData.ParamConvertType.REQUEST_PARAM);
+ paramMetaData.setParamName("userName");
+ paramMetaData.setRequired(false);
+
+ ObjectNode paramMap = objectMapper.createObjectNode();
+ HttpContext httpContext = new HttpContext(null,null,false);
+ Object[] args = ParameterParser.getArgValues(
+ new ParamMetaData[]{paramMetaData},
+ method,
+ paramMap, httpContext
+ );
+
+ assertEquals(1, args.length);
+ assertNull(args[0]);
+ }
+
+ @Test
+ void testGetArgValuesWithJavaBeanParam() throws Exception {
+ Method method = TestClassB.class.getMethod("objectMethod", User.class);
+
+ ParamMetaData paramMetaData = new ParamMetaData();
+
paramMetaData.setParamConvertType(ParamMetaData.ParamConvertType.MODEL_ATTRIBUTE);
+ ObjectNode paramMap = objectMapper.createObjectNode();
+ ObjectNode bodyNode = paramMap.putObject("param");
+ bodyNode.put("name", "LiHua");
+ bodyNode.put("age", 10);
+ HttpContext httpContext = new HttpContext(null, null, false);
+ Object[] args = ParameterParser.getArgValues(
+ new ParamMetaData[]{paramMetaData},
+ method,
+ paramMap, httpContext
+ );
+
+ assertEquals(1, args.length);
+ assertTrue(args[0] instanceof User);
+ assertEquals("LiHua", ((User) args[0]).name);
+ assertEquals(10, ((User) args[0]).age);
+ }
+
+
+ // Test support class
class TestClass {
public void objectMethod(Object obj) {
}
}
+
+ // Test support classA
+ class TestClassA{
+ public void objectMethod(String userName){
+
+ }
+ }
+
+ // Test support classB
+ class TestClassB{
+ public void objectMethod(User user){
+
+ }
+ }
+
+ static class User{
+ String name;
+ Integer age;
+
+ public User(){
+ }
+
+ public void setName(String name){
+ this.name = name;
+ }
+
+ public void setAge(Integer age){
+ this.age = age;
+ }
+ }
}
\ No newline at end of file
diff --git
a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/http/RestControllerBeanPostProcessor.java
b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/http/RestControllerBeanPostProcessor.java
index ec96beff70..64fad3e381 100644
---
a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/http/RestControllerBeanPostProcessor.java
+++
b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/http/RestControllerBeanPostProcessor.java
@@ -34,11 +34,17 @@ import
org.springframework.web.bind.annotation.RestController;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static
org.springframework.web.bind.annotation.ValueConstants.DEFAULT_NONE;
/**
* Handles classes annotated with @RestController to establish a request path
-> controller mapping relationship
@@ -50,6 +56,8 @@ public class RestControllerBeanPostProcessor implements
BeanPostProcessor {
private static final List<Class<? extends Annotation>> MAPPING_CLASS = new
ArrayList<>();
private static final Map<Class<? extends Annotation>,
ParamMetaData.ParamConvertType> MAPPING_PARAM_TYPE = new HashMap<>();
+ private static final Set<Class<?>> SIMPLE_TYPE = new HashSet<>();
+ private static final Set<Class<?>> SPECIAL_INJECTED_TYPE = new HashSet<>();
static {
MAPPING_CLASS.add(GetMapping.class);
@@ -61,6 +69,31 @@ public class RestControllerBeanPostProcessor implements
BeanPostProcessor {
MAPPING_PARAM_TYPE.put(RequestParam.class,
ParamMetaData.ParamConvertType.REQUEST_PARAM);
MAPPING_PARAM_TYPE.put(RequestBody.class,
ParamMetaData.ParamConvertType.REQUEST_BODY);
MAPPING_PARAM_TYPE.put(ModelAttribute.class,
ParamMetaData.ParamConvertType.MODEL_ATTRIBUTE);
+
+ SIMPLE_TYPE.add(String.class);
+ SIMPLE_TYPE.add(Integer.class);
+ SIMPLE_TYPE.add(int.class);
+ SIMPLE_TYPE.add(Long.class);
+ SIMPLE_TYPE.add(long.class);
+ SIMPLE_TYPE.add(Boolean.class);
+ SIMPLE_TYPE.add(boolean.class);
+ SIMPLE_TYPE.add(Double.class);
+ SIMPLE_TYPE.add(double.class);
+ SIMPLE_TYPE.add(Float.class);
+ SIMPLE_TYPE.add(float.class);
+ SIMPLE_TYPE.add(Short.class);
+ SIMPLE_TYPE.add(short.class);
+ SIMPLE_TYPE.add(Byte.class);
+ SIMPLE_TYPE.add(byte.class);
+ SIMPLE_TYPE.add(Character.class);
+ SIMPLE_TYPE.add(char.class);
+ SIMPLE_TYPE.add(java.math.BigDecimal.class);
+ SIMPLE_TYPE.add(java.math.BigInteger.class);
+ SIMPLE_TYPE.add(java.util.Date.class);
+ SIMPLE_TYPE.add(java.time.LocalDate.class);
+ SIMPLE_TYPE.add(java.time.LocalDateTime.class);
+
+
SPECIAL_INJECTED_TYPE.add(org.apache.seata.common.rpc.http.HttpContext.class);
}
@Override
@@ -106,19 +139,20 @@ public class RestControllerBeanPostProcessor implements
BeanPostProcessor {
Class<?>[] parameterTypes = method.getParameterTypes();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
ParamMetaData[] paramMetaDatas = new
ParamMetaData[parameterTypes.length];
+ Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameterTypes.length; i++) {
+ Annotation matchedAnnotation = null;
Class<? extends Annotation> parameterAnnotationType = null;
if (parameterAnnotations[i] != null &&
parameterAnnotations[i].length > 0) {
- parameterAnnotationType =
parameterAnnotations[i][0].annotationType();
- }
-
- if (parameterAnnotationType == null) {
- parameterAnnotationType = RequestParam.class;
+ for (Annotation annotation : parameterAnnotations[i]) {
+ if
(MAPPING_PARAM_TYPE.containsKey(annotation.annotationType())) {
+ parameterAnnotationType = annotation.annotationType();
+ matchedAnnotation = annotation;
+ break;
+ }
+ }
}
-
- ParamMetaData paramMetaData = new ParamMetaData();
- ParamMetaData.ParamConvertType paramConvertType =
MAPPING_PARAM_TYPE.get(parameterAnnotationType);
- paramMetaData.setParamConvertType(paramConvertType);
+ ParamMetaData paramMetaData =
buildParamMetaData(matchedAnnotation, parameterTypes[i],
parameterAnnotationType, parameters[i]);
paramMetaDatas[i] = paramMetaData;
}
int maxSize = Math.max(prePaths.size(), postPaths.size());
@@ -143,5 +177,67 @@ public class RestControllerBeanPostProcessor implements
BeanPostProcessor {
}
}
+ private static ParamMetaData buildParamMetaData(Annotation
matchedAnnotation, Class<?> parameterType,
+ Class<? extends
Annotation> parameterAnnotationType, Parameter parameter) {
+ ParamMetaData paramMetaData = new ParamMetaData();
+
+ // No annotation on the parameter: resolve the default annotation type
based on the parameter type
+ if (parameterAnnotationType == null) {
+ parameterAnnotationType =
resolveDefaultAnnotationType(parameterType);
+ ParamMetaData.ParamConvertType paramConvertType =
MAPPING_PARAM_TYPE.get(parameterAnnotationType);
+ paramMetaData.setParamConvertType(paramConvertType);
+ if (parameterAnnotationType == RequestParam.class) {
+ paramMetaData.setParamName(parameter.getName());
+ paramMetaData.setRequired(true);
+ paramMetaData.setDefaultValue(DEFAULT_NONE);
+ }
+ // Annotation is present on the parameter; proceed with standard
parsing logic
+ } else {
+ ParamMetaData.ParamConvertType paramConvertType =
MAPPING_PARAM_TYPE.get(parameterAnnotationType);
+ paramMetaData.setParamConvertType(paramConvertType);
+ if (parameterAnnotationType == RequestParam.class) {
+ RequestParam requestParam = (RequestParam) matchedAnnotation;
+ boolean required = true;
+ String defaultValue = null;
+ String paramName = Optional.ofNullable(requestParam.name())
+ .filter(name -> !name.isEmpty())
+ .orElseGet(() -> {
+ String value = requestParam.value();
+ return !value.isEmpty() ? value :
parameter.getName();
+ });
+
+ required = requestParam.required();
+ defaultValue = requestParam.defaultValue();
+
+ if (!DEFAULT_NONE.equals(defaultValue)) {
+ required = false;
+ }
+
+ paramMetaData.setParamName(paramName);
+ paramMetaData.setRequired(required);
+ paramMetaData.setDefaultValue(defaultValue);
+ }
+ }
+
+ return paramMetaData;
+ }
+
+ /**
+ * Determines the default annotation type for a parameter based on its
class.
+ * Returns:
+ * - null for special injected types (e.g., HttpContext),
+ * - RequestParam for primitives, simple types, or MultipartFile,
+ * - ModelAttribute for all others.
+ */
+ private static Class<? extends Annotation>
resolveDefaultAnnotationType(Class<?> paramType) {
+ if (SPECIAL_INJECTED_TYPE.stream().anyMatch(t ->
t.isAssignableFrom(paramType))) {
+ return null;
+ } else if (paramType.isPrimitive() || SIMPLE_TYPE.contains(paramType)
+ ||
org.springframework.web.multipart.MultipartFile.class.isAssignableFrom(paramType))
{
+ return RequestParam.class;
+ } else {
+ return ModelAttribute.class;
+ }
+ }
}
\ No newline at end of file
diff --git
a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/http/RestControllerBeanPostProcessorTest.java
b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/http/RestControllerBeanPostProcessorTest.java
index 33805817a2..6a5c984e75 100644
---
a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/http/RestControllerBeanPostProcessorTest.java
+++
b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/http/RestControllerBeanPostProcessorTest.java
@@ -16,20 +16,34 @@
*/
package org.apache.seata.spring.boot.autoconfigure.http;
+import org.apache.seata.common.rpc.http.HttpContext;
import org.apache.seata.core.rpc.netty.http.ControllerManager;
import org.apache.seata.core.rpc.netty.http.HttpInvocation;
+import org.apache.seata.core.rpc.netty.http.ParamMetaData;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.mockito.Mock;
+import org.mockito.MockedStatic;
import org.mockito.MockitoAnnotations;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
-import static org.mockito.Mockito.verify;
+import javax.annotation.Nonnull;
-public class RestControllerBeanPostProcessorTest {
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.times;
- @Mock
- private ControllerManager controllerManager;
+public class RestControllerBeanPostProcessorTest {
private RestControllerBeanPostProcessor processor;
@@ -44,13 +58,104 @@ public class RestControllerBeanPostProcessorTest {
// Mock the bean and its annotations
TestController mockBean = new TestController();
+ try (MockedStatic<ControllerManager> mocked =
mockStatic(ControllerManager.class)) {
+ // Call the method under test
+ processor.postProcessAfterInitialization(mockBean,
"testController");
+
+ // Verify that the paths were added correctly
+ mocked.verify(() ->
ControllerManager.addHttpInvocation(any(HttpInvocation.class)), times(2));
+ }
+ }
+
+ @Test
+ public void testRegisterHttpInvocationWithCorrectMetadata() {
+ // Mock the bean and its annotations
+ TestApiController controller = new TestApiController();
+
// Call the method under test
- processor.postProcessAfterInitialization(mockBean, "testController");
+ processor.postProcessAfterInitialization(controller,
"testApiController");
+
+ // Verify whether the parsed data is correct
+ HttpInvocation getInvocation =
ControllerManager.getHttpInvocation("/api/get");
+ assertNotNull(getInvocation, "getMethod should be registered");
+ assertEquals("getMethod", getInvocation.getMethod().getName());
+ assertSame(controller, getInvocation.getController());
+
+ // Verify whether the defaultValue attribute "value" of @RequestParam
is correct
+ ParamMetaData[] getParams = getInvocation.getParamMetaData();
+ assertEquals(1, getParams.length);
+ assertEquals("param", getParams[0].getParamName());
+ assertEquals("defaultValue", getParams[0].getDefaultValue());
+ assertEquals(ParamMetaData.ParamConvertType.REQUEST_PARAM,
getParams[0].getParamConvertType());
+ assertFalse(getParams[0].isRequired());
+
+ // Verify whether the default value of the "required" attribute of
@RequestParam is correct
+ HttpInvocation postInvocation =
ControllerManager.getHttpInvocation("/api/post");
+ assertNotNull(postInvocation, "postMethod should be registered");
+ assertEquals("postMethod", postInvocation.getMethod().getName());
+ assertSame(controller, postInvocation.getController());
+
+ // Verify whether the value of the "name" attribute of @RequestParam
is correct
+ ParamMetaData[] postParams = postInvocation.getParamMetaData();
+ assertEquals(1, postParams.length);
+ assertEquals("requestBody", postParams[0].getParamName());
+ assertEquals(ParamMetaData.ParamConvertType.REQUEST_PARAM,
postParams[0].getParamConvertType());
+
+ HttpInvocation updateInvocation =
ControllerManager.getHttpInvocation("/api/update");
+ assertNotNull(updateInvocation, "updateMethod should be registered");
+ assertEquals("updateMethod", updateInvocation.getMethod().getName());
+ assertSame(controller, updateInvocation.getController());
- // Verify that the paths were added correctly
- HttpInvocation httpInvocation = new HttpInvocation();
- httpInvocation.setPath("/path");
- verify(controllerManager).addHttpInvocation(httpInvocation);
+ ParamMetaData[] updateParams = updateInvocation.getParamMetaData();
+ assertEquals(2, updateParams.length);
+
+ // Verify whether the value attribute of @RequestParam is correct
+ assertEquals("userName", updateParams[0].getParamName());
+ assertEquals(ParamMetaData.ParamConvertType.REQUEST_PARAM,
updateParams[0].getParamConvertType());
+ assertEquals("age", updateParams[1].getParamName());
+
+ // Verify whether @RequestParam can be correctly parsed when there are
multiple annotations before a parameter
+ assertEquals(ParamMetaData.ParamConvertType.REQUEST_PARAM,
updateParams[1].getParamConvertType());
+ assertFalse(updateParams[1].isRequired());
+ }
+
+ @Test
+ public void testRegisterHttpInvocationWithNoAnnotation() {
+ // Mock the bean and its annotations
+ TestNonController controller = new TestNonController();
+
+ // Call the method under test
+ processor.postProcessAfterInitialization(controller,
"testNonController");
+
+ // Verify whether the parsed data is correct
+ HttpInvocation getInvocation =
ControllerManager.getHttpInvocation("/non/get");
+ assertNotNull(getInvocation, "getMethod should be registered");
+ assertEquals("getMethod", getInvocation.getMethod().getName());
+ assertSame(controller, getInvocation.getController());
+
+ ParamMetaData[] getParams = getInvocation.getParamMetaData();
+ assertEquals(1, getParams.length);
+ assertEquals("param", getParams[0].getParamName());
+ assertEquals(ParamMetaData.ParamConvertType.REQUEST_PARAM,
getParams[0].getParamConvertType());
+ assertTrue(getParams[0].isRequired());
+
+ HttpInvocation postInvocation =
ControllerManager.getHttpInvocation("/non/post");
+ assertNotNull(postInvocation, "postMethod should be registered");
+ assertEquals("postMethod", postInvocation.getMethod().getName());
+ assertSame(controller, postInvocation.getController());
+
+ ParamMetaData[] postParams = postInvocation.getParamMetaData();
+ assertEquals(1, postParams.length);
+ assertEquals(ParamMetaData.ParamConvertType.MODEL_ATTRIBUTE,
postParams[0].getParamConvertType());
+
+ HttpInvocation updateInvocation =
ControllerManager.getHttpInvocation("/non/update");
+ assertNotNull(updateInvocation, "updateMethod should be registered");
+ assertEquals("updateMethod", updateInvocation.getMethod().getName());
+ assertSame(controller, updateInvocation.getController());
+
+ ParamMetaData[] updateParams = updateInvocation.getParamMetaData();
+ assertEquals(1, updateParams.length);
+ assertNull(updateParams[0].getParamConvertType());
}
@RestController
@@ -67,7 +172,50 @@ public class RestControllerBeanPostProcessorTest {
return "POST";
}
}
-}
+ @RestController
+ @RequestMapping("/api")
+ static class TestApiController {
+
+ @GetMapping("/get")
+ public String getMethod(@RequestParam(defaultValue = "defaultValue")
String param) {
+ return "GET";
+ }
+
+ @PostMapping("/post")
+ public String postMethod(@RequestParam(name = "requestBody") String
body) {
+ return "POST";
+ }
+
+ @GetMapping("/update")
+ public String updateMethod(@RequestParam(value = "userName") String
name,
+ @Nonnull @RequestParam(required = false)
Integer age) {
+ return "update";
+ }
+ }
+
+ @RestController
+ @RequestMapping("/non")
+ static class TestNonController {
+ @GetMapping("/get")
+ public String getMethod(String param) {
+ return "GET";
+ }
+ @PostMapping("/post")
+ public String postMethod(User user) {
+ return "POST";
+ }
+
+ @GetMapping("/update")
+ public String updateMethod(HttpContext httpContext) {
+ return "update";
+ }
+ }
+
+ static class User{
+ String name;
+ Integer age;
+ }
+}
\ No newline at end of file
diff --git
a/server/src/main/java/org/apache/seata/server/controller/ClusterController.java
b/server/src/main/java/org/apache/seata/server/controller/ClusterController.java
index b701f26537..b6edda641e 100644
---
a/server/src/main/java/org/apache/seata/server/controller/ClusterController.java
+++
b/server/src/main/java/org/apache/seata/server/controller/ClusterController.java
@@ -111,13 +111,9 @@ public class ClusterController {
public void watch(HttpContext context, @RequestBody Map<String, Object>
groupTerms,
@RequestParam(defaultValue = "28000") Integer timeout) {
context.setAsync(true);
- if (timeout == null) {
- timeout = 28000;
- }
- Integer finalTimeout = timeout;
groupTerms.forEach((group, term) -> {
Watcher<HttpContext> watcher =
- new Watcher<>(group, context, finalTimeout,
Long.parseLong(String.valueOf(term)));
+ new Watcher<>(group, context, timeout,
Long.parseLong(String.valueOf(term)));
clusterWatcherManager.registryWatcher(watcher);
});
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]