You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by al...@apache.org on 2024/07/29 02:54:09 UTC

(dubbo) branch 3.3 updated: Generic param support (#14471)

This is an automated email from the ASF dual-hosted git repository.

albumenj pushed a commit to branch 3.3
in repository https://gitbox.apache.org/repos/asf/dubbo.git


The following commit(s) were added to refs/heads/3.3 by this push:
     new e286565597 Generic param support (#14471)
e286565597 is described below

commit e2865655971690d6c0a74da9b5d8c3e9336ecca5
Author: Sean Yang <ox...@gmail.com>
AuthorDate: Mon Jul 29 10:54:03 2024 +0800

    Generic param support (#14471)
    
    * Generic param support
    
    * OPTIONS request support
    
    * Fix yml constructor
---
 .../org/apache/dubbo/common/utils/MethodUtils.java |  15 +++
 .../rest/support/jaxrs/BodyArgumentResolver.java   |   2 +-
 .../support/jaxrs/FallbackArgumentResolver.java    |   6 +-
 .../support/jaxrs/JaxrsRequestMappingResolver.java |   3 +-
 .../tri/rest/support/jaxrs/JaxrsRestToolKit.java   |   2 +-
 .../jaxrs/filter/InterceptorContextImpl.java       |   2 +-
 .../spring/MatrixVariableArgumentResolver.java     |   2 +-
 .../spring/RequestBodyArgumentResolver.java        |   6 +-
 .../spring/SpringMvcRequestMappingResolver.java    |  19 ++-
 .../tri/rest/support/spring/SpringRestToolKit.java |  47 +++++--
 .../rest/support/spring/RestProtocolTest.groovy    | 142 ++++++++++++++++-----
 .../support/spring/service/SpringDemoService.java  |  64 +++++++++-
 .../spring/service/SpringDemoServiceImpl.java      |  10 +-
 .../src/test/resources/log4j2-test.xml             |   1 +
 .../apache/dubbo/remoting/http12/HttpMethods.java  |   4 +-
 .../http12/message/HttpMessageDecoder.java         |  16 +++
 .../remoting/http12/message/codec/JsonCodec.java   |  12 ++
 .../remoting/http12/message/codec/JsonPbCodec.java |   8 ++
 .../remoting/http12/message/codec/YamlCodec.java   |  19 ++-
 .../apache/dubbo/remoting/http12/rest/Mapping.java |   2 +
 .../tri/h12/HttpMessageDecoderWrapper.java         |  11 ++
 .../dubbo/rpc/protocol/tri/rest/Messages.java      |   4 +-
 .../protocol/tri/rest/RestHttpMessageCodec.java    |  17 ++-
 .../tri/rest/argument/GeneralTypeConverter.java    |  49 ++++++-
 .../NamedValueArgumentResolverSupport.java         |  17 +--
 .../mapping/DefaultRequestMappingRegistry.java     |  30 ++++-
 .../protocol/tri/rest/mapping/RequestMapping.java  |  12 ++
 .../rest/mapping/RestRequestHandlerMapping.java    |  40 +++++-
 .../rest/mapping/condition/MethodsCondition.java   |  15 ++-
 .../mapping/condition/ServiceVersionCondition.java |   2 +-
 .../tri/rest/mapping/meta/AnnotationSupport.java   |   2 +-
 .../protocol/tri/rest/mapping/meta/BeanMeta.java   |   2 +-
 .../protocol/tri/rest/mapping/meta/MethodMeta.java |  61 ++++++++-
 .../tri/rest/mapping/meta/MethodParameterMeta.java |   9 --
 .../tri/rest/mapping/meta/NamedValueMeta.java      |  13 ++
 .../tri/rest/mapping/meta/ParameterMeta.java       |  26 ++--
 .../support/basic/BasicRequestMappingResolver.java |  17 ++-
 .../support/basic/FallbackArgumentResolver.java    |  66 ++++++++--
 .../rest/support/basic/ParamArgumentResolver.java  |   8 +-
 .../tri/rest/util/AbstractRestToolKit.java         |  13 +-
 .../rpc/protocol/tri/rest/util/MethodWalker.java   |   3 -
 .../rpc/protocol/tri/rest/util/RequestUtils.java   | 110 ++++++----------
 .../tri/rest/support/basic/RestProtocolTest.groovy |  41 +++++-
 .../rpc/protocol/tri/rest/service/DemoService.java |  21 +++
 .../protocol/tri/rest/service/DemoServiceImpl.java |  43 +++++++
 45 files changed, 784 insertions(+), 230 deletions(-)

diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MethodUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MethodUtils.java
index a4b829ce45..a826cd1df5 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MethodUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MethodUtils.java
@@ -462,4 +462,19 @@ public interface MethodUtils {
             return -1;
         }
     }
+
+    static String toShortString(Method method) {
+        StringBuilder sb = new StringBuilder(64);
+        sb.append(method.getDeclaringClass().getName());
+        sb.append('.').append(method.getName()).append('(');
+        Class<?>[] parameterTypes = method.getParameterTypes();
+        for (int i = 0, len = parameterTypes.length; i < len; i++) {
+            if (i > 0) {
+                sb.append(", ");
+            }
+            sb.append(parameterTypes[i].getSimpleName());
+        }
+        sb.append(')');
+        return sb.toString();
+    }
 }
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BodyArgumentResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BodyArgumentResolver.java
index 5d46134103..fc3640aa10 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BodyArgumentResolver.java
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BodyArgumentResolver.java
@@ -51,6 +51,6 @@ public class BodyArgumentResolver implements AnnotationBaseArgumentResolver<Anno
                 throw new RestException(e);
             }
         }
-        return RequestUtils.decodeBody(request, type);
+        return RequestUtils.decodeBody(request, parameter.getActualGenericType());
     }
 }
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FallbackArgumentResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FallbackArgumentResolver.java
index 0ecc924df7..07076ba08f 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FallbackArgumentResolver.java
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/FallbackArgumentResolver.java
@@ -45,7 +45,7 @@ public class FallbackArgumentResolver extends AbstractArgumentResolver {
 
     @Override
     protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) {
-        Object value = RequestUtils.decodeBody(request, meta.type());
+        Object value = RequestUtils.decodeBody(request, meta.genericType());
         if (value != null) {
             return value;
         }
@@ -65,7 +65,7 @@ public class FallbackArgumentResolver extends AbstractArgumentResolver {
                 throw new RestException(e);
             }
         }
-        Object value = RequestUtils.decodeBody(request, meta.type());
+        Object value = RequestUtils.decodeBody(request, meta.genericType());
         if (value != null) {
             return value;
         }
@@ -77,7 +77,7 @@ public class FallbackArgumentResolver extends AbstractArgumentResolver {
 
     @Override
     protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) {
-        Object value = RequestUtils.decodeBody(request, meta.type());
+        Object value = RequestUtils.decodeBody(request, meta.genericType());
         if (value != null) {
             return value;
         }
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java
index 72cb325351..f9fc66768e 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRequestMappingResolver.java
@@ -22,7 +22,6 @@ import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping.Builder;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver;
-import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ServiceVersionCondition;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationSupport;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta;
@@ -76,7 +75,7 @@ public class JaxrsRequestMappingResolver implements RequestMappingResolver {
         return builder(methodMeta, path, httpMethod)
                 .name(methodMeta.getMethod().getName())
                 .contextPath(methodMeta.getServiceMeta().getContextPath())
-                .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion()))
+                .service(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())
                 .cors(globalCorsMeta)
                 .build();
     }
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRestToolKit.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRestToolKit.java
index 56c3365e53..4ce80939a8 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRestToolKit.java
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsRestToolKit.java
@@ -54,7 +54,7 @@ final class JaxrsRestToolKit extends AbstractRestToolKit {
         }
 
         ParamConverter converter = paramConverterFactory.getParamConverter(
-                parameter.getType(), parameter.getGenericType(), parameter.getRealAnnotations());
+                parameter.getType(), parameter.getGenericType(), parameter.getRawAnnotations());
         if (converter != null) {
             return value instanceof String ? converter.fromString((String) value) : converter.toString(value);
         }
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/InterceptorContextImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/InterceptorContextImpl.java
index ee67447bb5..1f64c21f9e 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/InterceptorContextImpl.java
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/InterceptorContextImpl.java
@@ -56,7 +56,7 @@ public abstract class InterceptorContextImpl implements InterceptorContext {
 
     @Override
     public Annotation[] getAnnotations() {
-        return getHandler().getMethod().getRealAnnotations();
+        return getHandler().getMethod().getRawAnnotations();
     }
 
     @Override
diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/MatrixVariableArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/MatrixVariableArgumentResolver.java
index 1ca99a5cb0..f68ff9cacf 100644
--- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/MatrixVariableArgumentResolver.java
+++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/MatrixVariableArgumentResolver.java
@@ -117,7 +117,7 @@ public class MatrixVariableArgumentResolver extends AbstractSpringArgumentResolv
 
         private final String pathVar;
 
-        public MatrixNamedValueMeta(String name, boolean required, String defaultValue, String pathVar) {
+        MatrixNamedValueMeta(String name, boolean required, String defaultValue, String pathVar) {
             super(name, required, defaultValue);
             this.pathVar = pathVar;
         }
diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestBodyArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestBodyArgumentResolver.java
index 4bc2bf1401..dcf3173f35 100644
--- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestBodyArgumentResolver.java
+++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RequestBodyArgumentResolver.java
@@ -50,7 +50,7 @@ public class RequestBodyArgumentResolver extends AbstractSpringArgumentResolver
             }
             return meta.parameterMeta().bind(request, response);
         }
-        return RequestUtils.decodeBody(request, meta.type());
+        return RequestUtils.decodeBody(request, meta.genericType());
     }
 
     @Override
@@ -66,7 +66,7 @@ public class RequestBodyArgumentResolver extends AbstractSpringArgumentResolver
         if (RequestUtils.isFormOrMultiPart(request)) {
             return request.formParameterValues(meta.name());
         }
-        return RequestUtils.decodeBody(request, meta.type());
+        return RequestUtils.decodeBody(request, meta.genericType());
     }
 
     @Override
@@ -74,6 +74,6 @@ public class RequestBodyArgumentResolver extends AbstractSpringArgumentResolver
         if (RequestUtils.isFormOrMultiPart(request)) {
             return RequestUtils.getFormParametersMap(request);
         }
-        return RequestUtils.decodeBody(request, meta.type());
+        return RequestUtils.decodeBody(request, meta.genericType());
     }
 }
diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java
index 728156a98d..bdb3aff019 100644
--- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java
+++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringMvcRequestMappingResolver.java
@@ -24,7 +24,6 @@ import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping.Builder;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver;
-import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ServiceVersionCondition;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta;
@@ -70,9 +69,11 @@ public class SpringMvcRequestMappingResolver implements RequestMappingResolver {
         String[] methods = requestMapping == null
                 ? httpExchange.getStringArray("method")
                 : requestMapping.getStringArray("method");
+        String[] paths = requestMapping == null ? httpExchange.getValueArray() : requestMapping.getValueArray();
         return builder(requestMapping, httpExchange, serviceMeta.findMergedAnnotation(Annotations.ResponseStatus))
                 .method(methods)
                 .name(serviceMeta.getType().getSimpleName())
+                .path(paths)
                 .contextPath(serviceMeta.getContextPath())
                 .cors(buildCorsMeta(serviceMeta.findMergedAnnotation(Annotations.CrossOrigin), methods))
                 .build();
@@ -92,14 +93,20 @@ public class SpringMvcRequestMappingResolver implements RequestMappingResolver {
         }
 
         ServiceMeta serviceMeta = methodMeta.getServiceMeta();
+        String name = methodMeta.getMethod().getName();
         String[] methods = requestMapping == null
                 ? httpExchange.getStringArray("method")
                 : requestMapping.getStringArray("method");
+        String[] paths = requestMapping == null ? httpExchange.getValueArray() : requestMapping.getValueArray();
+        if (paths.length == 0) {
+            paths = new String[] {'/' + name};
+        }
         return builder(requestMapping, httpExchange, methodMeta.findMergedAnnotation(Annotations.ResponseStatus))
                 .method(methods)
-                .name(methodMeta.getMethod().getName())
+                .name(name)
+                .path(paths)
                 .contextPath(serviceMeta.getContextPath())
-                .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion()))
+                .service(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())
                 .cors(buildCorsMeta(methodMeta.findMergedAnnotation(Annotations.CrossOrigin), methods))
                 .build();
     }
@@ -116,12 +123,10 @@ public class SpringMvcRequestMappingResolver implements RequestMappingResolver {
             }
         }
         if (requestMapping == null) {
-            return builder.path(httpExchange.getValueArray())
-                    .consume(httpExchange.getStringArray("contentType"))
+            return builder.consume(httpExchange.getStringArray("contentType"))
                     .produce(httpExchange.getStringArray("accept"));
         }
-        return builder.path(requestMapping.getValueArray())
-                .param(requestMapping.getStringArray("params"))
+        return builder.param(requestMapping.getStringArray("params"))
                 .header(requestMapping.getStringArray("headers"))
                 .consume(requestMapping.getStringArray("consumes"))
                 .produce(requestMapping.getStringArray("produces"));
diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringRestToolKit.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringRestToolKit.java
index a2638c7748..e8c75ae6c8 100644
--- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringRestToolKit.java
+++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/SpringRestToolKit.java
@@ -16,12 +16,17 @@
  */
 package org.apache.dubbo.rpc.protocol.tri.rest.support.spring;
 
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.config.spring.extension.SpringExtensionInjector;
 import org.apache.dubbo.remoting.http12.HttpRequest;
 import org.apache.dubbo.remoting.http12.HttpResponse;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.apache.dubbo.rpc.protocol.tri.rest.Messages;
 import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants;
+import org.apache.dubbo.rpc.protocol.tri.rest.RestException;
 import org.apache.dubbo.rpc.protocol.tri.rest.argument.GeneralTypeConverter;
 import org.apache.dubbo.rpc.protocol.tri.rest.argument.TypeConverter;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodParameterMeta;
@@ -49,6 +54,9 @@ import org.springframework.util.PropertyPlaceholderHelper;
 
 final class SpringRestToolKit implements RestToolKit {
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(SpringRestToolKit.class);
+
+    private final Map<MethodParameterMeta, TypeDescriptor> cache = CollectionUtils.newConcurrentHashMap();
     private final ConfigurableBeanFactory beanFactory;
     private final PropertyPlaceholderHelper placeholderHelper;
     private final ConfigurationWrapper configuration;
@@ -98,23 +106,42 @@ final class SpringRestToolKit implements RestToolKit {
 
     @Override
     public Object convert(Object value, ParameterMeta parameter) {
-        if (value instanceof Collection) {
+        boolean tried = false;
+        if (value instanceof Collection || value instanceof Map) {
+            tried = true;
             Object target = typeConverter.convert(value, parameter.getGenericType());
             if (target != null) {
                 return target;
             }
         }
-        TypeDescriptor targetType = (TypeDescriptor) parameter.getTypeDescriptor();
-        if (targetType == null) {
-            MethodParameterMeta meta = (MethodParameterMeta) parameter;
-            targetType = new TypeDescriptor(new MethodParameter(meta.getMethod(), meta.getIndex()));
-            parameter.setTypeDescriptor(targetType);
+        if (parameter instanceof MethodParameterMeta) {
+            TypeDescriptor targetType = cache.computeIfAbsent(
+                    (MethodParameterMeta) parameter,
+                    k -> new TypeDescriptor(new MethodParameter(k.getMethod(), k.getIndex())));
+            TypeDescriptor sourceType = TypeDescriptor.forObject(value);
+            if (conversionService.canConvert(sourceType, targetType)) {
+                try {
+                    return conversionService.convert(value, sourceType, targetType);
+                } catch (Throwable t) {
+                    LOGGER.debug(
+                            "Spring convert value '{}' from type [{}] to type [{}] failed",
+                            value,
+                            value.getClass(),
+                            parameter.getGenericType(),
+                            t);
+                }
+            }
         }
-        TypeDescriptor sourceType = TypeDescriptor.forObject(value);
-        if (conversionService.canConvert(sourceType, targetType)) {
-            return conversionService.convert(value, sourceType, targetType);
+        Object target = tried ? null : typeConverter.convert(value, parameter.getGenericType());
+        if (target == null && value != null) {
+            throw new RestException(
+                    Messages.ARGUMENT_CONVERT_ERROR,
+                    parameter.getName(),
+                    value,
+                    value.getClass(),
+                    parameter.getGenericType());
         }
-        return typeConverter.convert(value, parameter.getGenericType());
+        return target;
     }
 
     @Override
diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RestProtocolTest.groovy b/dubbo-plugin/dubbo-rest-spring/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RestProtocolTest.groovy
index ec1935b122..88b222a337 100644
--- a/dubbo-plugin/dubbo-rest-spring/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RestProtocolTest.groovy
+++ b/dubbo-plugin/dubbo-rest-spring/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RestProtocolTest.groovy
@@ -17,56 +17,138 @@
 
 package org.apache.dubbo.rpc.protocol.tri.rest.support.spring
 
-import org.apache.dubbo.rpc.protocol.tri.rest.service.DemoServiceImpl
+import org.apache.dubbo.remoting.http12.message.MediaType
+import org.apache.dubbo.rpc.protocol.tri.rest.service.Book
 import org.apache.dubbo.rpc.protocol.tri.rest.support.spring.service.SpringDemoServiceImpl
+import org.apache.dubbo.rpc.protocol.tri.rest.support.spring.service.SpringDemoService
 import org.apache.dubbo.rpc.protocol.tri.rest.test.BaseServiceTest
+import org.apache.dubbo.rpc.protocol.tri.test.TestRequest
 import org.apache.dubbo.rpc.protocol.tri.test.TestRunnerBuilder
 
 class RestProtocolTest extends BaseServiceTest {
 
     @Override
     void setupService(TestRunnerBuilder builder) {
-        builder.provider(new DemoServiceImpl())
-        builder.provider(new SpringDemoServiceImpl())
+        builder.provider(SpringDemoService.class, new SpringDemoServiceImpl())
     }
 
     def "hello world"() {
         expect:
             runner.get(path) == output
         where:
-            path                    | output
-            '/hello?name=world'     | 'hello world'
-            '/hello/?name=world'    | 'hello world'
-            '/hello.yml?name=world' | 'hello world'
+            path                           | output
+            '/spring/hello?name=world'     | 'hello world'
+            '/spring/hello/?name=world'    | 'hello world'
+            '/spring/hello.yml?name=world' | 'hello world'
+    }
+
+    def "list argument body test"() {
+        expect:
+            runner.post(path, body) contains output
+        where:
+            path                            | body    | output
+            '/spring/listArgBodyTest?age=2' | '[1,2]' | '[1,2]'
+    }
+
+    def "map argument body test"() {
+        expect:
+            runner.post(path, body) contains output
+        where:
+            path                           | body                        | output
+            '/spring/mapArgBodyTest?age=2' | '{"1":["2",3],"4":[5,"6"]}' | '{4:[5,6],1:[2,3]}'
+    }
+
+    def "bean argument get test"() {
+        expect:
+            runner.get(path, Book.class).price == output
+        where:
+            path                          | output
+            '/spring/beanArgTest'         | 0
+            '/spring/beanArgTest?quote=5' | 5
+    }
+
+    def "bean argument post test"() {
+        expect:
+            runner.post(path, body, Book.class).price == output
+        where:
+            path                   | body           | output
+            '/spring/beanArgTest'  | [:]            | 0
+            '/spring/beanArgTest'  | [price: 6]     | 6
+            '/spring/beanArgTest2' | '{"price": 5}' | 5
     }
 
     def "spring bean argument test"() {
         expect:
             runner.get(path) contains output
         where:
-            path                                                                                  | output
-            '/springBean?id=1&name=sam'                                                           | '"id":1,"name":"sam"'
-            '/springBean?name=sam&phone=123&email=a@b.com'                                        | '"email":"a@b.com","name":"sam","phone":"123"'
-            '/springBean?group.name=g1&group.owner.name=jack'                                     | '"group":{"id":0,"name":"g1","owner":{"name":"jack"'
-            '/springBean?group.parent.parent.children[0].name=xx'                                 | '"group":{"id":0,"parent":{"id":0,"parent":{"children":[{"id":0,"name":"xx"}],"id":0}}}'
-            '/springBean?ids=3&ids=4'                                                             | '"ids":[3,4]'
-            '/springBean?ids[]=3&ids[]=4'                                                         | '"ids":[3,4]'
-            '/springBean?ids[1]=3&ids[2]=4'                                                       | '"ids":[0,3,4]'
-            '/springBean?scores=3&scores=4'                                                       | '"scores":[3,4]'
-            '/springBean?scores[]=3&scores[]=4'                                                   | '"scores":[3,4]'
-            '/springBean?scores[1]=3&scores[2]=4'                                                 | '"scores":[null,3,4]'
-            '/springBean?tags[0].name=a&tags[0].value=b&tags[1].name=c&tags[1].value=d'           | '"tags":[{"name":"a","value":"b"},{"name":"c","value":"d"}]'
-            '/springBean?tagsA[0].name=a&tagsA[0].value=b&tagsA[1].name=c&tagsA[1].value=d'       | '"tagsA":[{"name":"a","value":"b"},{"name":"c","value":"d"}]'
-            '/springBean?tagsB[0].name=e&tagsB[1].name=c&tagsB[1].value=d'                        | '"tagsB":[{"name":"e","value":"b"},{"name":"c","value":"d"}]'
-            '/springBean?tagsC[0].name=e&tagsC[1].name=c&tagsC[1].value=d'                        | '"tagsC":[{"name":"e","value":"b"},{"name":"c","value":"d"}]'
-            '/springBean?groupMaps[0].one.name=a&groupMaps[1].two.name=b'                         | '"groupMaps":[{},{}]' //x
-            '/springBean?id=1&features.a=xx&features.b=2'                                         | '"features":{},"id":1' //x
-            '/springBean?id=1&features[a]=xx&features[b]=2'                                       | '"features":{"a":"xx","b":"2"}'
-            '/springBean?group.id=2&group.features.a=1&group.features.b=xx'                       | '"group":{"features":{},"id":2}' //x
-            '/springBean?tagMap.a.name=a&tagMap.a.value=b&tagMap.b.name=c&tagMap.b.value=d'       | '"tagMap":{}' //x
-            '/springBean?tagMapA.a.name=e&tagMapA.b.name=c&tagMapA.b.value=d'                     | '"tagMapA":{"a":{"name":"a","value":"b"}}' //x
-            '/springBean?tagMapB[2].name=a&tagMapB[2].value=b&tagMapB[3].name=c'                  | '"tagMapB":{2:{"name":"a","value":"b"},3:{"name":"c"}}'
-            '/springBean?groupsMap.one[0].name=a&groupsMap.one[1].name=b&groupsMap.two[1].name=c' | '"groupsMap":{}' //x
+            path                                                                             | output
+            '/spring/bean?id=1&name=sam'                                                     | '"id":1,"name":"sam"'
+            '/spring/bean?name=sam&phone=123&email=a@b.com'                                  | '"email":"a@b.com","name":"sam","phone":"123"'
+            '/spring/bean?group.name=g1&group.owner.name=jack'                               | '"group":{"id":0,"name":"g1","owner":{"name":"jack"'
+            '/spring/bean?group.parent.parent.children[0].name=xx'                           | '"group":{"id":0,"parent":{"id":0,"parent":{"children":[{"id":0,"name":"xx"}],"id":0}}}'
+            '/spring/bean?ids=3&ids=4'                                                       | '"ids":[3,4]'
+            '/spring/bean?ids[]=3&ids[]=4'                                                   | '"ids":[3,4]'
+            '/spring/bean?ids[1]=3&ids[2]=4'                                                 | '"ids":[0,3,4]'
+            '/spring/bean?scores=3&scores=4'                                                 | '"scores":[3,4]'
+            '/spring/bean?scores[]=3&scores[]=4'                                             | '"scores":[3,4]'
+            '/spring/bean?scores[1]=3&scores[2]=4'                                           | '"scores":[null,3,4]'
+            '/spring/bean?tags[0].name=a&tags[0].value=b&tags[1].name=c&tags[1].value=d'     | '"tags":[{"name":"a","value":"b"},{"name":"c","value":"d"}]'
+            '/spring/bean?tagsA[0].name=a&tagsA[0].value=b&tagsA[1].name=c&tagsA[1].value=d' | '"tagsA":[{"name":"a","value":"b"},{"name":"c","value":"d"}]'
+            '/spring/bean?tagsB[0].name=e&tagsB[1].name=c&tagsB[1].value=d'                  | '"tagsB":[{"name":"e","value":"b"},{"name":"c","value":"d"}]'
+            '/spring/bean?tagsC[0].name=e&tagsC[1].name=c&tagsC[1].value=d'                  | '"tagsC":[{"name":"e","value":"b"},{"name":"c","value":"d"}]'
+            '/spring/bean?id=1&features[a]=xx&features[b]=2'                                 | '"features":{"a":"xx","b":"2"}'
+            '/spring/bean?tagMapB[2].name=a&tagMapB[2].value=b&tagMapB[3].name=c'            | '"tagMapB":{2:{"name":"a","value":"b"},3:{"name":"c"}}'
+    }
+
+    def "bean body test"() {
+        expect:
+            runner.post(path, body) contains output
+        where:
+            path                         | body                                          | output
+            '/spring/beanBodyTest?age=2' | '[{"id":1,"name":"g1"},{"id":2,"name":"g2"}]' | '[{"id":1,"name":"g1"},{"id":2,"name":"g2"}]'
+    }
+
+    def "urlEncodeForm test"() {
+        given:
+            def request = new TestRequest(
+                path: path,
+                contentType: MediaType.APPLICATION_FROM_URLENCODED,
+                params: [
+                    'name': 'Sam',
+                    'age' : 8
+                ]
+            )
+        expect:
+            runner.post(request) == output
+        where:
+            path              | output
+            '/spring/argTest' | 'Sam is 8 years old'
+    }
+
+    def "no interface method test"() {
+        expect:
+            runner.get(path) contains output
+        where:
+            path                            | output
+            '/spring/noInterface'           | 'ok'
+            '/spring/noInterfaceAndMapping' | '404'
+    }
+
+    def "use interface argument name test"() {
+        expect:
+            runner.get(path) contains output
+        where:
+            path                           | output
+            '/spring/argNameTest?name=Sam' | 'Sam'
+    }
+
+    def "pb server stream test"() {
+        expect:
+            runner.posts(path, body).size() == output
+        where:
+            path                     | body               | output
+            '/spring/pbServerStream' | '{"service": "3"}' | 3
+            '/spring/pbServerStream' | '{}'               | 0
     }
 
 }
diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoService.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoService.java
index bdf66f566a..4d64b125ab 100644
--- a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoService.java
+++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoService.java
@@ -16,12 +16,70 @@
  */
 package org.apache.dubbo.rpc.protocol.tri.rest.support.spring.service;
 
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.rpc.protocol.tri.rest.service.Book;
+import org.apache.dubbo.rpc.protocol.tri.rest.service.User.Group;
 import org.apache.dubbo.rpc.protocol.tri.rest.service.User.UserEx;
 
-import org.springframework.web.bind.annotation.GetMapping;
+import java.util.List;
+import java.util.Map;
 
+import io.grpc.health.v1.HealthCheckRequest;
+import io.grpc.health.v1.HealthCheckResponse;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@RequestMapping("/spring")
 public interface SpringDemoService {
 
-    @GetMapping("/springBean")
-    UserEx beanArgTest(UserEx user);
+    @RequestMapping
+    String hello(String name);
+
+    @RequestMapping
+    String argTest(String name, int age);
+
+    @RequestMapping
+    Book beanArgTest(@RequestBody(required = false) Book book, Integer quote);
+
+    @RequestMapping
+    Book beanArgTest2(@RequestBody Book book);
+
+    @RequestMapping("/bean")
+    UserEx advanceBeanArgTest(UserEx user);
+
+    @RequestMapping
+    List<Integer> listArgBodyTest(@RequestBody List<Integer> list, int age);
+
+    @RequestMapping
+    List<Integer> listArgBodyTest2(List<Integer> list, int age);
+
+    @RequestMapping
+    Map<Integer, List<Long>> mapArgBodyTest(@RequestBody Map<Integer, List<Long>> map, int age);
+
+    @RequestMapping
+    Map<Integer, List<Long>> mapArgBodyTest2(Map<Integer, List<Long>> map, int age);
+
+    @RequestMapping
+    List<Group> beanBodyTest(@RequestBody List<Group> groups, int age);
+
+    @RequestMapping
+    List<Group> beanBodyTest2(List<Group> groups, int age);
+
+    @RequestMapping
+    Book buy(Book book);
+
+    @RequestMapping("/buy2")
+    Book buy(Book book, int count);
+
+    @RequestMapping
+    String say(String name, Long count);
+
+    @RequestMapping
+    String say(String name);
+
+    @RequestMapping
+    String argNameTest(String name);
+
+    @RequestMapping
+    void pbServerStream(@RequestBody HealthCheckRequest request, StreamObserver<HealthCheckResponse> responseObserver);
 }
diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoServiceImpl.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoServiceImpl.java
index 97ef3cfe71..e2954d27e5 100644
--- a/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoServiceImpl.java
+++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoServiceImpl.java
@@ -16,12 +16,6 @@
  */
 package org.apache.dubbo.rpc.protocol.tri.rest.support.spring.service;
 
-import org.apache.dubbo.rpc.protocol.tri.rest.service.User.UserEx;
+import org.apache.dubbo.rpc.protocol.tri.rest.service.DemoServiceImpl;
 
-public class SpringDemoServiceImpl implements SpringDemoService {
-
-    @Override
-    public UserEx beanArgTest(UserEx user) {
-        return user;
-    }
-}
+public class SpringDemoServiceImpl extends DemoServiceImpl implements SpringDemoService {}
diff --git a/dubbo-plugin/dubbo-rest-spring/src/test/resources/log4j2-test.xml b/dubbo-plugin/dubbo-rest-spring/src/test/resources/log4j2-test.xml
index 91337f333b..8fff1d4ace 100644
--- a/dubbo-plugin/dubbo-rest-spring/src/test/resources/log4j2-test.xml
+++ b/dubbo-plugin/dubbo-rest-spring/src/test/resources/log4j2-test.xml
@@ -24,6 +24,7 @@
     <Loggers>
         <Logger name="org.apache.dubbo.rpc.protocol.tri.stream.StreamUtils" level="error"/>
         <Logger name="org.apache.dubbo.rpc.protocol.tri.h12" level="debug"/>
+        <Logger name="org.apache.dubbo.rpc.protocol.tri.rest" level="debug"/>
         <Root level="info">
             <AppenderRef ref="Console"/>
         </Root>
diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpMethods.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpMethods.java
index 8cdf0e9f73..4792ccd9bd 100644
--- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpMethods.java
+++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpMethods.java
@@ -20,12 +20,12 @@ import java.nio.charset.StandardCharsets;
 
 public enum HttpMethods {
     GET,
+    HEAD,
     POST,
     PUT,
+    PATCH,
     DELETE,
-    HEAD,
     OPTIONS,
-    PATCH,
     TRACE;
 
     public static final byte[][] HTTP_METHODS_BYTES;
diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoder.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoder.java
index 448646bb36..53c4e84779 100644
--- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoder.java
+++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoder.java
@@ -20,6 +20,8 @@ import org.apache.dubbo.common.utils.ArrayUtils;
 import org.apache.dubbo.remoting.http12.exception.DecodeException;
 
 import java.io.InputStream;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
 import java.nio.charset.Charset;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
@@ -28,6 +30,16 @@ public interface HttpMessageDecoder extends CodecMediaType {
 
     Object decode(InputStream inputStream, Class<?> targetType, Charset charset) throws DecodeException;
 
+    default Object decode(InputStream inputStream, Type targetType, Charset charset) throws DecodeException {
+        if (targetType instanceof Class) {
+            return decode(inputStream, (Class<?>) targetType, charset);
+        }
+        if (targetType instanceof ParameterizedType) {
+            return decode(inputStream, (Class<?>) ((ParameterizedType) targetType).getRawType(), charset);
+        }
+        throw new DecodeException("targetType " + targetType + " is not a class");
+    }
+
     default Object[] decode(InputStream inputStream, Class<?>[] targetTypes, Charset charset) throws DecodeException {
         return new Object[] {decode(inputStream, ArrayUtils.isEmpty(targetTypes) ? null : targetTypes[0], charset)};
     }
@@ -36,6 +48,10 @@ public interface HttpMessageDecoder extends CodecMediaType {
         return decode(inputStream, targetType, UTF_8);
     }
 
+    default Object decode(InputStream inputStream, Type targetType) throws DecodeException {
+        return decode(inputStream, targetType, UTF_8);
+    }
+
     default Object[] decode(InputStream inputStream, Class<?>[] targetTypes) throws DecodeException {
         return decode(inputStream, targetTypes, UTF_8);
     }
diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java
index edeee5e01b..32281439cc 100644
--- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java
+++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java
@@ -26,6 +26,7 @@ import org.apache.dubbo.remoting.http12.message.MediaType;
 
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.lang.reflect.Type;
 import java.nio.charset.Charset;
 import java.util.List;
 
@@ -64,6 +65,17 @@ public class JsonCodec implements HttpMessageCodec {
         }
     }
 
+    @Override
+    public Object decode(InputStream is, Type targetType, Charset charset) throws DecodeException {
+        try {
+            return JsonUtils.toJavaObject(StreamUtils.toString(is, charset), targetType);
+        } catch (HttpStatusException e) {
+            throw e;
+        } catch (Throwable t) {
+            throw new DecodeException("Error decoding json", t);
+        }
+    }
+
     @Override
     public Object[] decode(InputStream is, Class<?>[] targetTypes, Charset charset) throws DecodeException {
         try {
diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodec.java
index a3b2dbdea4..bb87ba882e 100644
--- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodec.java
+++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodec.java
@@ -25,6 +25,7 @@ import org.apache.dubbo.remoting.http12.exception.HttpStatusException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.lang.reflect.Type;
 import java.nio.charset.Charset;
 
 import com.google.protobuf.Message;
@@ -63,6 +64,13 @@ public final class JsonPbCodec extends JsonCodec {
         return super.decode(is, targetType, charset);
     }
 
+    @Override
+    public Object decode(InputStream is, Type targetType, Charset charset) throws DecodeException {
+        return targetType instanceof Class
+                ? decode(is, (Class<?>) targetType, charset)
+                : super.decode(is, targetType, charset);
+    }
+
     @Override
     public Object[] decode(InputStream is, Class<?>[] targetTypes, Charset charset) throws DecodeException {
         try {
diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodec.java
index 9362bcaf8b..01cce80e43 100644
--- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodec.java
+++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/YamlCodec.java
@@ -18,6 +18,7 @@ package org.apache.dubbo.remoting.http12.message.codec;
 
 import org.apache.dubbo.common.utils.ClassUtils;
 import org.apache.dubbo.common.utils.DefaultSerializeClassChecker;
+import org.apache.dubbo.common.utils.JsonUtils;
 import org.apache.dubbo.remoting.http12.exception.DecodeException;
 import org.apache.dubbo.remoting.http12.exception.EncodeException;
 import org.apache.dubbo.remoting.http12.exception.HttpStatusException;
@@ -28,6 +29,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.lang.reflect.Type;
 import java.nio.charset.Charset;
 import java.util.Iterator;
 
@@ -51,10 +53,25 @@ public class YamlCodec implements HttpMessageCodec {
         }
     }
 
+    @Override
+    public Object decode(InputStream is, Type targetType, Charset charset) throws DecodeException {
+        if (targetType instanceof Class) {
+            return decode(is, (Class<?>) targetType, charset);
+        }
+
+        try (InputStreamReader reader = new InputStreamReader(is, charset)) {
+            return JsonUtils.convertObject(createYaml().load(reader), targetType);
+        } catch (HttpStatusException e) {
+            throw e;
+        } catch (Throwable t) {
+            throw new DecodeException("Error decoding yaml", t);
+        }
+    }
+
     @Override
     public Object[] decode(InputStream is, Class<?>[] targetTypes, Charset charset) throws DecodeException {
         try (InputStreamReader reader = new InputStreamReader(is, charset)) {
-            Yaml yaml = new Yaml();
+            Yaml yaml = createYaml();
             Iterator<Object> iterator = yaml.loadAll(reader).iterator();
             int len = targetTypes.length;
             Object[] results = new Object[len];
diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Mapping.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Mapping.java
index a05e479306..0aea2292ed 100644
--- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Mapping.java
+++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Mapping.java
@@ -42,4 +42,6 @@ public @interface Mapping {
     String[] consumes() default {};
 
     String[] produces() default {};
+
+    boolean disabled() default false;
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpMessageDecoderWrapper.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpMessageDecoderWrapper.java
index b50a7c3d98..22bf82a8a9 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpMessageDecoderWrapper.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpMessageDecoderWrapper.java
@@ -21,6 +21,7 @@ import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder;
 import org.apache.dubbo.remoting.http12.message.MediaType;
 
 import java.io.InputStream;
+import java.lang.reflect.Type;
 import java.nio.charset.Charset;
 
 public final class HttpMessageDecoderWrapper implements HttpMessageDecoder {
@@ -43,6 +44,11 @@ public final class HttpMessageDecoderWrapper implements HttpMessageDecoder {
         return httpMessageDecoder.decode(inputStream, targetType, this.charset);
     }
 
+    @Override
+    public Object decode(InputStream inputStream, Type targetType, Charset charset) throws DecodeException {
+        return httpMessageDecoder.decode(inputStream, targetType, this.charset);
+    }
+
     @Override
     public Object[] decode(InputStream inputStream, Class<?>[] targetTypes, Charset charset) throws DecodeException {
         return httpMessageDecoder.decode(inputStream, targetTypes, this.charset);
@@ -53,6 +59,11 @@ public final class HttpMessageDecoderWrapper implements HttpMessageDecoder {
         return httpMessageDecoder.decode(inputStream, targetType, charset);
     }
 
+    @Override
+    public Object decode(InputStream inputStream, Type targetType) throws DecodeException {
+        return httpMessageDecoder.decode(inputStream, targetType, charset);
+    }
+
     @Override
     public Object[] decode(InputStream inputStream, Class<?>[] targetTypes) throws DecodeException {
         return httpMessageDecoder.decode(inputStream, targetTypes, charset);
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/Messages.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/Messages.java
index 2abcff1220..3ded19a441 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/Messages.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/Messages.java
@@ -34,9 +34,9 @@ public enum Messages {
     AMBIGUOUS_MAPPING("Ambiguous mapping for ''{0}'': [{1}, {2}]"),
     EXTENSION_INIT_FAILED("Rest extension: ''{0}'' initialization failed for invoker: ''{1}''"),
     ARGUMENT_NAME_MISSING("Name for argument of type [{0}] not specified, and parameter name information not "
-            + "available via reflection. Ensure that the compiler uses the '-parameters' flag."),
+            + "available via reflection. Ensure that the compiler uses the '-parameters' flag"),
     ARGUMENT_VALUE_MISSING("Missing argument ''{0}'' for method parameter of type [{1}]", 412),
-    ARGUMENT_CONVERT_ERROR("Convert argument ''{0}'' value [{1}] from type [{2}] to type [{3}] error", 412),
+    ARGUMENT_CONVERT_ERROR("Could not convert argument ''{0}'' value ''{1}'' from type [{2}] to type [{3}]", 412),
     ARGUMENT_COULD_NOT_RESOLVED("Could not resolve ''{0}'', no suitable resolver", 400),
     ARGUMENT_BIND_ERROR("Bind argument ''{0}'' of type [{1}] error", 400),
     INTERNAL_ERROR("Rest internal error");
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestHttpMessageCodec.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestHttpMessageCodec.java
index 1451c8d2d1..cd6df6f4d6 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestHttpMessageCodec.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestHttpMessageCodec.java
@@ -29,7 +29,9 @@ import org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver;
 import org.apache.dubbo.rpc.protocol.tri.rest.argument.TypeConverter;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta;
 
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.charset.Charset;
@@ -73,7 +75,7 @@ public final class RestHttpMessageCodec implements HttpMessageDecoder, HttpMessa
 
     @Override
     public Object[] decode(InputStream inputStream, Class<?>[] targetTypes, Charset charset) throws DecodeException {
-        request.setInputStream(inputStream);
+        request.setInputStream(decodeInputStream(inputStream));
         ParameterMeta[] parameters = this.parameters;
         int len = parameters.length;
         if (len == 0) {
@@ -91,6 +93,19 @@ public final class RestHttpMessageCodec implements HttpMessageDecoder, HttpMessa
         encode(os, data);
     }
 
+    private InputStream decodeInputStream(InputStream is) {
+        if (is.getClass() == ByteArrayInputStream.class) {
+            return is;
+        }
+        try {
+            byte[] bytes = new byte[is.available()];
+            is.read(bytes);
+            return new ByteArrayInputStream(bytes);
+        } catch (IOException e) {
+            throw new DecodeException(e);
+        }
+    }
+
     @Override
     public void encode(OutputStream os, Object data) throws EncodeException {
         if (data != null) {
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/GeneralTypeConverter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/GeneralTypeConverter.java
index 9caef42987..a985704823 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/GeneralTypeConverter.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/GeneralTypeConverter.java
@@ -615,8 +615,9 @@ public class GeneralTypeConverter implements TypeConverter {
 
         try {
             return JsonUtils.convertObject(source, targetClass);
-        } catch (Exception e) {
-            LOGGER.debug("JSON convert from [{}] to [{}] failed", sourceClass.getName(), targetClass.getName(), e);
+        } catch (Throwable t) {
+            String msg = "JSON convert value '{}' from type [{}] to type [{}] failed";
+            LOGGER.debug(msg, source, sourceClass, targetClass, t);
         }
 
         return null;
@@ -660,6 +661,20 @@ public class GeneralTypeConverter implements TypeConverter {
 
                 if (Collection.class.isAssignableFrom(targetClass)) {
                     Type itemType = getActualGenericType(argTypes[0]);
+                    if (itemType instanceof Class && targetClass.isInstance(source)) {
+                        boolean same = true;
+                        Class<?> itemClass = (Class<?>) itemType;
+                        for (Object item : (Collection) source) {
+                            if (item != null && !itemClass.isInstance(item)) {
+                                same = false;
+                                break;
+                            }
+                        }
+                        if (same) {
+                            return source;
+                        }
+                    }
+
                     Collection items = toCollection(source);
                     Collection targetItems = createCollection(targetClass, items.size());
                     for (Object item : items) {
@@ -671,6 +686,28 @@ public class GeneralTypeConverter implements TypeConverter {
                 if (Map.class.isAssignableFrom(targetClass)) {
                     Type keyType = argTypes[0];
                     Type valueType = argTypes[1];
+
+                    if (keyType instanceof Class && valueType instanceof Class && targetClass.isInstance(source)) {
+                        boolean same = true;
+                        Class<?> keyClass = (Class<?>) keyType;
+                        Class<?> valueClass = (Class<?>) valueType;
+                        for (Map.Entry entry : ((Map<Object, Object>) source).entrySet()) {
+                            Object key = entry.getKey();
+                            if (key != null && !keyClass.isInstance(key)) {
+                                same = false;
+                                break;
+                            }
+                            Object value = entry.getValue();
+                            if (value != null && !valueClass.isInstance(value)) {
+                                same = false;
+                                break;
+                            }
+                        }
+                        if (same) {
+                            return source;
+                        }
+                    }
+
                     Class<?> mapValueClass = TypeUtils.getMapValueType(targetClass);
                     boolean multiValue = mapValueClass != null && Collection.class.isAssignableFrom(mapValueClass);
 
@@ -725,9 +762,9 @@ public class GeneralTypeConverter implements TypeConverter {
 
         try {
             return JsonUtils.convertObject(source, targetType);
-        } catch (Exception e) {
-            String name = source.getClass().getName();
-            LOGGER.debug("JSON convert from [{}] to [{}] failed", name, targetType.getTypeName(), e);
+        } catch (Throwable t) {
+            String msg = "JSON convert value '{}' from type [{}] to type [{}] failed";
+            LOGGER.debug(msg, source, source.getClass(), targetType, t);
         }
 
         return null;
@@ -1099,7 +1136,7 @@ public class GeneralTypeConverter implements TypeConverter {
                         .determineHttpMessageDecoder(MediaType.APPLICATION_JSON.getName())
                         .decode(new ByteArrayInputStream(value.getBytes(UTF_8)), (Class<?>) targetType);
             } catch (Throwable t) {
-                LOGGER.debug("Failed to parse [{}] from json string [{}]", targetType, value, t);
+                LOGGER.debug("Failed to parse value '{}' from json string '{}'", targetType, value, t);
             }
         }
         return null;
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/NamedValueArgumentResolverSupport.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/NamedValueArgumentResolverSupport.java
index d7401c57cf..80deb647b1 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/NamedValueArgumentResolverSupport.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/NamedValueArgumentResolverSupport.java
@@ -26,11 +26,9 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.NamedValueMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils;
 
-import java.lang.reflect.Type;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
-import java.util.Optional;
 
 public abstract class NamedValueArgumentResolverSupport {
 
@@ -64,18 +62,13 @@ public abstract class NamedValueArgumentResolverSupport {
             meta.setName(parameterMeta.getRequiredName());
         }
 
-        Class<?> type = parameterMeta.getType();
-        Type genericType = parameterMeta.getGenericType();
-        if (type == Optional.class) {
-            Class<?> actualType = TypeUtils.getNestedActualType(genericType, 0);
-            meta.setType(actualType == null ? Object.class : actualType);
-        } else {
-            meta.setType(type);
-        }
+        Class<?> type = parameterMeta.getActualType();
+        meta.setType(type);
+        meta.setGenericType(parameterMeta.getActualGenericType());
         if (type.isArray()) {
-            meta.setNestedTypes(new Class<?>[] {type.getComponentType()});
+            meta.setNestedTypes(new Class<?>[] {type});
         } else {
-            meta.setNestedTypes(TypeUtils.getNestedActualTypes(genericType));
+            meta.setNestedTypes(TypeUtils.getNestedActualTypes(meta.genericType()));
         }
         meta.setParameterMeta(parameterMeta);
         return meta;
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java
index 17edec2101..8647c09e78 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/DefaultRequestMappingRegistry.java
@@ -19,6 +19,10 @@ package org.apache.dubbo.rpc.protocol.tri.rest.mapping;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.Configuration;
 import org.apache.dubbo.common.config.ConfigurationUtils;
+import org.apache.dubbo.common.constants.LoggerCodeConstants;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.config.nested.RestConfig;
 import org.apache.dubbo.remoting.http12.HttpRequest;
 import org.apache.dubbo.remoting.http12.message.MethodMetadata;
@@ -29,6 +33,7 @@ import org.apache.dubbo.rpc.model.ReflectionMethodDescriptor;
 import org.apache.dubbo.rpc.model.ReflectionServiceDescriptor;
 import org.apache.dubbo.rpc.model.ServiceDescriptor;
 import org.apache.dubbo.rpc.protocol.tri.DescriptorUtils;
+import org.apache.dubbo.rpc.protocol.tri.rest.Messages;
 import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants;
 import org.apache.dubbo.rpc.protocol.tri.rest.RestInitializeException;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RadixTree.Match;
@@ -53,6 +58,9 @@ import static org.apache.dubbo.rpc.protocol.tri.rest.Messages.AMBIGUOUS_MAPPING;
 
 public final class DefaultRequestMappingRegistry implements RequestMappingRegistry {
 
+    private static final ErrorTypeAwareLogger LOGGER =
+            LoggerFactory.getErrorTypeAwareLogger(DefaultRequestMappingRegistry.class);
+
     private final FrameworkModel frameworkModel;
     private final ContentNegotiator contentNegotiator;
     private final ReadWriteLock lock = new ReentrantReadWriteLock();
@@ -100,6 +108,10 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist
         new MethodWalker().walk(service.getClass(), (classes, consumer) -> {
             for (int i = 0, size = resolvers.size(); i < size; i++) {
                 RequestMappingResolver resolver = resolvers.get(i);
+                if (LOGGER.isInfoEnabled()) {
+                    String name = resolver.getClass().getSimpleName();
+                    LOGGER.info("{} resolve rest mappings from [{}]", name, CollectionUtils.first(classes));
+                }
                 ServiceMeta serviceMeta = new ServiceMeta(classes, sd, service, url, resolver.getRestToolKit());
                 if (!resolver.accept(serviceMeta)) {
                     continue;
@@ -142,7 +154,13 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist
             registration.mapping = mapping;
             registration.meta = handler;
             for (PathExpression path : mapping.getPathCondition().getExpressions()) {
-                tree.addPath(path, registration);
+                Registration exists = tree.addPath(path, registration);
+                if (exists == null) {
+                    LOGGER.debug("Register rest mapping path: '{}' -> {}", path, mapping);
+                } else if (LOGGER.isWarnEnabled()) {
+                    String msg = Messages.DUPLICATE_MAPPING.format(path, mapping, exists.mapping);
+                    LOGGER.warn(LoggerCodeConstants.INTERNAL_ERROR, "", "", msg);
+                }
             }
         } finally {
             lock.writeLock().unlock();
@@ -232,6 +250,9 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist
                 }
                 return c1.variableMap.size() - c2.variableMap.size();
             });
+
+            LOGGER.debug("Candidate rest mappings: {}", candidates);
+
             Candidate first = candidates.get(0);
             Candidate second = candidates.get(1);
             if (first.mapping.compareTo(second.mapping, request) == 0) {
@@ -246,6 +267,8 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist
         request.setAttribute(RestConstants.MAPPING_ATTRIBUTE, mapping);
         request.setAttribute(RestConstants.HANDLER_ATTRIBUTE, handler);
 
+        LOGGER.debug("Matched rest mapping={}, method={}", mapping, handler.getMethod());
+
         if (!winner.variableMap.isEmpty()) {
             request.setAttribute(RestConstants.URI_TEMPLATE_VARIABLES_ATTRIBUTE, winner.variableMap);
         }
@@ -368,5 +391,10 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist
         HandlerMeta meta;
         PathExpression expression;
         Map<String, String> variableMap;
+
+        @Override
+        public String toString() {
+            return "Candidate{mapping=" + mapping + ", method=" + meta.getMethod() + '}';
+        }
     }
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java
index bd84d96f3e..99abe12fc0 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMapping.java
@@ -29,6 +29,7 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ParamsCondition;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathCondition;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ProducesCondition;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ServiceVersionCondition;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ResponseMeta;
 
@@ -203,6 +204,10 @@ public final class RequestMapping implements Condition<RequestMapping, HttpReque
         return pathCondition;
     }
 
+    public MethodsCondition getMethodsCondition() {
+        return methodsCondition;
+    }
+
     public ProducesCondition getProducesCondition() {
         return producesCondition;
     }
@@ -420,6 +425,13 @@ public final class RequestMapping implements Condition<RequestMapping, HttpReque
             return this;
         }
 
+        public Builder service(String ServiceGroup, String ServiceVersion) {
+            if (ServiceGroup != null || ServiceVersion != null) {
+                customCondition = new ServiceVersionCondition(ServiceGroup, ServiceVersion);
+            }
+            return this;
+        }
+
         public Builder cors(CorsMeta cors) {
             this.cors = cors;
             return this;
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java
index e04b9794de..1fe540af6d 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RestRequestHandlerMapping.java
@@ -19,10 +19,15 @@ package org.apache.dubbo.rpc.protocol.tri.rest.mapping;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.beans.factory.ScopeBeanFactory;
 import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.remoting.http12.HttpMethods;
 import org.apache.dubbo.remoting.http12.HttpRequest;
 import org.apache.dubbo.remoting.http12.HttpResponse;
+import org.apache.dubbo.remoting.http12.HttpResult;
+import org.apache.dubbo.remoting.http12.HttpStatus;
+import org.apache.dubbo.remoting.http12.exception.HttpResultPayloadException;
 import org.apache.dubbo.remoting.http12.message.MediaType;
 import org.apache.dubbo.remoting.http12.message.codec.CodecUtils;
 import org.apache.dubbo.rpc.model.FrameworkModel;
@@ -33,14 +38,19 @@ import org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver;
 import org.apache.dubbo.rpc.protocol.tri.rest.argument.CompositeArgumentResolver;
 import org.apache.dubbo.rpc.protocol.tri.rest.argument.GeneralTypeConverter;
 import org.apache.dubbo.rpc.protocol.tri.rest.argument.TypeConverter;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.MethodsCondition;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.HandlerMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils;
 import org.apache.dubbo.rpc.protocol.tri.route.RequestHandler;
 import org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping;
 
+import java.util.Set;
+
 @Activate(order = -2000)
 public final class RestRequestHandlerMapping implements RequestHandlerMapping {
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(RestRequestHandlerMapping.class);
+
     private final FrameworkModel frameworkModel;
     private final RequestMappingRegistry requestMappingRegistry;
     private final ArgumentResolver argumentResolver;
@@ -60,11 +70,18 @@ public final class RestRequestHandlerMapping implements RequestHandlerMapping {
 
     @Override
     public RequestHandler getRequestHandler(URL url, HttpRequest request, HttpResponse response) {
+        LOGGER.debug("Received http request: {}", request);
+
         HandlerMeta meta = requestMappingRegistry.lookup(request);
         if (meta == null) {
             return null;
         }
 
+        String method = request.method();
+        if (HttpMethods.OPTIONS.name().equals(method)) {
+            handleOptionsRequest(request);
+        }
+
         String requestMediaType = request.mediaType();
         String responseMediaType = contentNegotiator.negotiate(request);
         if (responseMediaType != null) {
@@ -84,7 +101,7 @@ public final class RestRequestHandlerMapping implements RequestHandlerMapping {
                 typeConverter,
                 codecUtils.determineHttpMessageEncoder(url, frameworkModel, responseMediaType));
 
-        if (HttpMethods.supportBody(request.method()) && !RequestUtils.isFormOrMultiPart(request)) {
+        if (HttpMethods.supportBody(method) && !RequestUtils.isFormOrMultiPart(request)) {
             if (StringUtils.isEmpty(requestMediaType)) {
                 requestMediaType = responseMediaType;
             }
@@ -93,6 +110,8 @@ public final class RestRequestHandlerMapping implements RequestHandlerMapping {
                     codecUtils.determineHttpMessageDecoder(url, frameworkModel, requestMediaType));
         }
 
+        LOGGER.debug("Content-type negotiate result: request='{}', response='{}'", requestMediaType, responseMediaType);
+
         RequestHandler handler = new RequestHandler(meta.getInvoker());
         handler.setHasStub(false);
         handler.setMethodDescriptor(meta.getMethodDescriptor());
@@ -103,6 +122,25 @@ public final class RestRequestHandlerMapping implements RequestHandlerMapping {
         return handler;
     }
 
+    private static void handleOptionsRequest(HttpRequest request) {
+        RequestMapping mapping = request.attribute(RestConstants.MAPPING_ATTRIBUTE);
+        MethodsCondition condition = mapping.getMethodsCondition();
+        if (condition == null) {
+            throw new HttpResultPayloadException(HttpResult.builder()
+                    .status(HttpStatus.NO_CONTENT)
+                    .header("allow", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS")
+                    .build());
+        }
+        Set<String> methods = condition.getMethods();
+        if (methods.size() == 1 && methods.contains(HttpMethods.OPTIONS.name())) {
+            return;
+        }
+        throw new HttpResultPayloadException(HttpResult.builder()
+                .status(HttpStatus.NO_CONTENT)
+                .header("allow", StringUtils.join(methods, ","))
+                .build());
+    }
+
     @Override
     public String getType() {
         return TripleConstant.TRIPLE_HANDLER_TYPE_REST;
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MethodsCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MethodsCondition.java
index 96b8eec3c4..1790d50f61 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MethodsCondition.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MethodsCondition.java
@@ -52,17 +52,22 @@ public final class MethodsCondition implements Condition<MethodsCondition, HttpR
     @Override
     public MethodsCondition match(HttpRequest request) {
         String method = request.method();
+
+        if (OPTIONS.name().equals(method)) {
+            if (request.hasHeader("origin") && request.hasHeader("access-control-request-method")) {
+                return new MethodsCondition(OPTIONS.name());
+            } else {
+                return this;
+            }
+        }
+
         if (methods.contains(method)) {
             return new MethodsCondition(method);
         }
+
         if (HEAD.name().equals(method) && methods.contains(GET.name())) {
             return new MethodsCondition(GET.name());
         }
-        if (OPTIONS.name().equals(method)
-                && request.hasHeader("origin")
-                && request.hasHeader("access-control-request-method")) {
-            return new MethodsCondition(OPTIONS.name());
-        }
 
         return null;
     }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ServiceVersionCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ServiceVersionCondition.java
index f00a26d57d..f1675c0a58 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ServiceVersionCondition.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ServiceVersionCondition.java
@@ -51,10 +51,10 @@ public final class ServiceVersionCondition implements Condition<ServiceVersionCo
         if (version == null) {
             version = request.header(RestConstants.HEADER_SERVICE_VERSION);
         }
-
         if (version != null && !version.equals(this.version)) {
             return null;
         }
+
         return this;
     }
 
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationSupport.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationSupport.java
index 35592ce137..c93a164ed8 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationSupport.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/AnnotationSupport.java
@@ -61,7 +61,7 @@ public abstract class AnnotationSupport {
         });
     }
 
-    public final Annotation[] getRealAnnotations() {
+    public final Annotation[] getRawAnnotations() {
         AnnotationMeta[] annotations = getAnnotations();
         int len = annotations.length;
         Annotation[] result = new Annotation[len];
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/BeanMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/BeanMeta.java
index e56d6e9243..5c827876d1 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/BeanMeta.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/BeanMeta.java
@@ -352,7 +352,7 @@ public final class BeanMeta {
         }
     }
 
-    public static final class NestedMeta extends NestableParameterMeta {
+    private static final class NestedMeta extends NestableParameterMeta {
 
         private final Class<?> type;
         private final Type genericType;
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodMeta.java
index ce3e2350d6..6e8fe11427 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodMeta.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodMeta.java
@@ -16,7 +16,11 @@
  */
 package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta;
 
+import org.apache.dubbo.common.utils.MethodUtils;
 import org.apache.dubbo.rpc.model.MethodDescriptor;
+import org.apache.dubbo.rpc.model.MethodDescriptor.RpcType;
+import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit;
+import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils;
 
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Method;
@@ -50,7 +54,14 @@ public final class MethodMeta extends AnnotationSupport {
     }
 
     public void initParameters() {
-        int count = method.getParameterCount();
+        RpcType rpcType = methodDescriptor.getRpcType();
+        if (rpcType == RpcType.CLIENT_STREAM || rpcType == RpcType.BI_STREAM) {
+            Type genericType = TypeUtils.getNestedGenericType(method.getGenericReturnType(), 0);
+            parameters = new ParameterMeta[] {new StreamParameterMeta(getToolKit(), genericType, method, hierarchy)};
+            return;
+        }
+
+        int count = rpcType == RpcType.SERVER_STREAM ? 1 : method.getParameterCount();
         List<List<Parameter>> parameterHierarchies = new ArrayList<>(count);
         for (int i = 0, len = hierarchy.size(); i < len; i++) {
             Method m = hierarchy.get(i);
@@ -66,7 +77,6 @@ public final class MethodMeta extends AnnotationSupport {
                 parameterHierarchy.add(mps[j]);
             }
         }
-
         String[] parameterNames = getToolKit().getParameterNames(method);
         ParameterMeta[] parameters = new ParameterMeta[count];
         for (int i = 0; i < count; i++) {
@@ -136,6 +146,51 @@ public final class MethodMeta extends AnnotationSupport {
 
     @Override
     public String toString() {
-        return "MethodMeta{method=" + method + '}';
+        return "MethodMeta{" + MethodUtils.toShortString(method) + '}';
+    }
+
+    private static final class StreamParameterMeta extends ParameterMeta {
+
+        private final Class<?> type;
+        private final Type genericType;
+        private final AnnotatedElement element;
+        private final List<? extends AnnotatedElement> elements;
+
+        StreamParameterMeta(
+                RestToolKit toolKit,
+                Type genericType,
+                AnnotatedElement element,
+                List<? extends AnnotatedElement> elements) {
+            super(toolKit, "value");
+            type = TypeUtils.getActualType(genericType);
+            this.genericType = genericType;
+            this.element = element;
+            this.elements = elements;
+        }
+
+        @Override
+        public String getDescription() {
+            return "Stream parameter [" + element + "]";
+        }
+
+        @Override
+        public Class<?> getType() {
+            return type;
+        }
+
+        @Override
+        public Type getGenericType() {
+            return genericType;
+        }
+
+        @Override
+        protected AnnotatedElement getAnnotatedElement() {
+            return element;
+        }
+
+        @Override
+        protected List<? extends AnnotatedElement> getAnnotatedElements() {
+            return elements;
+        }
     }
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodParameterMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodParameterMeta.java
index 845acf7d60..e9b82cf482 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodParameterMeta.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/MethodParameterMeta.java
@@ -16,9 +16,6 @@
  */
 package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta;
 
-import org.apache.dubbo.rpc.model.MethodDescriptor;
-import org.apache.dubbo.rpc.model.MethodDescriptor.RpcType;
-
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Method;
 import java.lang.reflect.Parameter;
@@ -48,12 +45,6 @@ public final class MethodParameterMeta extends ParameterMeta {
         return parameter;
     }
 
-    @Override
-    public boolean isSingle() {
-        MethodDescriptor methodDescriptor = methodMeta.getMethodDescriptor();
-        return methodDescriptor.getRpcType() != RpcType.UNARY || methodDescriptor.getParameterClasses().length == 1;
-    }
-
     @Override
     public int getIndex() {
         return index;
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/NamedValueMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/NamedValueMeta.java
index 04c77875b8..1713668acc 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/NamedValueMeta.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/NamedValueMeta.java
@@ -16,6 +16,7 @@
  */
 package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta;
 
+import java.lang.reflect.Type;
 import java.util.Arrays;
 
 public class NamedValueMeta {
@@ -24,6 +25,7 @@ public class NamedValueMeta {
     private final boolean required;
     private final String defaultValue;
     private Class<?> type;
+    private Type genericType;
     private Class<?>[] nestedTypes;
     private ParameterMeta parameterMeta;
 
@@ -63,6 +65,14 @@ public class NamedValueMeta {
         this.type = type;
     }
 
+    public Type genericType() {
+        return genericType;
+    }
+
+    public void setGenericType(Type genericType) {
+        this.genericType = genericType;
+    }
+
     public Class<?> nestedType() {
         return nestedTypes == null ? null : nestedTypes[0];
     }
@@ -99,6 +109,9 @@ public class NamedValueMeta {
         }
         if (type != null) {
             sb.append(", type=").append(type);
+            if (genericType != type) {
+                sb.append(", genericType=").append(genericType);
+            }
         }
         if (nestedTypes != null) {
             sb.append(", nestedTypes=").append(Arrays.toString(nestedTypes));
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ParameterMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ParameterMeta.java
index 2def707122..33f02fdb31 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ParameterMeta.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ParameterMeta.java
@@ -25,6 +25,7 @@ import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils;
 
 import javax.annotation.Nullable;
 
+import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.Collection;
 import java.util.Optional;
@@ -35,7 +36,7 @@ public abstract class ParameterMeta extends AnnotationSupport {
     private final String name;
     private Boolean simple;
     private Class<?> actualType;
-    private Object typeDescriptor;
+    private Type actualGenericType;
 
     protected ParameterMeta(RestToolKit toolKit, String prefix, String name) {
         super(toolKit);
@@ -93,22 +94,25 @@ public abstract class ParameterMeta extends AnnotationSupport {
         return type;
     }
 
-    public final Object getTypeDescriptor() {
-        return typeDescriptor;
-    }
-
-    public final void setTypeDescriptor(Object typeDescriptor) {
-        this.typeDescriptor = typeDescriptor;
+    public final Type getActualGenericType() {
+        Type type = actualGenericType;
+        if (type == null) {
+            type = getGenericType();
+            if (type instanceof ParameterizedType && ((ParameterizedType) type).getRawType() == Optional.class) {
+                type = TypeUtils.getNestedGenericType(getGenericType(), 0);
+                if (type == null) {
+                    type = Object.class;
+                }
+            }
+            actualGenericType = type;
+        }
+        return type;
     }
 
     public final Object bind(HttpRequest request, HttpResponse response) {
         return getToolKit().bind(this, request, response);
     }
 
-    public boolean isSingle() {
-        return false;
-    }
-
     public int getIndex() {
         return -1;
     }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRequestMappingResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRequestMappingResolver.java
index db39ea43f1..7c6377f873 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRequestMappingResolver.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRequestMappingResolver.java
@@ -18,12 +18,12 @@ package org.apache.dubbo.rpc.protocol.tri.rest.support.basic;
 
 import org.apache.dubbo.common.extension.Activate;
 import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.remoting.http12.rest.Mapping;
 import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping.Builder;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver;
-import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ServiceVersionCondition;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta;
@@ -52,12 +52,13 @@ public class BasicRequestMappingResolver implements RequestMappingResolver {
 
     @Override
     public boolean accept(MethodMeta methodMeta) {
-        return methodMeta.getMethodDescriptor() != null || methodMeta.findAnnotation(Annotations.Mapping) != null;
+        AnnotationMeta<Mapping> mapping = methodMeta.findAnnotation(Mapping.class);
+        return mapping != null ? !mapping.getAnnotation().disabled() : methodMeta.getMethodDescriptor() != null;
     }
 
     @Override
     public RequestMapping resolve(ServiceMeta serviceMeta) {
-        AnnotationMeta<?> mapping = serviceMeta.findAnnotation(Annotations.Mapping);
+        AnnotationMeta<Mapping> mapping = serviceMeta.findAnnotation(Mapping.class);
         Builder builder = builder(mapping);
 
         String[] paths = getPaths(mapping);
@@ -75,12 +76,16 @@ public class BasicRequestMappingResolver implements RequestMappingResolver {
     @Override
     public RequestMapping resolve(MethodMeta methodMeta) {
         Method method = methodMeta.getMethod();
-        AnnotationMeta<?> mapping = methodMeta.findAnnotation(Annotations.Mapping);
+        AnnotationMeta<Mapping> mapping = methodMeta.findAnnotation(Mapping.class);
+        if (mapping != null && mapping.getAnnotation().disabled()) {
+            return null;
+        }
+
         Builder builder = builder(mapping);
 
         String[] paths = getPaths(mapping);
         if (paths.length == 0) {
-            builder.path(method.getName()).sig(TypeUtils.buildSig(method));
+            builder.path('/' + method.getName()).sig(TypeUtils.buildSig(method));
         } else {
             builder.path(paths);
         }
@@ -90,7 +95,7 @@ public class BasicRequestMappingResolver implements RequestMappingResolver {
             globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel);
         }
         return builder.name(method.getName())
-                .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion()))
+                .service(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion())
                 .cors(globalCorsMeta)
                 .build();
     }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/FallbackArgumentResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/FallbackArgumentResolver.java
index ab4cbd97a6..ef788d6084 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/FallbackArgumentResolver.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/FallbackArgumentResolver.java
@@ -20,8 +20,15 @@ import org.apache.dubbo.common.extension.Activate;
 import org.apache.dubbo.remoting.http12.HttpMethods;
 import org.apache.dubbo.remoting.http12.HttpRequest;
 import org.apache.dubbo.remoting.http12.HttpResponse;
+import org.apache.dubbo.remoting.http12.exception.DecodeException;
+import org.apache.dubbo.remoting.http12.rest.Param;
+import org.apache.dubbo.remoting.http12.rest.ParamType;
+import org.apache.dubbo.rpc.model.MethodDescriptor.RpcType;
 import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants;
 import org.apache.dubbo.rpc.protocol.tri.rest.argument.AbstractArgumentResolver;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodParameterMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.NamedValueMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils;
@@ -40,7 +47,21 @@ public class FallbackArgumentResolver extends AbstractArgumentResolver {
 
     @Override
     protected NamedValueMeta createNamedValueMeta(ParameterMeta param) {
-        return new NamedValueMeta(param.isAnnotated(Annotations.Nonnull), null);
+        boolean noBodyParam = true;
+        int paramCount = -1;
+        if (param instanceof MethodParameterMeta) {
+            MethodMeta methodMeta = ((MethodParameterMeta) param).getMethodMeta();
+            ParameterMeta[] paramMetas = methodMeta.getParameters();
+            for (ParameterMeta paramMeta : paramMetas) {
+                AnnotationMeta<Param> anno = paramMeta.findAnnotation(Param.class);
+                if (anno != null && anno.getAnnotation().type() == ParamType.Body) {
+                    noBodyParam = false;
+                    break;
+                }
+            }
+            paramCount = methodMeta.getMethodDescriptor().getRpcType() != RpcType.UNARY ? 1 : paramMetas.length;
+        }
+        return new FallbackNamedValueMeta(param.isAnnotated(Annotations.Nonnull), noBodyParam, paramCount);
     }
 
     @Override
@@ -54,17 +75,24 @@ public class FallbackArgumentResolver extends AbstractArgumentResolver {
     }
 
     protected Object doResolveValue(NamedValueMeta meta, boolean single, HttpRequest request, HttpResponse response) {
-        ParameterMeta parameter = meta.parameterMeta();
+        FallbackNamedValueMeta fm = (FallbackNamedValueMeta) meta;
+
         if (HttpMethods.supportBody(request.method())) {
-            Object body = RequestUtils.decodeBody(request, parameter.getType(), parameter.isSingle());
-            if (body != null) {
-                if (parameter.getType().isInstance(body)) {
-                    return body;
-                } else if (body instanceof List) {
+            if (fm.paramCount == 1) {
+                try {
+                    Object body = RequestUtils.decodeBody(request, meta.genericType());
+                    if (body != null) {
+                        return body;
+                    }
+                } catch (DecodeException ignored) {
+                }
+            }
+            if (fm.noBodyParam) {
+                Object body = RequestUtils.decodeBodyAsObject(request);
+                if (body instanceof List) {
                     List<?> list = (List<?>) body;
-                    int index = parameter.getIndex();
-                    if (index >= 0 && list.size() > index) {
-                        return list.get(index);
+                    if (list.size() == fm.paramCount) {
+                        return list.get(meta.parameterMeta().getIndex());
                     }
                 } else if (body instanceof Map) {
                     Object value = ((Map<?, ?>) body).get(meta.name());
@@ -74,13 +102,15 @@ public class FallbackArgumentResolver extends AbstractArgumentResolver {
                 }
             }
         }
+
         if (single) {
             String value = request.parameter(meta.name());
-            if (parameter.isSimple() || RestUtils.isMaybeJSONObject(value)) {
+            if (meta.parameterMeta().isSimple() || RestUtils.isMaybeJSONObject(value)) {
                 return value;
             }
-            return parameter.bind(request, response);
+            return meta.parameterMeta().bind(request, response);
         }
+
         return request.parameterValues(meta.name());
     }
 
@@ -88,4 +118,16 @@ public class FallbackArgumentResolver extends AbstractArgumentResolver {
     protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) {
         return resolveValue(meta, request, response);
     }
+
+    private static class FallbackNamedValueMeta extends NamedValueMeta {
+
+        private final boolean noBodyParam;
+        private final int paramCount;
+
+        FallbackNamedValueMeta(boolean required, boolean noBodyParam, int paramCount) {
+            super(required, null);
+            this.noBodyParam = noBodyParam;
+            this.paramCount = paramCount;
+        }
+    }
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/ParamArgumentResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/ParamArgumentResolver.java
index 52456c898c..264df7d334 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/ParamArgumentResolver.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/ParamArgumentResolver.java
@@ -84,7 +84,7 @@ public class ParamArgumentResolver extends AbstractAnnotationBaseArgumentResolve
                 if (RequestUtils.isFormOrMultiPart(request)) {
                     return request.formParameter(meta.name());
                 }
-                return RequestUtils.decodeBody(request, meta.type());
+                return RequestUtils.decodeBody(request, meta.genericType());
         }
         return null;
     }
@@ -135,7 +135,7 @@ public class ParamArgumentResolver extends AbstractAnnotationBaseArgumentResolve
                 if (RequestUtils.isFormOrMultiPart(request)) {
                     return request.formParameterValues(meta.name());
                 }
-                return RequestUtils.decodeBody(request, meta.type());
+                return RequestUtils.decodeBody(request, meta.genericType());
         }
         return null;
     }
@@ -179,7 +179,7 @@ public class ParamArgumentResolver extends AbstractAnnotationBaseArgumentResolve
                 if (RequestUtils.isFormOrMultiPart(request)) {
                     return RequestUtils.getFormParametersMap(request);
                 }
-                return RequestUtils.decodeBody(request, meta.type());
+                return RequestUtils.decodeBody(request, meta.genericType());
         }
         return null;
     }
@@ -209,7 +209,7 @@ public class ParamArgumentResolver extends AbstractAnnotationBaseArgumentResolve
 
         private final ParamType paramType;
 
-        public ParamNamedValueMeta(String name, boolean required, String defaultValue, ParamType paramType) {
+        ParamNamedValueMeta(String name, boolean required, String defaultValue, ParamType paramType) {
             super(name, required, defaultValue);
             this.paramType = paramType;
         }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/AbstractRestToolKit.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/AbstractRestToolKit.java
index c35cd414d5..7ddb719c58 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/AbstractRestToolKit.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/AbstractRestToolKit.java
@@ -19,6 +19,8 @@ package org.apache.dubbo.rpc.protocol.tri.rest.util;
 import org.apache.dubbo.common.config.Environment;
 import org.apache.dubbo.common.utils.AnnotationUtils;
 import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.apache.dubbo.rpc.protocol.tri.rest.Messages;
+import org.apache.dubbo.rpc.protocol.tri.rest.RestException;
 import org.apache.dubbo.rpc.protocol.tri.rest.argument.GeneralTypeConverter;
 import org.apache.dubbo.rpc.protocol.tri.rest.argument.TypeConverter;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta;
@@ -50,7 +52,16 @@ public abstract class AbstractRestToolKit implements RestToolKit {
 
     @Override
     public Object convert(Object value, ParameterMeta parameter) {
-        return typeConverter.convert(value, parameter.getGenericType());
+        Object target = typeConverter.convert(value, parameter.getGenericType());
+        if (target == null && value != null) {
+            throw new RestException(
+                    Messages.ARGUMENT_CONVERT_ERROR,
+                    parameter.getName(),
+                    value,
+                    value.getClass(),
+                    parameter.getGenericType());
+        }
+        return target;
     }
 
     @Override
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/MethodWalker.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/MethodWalker.java
index 61413c0633..ccb7e95fdc 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/MethodWalker.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/MethodWalker.java
@@ -54,9 +54,6 @@ public final class MethodWalker {
         for (Method method : clazz.getDeclaredMethods()) {
             int modifiers = method.getModifiers();
             if ((modifiers & (Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC) {
-                if (Modifier.isAbstract(modifiers) && method.getDeclaredAnnotations().length == 0) {
-                    continue;
-                }
                 methodsMap
                         .computeIfAbsent(Key.of(method), k -> new ArrayList<>())
                         .add(method);
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java
index b75561a0ee..45d4cde2f0 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RequestUtils.java
@@ -25,10 +25,10 @@ import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder;
 import org.apache.dubbo.remoting.http12.message.MediaType;
 import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Type;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
@@ -169,92 +169,60 @@ public final class RequestUtils {
         return result == null ? Collections.emptyList() : result;
     }
 
-    public static Object decodeBody(HttpRequest request, Class<?> type) {
+    public static Object decodeBody(HttpRequest request, Type type) {
         HttpMessageDecoder decoder = request.attribute(RestConstants.BODY_DECODER_ATTRIBUTE);
         if (decoder == null) {
             return null;
         }
-        Object value = request.attribute(RestConstants.BODY_ATTRIBUTE);
-        if (value == null) {
-            if (decoder.mediaType().isPureText()) {
-                type = String.class;
-            }
-            InputStream input = request.inputStream();
-            try {
-                int available = input.available();
-                if (available == 0) {
-                    return emptyDefault(type);
-                }
-            } catch (IOException e) {
-                throw new DecodeException("Error reading input", e);
-            }
-
-            value = decoder.decode(input, type, request.charsetOrDefault());
-            request.setAttribute(RestConstants.BODY_ATTRIBUTE, value);
-        }
-        return value;
-    }
-
-    public static Object decodeBody(HttpRequest request, Class<?> type, boolean single) {
-        HttpMessageDecoder decoder = request.attribute(RestConstants.BODY_DECODER_ATTRIBUTE);
-        if (decoder == null) {
-            return null;
-        }
-        MediaType mediaType = decoder.mediaType();
-        if (mediaType == MediaType.APPLICATION_FROM_URLENCODED || mediaType == MediaType.MULTIPART_FORM_DATA) {
-            return null;
+        if (decoder.mediaType().isPureText()) {
+            type = String.class;
         }
 
+        InputStream is = request.inputStream();
         try {
-            InputStream input = request.inputStream();
-            if (single) {
-                if (mediaType.isPureText()) {
-                    type = String.class;
-                }
-                int available = input.available();
-                if (available == 0) {
-                    return emptyDefault(type);
-                }
-                if (!input.markSupported()) {
-                    byte[] bytes = new byte[available];
-                    input.read(bytes);
-                    input = new ByteArrayInputStream(bytes);
-                }
-                try {
-                    return decoder.decode(input, type, request.charsetOrDefault());
-                } catch (Throwable t) {
-                    input.reset();
+            int available = is.available();
+            if (available == 0) {
+                if (type instanceof Class) {
+                    Class<?> clazz = (Class<?>) type;
+                    if (clazz == String.class) {
+                        return StringUtils.EMPTY_STRING;
+                    }
+                    if (clazz == byte[].class) {
+                        return new byte[0];
+                    }
                 }
+                return null;
             }
+        } catch (IOException e) {
+            throw new DecodeException("Error reading is", e);
+        }
 
-            Object value = request.attribute(RestConstants.BODY_ATTRIBUTE);
-            if (value == null) {
-                if (mediaType == MediaType.APPLICATION_JSON || mediaType == MediaType.APPLICATION_YAML) {
-                    type = Object.class;
-                } else if (mediaType.isPureText()) {
-                    type = String.class;
+        boolean canMark = is.markSupported();
+        try {
+            if (canMark) {
+                is.mark(Integer.MAX_VALUE);
+            }
+            return decoder.decode(is, type, request.charsetOrDefault());
+        } finally {
+            try {
+                if (canMark) {
+                    is.reset();
                 } else {
-                    return null;
-                }
-                int available = input.available();
-                if (available != 0) {
-                    value = decoder.decode(input, type, request.charsetOrDefault());
+                    is.close();
                 }
-                request.setAttribute(RestConstants.BODY_ATTRIBUTE, value);
+            } catch (IOException ignored) {
             }
-            return value;
-        } catch (IOException e) {
-            throw new DecodeException("Error reading input", e);
         }
     }
 
-    private static Object emptyDefault(Class<?> type) {
-        if (type == String.class) {
-            return StringUtils.EMPTY_STRING;
-        }
-        if (type == byte[].class) {
-            return new byte[0];
+    public static Object decodeBodyAsObject(HttpRequest request) {
+        Object value = request.attribute(RestConstants.BODY_ATTRIBUTE);
+        if (value == null) {
+            value = decodeBody(request, Object.class);
+            if (value != null) {
+                request.setAttribute(RestConstants.BODY_ATTRIBUTE, value);
+            }
         }
-        return null;
+        return value;
     }
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/RestProtocolTest.groovy b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/RestProtocolTest.groovy
index 23692bc020..572adc6f09 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/RestProtocolTest.groovy
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/RestProtocolTest.groovy
@@ -51,6 +51,24 @@ class RestProtocolTest extends BaseServiceTest {
             '/argTest?name=Sam' | '{"age": 8}'                | 'Sam is 8 years old'
     }
 
+    def "list argument body test"() {
+        expect:
+            runner.post(path, body) contains output
+        where:
+            path                      | body        | output
+            '/listArgBodyTest?age=2'  | '[1,2]'     | '[1,2]'
+            '/listArgBodyTest2?age=1' | '[[1,2],2]' | '[1,2]'
+    }
+
+    def "map argument body test"() {
+        expect:
+            runner.post(path, body) contains output
+        where:
+            path                     | body                        | output
+            '/mapArgBodyTest?age=2'  | '{"1":["2",3],"4":[5,"6"]}' | '{4:[5,6],1:[2,3]}'
+            '/mapArgBodyTest2?age=1' | '[{"1":[2,3],"4":[5,6]},2]' | '{4:[5,6],1:[2,3]}'
+    }
+
     def "bean argument test"() {
         expect:
             runner.post(path, body, Book.class).name == output
@@ -77,12 +95,14 @@ class RestProtocolTest extends BaseServiceTest {
         expect:
             runner.post(path, body, Book.class).price == output
         where:
-            path           | body                                 | output
-            '/beanArgTest' | []                                   | 0
-            '/beanArgTest' | [quote: 5]                           | 5
-            '/beanArgTest' | [book: new Book(price: 6)]           | 6
-            '/beanArgTest' | [book: new Book(price: 6), quote: 5] | 5
-            '/beanArgTest' | [price: 6, quote: 5]                 | 5
+            path            | body                                 | output
+            '/beanArgTest'  | []                                   | 0
+            '/beanArgTest'  | [quote: 5]                           | 5
+            '/beanArgTest'  | [book: new Book(price: 6)]           | 6
+            '/beanArgTest'  | [book: new Book(price: 6), quote: 5] | 5
+            '/beanArgTest'  | [price: 6, quote: 5]                 | 5
+            '/beanArgTest2' | '{"price": 5}'                       | 5
+            '/beanArgTest2' | '[{"price": 5}]'                     | 5
     }
 
     def "advance bean argument get test"() {
@@ -116,6 +136,15 @@ class RestProtocolTest extends BaseServiceTest {
             '/bean?groupsMap.one[0].name=a&groupsMap.one[1].name=b&groupsMap.two[1].name=c' | '"groupsMap":{"one":[{"id":0,"name":"a"},{"id":0,"name":"b"}],"two":[null,{"id":0,"name":"c"}]}'
     }
 
+    def "bean body test"() {
+        expect:
+            runner.post(path, body) contains output
+        where:
+            path                   | body                                              | output
+            '/beanBodyTest?age=2'  | '[{"id":1,"name":"g1"},{"id":2,"name":"g2"}]'     | '[{"id":1,"name":"g1"},{"id":2,"name":"g2"}]'
+            '/beanBodyTest2?age=1' | '[[{"id":1,"name":"g1"},{"id":2,"name":"g2"}],2]' | '[{"id":1,"name":"g1"},{"id":2,"name":"g2"}]'
+    }
+
     def "urlEncodeForm test"() {
         given:
             def request = new TestRequest(
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoService.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoService.java
index cb3cc8f1da..732fcde91e 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoService.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoService.java
@@ -18,11 +18,18 @@ package org.apache.dubbo.rpc.protocol.tri.rest.service;
 
 import org.apache.dubbo.common.stream.StreamObserver;
 import org.apache.dubbo.remoting.http12.rest.Mapping;
+import org.apache.dubbo.remoting.http12.rest.Param;
+import org.apache.dubbo.rpc.protocol.tri.rest.service.User.Group;
 import org.apache.dubbo.rpc.protocol.tri.rest.service.User.UserEx;
 
+import java.util.List;
+import java.util.Map;
+
 import io.grpc.health.v1.HealthCheckRequest;
 import io.grpc.health.v1.HealthCheckResponse;
 
+import static org.apache.dubbo.remoting.http12.rest.ParamType.Body;
+
 @Mapping("/")
 public interface DemoService {
 
@@ -32,9 +39,23 @@ public interface DemoService {
 
     Book beanArgTest(Book book, Integer quote);
 
+    Book beanArgTest2(Book book);
+
     @Mapping("/bean")
     UserEx advanceBeanArgTest(UserEx user);
 
+    List<Integer> listArgBodyTest(@Param(type = Body) List<Integer> list, int age);
+
+    List<Integer> listArgBodyTest2(List<Integer> list, int age);
+
+    Map<Integer, List<Long>> mapArgBodyTest(@Param(type = Body) Map<Integer, List<Long>> map, int age);
+
+    Map<Integer, List<Long>> mapArgBodyTest2(Map<Integer, List<Long>> map, int age);
+
+    List<Group> beanBodyTest(@Param(type = Body) List<Group> groups, int age);
+
+    List<Group> beanBodyTest2(List<Group> groups, int age);
+
     Book buy(Book book);
 
     @Mapping("/buy2")
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoServiceImpl.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoServiceImpl.java
index d17e71a00d..39c2f9795a 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoServiceImpl.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoServiceImpl.java
@@ -17,10 +17,15 @@
 package org.apache.dubbo.rpc.protocol.tri.rest.service;
 
 import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.common.utils.Assert;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.remoting.http12.rest.Mapping;
+import org.apache.dubbo.rpc.protocol.tri.rest.service.User.Group;
 import org.apache.dubbo.rpc.protocol.tri.rest.service.User.UserEx;
 
+import java.util.List;
+import java.util.Map;
+
 import io.grpc.health.v1.HealthCheckRequest;
 import io.grpc.health.v1.HealthCheckResponse;
 import io.grpc.health.v1.HealthCheckResponse.ServingStatus;
@@ -48,11 +53,49 @@ public class DemoServiceImpl implements DemoService {
         return book;
     }
 
+    @Override
+    public Book beanArgTest2(Book book) {
+        return beanArgTest(book, null);
+    }
+
     @Override
     public UserEx advanceBeanArgTest(UserEx user) {
         return user;
     }
 
+    @Override
+    public List<Integer> listArgBodyTest(List<Integer> list, int age) {
+        Assert.assertTrue(age == 2, "age must be 2");
+        return list;
+    }
+
+    @Override
+    public List<Integer> listArgBodyTest2(List<Integer> list, int age) {
+        return listArgBodyTest(list, age);
+    }
+
+    @Override
+    public Map<Integer, List<Long>> mapArgBodyTest(Map<Integer, List<Long>> map, int age) {
+        Assert.assertTrue(age == 2, "age must be 2");
+        return map;
+    }
+
+    @Override
+    public Map<Integer, List<Long>> mapArgBodyTest2(Map<Integer, List<Long>> map, int age) {
+        return mapArgBodyTest(map, age);
+    }
+
+    @Override
+    public List<Group> beanBodyTest(List<Group> groups, int age) {
+        Assert.assertTrue(age == 2, "age must be 2");
+        return groups;
+    }
+
+    @Override
+    public List<Group> beanBodyTest2(List<Group> groups, int age) {
+        return beanBodyTest(groups, age);
+    }
+
     @Override
     public Book buy(Book book) {
         return book;