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/23 07:47:27 UTC

(dubbo) branch 3.3 updated: Rest Refine (#14334)

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 c5e2f93117 Rest Refine (#14334)
c5e2f93117 is described below

commit c5e2f93117d322ae66b2bbbca967fad0b64c1806
Author: Sean Yang <ox...@gmail.com>
AuthorDate: Tue Jul 23 15:47:21 2024 +0800

    Rest Refine (#14334)
---
 .artifacts                                         |   1 -
 .editorconfig                                      |  22 +-
 .github/workflows/build-and-test-pr.yml            |   2 +-
 .../common/utils/SystemPropertyConfigUtils.java    |   5 +-
 .../dubbo/config/nested/AggregationConfig.java     |   2 +
 .../apache/dubbo/config/nested/BaggageConfig.java  |   2 +
 .../dubbo/config/{ => nested}/CorsConfig.java      |   5 +-
 .../apache/dubbo/config/nested/ExporterConfig.java |   2 +
 .../dubbo/config/nested/HistogramConfig.java       |   2 +
 .../apache/dubbo/config/nested/Http3Config.java    | 250 +++++++++++++
 .../dubbo/config/nested/PrometheusConfig.java      |   2 +
 .../dubbo/config/nested/PropagationConfig.java     |   2 +
 .../dubbo/config/{ => nested}/RestConfig.java      |  74 ++--
 .../apache/dubbo/config/nested/SamplingConfig.java |   2 +
 .../apache/dubbo/config/nested/ServletConfig.java  |  81 +++++
 .../apache/dubbo/config/nested/TripleConfig.java   | 274 ++-------------
 .../rpc/model/ReflectionServiceDescriptor.java     |   3 +-
 .../src/main/resources/application.yml             |   6 +-
 dubbo-dependencies-bom/pom.xml                     |  36 +-
 .../dubbo-dependencies-zookeeper-curator5/pom.xml  |   4 +-
 dubbo-distribution/dubbo-all-shaded/pom.xml        |   8 -
 dubbo-distribution/dubbo-all/pom.xml               |   8 -
 dubbo-distribution/dubbo-bom/pom.xml               |   5 -
 .../filter/AbstractObservationFilterTest.java      |  84 -----
 .../filter/ObservationReceiverFilterTest.java      | 128 -------
 .../filter/ObservationSenderFilterTest.java        |  76 ----
 dubbo-plugin/dubbo-rest-jaxrs/pom.xml              |  47 +++
 .../tri/rest/support/jaxrs/BeanArgumentBinder.java | 276 +++------------
 ...stAdaptee.java => JaxrsHttpRequestAdapter.java} |   4 +-
 ...eAdaptee.java => JaxrsHttpResponseAdapter.java} |   4 +-
 .../tri/rest/support/jaxrs/JaxrsRestToolKit.java   |  31 +-
 .../rest/support/jaxrs/ParamConverterFactory.java  |  59 ++--
 .../jaxrs/filter/ContainerRequestContextImpl.java  |   6 +-
 .../tri/rest/support/jaxrs/RestProtocolTest.groovy |  78 +++++
 .../jaxrs/compatible/JaxrsRestProtocolTest.java    |  22 --
 .../JaxrsDemoService.java}                         |  25 +-
 .../JaxrsDemoServiceImpl.java}                     |  24 +-
 .../ParamConverterProviderImpl.java                |  20 +-
 .../User.java}                                     |  57 ++-
 .../tri/rest/support/jaxrs/service/UserForm.java   |  46 ++-
 .../javax.ws.rs.ext.ParamConverterProvider         |   2 +-
 .../src/test/resources/log4j2-test.xml             |   1 +
 dubbo-plugin/dubbo-rest-servlet/pom.xml            |  46 ---
 ...moting.http12.message.HttpMessageAdapterFactory |   1 -
 ...rpc.protocol.tri.rest.argument.ArgumentResolver |   1 -
 ...c.protocol.tri.rest.filter.RestExtensionAdapter |   1 -
 .../src/test/resources/log4j2-test.xml             |  30 --
 dubbo-plugin/dubbo-rest-spring/pom.xml             |  49 ++-
 .../spring/AbstractSpringArgumentResolver.java     |  10 +-
 .../tri/rest/support/spring/Annotations.java       |   2 +
 .../rest/support/spring/BeanArgumentBinder.java    | 103 +-----
 ...esolver.java => BindParamArgumentResolver.java} |  29 +-
 .../spring/SpringMvcRequestMappingResolver.java    |  37 +-
 .../tri/rest/support/spring/SpringRestToolKit.java |   7 +
 ...rpc.protocol.tri.rest.argument.ArgumentResolver |   1 +
 .../rest/support/spring/RestProtocolTest.groovy    |  72 ++++
 .../support/spring/service/SpringDemoService.java} |  15 +-
 .../spring/service/SpringDemoServiceImpl.java}     |   9 +-
 .../src/test/resources/log4j2-test.xml             |   1 +
 dubbo-plugin/dubbo-triple-servlet/pom.xml          |  29 +-
 .../rest/support/servlet/DummyFilterConfig.java    |   0
 .../rest/support/servlet/DummyServletContext.java  |  40 ++-
 .../tri/rest/support/servlet/FilterAdapter.java    |   0
 .../protocol/tri/rest/support/servlet/Helper.java  |   0
 .../rest/support/servlet/HttpSessionFactory.java   |   0
 .../support/servlet/ServletArgumentResolver.java   |   0
 .../servlet/ServletHttpMessageAdapterFactory.java  |  10 +-
 .../servlet/ServletHttpRequestAdapter.java}        |  10 +-
 .../servlet/ServletHttpResponseAdapter.java}       |  11 +-
 .../protocol/tri/servlet/HttpMetadataAdapter.java  |   6 +-
 .../rpc/protocol/tri/servlet/TripleFilter.java     |   3 +-
 ...moting.http12.message.HttpMessageAdapterFactory |   2 +
 ...rpc.protocol.tri.rest.argument.ArgumentResolver |   2 +
 ...c.protocol.tri.rest.filter.RestExtensionAdapter |   2 +
 dubbo-plugin/pom.xml                               |   1 -
 .../remoting/http12/CompositeInputStream.java      |  37 +-
 .../apache/dubbo/remoting/http12/HttpRequest.java  |   2 +
 .../http12/message/DefaultHttpRequest.java         |  15 +-
 .../remoting/http12/message/codec/CodecUtils.java  |   8 +
 .../apache/dubbo/remoting/http12/rest/Mapping.java |  41 +--
 .../apache/dubbo/remoting/http12/rest/Param.java   |  39 +--
 .../dubbo/remoting/http12/rest/ParamType.java      |  18 +-
 .../dubbo/remoting/transport/netty4/Helper.java    |  55 ++-
 .../dubbo-remoting-zookeeper-curator5/pom.xml      |   5 -
 .../main/java/org/apache/dubbo/rpc/Constants.java  |   4 +-
 dubbo-rpc/dubbo-rpc-triple/pom.xml                 |  45 +++
 .../dubbo/rpc/protocol/tri/TripleConstant.java     |   1 -
 .../tri/h12/HttpRequestHandlerMapping.java         | 121 -------
 .../protocol/tri/h12/UnaryServerCallListener.java  |  10 +-
 .../h12/grpc/GrpcHttp2ServerTransportListener.java |  15 +
 .../tri/h12/grpc/GrpcRequestHandlerMapping.java    |  57 ++-
 .../DefaultHttp11ServerTransportListener.java      |   2 +-
 .../http2/GenericHttp2ServerTransportListener.java |  21 +-
 .../dubbo/rpc/protocol/tri/rest/Messages.java      |   2 +-
 .../dubbo/rpc/protocol/tri/rest/RestConstants.java |   6 +-
 .../protocol/tri/rest/RestHttpMessageCodec.java    |   4 +
 .../rest/argument/CompositeArgumentResolver.java   |   4 +
 .../tri/rest/argument/GeneralTypeConverter.java    |  28 +-
 .../NamedValueArgumentResolverSupport.java         |  27 +-
 .../protocol/tri/rest/cors/CorsHeaderFilter.java   |  20 +-
 .../tri/rest/mapping/ContentNegotiator.java        |   4 +
 .../mapping/DefaultRequestMappingRegistry.java     | 227 +++++++++---
 .../rpc/protocol/tri/rest/mapping/RadixTree.java   | 101 ++----
 .../protocol/tri/rest/mapping/RequestMapping.java  |  95 +++--
 .../tri/rest/mapping/RequestMappingResolver.java   |   4 +
 .../tri/rest/mapping/condition/PathExpression.java |  22 +-
 .../tri/rest/mapping/condition/PathParser.java     |  47 +--
 .../tri/rest/mapping/condition/PathSegment.java    |  17 +-
 .../tri/rest/mapping/meta/AnnotationSupport.java   |  20 +-
 .../protocol/tri/rest/mapping/meta/BeanMeta.java   | 387 +++++++++++++++++++++
 .../protocol/tri/rest/mapping/meta/CorsMeta.java   |  24 +-
 .../protocol/tri/rest/mapping/meta/MethodMeta.java |  31 +-
 .../tri/rest/mapping/meta/MethodParameterMeta.java |  10 +
 .../tri/rest/mapping/meta/ParameterMeta.java       |  24 +-
 .../tri/rest/mapping/meta/ServiceMeta.java         |  14 +-
 .../tri/rest/support/basic}/Annotations.java       |  26 +-
 .../support/basic/BasicRequestMappingResolver.java | 121 +++++++
 .../tri/rest/support/basic/BasicRestToolKit.java   |  28 +-
 .../tri/rest/support/basic/BeanArgumentBinder.java | 375 ++++++++++++++++++++
 .../support/basic/FallbackArgumentResolver.java    |  91 +++++
 .../rest/support/basic/ParamArgumentResolver.java  | 217 ++++++++++++
 ...ltRestToolKit.java => AbstractRestToolKit.java} |  16 +-
 .../rpc/protocol/tri/rest/util/KeyString.java      | 118 +++++++
 .../rpc/protocol/tri/rest/util/PathUtils.java      |  27 +-
 .../rpc/protocol/tri/rest/util/RequestUtils.java   |  85 ++++-
 .../rpc/protocol/tri/rest/util/RestUtils.java      |  31 ++
 .../rpc/protocol/tri/rest/util/TypeUtils.java      | 170 ++++++++-
 ...rpc.protocol.tri.rest.argument.ArgumentResolver |   2 +
 ...rotocol.tri.rest.mapping.RequestMappingResolver |   1 +
 ...bo.rpc.protocol.tri.route.RequestHandlerMapping |   3 +-
 .../protocol/tri/rest/mapping/RadixTreeTest.groovy |  47 +++
 .../mapping/condition/PathExpressionTest.groovy    | 261 ++++++++++++++
 .../tri/rest/support/basic/RestProtocolTest.groovy | 191 ++++++++++
 .../protocol/tri/rest/test/BaseServiceTest.groovy  |  39 ++-
 .../protocol/tri/rest/util/PathUtilsTest.groovy    | 147 ++++++++
 .../protocol/tri/rest/mapping/RadixTreeTest.java   |  56 ---
 .../tri/rest/mapping/condition/PathParserTest.java |  49 ---
 .../dubbo/rpc/protocol/tri/rest/service/Book.java  |  65 ++++
 .../rpc/protocol/tri/rest/service/DemoService.java |  50 +++
 .../protocol/tri/rest/service/DemoServiceImpl.java | 103 ++++++
 .../dubbo/rpc/protocol/tri/rest/service/User.java  | 283 +++++++++++++++
 .../rpc/protocol/tri/test/MockH2StreamChannel.java |  78 +++++
 .../protocol/tri/test/MockHttp2OutputMessage.java  |  37 +-
 .../dubbo/rpc/protocol/tri/test/TestProtocol.java  |  65 ++++
 .../dubbo/rpc/protocol/tri/test/TestRequest.java   | 209 +++++++++++
 .../dubbo/rpc/protocol/tri/test/TestResponse.java  | 119 +++++++
 .../dubbo/rpc/protocol/tri/test/TestRunner.java    |  76 ++++
 .../rpc/protocol/tri/test/TestRunnerBuilder.java   |  78 +++++
 .../rpc/protocol/tri/test/TestRunnerImpl.java      | 341 ++++++++++++++++++
 .../TestServerTransportListener.java}              |  32 +-
 .../dubbo/internal/org.apache.dubbo.rpc.Protocol   |   1 +
 ...ubbo.rpc.protocol.tri.rest.filter.RestExtension |   0
 ...c.protocol.tri.rest.filter.RestExtensionAdapter |   0
 .../src/test/resources/log4j2-test.xml             |   1 +
 .../DubboTriple3AutoConfiguration.java             |   8 +-
 .../DubboTripleAutoConfiguration.java              |  12 +-
 .../DubboConfigurationProperties.java              |  50 ---
 .../dubbo-spring-boot-starter/pom.xml              |   1 -
 dubbo-test/dubbo-dependencies-all/pom.xml          |   5 -
 pom.xml                                            |   8 +-
 160 files changed, 5704 insertions(+), 1951 deletions(-)

diff --git a/.artifacts b/.artifacts
index 41ce8e207a..2c21cd0bca 100644
--- a/.artifacts
+++ b/.artifacts
@@ -115,6 +115,5 @@ dubbo-tracing
 dubbo-xds
 dubbo-plugin-loom
 dubbo-rest-jaxrs
-dubbo-rest-servlet
 dubbo-rest-spring
 dubbo-triple-servlet
diff --git a/.editorconfig b/.editorconfig
index 5de62e0893..5afaa5666b 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -42,26 +42,40 @@ ij_java_keep_simple_lambdas_in_one_line = true
 ij_java_keep_simple_methods_in_one_line = true
 ij_java_keep_blank_lines_in_code = 1
 ij_java_keep_blank_lines_in_declarations = 1
+ij_java_blank_lines_after_class_header = 1
 ij_java_class_count_to_use_import_on_demand = 999
 ij_java_names_count_to_use_import_on_demand = 999
 ij_java_imports_layout = org.apache.dubbo.**, |, javax.**, |, java.**, |, *, |, $*
 ij_java_insert_inner_class_imports = true
 ij_java_space_before_array_initializer_left_brace = true
 ij_java_method_parameters_new_line_after_left_paren = true
-ij_java_wrap_comments = true
-ij_java_wrap_long_lines = true
+ij_java_wrap_comments = false
+ij_java_wrap_long_lines = false
 ij_java_enum_constants_wrap = split_into_lines
-ij_java_method_call_chain_wrap = split_into_lines
+ij_java_method_call_chain_wrap = on_every_item
 ij_java_method_parameters_wrap = on_every_item
-ij_java_extends_list_wrap = on_every_item
+ij_java_extends_list_wrap = normal
 ij_java_extends_keyword_wrap = normal
 ij_java_binary_operation_wrap = normal
 ij_java_binary_operation_sign_on_next_line = true
 
+[*.groovy]
+max_line_length = 180
+ij_groovy_label_indent_size = 4
+ij_groovy_keep_blank_lines_in_code = 1
+ij_groovy_keep_blank_lines_in_declarations = 1
+ij_groovy_blank_lines_after_class_header = 1
+ij_groovy_class_count_to_use_import_on_demand = 999
+ij_groovy_names_count_to_use_import_on_demand = 999
+ij_groovy_imports_layout = org.apache.dubbo.**, |, javax.**, |, java.**, |, *, |, $*
+ij_groovy_space_after_type_cast = false
+
 [*.json]
 tab_width = 2
+indent_size = 2
 
 [*.{yml,yaml}]
+tab_width = 2
 indent_size = 2
 
 [*.xml]
diff --git a/.github/workflows/build-and-test-pr.yml b/.github/workflows/build-and-test-pr.yml
index f179ec5495..7a40dbca15 100644
--- a/.github/workflows/build-and-test-pr.yml
+++ b/.github/workflows/build-and-test-pr.yml
@@ -326,7 +326,7 @@ jobs:
           cd test && bash ./build-test-image.sh
       - name: "Run tests"
         run: cd test && bash ./run-tests.sh
-      - name: "merge jacoco resule"
+      - name: "merge jacoco result"
         run: |
           cd test/dubbo-test-jacoco-merger && mvn clean compile exec:java -Dexec.mainClass="org.apache.dubbo.test.JacocoMerge" -Dexec.args="${{github.workspace}}"
       - name: "Upload jacoco"
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/SystemPropertyConfigUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/SystemPropertyConfigUtils.java
index 857807e79e..87f0ce3a14 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/SystemPropertyConfigUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/SystemPropertyConfigUtils.java
@@ -37,7 +37,10 @@ public class SystemPropertyConfigUtils {
             for (Field field : fields) {
                 try {
                     assert systemProperties != null;
-                    systemProperties.add((String) field.get(null));
+                    Object value = field.get(null);
+                    if (value instanceof String) {
+                        systemProperties.add((String) value);
+                    }
                 } catch (IllegalAccessException e) {
                     throw new IllegalStateException(
                             String.format("%s does not have field of %s", clazz.getName(), field.getName()));
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/AggregationConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/AggregationConfig.java
index 553d834354..bcbdf052a8 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/AggregationConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/AggregationConfig.java
@@ -23,6 +23,8 @@ import java.io.Serializable;
  */
 public class AggregationConfig implements Serializable {
 
+    private static final long serialVersionUID = 4878693820314125085L;
+
     /**
      * Enable aggregation or not.
      */
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/BaggageConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/BaggageConfig.java
index 45bea909de..6ff8e9df9f 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/BaggageConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/BaggageConfig.java
@@ -27,6 +27,8 @@ import java.util.List;
  */
 public class BaggageConfig implements Serializable {
 
+    private static final long serialVersionUID = -4750259290735346439L;
+
     /**
      * Whether baggage is enabled or not.
      */
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/CorsConfig.java
similarity index 97%
rename from dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java
rename to dubbo-common/src/main/java/org/apache/dubbo/config/nested/CorsConfig.java
index 657f20d039..1b604927d6 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/CorsConfig.java
@@ -14,12 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.config;
+package org.apache.dubbo.config.nested;
 
 import java.io.Serializable;
 
 public class CorsConfig implements Serializable {
-    private static final long serialVersionUID = 1L;
+
+    private static final long serialVersionUID = -7106481576053641726L;
 
     /**
      * A list of origins for which cross-origin requests are allowed. Values may be a specific domain, e.g.
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/ExporterConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/ExporterConfig.java
index 6edfc53204..fd17a99c53 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/ExporterConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/ExporterConfig.java
@@ -28,6 +28,8 @@ import java.util.Map;
  */
 public class ExporterConfig implements Serializable {
 
+    private static final long serialVersionUID = -559392305178067845L;
+
     /**
      * Configuration for the Zipkin.
      */
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/HistogramConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/HistogramConfig.java
index 504faf03c8..66dbf466e0 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/HistogramConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/HistogramConfig.java
@@ -23,6 +23,8 @@ import java.io.Serializable;
  */
 public class HistogramConfig implements Serializable {
 
+    private static final long serialVersionUID = 8152538916051803031L;
+
     /**
      * Whether histograms are enabled or not. Default is not enabled (false).
      */
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/Http3Config.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/Http3Config.java
new file mode 100644
index 0000000000..757516196f
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/Http3Config.java
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.config.nested;
+
+import java.io.Serializable;
+
+public class Http3Config implements Serializable {
+
+    private static final long serialVersionUID = -4443828713331129834L;
+
+    /**
+     * Enable http3 support
+     * <p>The default value is false.
+     */
+    private Boolean enable;
+
+    /**
+     * See <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_data">set_initial_max_data</a>.
+     * <p>The default value is 8MiB.
+     */
+    private Integer initialMaxData;
+
+    /**
+     * If configured this will enable <a href="https://tools.ietf.org/html/draft-ietf-quic-datagram-01">Datagram support.</a>
+     */
+    private Integer recvQueueLen;
+
+    /**
+     * If configured this will enable <a href="https://tools.ietf.org/html/draft-ietf-quic-datagram-01">Datagram support.</a>
+     */
+    private Integer sendQueueLen;
+
+    /**
+     * See
+     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_local">set_initial_max_stream_data_bidi_local</a>.
+     * <p>The default value is 1MiB.
+     */
+    private Integer initialMaxStreamDataBidiLocal;
+
+    /**
+     * See
+     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_remote">set_initial_max_stream_data_bidi_remote</a>.
+     * <p>The default value is 1MiB.
+     */
+    private Integer initialMaxStreamDataBidiRemote;
+
+    /**
+     * See
+     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_stream_data_uni">set_initial_max_stream_data_uni</a>.
+     * <p>The default value is 0.
+     */
+    private Integer initialMaxStreamDataUni;
+
+    /**
+     * See
+     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_streams_bidi">set_initial_max_streams_bidi</a>.
+     * <p>The default value is 1B(2^30).
+     */
+    private Long initialMaxStreamsBidi;
+
+    /**
+     * See
+     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_streams_uni">set_initial_max_streams_uni</a>.
+     * <p>
+     * <p>The default value is 1B(2^30).
+     */
+    private Long initialMaxStreamsUni;
+
+    /**
+     * See
+     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_ack_delay_exponent">set_ack_delay_exponent</a>.
+     * <p>The default value is 3.
+     */
+    private Integer maxAckDelayExponent;
+
+    /**
+     * See
+     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_max_ack_delay">set_max_ack_delay</a>.
+     * <p>The default value is 25 milliseconds.
+     */
+    private Integer maxAckDelay;
+
+    /**
+     * See
+     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_disable_active_migration">set_disable_active_migration</a>.
+     * <p>The default value is {@code false}.
+     */
+    private Boolean disableActiveMigration;
+
+    /**
+     * See
+     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.enable_hystart">enable_hystart</a>.
+     * <p>The default value is {@code true}.
+     */
+    private Boolean enableHystart;
+
+    /**
+     * Sets the congestion control algorithm to use.
+     * <p>Supported algorithms are {@code "RENO"} or {@code "CUBIC"} or {@code "BBR"}.
+     * <p>The default value is {@code "CUBIC"}.
+     */
+    private String ccAlgorithm;
+
+    public Boolean getEnable() {
+        return enable;
+    }
+
+    public void setEnable(Boolean enable) {
+        this.enable = enable;
+    }
+
+    public Integer getInitialMaxData() {
+        return initialMaxData;
+    }
+
+    public void setInitialMaxData(Integer initialMaxData) {
+        this.initialMaxData = initialMaxData;
+    }
+
+    public Integer getRecvQueueLen() {
+        return recvQueueLen;
+    }
+
+    public void setRecvQueueLen(Integer recvQueueLen) {
+        this.recvQueueLen = recvQueueLen;
+    }
+
+    public Integer getSendQueueLen() {
+        return sendQueueLen;
+    }
+
+    public void setSendQueueLen(Integer sendQueueLen) {
+        this.sendQueueLen = sendQueueLen;
+    }
+
+    public Integer getInitialMaxStreamDataBidiLocal() {
+        return initialMaxStreamDataBidiLocal;
+    }
+
+    public void setInitialMaxStreamDataBidiLocal(Integer initialMaxStreamDataBidiLocal) {
+        this.initialMaxStreamDataBidiLocal = initialMaxStreamDataBidiLocal;
+    }
+
+    public Integer getInitialMaxStreamDataBidiRemote() {
+        return initialMaxStreamDataBidiRemote;
+    }
+
+    public void setInitialMaxStreamDataBidiRemote(Integer initialMaxStreamDataBidiRemote) {
+        this.initialMaxStreamDataBidiRemote = initialMaxStreamDataBidiRemote;
+    }
+
+    public Integer getInitialMaxStreamDataUni() {
+        return initialMaxStreamDataUni;
+    }
+
+    public void setInitialMaxStreamDataUni(Integer initialMaxStreamDataUni) {
+        this.initialMaxStreamDataUni = initialMaxStreamDataUni;
+    }
+
+    public Long getInitialMaxStreamsBidi() {
+        return initialMaxStreamsBidi;
+    }
+
+    public void setInitialMaxStreamsBidi(Long initialMaxStreamsBidi) {
+        this.initialMaxStreamsBidi = initialMaxStreamsBidi;
+    }
+
+    public Long getInitialMaxStreamsUni() {
+        return initialMaxStreamsUni;
+    }
+
+    public void setInitialMaxStreamsUni(Long initialMaxStreamsUni) {
+        this.initialMaxStreamsUni = initialMaxStreamsUni;
+    }
+
+    public Integer getMaxAckDelayExponent() {
+        return maxAckDelayExponent;
+    }
+
+    public void setMaxAckDelayExponent(Integer maxAckDelayExponent) {
+        this.maxAckDelayExponent = maxAckDelayExponent;
+    }
+
+    public Integer getMaxAckDelay() {
+        return maxAckDelay;
+    }
+
+    public void setMaxAckDelay(Integer maxAckDelay) {
+        this.maxAckDelay = maxAckDelay;
+    }
+
+    public Boolean getDisableActiveMigration() {
+        return disableActiveMigration;
+    }
+
+    public void setDisableActiveMigration(Boolean disableActiveMigration) {
+        this.disableActiveMigration = disableActiveMigration;
+    }
+
+    public Boolean getEnableHystart() {
+        return enableHystart;
+    }
+
+    public void setEnableHystart(Boolean enableHystart) {
+        this.enableHystart = enableHystart;
+    }
+
+    public String getCcAlgorithm() {
+        return ccAlgorithm;
+    }
+
+    public void setCcAlgorithm(String ccAlgorithm) {
+        this.ccAlgorithm = ccAlgorithm;
+    }
+
+    public void checkDefault() {
+        if (initialMaxData == null) {
+            initialMaxData = 1 << 23;
+        }
+        if (initialMaxStreamDataBidiLocal == null) {
+            initialMaxStreamDataBidiLocal = 1 << 20;
+        }
+        if (initialMaxStreamDataBidiRemote == null) {
+            initialMaxStreamDataBidiRemote = 1 << 20;
+        }
+        if (initialMaxStreamDataUni == null) {
+            initialMaxStreamDataUni = 1 << 20;
+        }
+        if (initialMaxStreamsBidi == null) {
+            initialMaxStreamsBidi = (long) 1 << 30;
+        }
+        if (initialMaxStreamsUni == null) {
+            initialMaxStreamsUni = (long) 1 << 30;
+        }
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PrometheusConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PrometheusConfig.java
index a9b8ded533..cc066678c9 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PrometheusConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PrometheusConfig.java
@@ -25,6 +25,8 @@ import java.io.Serializable;
  */
 public class PrometheusConfig implements Serializable {
 
+    private static final long serialVersionUID = 2238807632335823129L;
+
     /**
      * Prometheus exporter configuration
      */
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java
index c7ee0ba7dd..d2e650d273 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java
@@ -23,6 +23,8 @@ import java.io.Serializable;
  */
 public class PropagationConfig implements Serializable {
 
+    private static final long serialVersionUID = -2570106396211532046L;
+
     public static final String B3 = "B3";
 
     public static final String W3C = "W3C";
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/RestConfig.java
similarity index 63%
rename from dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java
rename to dubbo-common/src/main/java/org/apache/dubbo/config/nested/RestConfig.java
index dc7e988928..518d3d3b6d 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/RestConfig.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.config;
+package org.apache.dubbo.config.nested;
 
 import org.apache.dubbo.config.support.Nested;
 
@@ -25,35 +25,14 @@ import java.io.Serializable;
  */
 public class RestConfig implements Serializable {
 
-    private static final long serialVersionUID = 1L;
-
-    /**
-     * Maximum allowed size for request bodies.
-     * Limits the size of request to prevent excessively large request.
-     * <p>The default value is 8MiB.
-     */
-    private Integer maxBodySize;
-
-    /**
-     * Maximum allowed size for response bodies.
-     * Limits the size of responses to prevent excessively large response.
-     * <p>The default value is 8MiB.
-     */
-    private Integer maxResponseBodySize;
+    private static final long serialVersionUID = -8068568976367034755L;
 
     /**
      * Whether path matching should be match paths with a trailing slash.
      * If enabled, a method mapped to "/users" also matches to "/users/".
      * <p>The default value is {@code true}.
      */
-    private Boolean trailingSlashMatch;
-
-    /**
-     * Whether path matching should be case-sensitive.
-     * If enabled, a method mapped to "/users" won't match to "/Users/".
-     * <p>The default value is {@code false}.
-     */
-    private Boolean caseSensitiveMatch;
+    private boolean trailingSlashMatch;
 
     /**
      * Whether path matching uses suffix pattern matching (".*").
@@ -62,7 +41,14 @@ public class RestConfig implements Serializable {
      * inferred from the URL suffix, e.g., ".json" for "application/json".
      * <p>The default value is {@code true}.
      */
-    private Boolean suffixPatternMatch;
+    private boolean suffixPatternMatch;
+
+    /**
+     * Whether path matching should be case-sensitive.
+     * If enabled, a method mapped to "/users" won't match to "/Users/".
+     * <p>The default value is {@code true}.
+     */
+    private boolean caseSensitiveMatch;
 
     /**
      * The parameter name that can be used to specify the response format.
@@ -71,49 +57,33 @@ public class RestConfig implements Serializable {
     private String formatParameterName;
 
     /**
-     *  The config is used to set the Global CORS configuration properties.
+     *  The cors configuration.
      */
     @Nested
     private CorsConfig cors;
 
-    public Integer getMaxBodySize() {
-        return maxBodySize;
-    }
-
-    public void setMaxBodySize(Integer maxBodySize) {
-        this.maxBodySize = maxBodySize;
-    }
-
-    public Integer getMaxResponseBodySize() {
-        return maxResponseBodySize;
-    }
-
-    public void setMaxResponseBodySize(Integer maxResponseBodySize) {
-        this.maxResponseBodySize = maxResponseBodySize;
-    }
-
-    public Boolean getTrailingSlashMatch() {
+    public boolean isTrailingSlashMatch() {
         return trailingSlashMatch;
     }
 
-    public void setTrailingSlashMatch(Boolean trailingSlashMatch) {
+    public void setTrailingSlashMatch(boolean trailingSlashMatch) {
         this.trailingSlashMatch = trailingSlashMatch;
     }
 
-    public Boolean getCaseSensitiveMatch() {
-        return caseSensitiveMatch;
+    public boolean isSuffixPatternMatch() {
+        return suffixPatternMatch;
     }
 
-    public void setCaseSensitiveMatch(Boolean caseSensitiveMatch) {
-        this.caseSensitiveMatch = caseSensitiveMatch;
+    public void setSuffixPatternMatch(boolean suffixPatternMatch) {
+        this.suffixPatternMatch = suffixPatternMatch;
     }
 
-    public Boolean getSuffixPatternMatch() {
-        return suffixPatternMatch;
+    public boolean isCaseSensitiveMatch() {
+        return caseSensitiveMatch;
     }
 
-    public void setSuffixPatternMatch(Boolean suffixPatternMatch) {
-        this.suffixPatternMatch = suffixPatternMatch;
+    public void setCaseSensitiveMatch(boolean caseSensitiveMatch) {
+        this.caseSensitiveMatch = caseSensitiveMatch;
     }
 
     public String getFormatParameterName() {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/SamplingConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/SamplingConfig.java
index 26dc0c2ea2..aa2ed072ed 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/SamplingConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/SamplingConfig.java
@@ -23,6 +23,8 @@ import java.io.Serializable;
  */
 public class SamplingConfig implements Serializable {
 
+    private static final long serialVersionUID = -7456034528275916549L;
+
     /**
      * Probability in the range from 0.0 to 1.0 that a trace will be sampled.
      */
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/ServletConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/ServletConfig.java
new file mode 100644
index 0000000000..30b67ebf57
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/ServletConfig.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.config.nested;
+
+import java.io.Serializable;
+
+public class ServletConfig implements Serializable {
+
+    private static final long serialVersionUID = 1091478303358670173L;
+
+    /**
+     * Enable servlet support, requests are transport through the servlet container,
+     * which only supports unary calls due to protocol limitations
+     * <p>The default value is false.
+     */
+    private Boolean enable;
+
+    /**
+     * The timeout in milliseconds for the servlet async timeout.
+     * <p>The default value is 180_000.
+     */
+    private Integer timeout;
+
+    /**
+     * The URL patterns that the servlet filter will be registered for.
+     * <p>The default value is '/*'.
+     */
+    private String[] filterUrlPatterns;
+
+    /**
+     * The order of the servlet filter.
+     * <p>The default value is -1000000.
+     */
+    private Integer filterOrder;
+
+    public Boolean getEnable() {
+        return enable;
+    }
+
+    public void setEnable(Boolean enable) {
+        this.enable = enable;
+    }
+
+    public Integer getTimeout() {
+        return timeout;
+    }
+
+    public void setTimeout(Integer timeout) {
+        this.timeout = timeout;
+    }
+
+    public String[] getFilterUrlPatterns() {
+        return filterUrlPatterns;
+    }
+
+    public void setFilterUrlPatterns(String[] filterUrlPatterns) {
+        this.filterUrlPatterns = filterUrlPatterns;
+    }
+
+    public Integer getFilterOrder() {
+        return filterOrder;
+    }
+
+    public void setFilterOrder(Integer filterOrder) {
+        this.filterOrder = filterOrder;
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/TripleConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/TripleConfig.java
index 96b2141b62..e0dca0664a 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/TripleConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/TripleConfig.java
@@ -16,6 +16,8 @@
  */
 package org.apache.dubbo.config.nested;
 
+import org.apache.dubbo.config.support.Nested;
+
 import java.io.Serializable;
 
 /**
@@ -26,14 +28,14 @@ public class TripleConfig implements Serializable {
     private static final long serialVersionUID = -3682252713701362155L;
 
     /**
-     * Maximum allowed size for HTTP1 request bodies.
+     * Maximum allowed size for HTTP request bodies.
      * Limits the size of request to prevent excessively large request.
      * <p>The default value is 8MiB.
      */
     private Integer maxBodySize;
 
     /**
-     * Maximum allowed size for HTTP1 response bodies.
+     * Maximum allowed size for HTTP response bodies.
      * Limits the size of responses to prevent excessively large response.
      * <p>The default value is 8MiB.
      */
@@ -41,10 +43,11 @@ public class TripleConfig implements Serializable {
 
     /**
      * Set the maximum chunk size.
-     * HTTP requests and responses can be quite large, in which case it's better to process the data as a stream of
-     * chunks.
+     * HTTP requests and responses can be quite large,
+     * in which case it's better to process the data as a stream of chunks.
      * This sets the limit, in bytes, at which Netty will send a chunk down the pipeline.
      * <p>The default value is 8MiB.
+     * <p>For HTTP/1
      */
     private Integer maxChunkSize;
 
@@ -53,6 +56,7 @@ public class TripleConfig implements Serializable {
      * This limits how much memory Netty will use when parsing HTTP header key-value pairs.
      * You would typically set this to the same value as {@link #setMaxInitialLineLength(Integer)}.
      * <p>The default value is 8KiB.
+     * <p>For HTTP/1
      */
     private Integer maxHeaderSize;
 
@@ -61,156 +65,62 @@ public class TripleConfig implements Serializable {
      * This limits how much memory Netty will use when parsed the initial HTTP header line.
      * You would typically set this to the same value as {@link #setMaxHeaderSize(Integer)}.
      * <p>The default value is 4096.
+     * <p>For HTTP/1
      */
     private Integer maxInitialLineLength;
 
     /**
      * Set the initial size of the temporary buffer used when parsing the lines of the HTTP headers.
      * <p>The default value is 16384 octets.
+     * <p>For HTTP/1
      */
     private Integer initialBufferSize;
 
     /**
      * The header table size.
+     * <p>For HTTP/1
      */
     private Integer headerTableSize;
 
     /**
-     * Whether to enable push, default is false.
+     * Whether to enable push
+     * <p>The default value is false.
+     * <p>For HTTP/2
      */
     private Boolean enablePush;
 
     /**
      * Maximum concurrent streams.
+     * <p>For HTTP/2
      */
     private Integer maxConcurrentStreams;
 
     /**
      * Initial window size.
+     * <p>For HTTP/2
      */
     private Integer initialWindowSize;
 
     /**
      * Maximum frame size.
+     * <p>For HTTP/2
      */
     private Integer maxFrameSize;
 
     /**
      * Maximum header list size.
+     * <p>For HTTP/2
      */
     private Integer maxHeaderListSize;
 
-    /**
-     * Enable http3 support
-     * <p>The default value is false.
-     */
-    private Boolean enableHttp3;
-
-    /**
-     * See <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_data">set_initial_max_data</a>.
-     * <p>The default value is 8MiB.
-     */
-    private Integer http3InitialMaxData;
-
-    /**
-     * If configured this will enable <a href="https://tools.ietf.org/html/draft-ietf-quic-datagram-01">Datagram support.</a>
-     */
-    private Integer http3RecvQueueLen;
-
-    /**
-     * If configured this will enable <a href="https://tools.ietf.org/html/draft-ietf-quic-datagram-01">Datagram support.</a>
-     */
-    private Integer http3SendQueueLen;
-
-    /**
-     * See
-     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_local">set_initial_max_stream_data_bidi_local</a>.
-     * <p>The default value is 1MiB.
-     */
-    private Integer http3InitialMaxStreamDataBidiLocal;
-
-    /**
-     * See
-     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_remote">set_initial_max_stream_data_bidi_remote</a>.
-     * <p>The default value is 1MiB.
-     */
-    private Integer http3InitialMaxStreamDataBidiRemote;
-
-    /**
-     * See
-     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_stream_data_uni">set_initial_max_stream_data_uni</a>.
-     * <p>The default value is 0.
-     */
-    private Integer http3InitialMaxStreamDataUni;
+    @Nested
+    private RestConfig rest;
 
-    /**
-     * See
-     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_streams_bidi">set_initial_max_streams_bidi</a>.
-     * <p>The default value is 1B(2^30).
-     */
-    private Long http3InitialMaxStreamsBidi;
+    @Nested
+    private Http3Config http3;
 
-    /**
-     * See
-     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_initial_max_streams_uni">set_initial_max_streams_uni</a>.
-     * <p>
-     * <p>The default value is 1B(2^30).
-     */
-    private Long http3InitialMaxStreamsUni;
-
-    /**
-     * See
-     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_ack_delay_exponent">set_ack_delay_exponent</a>.
-     * <p>The default value is 3.
-     */
-    private Integer http3MaxAckDelayExponent;
-
-    /**
-     * See
-     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_max_ack_delay">set_max_ack_delay</a>.
-     * <p>The default value is 25 milliseconds.
-     */
-    private Integer http3MaxAckDelay;
-
-    /**
-     * See
-     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.set_disable_active_migration">set_disable_active_migration</a>.
-     * <p>The default value is {@code false}.
-     */
-    private Boolean http3DisableActiveMigration;
-
-    /**
-     * See
-     * <a href="https://docs.rs/quiche/0.6.0/quiche/struct.Config.html#method.enable_hystart">enable_hystart</a>.
-     * <p>The default value is {@code true}.
-     */
-    private Boolean http3EnableHystart;
-
-    /**
-     * Sets the congestion control algorithm to use.
-     * <p>Supported algorithms are {@code "RENO"} or {@code "CUBIC"} or {@code "BBR"}.
-     * <p>The default value is {@code "CUBIC"}.
-     */
-    private String http3CcAlgorithm;
-
-    /**
-     * Enable servlet support, requests are transport through the servlet container,
-     * which only supports unary calls due to protocol limitations
-     * <p>The default value is false.
-     */
-    private Boolean enableServlet;
-
-    /**
-     * The URL patterns that the servlet filter will be registered for.
-     * <p>The default value is '/*'.
-     */
-    private String[] servletFilterUrlPatterns;
-
-    /**
-     * The order of the servlet filter.
-     * <p>The default value is -1000000.
-     */
-    private Integer servletFilterOrder;
+    @Nested
+    private ServletConfig servlet;
 
     public Integer getMaxBodySize() {
         return maxBodySize;
@@ -308,140 +218,28 @@ public class TripleConfig implements Serializable {
         this.maxHeaderListSize = maxHeaderListSize;
     }
 
-    public Boolean getEnableHttp3() {
-        return enableHttp3;
-    }
-
-    public void setEnableHttp3(Boolean enableHttp3) {
-        this.enableHttp3 = enableHttp3;
-    }
-
-    public Integer getHttp3InitialMaxData() {
-        return http3InitialMaxData;
-    }
-
-    public void setHttp3InitialMaxData(Integer http3InitialMaxData) {
-        this.http3InitialMaxData = http3InitialMaxData;
-    }
-
-    public Integer getHttp3RecvQueueLen() {
-        return http3RecvQueueLen;
-    }
-
-    public void setHttp3RecvQueueLen(Integer http3RecvQueueLen) {
-        this.http3RecvQueueLen = http3RecvQueueLen;
-    }
-
-    public Integer getHttp3SendQueueLen() {
-        return http3SendQueueLen;
-    }
-
-    public void setHttp3SendQueueLen(Integer http3SendQueueLen) {
-        this.http3SendQueueLen = http3SendQueueLen;
-    }
-
-    public Integer getHttp3InitialMaxStreamDataBidiLocal() {
-        return http3InitialMaxStreamDataBidiLocal;
-    }
-
-    public void setHttp3InitialMaxStreamDataBidiLocal(Integer http3InitialMaxStreamDataBidiLocal) {
-        this.http3InitialMaxStreamDataBidiLocal = http3InitialMaxStreamDataBidiLocal;
-    }
-
-    public Integer getHttp3InitialMaxStreamDataBidiRemote() {
-        return http3InitialMaxStreamDataBidiRemote;
-    }
-
-    public void setHttp3InitialMaxStreamDataBidiRemote(Integer http3InitialMaxStreamDataBidiRemote) {
-        this.http3InitialMaxStreamDataBidiRemote = http3InitialMaxStreamDataBidiRemote;
-    }
-
-    public Integer getHttp3InitialMaxStreamDataUni() {
-        return http3InitialMaxStreamDataUni;
-    }
-
-    public void setHttp3InitialMaxStreamDataUni(Integer http3InitialMaxStreamDataUni) {
-        this.http3InitialMaxStreamDataUni = http3InitialMaxStreamDataUni;
-    }
-
-    public Long getHttp3InitialMaxStreamsBidi() {
-        return http3InitialMaxStreamsBidi;
-    }
-
-    public void setHttp3InitialMaxStreamsBidi(Long http3InitialMaxStreamsBidi) {
-        this.http3InitialMaxStreamsBidi = http3InitialMaxStreamsBidi;
-    }
-
-    public Long getHttp3InitialMaxStreamsUni() {
-        return http3InitialMaxStreamsUni;
-    }
-
-    public void setHttp3InitialMaxStreamsUni(Long http3InitialMaxStreamsUni) {
-        this.http3InitialMaxStreamsUni = http3InitialMaxStreamsUni;
-    }
-
-    public Integer getHttp3MaxAckDelayExponent() {
-        return http3MaxAckDelayExponent;
-    }
-
-    public void setHttp3MaxAckDelayExponent(Integer http3MaxAckDelayExponent) {
-        this.http3MaxAckDelayExponent = http3MaxAckDelayExponent;
-    }
-
-    public Integer getHttp3MaxAckDelay() {
-        return http3MaxAckDelay;
-    }
-
-    public void setHttp3MaxAckDelay(Integer http3MaxAckDelay) {
-        this.http3MaxAckDelay = http3MaxAckDelay;
-    }
-
-    public Boolean getHttp3DisableActiveMigration() {
-        return http3DisableActiveMigration;
-    }
-
-    public void setHttp3DisableActiveMigration(Boolean http3DisableActiveMigration) {
-        this.http3DisableActiveMigration = http3DisableActiveMigration;
-    }
-
-    public Boolean getHttp3EnableHystart() {
-        return http3EnableHystart;
-    }
-
-    public void setHttp3EnableHystart(Boolean http3EnableHystart) {
-        this.http3EnableHystart = http3EnableHystart;
-    }
-
-    public String getHttp3CcAlgorithm() {
-        return http3CcAlgorithm;
-    }
-
-    public void setHttp3CcAlgorithm(String http3CcAlgorithm) {
-        this.http3CcAlgorithm = http3CcAlgorithm;
-    }
-
-    public Boolean getEnableServlet() {
-        return enableServlet;
+    public RestConfig getRest() {
+        return rest;
     }
 
-    public void setEnableServlet(Boolean enableServlet) {
-        this.enableServlet = enableServlet;
+    public void setRest(RestConfig rest) {
+        this.rest = rest;
     }
 
-    public String[] getServletFilterUrlPatterns() {
-        return servletFilterUrlPatterns;
+    public Http3Config getHttp3() {
+        return http3;
     }
 
-    public void setServletFilterUrlPatterns(String[] servletFilterUrlPatterns) {
-        this.servletFilterUrlPatterns = servletFilterUrlPatterns;
+    public void setHttp3(Http3Config http3) {
+        this.http3 = http3;
     }
 
-    public Integer getServletFilterOrder() {
-        return servletFilterOrder;
+    public ServletConfig getServlet() {
+        return servlet;
     }
 
-    public void setServletFilterOrder(Integer servletFilterOrder) {
-        this.servletFilterOrder = servletFilterOrder;
+    public void setServlet(ServletConfig servlet) {
+        this.servlet = servlet;
     }
 
     public void checkDefault() {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ReflectionServiceDescriptor.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ReflectionServiceDescriptor.java
index dc04146933..9a3c0c491b 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ReflectionServiceDescriptor.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ReflectionServiceDescriptor.java
@@ -48,7 +48,8 @@ public class ReflectionServiceDescriptor implements ServiceDescriptor {
     }
 
     public void addMethod(MethodDescriptor methodDescriptor) {
-        methods.put(methodDescriptor.getMethodName(), Collections.singletonList(methodDescriptor));
+        methods.computeIfAbsent(methodDescriptor.getMethodName(), k -> new ArrayList<>(1))
+                .add(methodDescriptor);
     }
 
     public ReflectionServiceDescriptor(Class<?> interfaceClass) {
diff --git a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/resources/application.yml b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/resources/application.yml
index b75b8578e9..e2642b6612 100644
--- a/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/resources/application.yml
+++ b/dubbo-demo/dubbo-demo-spring-boot/dubbo-demo-spring-boot-provider/src/main/resources/application.yml
@@ -24,9 +24,11 @@ dubbo:
     qos-enable: false
   protocol:
     name: tri
-    port: ${server.port}
+    port: -1
+    #port: ${server.port}
     triple:
-      enable-servlet: true
+      servlet:
+        enable: true
   registry:
     id: zk-registry
     address: zookeeper://127.0.0.1:2181
diff --git a/dubbo-dependencies-bom/pom.xml b/dubbo-dependencies-bom/pom.xml
index ae59978c1a..a29d7da02c 100644
--- a/dubbo-dependencies-bom/pom.xml
+++ b/dubbo-dependencies-bom/pom.xml
@@ -17,7 +17,6 @@
   -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
-
   <parent>
     <groupId>org.apache</groupId>
     <artifactId>apache</artifactId>
@@ -109,7 +108,7 @@
     <protobuf-java_version>3.25.3</protobuf-java_version>
     <javax_annotation-api_version>1.3.2</javax_annotation-api_version>
     <servlet_version>3.1.0</servlet_version>
-    <servlet6_version>6.1.0</servlet6_version>
+    <jakarta_servlet_version>6.1.0</jakarta_servlet_version>
     <jetty_version>9.4.54.v20240208</jetty_version>
     <validation_new_version>3.1.0</validation_new_version>
     <validation_version>1.1.0.Final</validation_version>
@@ -152,8 +151,7 @@
     <log4j2_version>2.23.1</log4j2_version>
     <commons_io_version>2.16.1</commons_io_version>
     <commons-codec_version>1.16.0</commons-codec_version>
-
-    <embedded_redis_version>1.4.3</embedded_redis_version>
+    <groovy_version>4.0.21</groovy_version>
 
     <!-- Test libs -->
     <junit_jupiter_version>5.9.3</junit_jupiter_version>
@@ -161,7 +159,9 @@
     <awaitility_version>4.2.0</awaitility_version>
     <hamcrest_version>2.2</hamcrest_version>
     <cglib_version>2.2.2</cglib_version>
+    <embedded_redis_version>1.4.3</embedded_redis_version>
     <mockito_version>4.11.0</mockito_version>
+    <spock_version>2.3-groovy-4.0</spock_version>
 
     <jaxb_version>2.2.7</jaxb_version>
     <activation_version>1.2.0</activation_version>
@@ -183,10 +183,11 @@
     <spotless.action>check</spotless.action>
     <dubbo-shared-resources.version>1.0.0</dubbo-shared-resources.version>
     <palantirJavaFormat.version>2.38.0</palantirJavaFormat.version>
-    <revision>3.3.0-beta.5-SNAPSHOT</revision>
     <open_feign_version>3.1.5</open_feign_version>
     <jakarta.xml.bind-api.version>4.0.1</jakarta.xml.bind-api.version>
     <jaxb-runtime.version>2.3.3-b02</jaxb-runtime.version>
+
+    <revision>3.3.0-beta.5-SNAPSHOT</revision>
   </properties>
 
   <dependencyManagement>
@@ -408,7 +409,7 @@
       <dependency>
         <groupId>jakarta.servlet</groupId>
         <artifactId>jakarta.servlet-api</artifactId>
-        <version>${servlet6_version}</version>
+        <version>${jakarta_servlet_version}</version>
         <scope>provided</scope>
       </dependency>
       <dependency>
@@ -616,6 +617,11 @@
         <artifactId>api</artifactId>
         <version>${envoy_api_version}</version>
       </dependency>
+      <dependency>
+        <groupId>org.apache.groovy</groupId>
+        <artifactId>groovy</artifactId>
+        <version>${groovy_version}</version>
+      </dependency>
 
       <!-- for dubbo-rpc-webservice -->
       <dependency>
@@ -742,6 +748,24 @@
         <version>${spring_version}</version>
         <scope>test</scope>
       </dependency>
+      <dependency>
+        <groupId>org.spockframework</groupId>
+        <artifactId>spock-core</artifactId>
+        <version>${spock_version}</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.spockframework</groupId>
+        <artifactId>spock-spring</artifactId>
+        <version>${spock_version}</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.spockframework</groupId>
+        <artifactId>spock-junit4</artifactId>
+        <version>${spock_version}</version>
+        <scope>test</scope>
+      </dependency>
 
       <dependency>
         <groupId>com.google.code.gson</groupId>
diff --git a/dubbo-dependencies/dubbo-dependencies-zookeeper-curator5/pom.xml b/dubbo-dependencies/dubbo-dependencies-zookeeper-curator5/pom.xml
index afdc096434..49d878d74e 100644
--- a/dubbo-dependencies/dubbo-dependencies-zookeeper-curator5/pom.xml
+++ b/dubbo-dependencies/dubbo-dependencies-zookeeper-curator5/pom.xml
@@ -17,7 +17,6 @@
   -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
-
   <parent>
     <groupId>org.apache</groupId>
     <artifactId>apache</artifactId>
@@ -31,7 +30,6 @@
   <packaging>pom</packaging>
 
   <properties>
-    <revision>3.3.0-beta.5-SNAPSHOT</revision>
     <maven_flatten_version>1.6.0</maven_flatten_version>
     <slf4j_version>1.7.36</slf4j_version>
     <curator5_version>5.1.0</curator5_version>
@@ -40,6 +38,8 @@
     <spotless.action>check</spotless.action>
     <dubbo-shared-resources.version>1.0.0</dubbo-shared-resources.version>
     <palantirJavaFormat.version>2.38.0</palantirJavaFormat.version>
+
+    <revision>3.3.0-beta.5-SNAPSHOT</revision>
   </properties>
 
   <dependencies>
diff --git a/dubbo-distribution/dubbo-all-shaded/pom.xml b/dubbo-distribution/dubbo-all-shaded/pom.xml
index 0eb7510b02..f1c94a1336 100644
--- a/dubbo-distribution/dubbo-all-shaded/pom.xml
+++ b/dubbo-distribution/dubbo-all-shaded/pom.xml
@@ -119,13 +119,6 @@
       <scope>compile</scope>
       <optional>true</optional>
     </dependency>
-    <dependency>
-      <groupId>org.apache.dubbo</groupId>
-      <artifactId>dubbo-rest-servlet</artifactId>
-      <version>${project.version}</version>
-      <scope>compile</scope>
-      <optional>true</optional>
-    </dependency>
     <dependency>
       <groupId>org.apache.dubbo</groupId>
       <artifactId>dubbo-rest-spring</artifactId>
@@ -480,7 +473,6 @@
                   <include>org.apache.dubbo:dubbo-rpc-injvm</include>
                   <include>org.apache.dubbo:dubbo-rpc-triple</include>
                   <include>org.apache.dubbo:dubbo-rest-jaxrs</include>
-                  <include>org.apache.dubbo:dubbo-rest-servlet</include>
                   <include>org.apache.dubbo:dubbo-rest-spring</include>
                   <include>org.apache.dubbo:dubbo-triple-servlet</include>
                   <include>org.apache.dubbo:dubbo-serialization-api</include>
diff --git a/dubbo-distribution/dubbo-all/pom.xml b/dubbo-distribution/dubbo-all/pom.xml
index 99b7a90c0b..c0c2e69313 100644
--- a/dubbo-distribution/dubbo-all/pom.xml
+++ b/dubbo-distribution/dubbo-all/pom.xml
@@ -269,13 +269,6 @@
       <scope>compile</scope>
       <optional>true</optional>
     </dependency>
-    <dependency>
-      <groupId>org.apache.dubbo</groupId>
-      <artifactId>dubbo-rest-servlet</artifactId>
-      <version>${project.version}</version>
-      <scope>compile</scope>
-      <optional>true</optional>
-    </dependency>
     <dependency>
       <groupId>org.apache.dubbo</groupId>
       <artifactId>dubbo-rest-spring</artifactId>
@@ -517,7 +510,6 @@
                   <include>org.apache.dubbo:dubbo-rpc-injvm</include>
                   <include>org.apache.dubbo:dubbo-rpc-triple</include>
                   <include>org.apache.dubbo:dubbo-rest-jaxrs</include>
-                  <include>org.apache.dubbo:dubbo-rest-servlet</include>
                   <include>org.apache.dubbo:dubbo-rest-spring</include>
                   <include>org.apache.dubbo:dubbo-triple-servlet</include>
                   <include>org.apache.dubbo:dubbo-serialization-api</include>
diff --git a/dubbo-distribution/dubbo-bom/pom.xml b/dubbo-distribution/dubbo-bom/pom.xml
index 48afc409d4..2e805de08f 100644
--- a/dubbo-distribution/dubbo-bom/pom.xml
+++ b/dubbo-distribution/dubbo-bom/pom.xml
@@ -329,11 +329,6 @@
         <artifactId>dubbo-rest-jaxrs</artifactId>
         <version>${project.version}</version>
       </dependency>
-      <dependency>
-        <groupId>org.apache.dubbo</groupId>
-        <artifactId>dubbo-rest-servlet</artifactId>
-        <version>${project.version}</version>
-      </dependency>
       <dependency>
         <groupId>org.apache.dubbo</groupId>
         <artifactId>dubbo-rest-spring</artifactId>
diff --git a/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/AbstractObservationFilterTest.java b/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/AbstractObservationFilterTest.java
deleted file mode 100644
index ce56ae3662..0000000000
--- a/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/AbstractObservationFilterTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.tracing.filter;
-
-import org.apache.dubbo.config.ApplicationConfig;
-import org.apache.dubbo.config.TracingConfig;
-import org.apache.dubbo.rpc.AppResponse;
-import org.apache.dubbo.rpc.BaseFilter;
-import org.apache.dubbo.rpc.Invoker;
-import org.apache.dubbo.rpc.RpcInvocation;
-import org.apache.dubbo.rpc.model.ApplicationModel;
-import org.apache.dubbo.tracing.MockInvocation;
-
-import io.micrometer.tracing.test.SampleTestRunner;
-import org.junit.jupiter.api.AfterEach;
-
-import static org.mockito.BDDMockito.given;
-import static org.mockito.Mockito.mock;
-
-abstract class AbstractObservationFilterTest extends SampleTestRunner {
-
-    ApplicationModel applicationModel;
-    RpcInvocation invocation;
-
-    BaseFilter filter;
-
-    Invoker<?> invoker = mock(Invoker.class);
-
-    static final String INTERFACE_NAME = "org.apache.dubbo.MockInterface";
-    static final String METHOD_NAME = "mockMethod";
-    static final String GROUP = "mockGroup";
-    static final String VERSION = "1.0.0";
-
-    @AfterEach
-    public void teardown() {
-        if (applicationModel != null) {
-            applicationModel.destroy();
-        }
-    }
-
-    abstract BaseFilter createFilter(ApplicationModel applicationModel);
-
-    void setupConfig() {
-        ApplicationConfig config = new ApplicationConfig();
-        config.setName("MockObservations");
-
-        applicationModel = ApplicationModel.defaultModel();
-        applicationModel.getApplicationConfigManager().setApplication(config);
-
-        invocation = new RpcInvocation(new MockInvocation());
-        invocation.addInvokedInvoker(invoker);
-
-        applicationModel.getBeanFactory().registerBean(getObservationRegistry());
-        TracingConfig tracingConfig = new TracingConfig();
-        tracingConfig.setEnabled(true);
-        applicationModel.getApplicationConfigManager().setTracing(tracingConfig);
-
-        filter = createFilter(applicationModel);
-
-        given(invoker.invoke(invocation)).willReturn(new AppResponse("success"));
-
-        initParam();
-    }
-
-    private void initParam() {
-        invocation.setTargetServiceUniqueName(GROUP + "/" + INTERFACE_NAME + ":" + VERSION);
-        invocation.setMethodName(METHOD_NAME);
-        invocation.setParameterTypes(new Class[] {String.class});
-    }
-}
diff --git a/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/ObservationReceiverFilterTest.java b/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/ObservationReceiverFilterTest.java
deleted file mode 100644
index 76eac81ad9..0000000000
--- a/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/ObservationReceiverFilterTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.tracing.filter;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.rpc.AppResponse;
-import org.apache.dubbo.rpc.Filter;
-import org.apache.dubbo.rpc.Invocation;
-import org.apache.dubbo.rpc.Invoker;
-import org.apache.dubbo.rpc.Result;
-import org.apache.dubbo.rpc.RpcContext;
-import org.apache.dubbo.rpc.RpcException;
-import org.apache.dubbo.rpc.model.ApplicationModel;
-
-import io.micrometer.common.KeyValues;
-import io.micrometer.core.tck.MeterRegistryAssert;
-import io.micrometer.tracing.Span;
-import io.micrometer.tracing.Tracer;
-import io.micrometer.tracing.test.simple.SpansAssert;
-import org.assertj.core.api.BDDAssertions;
-
-class ObservationReceiverFilterTest extends AbstractObservationFilterTest {
-
-    @Override
-    public SampleTestRunnerConsumer yourCode() {
-        return (buildingBlocks, meterRegistry) -> {
-            setupConfig();
-            setupAttachments(buildingBlocks.getTracer());
-            invoker = new AssertingInvoker(buildingBlocks.getTracer());
-
-            ObservationReceiverFilter senderFilter = (ObservationReceiverFilter) filter;
-            senderFilter.invoke(invoker, invocation);
-            senderFilter.onResponse(null, invoker, invocation);
-
-            MeterRegistryAssert.then(meterRegistry)
-                    .hasMeterWithNameAndTags(
-                            "rpc.server.duration",
-                            KeyValues.of(
-                                    "rpc.method",
-                                    "mockMethod",
-                                    "rpc.service",
-                                    "DemoService",
-                                    "rpc.system",
-                                    "apache_dubbo"));
-            SpansAssert.then(buildingBlocks.getFinishedSpans())
-                    .hasASpanWithNameIgnoreCase("DemoService/mockMethod", spanAssert -> spanAssert
-                            .hasTag("rpc.method", "mockMethod")
-                            .hasTag("rpc.service", "DemoService")
-                            .hasTag("rpc.system", "apache_dubbo"));
-        };
-    }
-
-    void setupAttachments(Tracer tracer) {
-        RpcContext.getServerAttachment()
-                .setUrl(URL.valueOf("test://test:11/test?accesslog=true&group=dubbo&version=1.1&side=consumer"));
-        RpcContext.getServerAttachment().setMethodName("foo");
-        RpcContext.getServerAttachment().setRemoteAddress("foo.bar.com", 8080);
-        RpcContext.getServerAttachment()
-                .setAttachment("X-B3-TraceId", tracer.currentSpan().context().traceId());
-        RpcContext.getServerAttachment()
-                .setAttachment("X-B3-SpanId", tracer.currentSpan().context().spanId());
-        RpcContext.getServerAttachment().setAttachment("X-B3-Sampled", "1");
-    }
-
-    @Override
-    Filter createFilter(ApplicationModel applicationModel) {
-        return new ObservationReceiverFilter(applicationModel);
-    }
-
-    static class AssertingInvoker implements Invoker {
-
-        private final String expectedTraceId;
-
-        private final String parentSpanId;
-
-        private final Tracer tracer;
-
-        AssertingInvoker(Tracer tracer) {
-            this.tracer = tracer;
-            this.expectedTraceId = tracer.currentSpan().context().traceId();
-            this.parentSpanId = tracer.currentSpan().context().spanId();
-        }
-
-        @Override
-        public URL getUrl() {
-            return null;
-        }
-
-        @Override
-        public boolean isAvailable() {
-            return true;
-        }
-
-        @Override
-        public void destroy() {}
-
-        @Override
-        public Class getInterface() {
-            return AssertingInvoker.class;
-        }
-
-        @Override
-        public Result invoke(Invocation invocation) throws RpcException {
-            Span span = this.tracer.currentSpan();
-            BDDAssertions.then(span.context().traceId())
-                    .as("Should propagate the trace id from the attributes")
-                    .isEqualTo(this.expectedTraceId);
-            BDDAssertions.then(span.context().spanId())
-                    .as("A child span must be created")
-                    .isNotEqualTo(this.parentSpanId);
-            return new AppResponse("OK");
-        }
-    }
-}
diff --git a/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/ObservationSenderFilterTest.java b/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/ObservationSenderFilterTest.java
deleted file mode 100644
index 8239c4128e..0000000000
--- a/dubbo-metrics/dubbo-tracing/src/test/java/org/apache/dubbo/tracing/filter/ObservationSenderFilterTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.tracing.filter;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.rpc.RpcContext;
-import org.apache.dubbo.rpc.cluster.filter.ClusterFilter;
-import org.apache.dubbo.rpc.model.ApplicationModel;
-
-import io.micrometer.common.KeyValues;
-import io.micrometer.core.tck.MeterRegistryAssert;
-import io.micrometer.tracing.test.simple.SpansAssert;
-import org.assertj.core.api.BDDAssertions;
-
-class ObservationSenderFilterTest extends AbstractObservationFilterTest {
-
-    @Override
-    public SampleTestRunnerConsumer yourCode() {
-        return (buildingBlocks, meterRegistry) -> {
-            setupConfig();
-            setupAttachments();
-
-            ObservationSenderFilter senderFilter = (ObservationSenderFilter) filter;
-            senderFilter.invoke(invoker, invocation);
-            senderFilter.onResponse(null, invoker, invocation);
-
-            BDDAssertions.then(invocation.getObjectAttachment("X-B3-TraceId")).isNotNull();
-            MeterRegistryAssert.then(meterRegistry)
-                    .hasMeterWithNameAndTags(
-                            "rpc.client.duration",
-                            KeyValues.of(
-                                    "net.peer.name",
-                                    "foo.bar.com",
-                                    "net.peer.port",
-                                    "8080",
-                                    "rpc.method",
-                                    "mockMethod",
-                                    "rpc.service",
-                                    "DemoService",
-                                    "rpc.system",
-                                    "apache_dubbo"));
-            SpansAssert.then(buildingBlocks.getFinishedSpans())
-                    .hasASpanWithNameIgnoreCase("DemoService/mockMethod", spanAssert -> spanAssert
-                            .hasTag("net.peer.name", "foo.bar.com")
-                            .hasTag("net.peer.port", "8080")
-                            .hasTag("rpc.method", "mockMethod")
-                            .hasTag("rpc.service", "DemoService")
-                            .hasTag("rpc.system", "apache_dubbo"));
-        };
-    }
-
-    void setupAttachments() {
-        RpcContext.getClientAttachment()
-                .setUrl(URL.valueOf("test://test:11/test?accesslog=true&group=dubbo&version=1.1&side=consumer"));
-        RpcContext.getClientAttachment().setRemoteAddress("foo.bar.com", 8080);
-    }
-
-    @Override
-    ClusterFilter createFilter(ApplicationModel applicationModel) {
-        return new ObservationSenderFilter(applicationModel);
-    }
-}
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/pom.xml b/dubbo-plugin/dubbo-rest-jaxrs/pom.xml
index 6370496dfa..32b43a2d00 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/pom.xml
+++ b/dubbo-plugin/dubbo-rest-jaxrs/pom.xml
@@ -62,5 +62,52 @@
       <artifactId>jaxb-runtime</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.yaml</groupId>
+      <artifactId>snakeyaml</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.dubbo</groupId>
+      <artifactId>dubbo-rpc-triple</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.spockframework</groupId>
+      <artifactId>spock-core</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.gmavenplus</groupId>
+        <artifactId>gmavenplus-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>compileTests</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+  <profiles>
+    <profile>
+      <id>rpc-rest-test</id>
+      <dependencies>
+        <dependency>
+          <groupId>org.apache.dubbo</groupId>
+          <artifactId>dubbo-rpc-rest</artifactId>
+          <version>3.3.0-beta.2</version>
+          <scope>test</scope>
+        </dependency>
+      </dependencies>
+    </profile>
+  </profiles>
 </project>
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BeanArgumentBinder.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BeanArgumentBinder.java
index 294875bd95..90f0da7955 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BeanArgumentBinder.java
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/BeanArgumentBinder.java
@@ -27,254 +27,90 @@ import org.apache.dubbo.rpc.protocol.tri.rest.RestException;
 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.mapping.meta.AnnotationMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.ConstructorMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.FieldMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.SetMethodMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta;
-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.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.lang.reflect.Parameter;
-import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 final class BeanArgumentBinder {
 
-    private final ArgumentResolver argumentResolver;
+    private static final ThreadLocal<Set<Class<?>>> LOCAL = new ThreadLocal<>();
 
     private final Map<Pair<Class<?>, String>, BeanMeta> cache = CollectionUtils.newConcurrentHashMap();
+    private final ArgumentResolver argumentResolver;
 
     BeanArgumentBinder(FrameworkModel frameworkModel) {
         ScopeBeanFactory beanFactory = frameworkModel.getBeanFactory();
         argumentResolver = beanFactory.getOrRegisterBean(CompositeArgumentResolver.class);
     }
 
-    public Object bind(ParameterMeta parameter, HttpRequest request, HttpResponse response) {
+    public Object bind(ParameterMeta paramMeta, HttpRequest request, HttpResponse response) {
+        Set<Class<?>> walked = LOCAL.get();
+        if (walked == null) {
+            LOCAL.set(new HashSet<>());
+        }
         try {
-            return resolveArgument(parameter, request, response);
+            return resolveArgument(paramMeta, request, response);
         } catch (Exception e) {
-            throw new RestException(Messages.ARGUMENT_BIND_ERROR, parameter.getName(), parameter.getType(), e);
-        }
-    }
-
-    private Object resolveArgument(ParameterMeta param, HttpRequest request, HttpResponse response) throws Exception {
-        AnnotationMeta<?> form = param.findAnnotation(Annotations.Form);
-        if (form != null || param.isHierarchyAnnotated(Annotations.BeanParam)) {
-            String prefix = form == null ? null : form.getString("prefix");
-            BeanMeta beanMeta = cache.computeIfAbsent(
-                    Pair.of(param.getActualType(), prefix),
-                    k -> new BeanMeta(param.getToolKit(), k.getValue(), k.getKey()));
-
-            ConstructorMeta constructor = beanMeta.constructor;
-            ParameterMeta[] parameters = constructor.parameters;
-            int len = parameters.length;
-            Object[] args = new Object[len];
-            for (int i = 0; i < len; i++) {
-                args[i] = resolveArgument(parameters[i], request, response);
-            }
-            Object bean = constructor.newInstance(args);
-
-            for (FieldMeta fieldMeta : beanMeta.fields) {
-                fieldMeta.set(bean, resolveArgument(fieldMeta, request, response));
-            }
-
-            for (SetMethodMeta methodMeta : beanMeta.methods) {
-                methodMeta.invoke(bean, resolveArgument(methodMeta, request, response));
+            throw new RestException(e, Messages.ARGUMENT_BIND_ERROR, paramMeta.getName(), paramMeta.getType());
+        } finally {
+            if (walked == null) {
+                LOCAL.remove();
             }
-            return bean;
         }
-
-        return argumentResolver.resolve(param, request, response);
     }
 
-    private static class BeanMeta {
-
-        private final List<FieldMeta> fields = new ArrayList<>();
-        private final List<SetMethodMeta> methods = new ArrayList<>();
-        private final ConstructorMeta constructor;
-
-        BeanMeta(RestToolKit toolKit, String prefix, Class<?> type) {
-            constructor = resolveConstructor(toolKit, prefix, type);
-            resolveFieldAndMethod(toolKit, prefix, type);
-        }
-
-        private ConstructorMeta resolveConstructor(RestToolKit toolKit, String prefix, Class<?> type) {
-            Constructor<?>[] constructors = type.getConstructors();
-            Constructor<?> ct = null;
-            if (constructors.length == 1) {
-                ct = constructors[0];
-            } else {
-                try {
-                    ct = type.getDeclaredConstructor();
-                } catch (NoSuchMethodException ignored) {
+    private Object resolveArgument(ParameterMeta paramMeta, HttpRequest request, HttpResponse response) {
+        if (paramMeta.isSimple()) {
+            return argumentResolver.resolve(paramMeta, request, response);
+        }
+
+        // Prevent infinite loops
+        if (LOCAL.get().add(paramMeta.getActualType())) {
+            AnnotationMeta<?> form = paramMeta.findAnnotation(Annotations.Form);
+            if (form != null || paramMeta.isHierarchyAnnotated(Annotations.BeanParam)) {
+                String prefix = form == null ? null : form.getString("prefix");
+                BeanMeta beanMeta = cache.computeIfAbsent(
+                        Pair.of(paramMeta.getActualType(), prefix),
+                        k -> new BeanMeta(paramMeta.getToolKit(), k.getValue(), k.getKey()));
+
+                ConstructorMeta constructor = beanMeta.getConstructor();
+                ParameterMeta[] parameters = constructor.getParameters();
+                Object bean;
+                int len = parameters.length;
+                if (len == 0) {
+                    bean = constructor.newInstance();
+                } else {
+                    Object[] args = new Object[len];
+                    for (int i = 0; i < len; i++) {
+                        args[i] = resolveArgument(parameters[i], request, response);
+                    }
+                    bean = constructor.newInstance(args);
                 }
-            }
-            if (ct == null) {
-                throw new IllegalArgumentException("No available default constructor found in " + type);
-            }
-            return new ConstructorMeta(toolKit, prefix, ct);
-        }
 
-        private void resolveFieldAndMethod(RestToolKit toolKit, String prefix, Class<?> type) {
-            if (type == Object.class) {
-                return;
-            }
-            for (Field field : type.getDeclaredFields()) {
-                int modifiers = field.getModifiers();
-                if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) {
-                    continue;
-                }
-                if (field.getAnnotations().length == 0) {
-                    continue;
-                }
-                if (!field.isAccessible()) {
-                    field.setAccessible(true);
-                }
-                fields.add(new FieldMeta(toolKit, prefix, field));
-            }
-            for (Method method : type.getDeclaredMethods()) {
-                if (method.getParameterCount() != 1) {
-                    continue;
+                Set<String> resolved = new HashSet<>();
+                for (FieldMeta fieldMeta : beanMeta.getFields()) {
+                    resolved.add(fieldMeta.getName());
+                    fieldMeta.setValue(bean, resolveArgument(fieldMeta, request, response));
                 }
-                int modifiers = method.getModifiers();
-                if ((modifiers & (Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.STATIC)) == Modifier.PUBLIC) {
-                    Parameter parameter = method.getParameters()[0];
-                    String name = method.getName();
-                    if (name.length() > 3 && name.startsWith("set")) {
-                        name = Character.toLowerCase(name.charAt(3)) + name.substring(4);
-                        methods.add(new SetMethodMeta(toolKit, method, parameter, prefix, name));
+
+                for (SetMethodMeta methodMeta : beanMeta.getMethods()) {
+                    if (resolved.contains(methodMeta.getName())) {
+                        continue;
                     }
+                    methodMeta.setValue(bean, resolveArgument(methodMeta, request, response));
                 }
-            }
-            resolveFieldAndMethod(toolKit, prefix, type.getSuperclass());
-        }
-    }
-
-    private static final class ConstructorMeta {
 
-        private final Constructor<?> constructor;
-        private final ConstructorParameterMeta[] parameters;
-
-        ConstructorMeta(RestToolKit toolKit, String prefix, Constructor<?> constructor) {
-            this.constructor = constructor;
-            parameters = initParameters(toolKit, prefix, constructor);
-        }
-
-        private ConstructorParameterMeta[] initParameters(RestToolKit toolKit, String prefix, Constructor<?> ct) {
-            Parameter[] cps = ct.getParameters();
-            int len = cps.length;
-            ConstructorParameterMeta[] parameters = new ConstructorParameterMeta[len];
-            for (int i = 0; i < len; i++) {
-                parameters[i] = new ConstructorParameterMeta(toolKit, cps[i], prefix);
+                return bean;
             }
-            return parameters;
         }
 
-        Object newInstance(Object[] args) throws Exception {
-            return constructor.newInstance(args);
-        }
-    }
-
-    public static final class ConstructorParameterMeta extends ParameterMeta {
-
-        private final Parameter parameter;
-
-        ConstructorParameterMeta(RestToolKit toolKit, Parameter parameter, String prefix) {
-            super(toolKit, prefix, parameter.isNamePresent() ? parameter.getName() : null);
-            this.parameter = parameter;
-        }
-
-        @Override
-        protected AnnotatedElement getAnnotatedElement() {
-            return parameter;
-        }
-
-        @Override
-        public Class<?> getType() {
-            return parameter.getType();
-        }
-
-        @Override
-        public Type getGenericType() {
-            return parameter.getParameterizedType();
-        }
-
-        @Override
-        public String getDescription() {
-            return "ConstructorParameter{" + parameter + '}';
-        }
-    }
-
-    private static final class FieldMeta extends ParameterMeta {
-
-        private final Field field;
-
-        FieldMeta(RestToolKit toolKit, String prefix, Field field) {
-            super(toolKit, prefix, field.getName());
-            this.field = field;
-        }
-
-        @Override
-        public Class<?> getType() {
-            return field.getType();
-        }
-
-        @Override
-        public Type getGenericType() {
-            return field.getGenericType();
-        }
-
-        @Override
-        protected AnnotatedElement getAnnotatedElement() {
-            return field;
-        }
-
-        public void set(Object bean, Object value) throws Exception {
-            field.set(bean, value);
-        }
-
-        @Override
-        public String getDescription() {
-            return "FieldParameter{" + field + '}';
-        }
-    }
-
-    private static final class SetMethodMeta extends ParameterMeta {
-
-        private final Method method;
-        private final Parameter parameter;
-
-        SetMethodMeta(RestToolKit toolKit, Method method, Parameter parameter, String prefix, String name) {
-            super(toolKit, prefix, name);
-            this.method = method;
-            this.parameter = parameter;
-        }
-
-        @Override
-        public Class<?> getType() {
-            return parameter.getType();
-        }
-
-        @Override
-        public Type getGenericType() {
-            return parameter.getParameterizedType();
-        }
-
-        @Override
-        protected AnnotatedElement getAnnotatedElement() {
-            return parameter;
-        }
-
-        public void invoke(Object bean, Object value) throws Exception {
-            method.invoke(bean, value);
-        }
-
-        @Override
-        public String getDescription() {
-            return "SetMethodParameter{" + method + '}';
-        }
+        return TypeUtils.nullDefault(paramMeta.getType());
     }
 }
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpRequestAdaptee.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpRequestAdapter.java
similarity index 97%
rename from dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpRequestAdaptee.java
rename to dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpRequestAdapter.java
index 3ff3f85381..6309415356 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpRequestAdaptee.java
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpRequestAdapter.java
@@ -33,14 +33,14 @@ import org.jboss.resteasy.specimpl.ResteasyHttpHeaders;
 import org.jboss.resteasy.spi.ResteasyAsynchronousContext;
 import org.jboss.resteasy.spi.ResteasyUriInfo;
 
-public final class JaxrsHttpRequestAdaptee implements org.jboss.resteasy.spi.HttpRequest {
+public final class JaxrsHttpRequestAdapter implements org.jboss.resteasy.spi.HttpRequest {
 
     private final HttpRequest request;
 
     private HttpHeaders headers;
     private ResteasyUriInfo uriInfo;
 
-    public JaxrsHttpRequestAdaptee(HttpRequest request) {
+    public JaxrsHttpRequestAdapter(HttpRequest request) {
         this.request = request;
     }
 
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpResponseAdaptee.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpResponseAdapter.java
similarity index 95%
rename from dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpResponseAdaptee.java
rename to dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpResponseAdapter.java
index 94a4bf417a..d0e7997256 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpResponseAdaptee.java
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/JaxrsHttpResponseAdapter.java
@@ -24,13 +24,13 @@ import javax.ws.rs.core.NewCookie;
 
 import java.io.OutputStream;
 
-public final class JaxrsHttpResponseAdaptee implements org.jboss.resteasy.spi.HttpResponse {
+public final class JaxrsHttpResponseAdapter implements org.jboss.resteasy.spi.HttpResponse {
 
     private final HttpResponse response;
 
     private MultivaluedMap<String, Object> headers;
 
-    public JaxrsHttpResponseAdaptee(HttpResponse response) {
+    public JaxrsHttpResponseAdapter(HttpResponse response) {
         this.response = response;
     }
 
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 89d1861dc5..56c3365e53 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
@@ -21,18 +21,15 @@ import org.apache.dubbo.remoting.http12.HttpResponse;
 import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta;
-import org.apache.dubbo.rpc.protocol.tri.rest.util.DefaultRestToolKit;
+import org.apache.dubbo.rpc.protocol.tri.rest.util.AbstractRestToolKit;
 
 import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.ext.ParamConverter;
 
-import java.util.Optional;
-
-final class JaxrsRestToolKit extends DefaultRestToolKit {
+final class JaxrsRestToolKit extends AbstractRestToolKit {
 
     private final BeanArgumentBinder binder;
-
     private final ParamConverterFactory paramConverterFactory;
 
     public JaxrsRestToolKit(FrameworkModel frameworkModel) {
@@ -41,8 +38,13 @@ final class JaxrsRestToolKit extends DefaultRestToolKit {
         paramConverterFactory = new ParamConverterFactory();
     }
 
-    @SuppressWarnings({"unchecked", "rawtypes"})
     @Override
+    public int getDialect() {
+        return RestConstants.DIALECT_JAXRS;
+    }
+
+    @Override
+    @SuppressWarnings({"unchecked", "rawtypes"})
     public Object convert(Object value, ParameterMeta parameter) {
         if (MultivaluedMap.class.isAssignableFrom(parameter.getType())) {
             if (value instanceof MultivaluedMap) {
@@ -51,26 +53,15 @@ final class JaxrsRestToolKit extends DefaultRestToolKit {
             return typeConverter.convert(value, MultivaluedHashMap.class);
         }
 
-        Optional<ParamConverter> optional = paramConverterFactory.getParamConverter(
+        ParamConverter converter = paramConverterFactory.getParamConverter(
                 parameter.getType(), parameter.getGenericType(), parameter.getRealAnnotations());
-        if (optional.isPresent()) {
-            ParamConverter paramConverter = optional.get();
-            Object result = value.getClass() == String.class
-                    ? paramConverter.fromString((String) value)
-                    : paramConverter.toString(value);
-            if (result != null) {
-                return result;
-            }
+        if (converter != null) {
+            return value instanceof String ? converter.fromString((String) value) : converter.toString(value);
         }
 
         return super.convert(value, parameter);
     }
 
-    @Override
-    public int getDialect() {
-        return RestConstants.DIALECT_JAXRS;
-    }
-
     @Override
     public Object bind(ParameterMeta parameter, HttpRequest request, HttpResponse response) {
         return binder.bind(parameter, request, response);
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/ParamConverterFactory.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/ParamConverterFactory.java
index bef0a63b90..15cf8dc36c 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/ParamConverterFactory.java
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/ParamConverterFactory.java
@@ -16,10 +16,9 @@
  */
 package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs;
 
+import org.apache.dubbo.common.lang.Nullable;
 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.common.utils.Pair;
 
 import javax.ws.rs.ext.ParamConverter;
 import javax.ws.rs.ext.ParamConverterProvider;
@@ -27,6 +26,7 @@ import javax.ws.rs.ext.ParamConverterProvider;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -34,40 +34,45 @@ import java.util.Optional;
 import java.util.ServiceLoader;
 
 import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_ERROR_LOAD_EXTENSION;
+import static org.apache.dubbo.common.logger.LoggerFactory.getErrorTypeAwareLogger;
 
-@SuppressWarnings({"rawtypes"})
-public class ParamConverterFactory {
+final class ParamConverterFactory {
 
-    private static final ErrorTypeAwareLogger logger =
-            LoggerFactory.getErrorTypeAwareLogger(ParamConverterFactory.class);
-    private final Map<Pair<Pair<Class<?>, Type>, Annotation[]>, Optional<ParamConverter>> cache =
-            CollectionUtils.newConcurrentHashMap();
+    private static final ErrorTypeAwareLogger logger = getErrorTypeAwareLogger(ParamConverterFactory.class);
+    private final Map<List<Object>, Optional<ParamConverter<?>>> cache = CollectionUtils.newConcurrentHashMap();
     private final List<ParamConverterProvider> providers = new ArrayList<>();
 
     ParamConverterFactory() {
-        ServiceLoader<ParamConverterProvider> serviceLoader = ServiceLoader.load(ParamConverterProvider.class);
-        Iterator<ParamConverterProvider> iterator = serviceLoader.iterator();
-        while (iterator.hasNext()) {
+        Iterator<ParamConverterProvider> it =
+                ServiceLoader.load(ParamConverterProvider.class).iterator();
+        while (it.hasNext()) {
             try {
-                ParamConverterProvider paramConverterProvider = iterator.next();
-                providers.add(paramConverterProvider);
-            } catch (Throwable e) {
-                logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "", "Spi Fail to load ParamConverterProvider");
+                providers.add(it.next());
+            } catch (Throwable t) {
+                logger.error(COMMON_ERROR_LOAD_EXTENSION, "", "", "Failed to load ParamConverterProvider", t);
             }
         }
     }
 
-    public <T> Optional<ParamConverter> getParamConverter(
-            Class<T> rawType, Type genericType, Annotation[] annotations) {
-        Pair<Pair<Class<?>, Type>, Annotation[]> pair = Pair.of(Pair.of(rawType, genericType), annotations);
-        return cache.computeIfAbsent(pair, k -> {
-            for (ParamConverterProvider provider : providers) {
-                ParamConverter converter = provider.getConverter(rawType, genericType, annotations);
-                if (converter != null) {
-                    return Optional.of(converter);
-                }
-            }
-            return Optional.empty();
-        });
+    @Nullable
+    @SuppressWarnings("unchecked")
+    public <T> ParamConverter<T> getParamConverter(Class<T> type, Type genericType, Annotation[] annotations) {
+        if (providers.isEmpty()) {
+            return null;
+        }
+        List<Object> key = new ArrayList<>(annotations.length + 2);
+        key.add(type);
+        key.add(genericType);
+        Collections.addAll(key, annotations);
+        return (ParamConverter<T>) cache.computeIfAbsent(key, k -> {
+                    for (ParamConverterProvider provider : providers) {
+                        ParamConverter<T> converter = provider.getConverter(type, genericType, annotations);
+                        if (converter != null) {
+                            return Optional.of(converter);
+                        }
+                    }
+                    return Optional.empty();
+                })
+                .orElse(null);
     }
 }
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerRequestContextImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerRequestContextImpl.java
index 907524f0f6..713c20120a 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerRequestContextImpl.java
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/filter/ContainerRequestContextImpl.java
@@ -20,8 +20,8 @@ import org.apache.dubbo.remoting.http12.HttpCookie;
 import org.apache.dubbo.remoting.http12.HttpRequest;
 import org.apache.dubbo.remoting.http12.HttpResponse;
 import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.Helper;
-import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.JaxrsHttpRequestAdaptee;
-import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.JaxrsHttpResponseAdaptee;
+import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.JaxrsHttpRequestAdapter;
+import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.JaxrsHttpResponseAdapter;
 import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.MultivaluedMapWrapper;
 
 import javax.ws.rs.container.ContainerRequestContext;
@@ -107,7 +107,7 @@ final class ContainerRequestContextImpl implements ContainerRequestContext {
     public Request getRequest() {
         Request req = this.req;
         if (req == null) {
-            req = new RequestImpl(new JaxrsHttpRequestAdaptee(request), new JaxrsHttpResponseAdaptee(response));
+            req = new RequestImpl(new JaxrsHttpRequestAdapter(request), new JaxrsHttpResponseAdapter(response));
             this.req = req;
         }
         return req;
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/RestProtocolTest.groovy b/dubbo-plugin/dubbo-rest-jaxrs/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/RestProtocolTest.groovy
new file mode 100644
index 0000000000..75e34de268
--- /dev/null
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/RestProtocolTest.groovy
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs
+
+import org.apache.dubbo.remoting.http12.message.MediaType
+import org.apache.dubbo.rpc.protocol.tri.rest.service.DemoServiceImpl
+import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.service.JaxrsDemoServiceImpl
+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 JaxrsDemoServiceImpl())
+    }
+
+    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'
+    }
+
+    def "form param test"() {
+        expect:
+            TestRequest request = new TestRequest(
+                path: path,
+                contentType: MediaType.APPLICATION_FROM_URLENCODED,
+                body: body
+            )
+            runner.post(request) == output
+        where:
+            path        | body                                      | output
+            '/formTest' | ['user.first': 'sam', 'user.last': 'lee'] | '{"contentType":"application/x-www-form-urlencoded","firstName":"sam","lastName":"lee"}'
+    }
+
+    def "bean param test"() {
+        expect:
+            TestRequest request = new TestRequest(
+                path: path,
+                contentType: MediaType.APPLICATION_FROM_URLENCODED,
+                body: body
+            )
+            runner.post(request) == output
+        where:
+            path                   | body                            | output
+            '/beanTest/2?name=sam' | ['first': 'sam', 'last': 'lee'] | '{"form":{"contentType":"application/x-www-form-urlencoded","firstName":"sam","lastName":"lee"},"id":2,"name":"sam"}'
+    }
+
+    def "param converter test"() {
+        expect:
+            runner.get(path) == output
+        where:
+            path                      | output
+            '/convertTest?user=3,sam' | '{"id":3,"name":"sam"}'
+    }
+}
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/JaxrsRestProtocolTest.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/JaxrsRestProtocolTest.java
index f46c31310a..fa4b681843 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/JaxrsRestProtocolTest.java
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/JaxrsRestProtocolTest.java
@@ -39,8 +39,6 @@ import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.Anot
 import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.AnotherUserRestServiceImpl;
 import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.HttpMethodService;
 import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.HttpMethodServiceImpl;
-import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.ParamConverterService;
-import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.ParamConverterServiceImpl;
 import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.RestDemoForTestException;
 import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.RestDemoService;
 import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.RestDemoServiceImpl;
@@ -564,24 +562,4 @@ class JaxrsRestProtocolTest {
         repository.registerProvider(providerModel);
         return url.setServiceModel(providerModel);
     }
-
-    @Test
-    void testParamConverter() {
-        ParamConverterService service = new ParamConverterServiceImpl();
-        URL exportUrl = URL.valueOf(
-                "tri://127.0.0.1:" + availablePort + "/rest?interface=" + ParamConverterService.class.getName());
-
-        URL nettyUrl = this.registerProvider(exportUrl, service, ParamConverterService.class);
-
-        tProtocol.export(proxy.getInvoker(service, ParamConverterService.class, nettyUrl));
-
-        ParamConverterService paramConverterService =
-                this.proxy.getProxy(protocol.refer(ParamConverterService.class, nettyUrl));
-
-        User user = paramConverterService.convert(User.getInstance());
-        User u = new User();
-        u.setAge(20);
-        u.setId(1L);
-        Assertions.assertEquals(u, user);
-    }
 }
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterService.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoService.java
similarity index 65%
copy from dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterService.java
copy to dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoService.java
index fa1c20c12d..d23ac72f4c 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterService.java
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoService.java
@@ -14,18 +14,27 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest;
-
-import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.User;
+package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.service;
 
+import javax.ws.rs.BeanParam;
 import javax.ws.rs.GET;
+import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.QueryParam;
 
-@Path("/ParamConverterService")
-public interface ParamConverterService {
+import org.jboss.resteasy.annotations.Form;
+
+public interface JaxrsDemoService {
+
+    @POST
+    @Path("/formTest")
+    UserForm formTest(@Form(prefix = "user") UserForm userForm);
+
+    @POST
+    @Path("/beanTest/{id}")
+    User getTest(@BeanParam User user);
 
-    @GET()
-    @Path("/convert")
-    User convert(@QueryParam("user") User user);
+    @GET
+    @Path("/convertTest")
+    User convertTest(@QueryParam("user") User user);
 }
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterService.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoServiceImpl.java
similarity index 67%
copy from dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterService.java
copy to dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoServiceImpl.java
index fa1c20c12d..0cab619b50 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterService.java
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/JaxrsDemoServiceImpl.java
@@ -14,18 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest;
+package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.service;
 
-import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.User;
+public class JaxrsDemoServiceImpl implements JaxrsDemoService {
 
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.QueryParam;
+    @Override
+    public UserForm formTest(UserForm userForm) {
+        return userForm;
+    }
 
-@Path("/ParamConverterService")
-public interface ParamConverterService {
+    @Override
+    public User getTest(User user) {
+        return user;
+    }
 
-    @GET()
-    @Path("/convert")
-    User convert(@QueryParam("user") User user);
+    @Override
+    public User convertTest(User user) {
+        return user;
+    }
 }
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterProviderImpl.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/ParamConverterProviderImpl.java
similarity index 73%
rename from dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterProviderImpl.java
rename to dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/ParamConverterProviderImpl.java
index 24d0a472cb..84b03445e1 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterProviderImpl.java
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/ParamConverterProviderImpl.java
@@ -14,9 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest;
+package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.service;
 
-import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.User;
+import org.apache.dubbo.common.utils.StringUtils;
 
 import javax.ws.rs.ext.ParamConverter;
 import javax.ws.rs.ext.ParamConverterProvider;
@@ -27,25 +27,29 @@ import java.lang.reflect.Type;
 public class ParamConverterProviderImpl implements ParamConverterProvider {
 
     @Override
-    public <T> ParamConverter<T> getConverter(Class<T> rawType, Type type, Annotation[] annotations) {
+    @SuppressWarnings("unchecked")
+    public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
         if (rawType.isAssignableFrom(User.class)) {
             return (ParamConverter<T>) new UserParamConverter();
         }
         return null;
     }
 
-    static class UserParamConverter implements ParamConverter<User> {
+    static final class UserParamConverter implements ParamConverter<User> {
         @Override
-        public User fromString(String param) {
+        public User fromString(String value) {
+            String[] arr = StringUtils.tokenize(value, ',');
             User user = new User();
-            user.setId(1L);
-            user.setAge(20);
+            if (arr.length > 1) {
+                user.setId(Long.valueOf(arr[0]));
+                user.setName(arr[1]);
+            }
             return user;
         }
 
         @Override
         public String toString(User user) {
-            return "User{" + "id=" + user.getId() + ", name='" + user.getName() + '\'' + ", age=" + user.getAge() + '}';
+            return "User{" + "id=" + user.getId() + ", name='" + user.getName() + "\"}";
         }
     }
 }
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterService.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/User.java
similarity index 50%
rename from dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterService.java
rename to dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/User.java
index fa1c20c12d..a901d1345f 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterService.java
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/User.java
@@ -14,18 +14,55 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest;
+package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.service;
 
-import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.User;
-
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
 
-@Path("/ParamConverterService")
-public interface ParamConverterService {
+public class User {
+
+    @PathParam("id")
+    private Long id;
+
+    @QueryParam("name")
+    private String name;
+
+    @BeanParam
+    private User owner;
+
+    @BeanParam
+    private UserForm form;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public User getOwner() {
+        return owner;
+    }
+
+    public void setOwner(User owner) {
+        this.owner = owner;
+    }
+
+    public UserForm getForm() {
+        return form;
+    }
 
-    @GET()
-    @Path("/convert")
-    User convert(@QueryParam("user") User user);
+    public void setForm(UserForm form) {
+        this.form = form;
+    }
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/UserForm.java
similarity index 52%
copy from dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java
copy to dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/UserForm.java
index c7ee0ba7dd..60f202f469 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/service/UserForm.java
@@ -14,35 +14,43 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.config.nested;
+package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.service;
 
-import java.io.Serializable;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
 
-/**
- * Configuration for the propagation.
- */
-public class PropagationConfig implements Serializable {
+public class UserForm {
+
+    @FormParam("first")
+    String firstName;
+
+    @FormParam("last")
+    String lastName;
 
-    public static final String B3 = "B3";
+    @HeaderParam("Content-Type")
+    String contentType;
 
-    public static final String W3C = "W3C";
+    public String getFirstName() {
+        return firstName;
+    }
 
-    /**
-     * Tracing context propagation type.
-     */
-    private String type = W3C;
+    public void setFirstName(String firstName) {
+        this.firstName = firstName;
+    }
 
-    public PropagationConfig() {}
+    public String getLastName() {
+        return lastName;
+    }
 
-    public PropagationConfig(String type) {
-        this.type = type;
+    public void setLastName(String lastName) {
+        this.lastName = lastName;
     }
 
-    public String getType() {
-        return type;
+    public String getContentType() {
+        return contentType;
     }
 
-    public void setType(String type) {
-        this.type = type;
+    public void setContentType(String contentType) {
+        this.contentType = contentType;
     }
 }
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/META-INF/services/javax.ws.rs.ext.ParamConverterProvider b/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/META-INF/services/javax.ws.rs.ext.ParamConverterProvider
index b794deb158..ab4379a54a 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/META-INF/services/javax.ws.rs.ext.ParamConverterProvider
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/META-INF/services/javax.ws.rs.ext.ParamConverterProvider
@@ -1 +1 @@
-org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest.ParamConverterProviderImpl
+org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.service.ParamConverterProviderImpl
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/log4j2-test.xml b/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/log4j2-test.xml
index 34d1c53e88..91337f333b 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/log4j2-test.xml
+++ b/dubbo-plugin/dubbo-rest-jaxrs/src/test/resources/log4j2-test.xml
@@ -23,6 +23,7 @@
     </Appenders>
     <Loggers>
         <Logger name="org.apache.dubbo.rpc.protocol.tri.stream.StreamUtils" level="error"/>
+        <Logger name="org.apache.dubbo.rpc.protocol.tri.h12" level="debug"/>
         <Root level="info">
             <AppenderRef ref="Console"/>
         </Root>
diff --git a/dubbo-plugin/dubbo-rest-servlet/pom.xml b/dubbo-plugin/dubbo-rest-servlet/pom.xml
deleted file mode 100644
index c4efd467da..0000000000
--- a/dubbo-plugin/dubbo-rest-servlet/pom.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one or more
-  ~ contributor license agreements.  See the NOTICE file distributed with
-  ~ this work for additional information regarding copyright ownership.
-  ~ The ASF licenses this file to You under the Apache License, Version 2.0
-  ~ (the "License"); you may not use this file except in compliance with
-  ~ the License.  You may obtain a copy of the License at
-  ~
-  ~     http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-  <parent>
-    <groupId>org.apache.dubbo</groupId>
-    <artifactId>dubbo-plugin</artifactId>
-    <version>${revision}</version>
-    <relativePath>../pom.xml</relativePath>
-  </parent>
-
-  <artifactId>dubbo-rest-servlet</artifactId>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.apache.dubbo</groupId>
-      <artifactId>dubbo-rpc-triple</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>javax.servlet</groupId>
-      <artifactId>javax.servlet-api</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.dubbo</groupId>
-      <artifactId>dubbo-remoting-netty4</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
-</project>
diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory b/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory
deleted file mode 100644
index ac45cbe947..0000000000
--- a/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory
+++ /dev/null
@@ -1 +0,0 @@
-servlet=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.ServletHttpMessageAdapterFactory
diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver b/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver
deleted file mode 100644
index 5f4a984c54..0000000000
--- a/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver
+++ /dev/null
@@ -1 +0,0 @@
-servlet=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.ServletArgumentResolver
diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter b/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter
deleted file mode 100644
index ec669308fb..0000000000
--- a/dubbo-plugin/dubbo-rest-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter
+++ /dev/null
@@ -1 +0,0 @@
-servlet-filter=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.FilterAdapter
diff --git a/dubbo-plugin/dubbo-rest-servlet/src/test/resources/log4j2-test.xml b/dubbo-plugin/dubbo-rest-servlet/src/test/resources/log4j2-test.xml
deleted file mode 100644
index 34d1c53e88..0000000000
--- a/dubbo-plugin/dubbo-rest-servlet/src/test/resources/log4j2-test.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ Licensed to the Apache Software Foundation (ASF) under one or more
-  ~ contributor license agreements.  See the NOTICE file distributed with
-  ~ this work for additional information regarding copyright ownership.
-  ~ The ASF licenses this file to You under the Apache License, Version 2.0
-  ~ (the "License"); you may not use this file except in compliance with
-  ~ the License.  You may obtain a copy of the License at
-  ~
-  ~     http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<Configuration status="WARN">
-    <Appenders>
-        <Console name="Console" target="SYSTEM_OUT" follow="true">
-            <PatternLayout pattern="%d{HH:mm:ss.SSS} |-%highlight{%-5p} [%t] %40.40c:%-3L -| %m%n%rEx{filters(jdk.internal.reflect,java.lang.reflect,sun.reflect,org.junit,org.mockito)}" charset="UTF-8"/>
-        </Console>
-    </Appenders>
-    <Loggers>
-        <Logger name="org.apache.dubbo.rpc.protocol.tri.stream.StreamUtils" level="error"/>
-        <Root level="info">
-            <AppenderRef ref="Console"/>
-        </Root>
-    </Loggers>
-</Configuration>
diff --git a/dubbo-plugin/dubbo-rest-spring/pom.xml b/dubbo-plugin/dubbo-rest-spring/pom.xml
index 21b3d9c084..a18d8a9f23 100644
--- a/dubbo-plugin/dubbo-rest-spring/pom.xml
+++ b/dubbo-plugin/dubbo-rest-spring/pom.xml
@@ -39,9 +39,14 @@
     </dependency>
     <dependency>
       <groupId>org.apache.dubbo</groupId>
-      <artifactId>dubbo-rest-servlet</artifactId>
+      <artifactId>dubbo-triple-servlet</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
     <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-context</artifactId>
@@ -71,5 +76,47 @@
       <artifactId>jaxb-runtime</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.apache.dubbo</groupId>
+      <artifactId>dubbo-rpc-triple</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.spockframework</groupId>
+      <artifactId>spock-core</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.gmavenplus</groupId>
+        <artifactId>gmavenplus-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>compileTests</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+  <profiles>
+    <profile>
+      <id>rpc-rest-test</id>
+      <dependencies>
+        <dependency>
+          <groupId>org.apache.dubbo</groupId>
+          <artifactId>dubbo-rpc-rest</artifactId>
+          <version>3.3.0-beta.2</version>
+          <scope>test</scope>
+        </dependency>
+      </dependencies>
+    </profile>
+  </profiles>
 </project>
diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java
index fef61723e7..5f3b89e278 100644
--- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java
+++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java
@@ -16,17 +16,25 @@
  */
 package org.apache.dubbo.rpc.protocol.tri.rest.support.spring;
 
+import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.rpc.protocol.tri.rest.argument.AbstractAnnotationBaseArgumentResolver;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.NamedValueMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta;
 
 import java.lang.annotation.Annotation;
+import java.util.Optional;
 
 public abstract class AbstractSpringArgumentResolver extends AbstractAnnotationBaseArgumentResolver {
 
     @Override
     protected NamedValueMeta createNamedValueMeta(ParameterMeta param, AnnotationMeta<Annotation> ann) {
-        return new NamedValueMeta(ann.getValue(), Helper.isRequired(ann), Helper.defaultValue(ann));
+        boolean required = param.getType() != Optional.class && Helper.isRequired(ann);
+        return new NamedValueMeta(ann.getValue(), required, Helper.defaultValue(ann));
+    }
+
+    @Override
+    protected Object filterValue(Object value, NamedValueMeta meta) {
+        return StringUtils.EMPTY_STRING.equals(value) ? meta.defaultValue() : value;
     }
 }
diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java
index be8b1c5865..17936d09ce 100644
--- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java
+++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java
@@ -31,9 +31,11 @@ public enum Annotations implements AnnotationEnum {
     RequestPart,
     RequestBody,
     ModelAttribute,
+    BindParam,
     ResponseStatus,
     CrossOrigin,
     ExceptionHandler,
+    HttpExchange("org.springframework.web.service.annotation.HttpExchange"),
     Nonnull("javax.annotation.Nonnull");
 
     private final String className;
diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BeanArgumentBinder.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BeanArgumentBinder.java
index 17005d5fda..d020e5425a 100644
--- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BeanArgumentBinder.java
+++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BeanArgumentBinder.java
@@ -26,15 +26,11 @@ 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.ArgumentResolver;
 import org.apache.dubbo.rpc.protocol.tri.rest.argument.CompositeArgumentResolver;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.ConstructorMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils;
-import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit;
 
-import java.lang.reflect.AnnotatedElement;
-import java.lang.reflect.Constructor;
 import java.lang.reflect.Modifier;
-import java.lang.reflect.Parameter;
-import java.lang.reflect.Type;
 import java.util.Map;
 
 import org.springframework.beans.MutablePropertyValues;
@@ -44,23 +40,25 @@ import org.springframework.validation.BindingResult;
 import org.springframework.validation.DataBinder;
 import org.springframework.web.bind.WebDataBinder;
 
+import static org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.resolveConstructor;
+
 final class BeanArgumentBinder {
 
+    private static final Map<Class<?>, ConstructorMeta> CACHE = CollectionUtils.newConcurrentHashMap();
+
     private final ArgumentResolver argumentResolver;
     private final ConversionService conversionService;
 
-    private final Map<Class<?>, ConstructorMeta> cache = CollectionUtils.newConcurrentHashMap();
-
     BeanArgumentBinder(FrameworkModel frameworkModel, ConversionService conversionService) {
         ScopeBeanFactory beanFactory = frameworkModel.getBeanFactory();
         argumentResolver = beanFactory.getOrRegisterBean(CompositeArgumentResolver.class);
         this.conversionService = conversionService;
     }
 
-    public Object bind(ParameterMeta parameter, HttpRequest request, HttpResponse response) {
-        String name = StringUtils.defaultIf(parameter.getName(), DataBinder.DEFAULT_OBJECT_NAME);
+    public Object bind(ParameterMeta paramMeta, HttpRequest request, HttpResponse response) {
+        String name = StringUtils.defaultIf(paramMeta.getName(), DataBinder.DEFAULT_OBJECT_NAME);
         try {
-            Object bean = buildBean(parameter, request, response);
+            Object bean = createBean(paramMeta, request, response);
             WebDataBinder binder = new WebDataBinder(bean, name);
             binder.setConversionService(conversionService);
             binder.bind(new MutablePropertyValues(RequestUtils.getParametersMap(request)));
@@ -70,89 +68,26 @@ final class BeanArgumentBinder {
             }
             return binder.getTarget();
         } catch (Exception e) {
-            throw new RestException(e, Messages.ARGUMENT_BIND_ERROR, name, parameter.getType());
+            throw new RestException(e, Messages.ARGUMENT_BIND_ERROR, name, paramMeta.getType());
         }
     }
 
-    private Object buildBean(ParameterMeta parameter, HttpRequest request, HttpResponse response) throws Exception {
-        Class<?> type = parameter.getActualType();
+    private Object createBean(ParameterMeta paramMeta, HttpRequest request, HttpResponse response) {
+        Class<?> type = paramMeta.getActualType();
         if (Modifier.isAbstract(type.getModifiers())) {
-            throw new IllegalStateException(Messages.ARGUMENT_COULD_NOT_RESOLVED.format(parameter.getDescription()));
+            throw new IllegalStateException(Messages.ARGUMENT_COULD_NOT_RESOLVED.format(paramMeta.getDescription()));
         }
-        ConstructorMeta ct = cache.computeIfAbsent(type, k -> resolveConstructor(parameter.getToolKit(), k));
-        ParameterMeta[] parameters = ct.parameters;
+        ConstructorMeta ct = CACHE.computeIfAbsent(type, k -> resolveConstructor(paramMeta.getToolKit(), null, type));
+        ParameterMeta[] parameters = ct.getParameters();
         int len = parameters.length;
+        if (len == 0) {
+            return ct.newInstance();
+        }
         Object[] args = new Object[len];
         for (int i = 0; i < len; i++) {
-            args[i] = argumentResolver.resolve(parameters[i], request, response);
+            ParameterMeta parameter = parameters[i];
+            args[i] = parameter.isSimple() ? argumentResolver.resolve(parameter, request, response) : null;
         }
         return ct.newInstance(args);
     }
-
-    private ConstructorMeta resolveConstructor(RestToolKit toolKit, Class<?> type) {
-        Constructor<?>[] constructors = type.getConstructors();
-        Constructor<?> ct = null;
-        if (constructors.length == 1) {
-            ct = constructors[0];
-        } else {
-            try {
-                ct = type.getDeclaredConstructor();
-            } catch (NoSuchMethodException ignored) {
-            }
-        }
-        if (ct == null) {
-            throw new IllegalArgumentException("No available default constructor found in " + type);
-        }
-        return new ConstructorMeta(toolKit, ct);
-    }
-
-    private static final class ConstructorMeta {
-
-        private final Constructor<?> constructor;
-        private final ConstructorParameterMeta[] parameters;
-
-        ConstructorMeta(RestToolKit toolKit, Constructor<?> constructor) {
-            this.constructor = constructor;
-            parameters = initParameters(toolKit, constructor);
-        }
-
-        private ConstructorParameterMeta[] initParameters(RestToolKit toolKit, Constructor<?> ct) {
-            Parameter[] cps = ct.getParameters();
-            int len = cps.length;
-            ConstructorParameterMeta[] parameters = new ConstructorParameterMeta[len];
-            for (int i = 0; i < len; i++) {
-                parameters[i] = new ConstructorParameterMeta(toolKit, cps[i]);
-            }
-            return parameters;
-        }
-
-        Object newInstance(Object[] args) throws Exception {
-            return constructor.newInstance(args);
-        }
-    }
-
-    public static final class ConstructorParameterMeta extends ParameterMeta {
-
-        private final Parameter parameter;
-
-        ConstructorParameterMeta(RestToolKit toolKit, Parameter parameter) {
-            super(toolKit, parameter.isNamePresent() ? parameter.getName() : null);
-            this.parameter = parameter;
-        }
-
-        @Override
-        protected AnnotatedElement getAnnotatedElement() {
-            return parameter;
-        }
-
-        @Override
-        public Class<?> getType() {
-            return parameter.getType();
-        }
-
-        @Override
-        public Type getGenericType() {
-            return parameter.getParameterizedType();
-        }
-    }
 }
diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BindParamArgumentResolver.java
similarity index 54%
copy from dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java
copy to dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BindParamArgumentResolver.java
index fef61723e7..a4906b7aec 100644
--- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java
+++ b/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/BindParamArgumentResolver.java
@@ -16,17 +16,42 @@
  */
 package org.apache.dubbo.rpc.protocol.tri.rest.support.spring;
 
+import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.remoting.http12.HttpRequest;
+import org.apache.dubbo.remoting.http12.HttpResponse;
 import org.apache.dubbo.rpc.protocol.tri.rest.argument.AbstractAnnotationBaseArgumentResolver;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta;
 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;
 
 import java.lang.annotation.Annotation;
 
-public abstract class AbstractSpringArgumentResolver extends AbstractAnnotationBaseArgumentResolver {
+@Activate(onClass = "org.springframework.web.bind.annotation.BindParam")
+public final class BindParamArgumentResolver extends AbstractAnnotationBaseArgumentResolver {
+
+    @Override
+    public Class<Annotation> accept() {
+        return Annotations.BindParam.type();
+    }
 
     @Override
     protected NamedValueMeta createNamedValueMeta(ParameterMeta param, AnnotationMeta<Annotation> ann) {
-        return new NamedValueMeta(ann.getValue(), Helper.isRequired(ann), Helper.defaultValue(ann));
+        return new NamedValueMeta(ann.getValue(), param.isAnnotated(Annotations.Nonnull), null);
+    }
+
+    @Override
+    protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) {
+        return request.parameter(meta.name());
+    }
+
+    @Override
+    protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) {
+        return request.parameterValues(meta.name());
+    }
+
+    @Override
+    protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) {
+        return RequestUtils.getParametersMap(request);
     }
 }
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 e6de4f568b..728156a98d 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
@@ -62,44 +62,50 @@ public class SpringMvcRequestMappingResolver implements RequestMappingResolver {
     @Override
     public RequestMapping resolve(ServiceMeta serviceMeta) {
         AnnotationMeta<?> requestMapping = serviceMeta.findMergedAnnotation(Annotations.RequestMapping);
-        if (requestMapping == null) {
+        AnnotationMeta<?> httpExchange = serviceMeta.findMergedAnnotation(Annotations.HttpExchange);
+        if (requestMapping == null && httpExchange == null) {
             return null;
         }
-        AnnotationMeta<?> responseStatus = serviceMeta.findMergedAnnotation(Annotations.ResponseStatus);
-        AnnotationMeta<?> crossOrigin = serviceMeta.findMergedAnnotation(Annotations.CrossOrigin);
-        String[] methods = requestMapping.getStringArray("method");
-        return builder(requestMapping, responseStatus)
+
+        String[] methods = requestMapping == null
+                ? httpExchange.getStringArray("method")
+                : requestMapping.getStringArray("method");
+        return builder(requestMapping, httpExchange, serviceMeta.findMergedAnnotation(Annotations.ResponseStatus))
                 .method(methods)
                 .name(serviceMeta.getType().getSimpleName())
                 .contextPath(serviceMeta.getContextPath())
-                .cors(buildCorsMeta(crossOrigin, methods))
+                .cors(buildCorsMeta(serviceMeta.findMergedAnnotation(Annotations.CrossOrigin), methods))
                 .build();
     }
 
     @Override
     public RequestMapping resolve(MethodMeta methodMeta) {
         AnnotationMeta<?> requestMapping = methodMeta.findMergedAnnotation(Annotations.RequestMapping);
-        if (requestMapping == null) {
+        AnnotationMeta<?> httpExchange = methodMeta.findMergedAnnotation(Annotations.HttpExchange);
+        if (requestMapping == null && httpExchange == null) {
             AnnotationMeta<?> exceptionHandler = methodMeta.getAnnotation(Annotations.ExceptionHandler);
             if (exceptionHandler != null) {
+                methodMeta.initParameters();
                 methodMeta.getServiceMeta().addExceptionHandler(methodMeta);
             }
             return null;
         }
+
         ServiceMeta serviceMeta = methodMeta.getServiceMeta();
-        AnnotationMeta<?> responseStatus = methodMeta.findMergedAnnotation(Annotations.ResponseStatus);
-        AnnotationMeta<?> crossOrigin = methodMeta.findMergedAnnotation(Annotations.CrossOrigin);
-        String[] methods = requestMapping.getStringArray("method");
-        return builder(requestMapping, responseStatus)
+        String[] methods = requestMapping == null
+                ? httpExchange.getStringArray("method")
+                : requestMapping.getStringArray("method");
+        return builder(requestMapping, httpExchange, methodMeta.findMergedAnnotation(Annotations.ResponseStatus))
                 .method(methods)
                 .name(methodMeta.getMethod().getName())
                 .contextPath(serviceMeta.getContextPath())
                 .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion()))
-                .cors(buildCorsMeta(crossOrigin, methods))
+                .cors(buildCorsMeta(methodMeta.findMergedAnnotation(Annotations.CrossOrigin), methods))
                 .build();
     }
 
-    private Builder builder(AnnotationMeta<?> requestMapping, AnnotationMeta<?> responseStatus) {
+    private Builder builder(
+            AnnotationMeta<?> requestMapping, AnnotationMeta<?> httpExchange, AnnotationMeta<?> responseStatus) {
         Builder builder = RequestMapping.builder();
         if (responseStatus != null) {
             HttpStatus value = responseStatus.getEnum("value");
@@ -109,6 +115,11 @@ public class SpringMvcRequestMappingResolver implements RequestMappingResolver {
                 builder.responseReason(reason);
             }
         }
+        if (requestMapping == null) {
+            return builder.path(httpExchange.getValueArray())
+                    .consume(httpExchange.getStringArray("contentType"))
+                    .produce(httpExchange.getStringArray("accept"));
+        }
         return builder.path(requestMapping.getValueArray())
                 .param(requestMapping.getStringArray("params"))
                 .header(requestMapping.getStringArray("headers"))
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 9e5011b683..a2638c7748 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
@@ -32,6 +32,7 @@ import org.apache.dubbo.rpc.protocol.tri.rest.util.RestUtils;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Method;
+import java.util.Collection;
 import java.util.Map;
 
 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
@@ -97,6 +98,12 @@ final class SpringRestToolKit implements RestToolKit {
 
     @Override
     public Object convert(Object value, ParameterMeta parameter) {
+        if (value instanceof Collection) {
+            Object target = typeConverter.convert(value, parameter.getGenericType());
+            if (target != null) {
+                return target;
+            }
+        }
         TypeDescriptor targetType = (TypeDescriptor) parameter.getTypeDescriptor();
         if (targetType == null) {
             MethodParameterMeta meta = (MethodParameterMeta) parameter;
diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver b/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver
index 7b0136aee7..36e95f532c 100644
--- a/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver
+++ b/dubbo-plugin/dubbo-rest-spring/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver
@@ -7,6 +7,7 @@ spring-request-attribute=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.R
 spring-request-part=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.RequestPartArgumentResolver
 spring-request-body=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.RequestBodyArgumentResolver
 spring-model-attribute=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.ModelAttributeArgumentResolver
+spring-bind-param=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.BindParamArgumentResolver
 spring-misc=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.SpringMiscArgumentResolver
 spring-fallback=org.apache.dubbo.rpc.protocol.tri.rest.support.spring.FallbackArgumentResolver
 
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
new file mode 100644
index 0000000000..ec1935b122
--- /dev/null
+++ b/dubbo-plugin/dubbo-rest-spring/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/RestProtocolTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.rpc.protocol.tri.rest.support.spring
+
+import org.apache.dubbo.rpc.protocol.tri.rest.service.DemoServiceImpl
+import org.apache.dubbo.rpc.protocol.tri.rest.support.spring.service.SpringDemoServiceImpl
+import org.apache.dubbo.rpc.protocol.tri.rest.test.BaseServiceTest
+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())
+    }
+
+    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'
+    }
+
+    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
+    }
+
+}
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterServiceImpl.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoService.java
similarity index 72%
copy from dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterServiceImpl.java
copy to dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoService.java
index 2b0bedfd21..bdf66f566a 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterServiceImpl.java
+++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoService.java
@@ -14,13 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest;
+package org.apache.dubbo.rpc.protocol.tri.rest.support.spring.service;
 
-import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.User;
+import org.apache.dubbo.rpc.protocol.tri.rest.service.User.UserEx;
 
-public class ParamConverterServiceImpl implements ParamConverterService {
-    @Override
-    public User convert(User user) {
-        return user;
-    }
+import org.springframework.web.bind.annotation.GetMapping;
+
+public interface SpringDemoService {
+
+    @GetMapping("/springBean")
+    UserEx beanArgTest(UserEx user);
 }
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterServiceImpl.java b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoServiceImpl.java
similarity index 76%
copy from dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterServiceImpl.java
copy to dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoServiceImpl.java
index 2b0bedfd21..97ef3cfe71 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterServiceImpl.java
+++ b/dubbo-plugin/dubbo-rest-spring/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/service/SpringDemoServiceImpl.java
@@ -14,13 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest;
+package org.apache.dubbo.rpc.protocol.tri.rest.support.spring.service;
 
-import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.User;
+import org.apache.dubbo.rpc.protocol.tri.rest.service.User.UserEx;
+
+public class SpringDemoServiceImpl implements SpringDemoService {
 
-public class ParamConverterServiceImpl implements ParamConverterService {
     @Override
-    public User convert(User user) {
+    public UserEx beanArgTest(UserEx user) {
         return user;
     }
 }
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 34d1c53e88..91337f333b 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
@@ -23,6 +23,7 @@
     </Appenders>
     <Loggers>
         <Logger name="org.apache.dubbo.rpc.protocol.tri.stream.StreamUtils" level="error"/>
+        <Logger name="org.apache.dubbo.rpc.protocol.tri.h12" level="debug"/>
         <Root level="info">
             <AppenderRef ref="Console"/>
         </Root>
diff --git a/dubbo-plugin/dubbo-triple-servlet/pom.xml b/dubbo-plugin/dubbo-triple-servlet/pom.xml
index 0258304f99..ab55ef6942 100644
--- a/dubbo-plugin/dubbo-triple-servlet/pom.xml
+++ b/dubbo-plugin/dubbo-triple-servlet/pom.xml
@@ -28,7 +28,7 @@
 
   <properties>
     <servlet4_version>4.0.1</servlet4_version>
-    <sources_directory>${project.build.directory}/generated-sources/java/org/apache/dubbo/rpc/protocol/tri/servlet/jakarta</sources_directory>
+    <sources_directory>${project.build.directory}/generated-sources/java/org/apache/dubbo/rpc/protocol/tri</sources_directory>
   </properties>
 
   <dependencies>
@@ -69,22 +69,43 @@
             <artifactId>maven-antrun-plugin</artifactId>
             <executions>
               <execution>
-                <id>copy-sources</id>
+                <id>generate-jakarta</id>
                 <goals>
                   <goal>run</goal>
                 </goals>
                 <phase>generate-sources</phase>
                 <configuration>
                   <target>
-                    <copy overwrite="true" todir="${sources_directory}">
+                    <copy overwrite="true" todir="${sources_directory}/servlet/jakarta">
                       <fileset dir="src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet" />
                     </copy>
-                    <replace token="tri.servlet;" value="tri.servlet.jakarta;">
+                    <copy overwrite="true" todir="${sources_directory}/rest/support/servlet/jakarta">
+                      <fileset dir="src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet" />
+                    </copy>
+                    <replace token=".servlet;" value=".servlet.jakarta;">
                       <fileset dir="${sources_directory}" />
                     </replace>
                     <replace token="javax.servlet" value="jakarta.servlet">
                       <fileset dir="${sources_directory}" />
                     </replace>
+                    <replace file="${sources_directory}/rest/support/servlet/jakarta/ServletHttpRequestAdapter.java">
+                      <replacetoken>/* jakarta placeholder */</replacetoken>
+                      <replacevalue><![CDATA[@Override
+    public String getRequestId() {
+        return "";
+    }
+
+    @Override
+    public String getProtocolRequestId() {
+        return "";
+    }
+
+    @Override
+    public jakarta.servlet.ServletConnection getServletConnection() {
+        return null;
+    }]]></replacevalue>
+                    </replace>
+                    <replace file="${sources_directory}/rest/support/servlet/jakarta/ServletHttpMessageAdapterFactory.java" token="-100" value="-200" />
                   </target>
                 </configuration>
               </execution>
diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyFilterConfig.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyFilterConfig.java
similarity index 100%
rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyFilterConfig.java
rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyFilterConfig.java
diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyServletContext.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyServletContext.java
similarity index 91%
rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyServletContext.java
rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyServletContext.java
index 3bafcd4367..e3b382537c 100644
--- a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyServletContext.java
+++ b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/DummyServletContext.java
@@ -28,6 +28,7 @@ import javax.servlet.RequestDispatcher;
 import javax.servlet.Servlet;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRegistration.Dynamic;
 import javax.servlet.SessionCookieConfig;
 import javax.servlet.SessionTrackingMode;
 import javax.servlet.descriptor.JspConfigDescriptor;
@@ -111,17 +112,14 @@ final class DummyServletContext implements ServletContext {
         return null;
     }
 
-    @Override
     public Servlet getServlet(String name) {
         throw new UnsupportedOperationException();
     }
 
-    @Override
     public Enumeration<Servlet> getServlets() {
         throw new UnsupportedOperationException();
     }
 
-    @Override
     public Enumeration<String> getServletNames() {
         throw new UnsupportedOperationException();
     }
@@ -131,7 +129,6 @@ final class DummyServletContext implements ServletContext {
         LoggerFactory.getLogger(DummyServletContext.class).info(msg);
     }
 
-    @Override
     public void log(Exception exception, String msg) {
         LoggerFactory.getLogger(DummyServletContext.class).info(msg, exception);
     }
@@ -211,6 +208,11 @@ final class DummyServletContext implements ServletContext {
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    public Dynamic addJspFile(String servletName, String jspFile) {
+        return null;
+    }
+
     @Override
     public <T extends Servlet> T createServlet(Class<T> clazz) {
         throw new UnsupportedOperationException();
@@ -315,4 +317,34 @@ final class DummyServletContext implements ServletContext {
     public String getVirtualServerName() {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public int getSessionTimeout() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setSessionTimeout(int i) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getRequestCharacterEncoding() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setRequestCharacterEncoding(String encoding) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getResponseCharacterEncoding() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setResponseCharacterEncoding(String encoding) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/FilterAdapter.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/FilterAdapter.java
similarity index 100%
rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/FilterAdapter.java
rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/FilterAdapter.java
diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/Helper.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/Helper.java
similarity index 100%
rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/Helper.java
rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/Helper.java
diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/HttpSessionFactory.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/HttpSessionFactory.java
similarity index 100%
rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/HttpSessionFactory.java
rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/HttpSessionFactory.java
diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletArgumentResolver.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletArgumentResolver.java
similarity index 100%
rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletArgumentResolver.java
rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletArgumentResolver.java
diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpMessageAdapterFactory.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpMessageAdapterFactory.java
similarity index 90%
rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpMessageAdapterFactory.java
rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpMessageAdapterFactory.java
index 1f85d58b74..1862593203 100644
--- a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpMessageAdapterFactory.java
+++ b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpMessageAdapterFactory.java
@@ -29,7 +29,7 @@ import javax.servlet.ServletContext;
 
 @Activate(order = -100, onClass = "javax.servlet.http.HttpServletRequest")
 public final class ServletHttpMessageAdapterFactory
-        implements HttpMessageAdapterFactory<ServletHttpRequestAdaptee, HttpMetadata, Void> {
+        implements HttpMessageAdapterFactory<ServletHttpRequestAdapter, HttpMetadata, Void> {
 
     private final FrameworkModel frameworkModel;
     private final ServletContext servletContext;
@@ -51,13 +51,13 @@ public final class ServletHttpMessageAdapterFactory
     }
 
     @Override
-    public ServletHttpRequestAdaptee adaptRequest(HttpMetadata rawRequest, HttpChannel channel) {
-        return new ServletHttpRequestAdaptee(rawRequest, channel, servletContext, httpSessionFactory);
+    public ServletHttpRequestAdapter adaptRequest(HttpMetadata rawRequest, HttpChannel channel) {
+        return new ServletHttpRequestAdapter(rawRequest, channel, servletContext, httpSessionFactory);
     }
 
     @Override
-    public HttpResponse adaptResponse(ServletHttpRequestAdaptee request, HttpMetadata rawRequest, Void rawResponse) {
-        return new ServletHttpResponseAdaptee();
+    public HttpResponse adaptResponse(ServletHttpRequestAdapter request, HttpMetadata rawRequest, Void rawResponse) {
+        return new ServletHttpResponseAdapter();
     }
 
     public FilterConfig adaptFilterConfig(String filterName) {
diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdaptee.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdapter.java
similarity index 98%
rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdaptee.java
rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdapter.java
index 6099a6c8d3..46c7b9a637 100644
--- a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdaptee.java
+++ b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpRequestAdapter.java
@@ -50,7 +50,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
-public class ServletHttpRequestAdaptee extends DefaultHttpRequest implements HttpServletRequest {
+public class ServletHttpRequestAdapter extends DefaultHttpRequest implements HttpServletRequest {
 
     private final ServletContext servletContext;
     private final HttpSessionFactory sessionFactory;
@@ -58,7 +58,7 @@ public class ServletHttpRequestAdaptee extends DefaultHttpRequest implements Htt
     private ServletInputStream sis;
     private BufferedReader reader;
 
-    public ServletHttpRequestAdaptee(
+    public ServletHttpRequestAdapter(
             HttpMetadata metadata,
             HttpChannel channel,
             ServletContext servletContext,
@@ -211,7 +211,6 @@ public class ServletHttpRequestAdaptee extends DefaultHttpRequest implements Htt
         return false;
     }
 
-    @Override
     public boolean isRequestedSessionIdFromUrl() {
         return false;
     }
@@ -376,7 +375,6 @@ public class ServletHttpRequestAdaptee extends DefaultHttpRequest implements Htt
         throw new UnsupportedOperationException();
     }
 
-    @Override
     public String getRealPath(String path) {
         return null;
     }
@@ -437,9 +435,11 @@ public class ServletHttpRequestAdaptee extends DefaultHttpRequest implements Htt
         return DispatcherType.REQUEST;
     }
 
+    /* jakarta placeholder */
+
     @Override
     public String toString() {
-        return "ServletHttpRequestAdaptee{" + fieldToString() + '}';
+        return "ServletHttpRequestAdapter{" + fieldToString() + '}';
     }
 
     private static final class HttpInputStream extends ServletInputStream {
diff --git a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdaptee.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdapter.java
similarity index 95%
rename from dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdaptee.java
rename to dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdapter.java
index 75068cf399..85e3f2c973 100644
--- a/dubbo-plugin/dubbo-rest-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdaptee.java
+++ b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/servlet/ServletHttpResponseAdapter.java
@@ -34,7 +34,7 @@ import java.util.Collection;
 import java.util.Date;
 import java.util.Locale;
 
-public class ServletHttpResponseAdaptee extends DefaultHttpResponse implements HttpServletResponse {
+public class ServletHttpResponseAdapter extends DefaultHttpResponse implements HttpServletResponse {
 
     private ServletOutputStream sos;
     private PrintWriter writer;
@@ -59,16 +59,18 @@ public class ServletHttpResponseAdaptee extends DefaultHttpResponse implements H
         return RequestUtils.encodeURL(url);
     }
 
-    @Override
     public String encodeUrl(String url) {
         return RequestUtils.encodeURL(url);
     }
 
-    @Override
     public String encodeRedirectUrl(String url) {
         return RequestUtils.encodeURL(url);
     }
 
+    public void sendRedirect(String location, int sc, boolean clearBuffer) {
+        sendRedirect(location);
+    }
+
     @Override
     public void setDateHeader(String name, long date) {
         setHeader(name, new Date(date));
@@ -89,7 +91,6 @@ public class ServletHttpResponseAdaptee extends DefaultHttpResponse implements H
         addHeader(name, String.valueOf(value));
     }
 
-    @Override
     public void setStatus(int sc, String sm) {
         setStatus(sc);
         setBody(sm);
@@ -182,7 +183,7 @@ public class ServletHttpResponseAdaptee extends DefaultHttpResponse implements H
 
     @Override
     public String toString() {
-        return "ServletHttpResponseAdaptee{" + fieldToString() + '}';
+        return "ServletHttpResponseAdapter{" + fieldToString() + '}';
     }
 
     private static final class HttpOutputStream extends ServletOutputStream {
diff --git a/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet/HttpMetadataAdapter.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet/HttpMetadataAdapter.java
index 15af2654c7..83e29af618 100644
--- a/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet/HttpMetadataAdapter.java
+++ b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet/HttpMetadataAdapter.java
@@ -25,7 +25,7 @@ import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.List;
 
-final class HttpMetadataAdapter implements Http2Header {
+public final class HttpMetadataAdapter implements Http2Header {
 
     private final HttpServletRequest request;
 
@@ -35,6 +35,10 @@ final class HttpMetadataAdapter implements Http2Header {
         this.request = request;
     }
 
+    public HttpServletRequest getRequest() {
+        return request;
+    }
+
     @Override
     public HttpHeaders headers() {
         HttpHeaders headers = this.headers;
diff --git a/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet/TripleFilter.java b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet/TripleFilter.java
index 17f1d107a6..b009588853 100644
--- a/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet/TripleFilter.java
+++ b/dubbo-plugin/dubbo-triple-servlet/src/main/java/org/apache/dubbo/rpc/protocol/tri/servlet/TripleFilter.java
@@ -20,6 +20,7 @@ import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.io.StreamUtils;
 import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.remoting.http12.h2.H2StreamChannel;
 import org.apache.dubbo.remoting.http12.h2.Http2InputMessageFrame;
 import org.apache.dubbo.remoting.http12.h2.Http2ServerTransportListenerFactory;
@@ -70,7 +71,7 @@ public class TripleFilter implements Filter {
         pathResolver = frameworkModel.getDefaultExtension(PathResolver.class);
         mappingRegistry = frameworkModel.getBeanFactory().getOrRegisterBean(DefaultRequestMappingRegistry.class);
         String timeoutString = config.getInitParameter("timeout");
-        defaultTimeout = timeoutString == null ? 180_000 : Integer.parseInt(timeoutString);
+        defaultTimeout = StringUtils.isEmpty(timeoutString) ? 180_000 : Integer.parseInt(timeoutString);
     }
 
     @Override
diff --git a/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory b/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory
new file mode 100644
index 0000000000..791e0a790b
--- /dev/null
+++ b/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory
@@ -0,0 +1,2 @@
+servlet=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.ServletHttpMessageAdapterFactory
+jakarta-servlet=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.jakarta.ServletHttpMessageAdapterFactory
diff --git a/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver b/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver
new file mode 100644
index 0000000000..fd026764ba
--- /dev/null
+++ b/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver
@@ -0,0 +1,2 @@
+servlet=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.ServletArgumentResolver
+jakarta-servlet=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.jakarta.ServletArgumentResolver
diff --git a/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter b/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter
new file mode 100644
index 0000000000..3a2169eb35
--- /dev/null
+++ b/dubbo-plugin/dubbo-triple-servlet/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter
@@ -0,0 +1,2 @@
+servlet-filter=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.FilterAdapter
+jakarta-servlet-filter=org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.jakarta.FilterAdapter
diff --git a/dubbo-plugin/pom.xml b/dubbo-plugin/pom.xml
index df9b5dccde..1cfb0efb14 100644
--- a/dubbo-plugin/pom.xml
+++ b/dubbo-plugin/pom.xml
@@ -41,7 +41,6 @@
     <module>dubbo-filter-cache</module>
     <module>dubbo-filter-validation</module>
     <module>dubbo-rest-jaxrs</module>
-    <module>dubbo-rest-servlet</module>
     <module>dubbo-rest-spring</module>
     <module>dubbo-triple-servlet</module>
   </modules>
diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/CompositeInputStream.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/CompositeInputStream.java
index 9b8b248eb0..58c069e52e 100644
--- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/CompositeInputStream.java
+++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/CompositeInputStream.java
@@ -60,6 +60,42 @@ public class CompositeInputStream extends InputStream {
         return -1;
     }
 
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        if (b == null) {
+            throw new NullPointerException();
+        } else if (off < 0 || len < 0 || len > b.length - off) {
+            throw new IndexOutOfBoundsException();
+        } else if (len == 0) {
+            return 0;
+        }
+
+        int total = 0;
+        InputStream inputStream;
+        while ((inputStream = inputStreams.peek()) != null) {
+            int available = inputStream.available();
+            if (available == 0) {
+                releaseHeadStream();
+                continue;
+            }
+
+            int read = inputStream.read(b, off + total, Math.min(len - total, available));
+            if (read != -1) {
+                total += read;
+                readIndex += read;
+                releaseIfNecessary(inputStream);
+
+                if (total == len) {
+                    return total;
+                }
+            } else {
+                releaseHeadStream();
+            }
+        }
+
+        return total > 0 ? total : -1;
+    }
+
     @Override
     public int available() {
         return totalAvailable - readIndex;
@@ -81,7 +117,6 @@ public class CompositeInputStream extends InputStream {
     private void releaseIfNecessary(InputStream inputStream) throws IOException {
         int available = inputStream.available();
         if (available == 0) {
-            inputStream.close();
             releaseHeadStream();
         }
     }
diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpRequest.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpRequest.java
index 50fcc001d7..4aa03ff4a5 100644
--- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpRequest.java
+++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpRequest.java
@@ -116,6 +116,8 @@ public interface HttpRequest extends RequestMetadata {
 
     Collection<String> queryParameterNames();
 
+    Map<String, List<String>> queryParameters();
+
     String formParameter(String name);
 
     List<String> formParameterValues(String name);
diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java
index 41dea99f99..5c88a5e1b0 100644
--- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java
+++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java
@@ -81,6 +81,10 @@ public class DefaultHttpRequest implements HttpRequest {
         }
     }
 
+    public HttpMetadata getMetadata() {
+        return metadata;
+    }
+
     @Override
     public boolean isHttp2() {
         return metadata instanceof Http2Header;
@@ -436,6 +440,11 @@ public class DefaultHttpRequest implements HttpRequest {
         return getDecoder().parameters().keySet();
     }
 
+    @Override
+    public Map<String, List<String>> queryParameters() {
+        return getDecoder().parameters();
+    }
+
     @Override
     public String formParameter(String name) {
         HttpPostRequestDecoder postDecoder = getPostDecoder();
@@ -475,7 +484,7 @@ public class DefaultHttpRequest implements HttpRequest {
                 values.add(HttpUtils.readPostValue(item));
             }
         }
-        return values;
+        return values == null ? Collections.emptyList() : values;
     }
 
     @Override
@@ -498,7 +507,7 @@ public class DefaultHttpRequest implements HttpRequest {
                 names.add(item.getName());
             }
         }
-        return names;
+        return names == null ? Collections.emptyList() : names;
     }
 
     @Override
@@ -544,7 +553,7 @@ public class DefaultHttpRequest implements HttpRequest {
                 allNames.add(item.getName());
             }
         }
-        return allNames;
+        return allNames == null ? Collections.emptyList() : allNames;
     }
 
     @Override
diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/CodecUtils.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/CodecUtils.java
index 281ee15eb9..98428bb9a8 100644
--- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/CodecUtils.java
+++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/CodecUtils.java
@@ -50,12 +50,20 @@ public final class CodecUtils {
                 .createCodec(url, frameworkModel, mediaType);
     }
 
+    public HttpMessageDecoder determineHttpMessageDecoder(String mediaType) {
+        return determineHttpMessageDecoder(null, null, mediaType);
+    }
+
     public HttpMessageEncoder determineHttpMessageEncoder(URL url, FrameworkModel frameworkModel, String mediaType) {
         return determineHttpMessageEncoderFactory(mediaType)
                 .orElseThrow(() -> new UnsupportedMediaTypeException(mediaType))
                 .createCodec(url, frameworkModel, mediaType);
     }
 
+    public HttpMessageEncoder determineHttpMessageEncoder(String mediaType) {
+        return determineHttpMessageEncoder(null, null, mediaType);
+    }
+
     public Optional<HttpMessageDecoderFactory> determineHttpMessageDecoderFactory(String mediaType) {
         Assert.notNull(mediaType, "mediaType must not be null");
         return decoderCache.computeIfAbsent(mediaType, k -> {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Mapping.java
similarity index 54%
copy from dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java
copy to dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Mapping.java
index c7ee0ba7dd..a05e479306 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java
+++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Mapping.java
@@ -14,35 +14,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.config.nested;
+package org.apache.dubbo.remoting.http12.rest;
 
-import java.io.Serializable;
+import org.apache.dubbo.remoting.http12.HttpMethods;
 
-/**
- * Configuration for the propagation.
- */
-public class PropagationConfig implements Serializable {
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Mapping {
 
-    public static final String B3 = "B3";
+    String[] value() default {};
 
-    public static final String W3C = "W3C";
+    String[] path() default {};
 
-    /**
-     * Tracing context propagation type.
-     */
-    private String type = W3C;
+    HttpMethods[] method() default {};
 
-    public PropagationConfig() {}
+    String[] params() default {};
 
-    public PropagationConfig(String type) {
-        this.type = type;
-    }
+    String[] headers() default {};
 
-    public String getType() {
-        return type;
-    }
+    String[] consumes() default {};
 
-    public void setType(String type) {
-        this.type = type;
-    }
+    String[] produces() default {};
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Param.java
similarity index 57%
copy from dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java
copy to dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Param.java
index c7ee0ba7dd..2ff94d4d32 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java
+++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/Param.java
@@ -14,35 +14,26 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.config.nested;
+package org.apache.dubbo.remoting.http12.rest;
 
-import java.io.Serializable;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 
-/**
- * Configuration for the propagation.
- */
-public class PropagationConfig implements Serializable {
-
-    public static final String B3 = "B3";
-
-    public static final String W3C = "W3C";
+@Target({ElementType.PARAMETER, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Param {
 
-    /**
-     * Tracing context propagation type.
-     */
-    private String type = W3C;
+    String DEFAULT_NONE = "\n\t\t\n\t\t\n_DEFAULT_NONE_\n\t\t\t\t\n";
 
-    public PropagationConfig() {}
+    ParamType type() default ParamType.Param;
 
-    public PropagationConfig(String type) {
-        this.type = type;
-    }
+    String value() default "";
 
-    public String getType() {
-        return type;
-    }
+    boolean required() default true;
 
-    public void setType(String type) {
-        this.type = type;
-    }
+    String defaultValue() default DEFAULT_NONE;
 }
diff --git a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterServiceImpl.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/ParamType.java
similarity index 72%
rename from dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterServiceImpl.java
rename to dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/ParamType.java
index 2b0bedfd21..fac5b864c4 100644
--- a/dubbo-plugin/dubbo-rest-jaxrs/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/support/jaxrs/compatible/rest/ParamConverterServiceImpl.java
+++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/rest/ParamType.java
@@ -14,13 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.rest;
+package org.apache.dubbo.remoting.http12.rest;
 
-import org.apache.dubbo.rpc.protocol.tri.rest.support.jaxrs.compatible.User;
-
-public class ParamConverterServiceImpl implements ParamConverterService {
-    @Override
-    public User convert(User user) {
-        return user;
-    }
+public enum ParamType {
+    PathVariable,
+    MatrixVariable,
+    Param,
+    Header,
+    Cookie,
+    Attribute,
+    Part,
+    Body
 }
diff --git a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/Helper.java b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/Helper.java
index b1d88ff901..6df29c4f06 100644
--- a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/Helper.java
+++ b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/Helper.java
@@ -18,6 +18,7 @@ package org.apache.dubbo.remoting.transport.netty4;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.config.context.ConfigManager;
+import org.apache.dubbo.config.nested.Http3Config;
 import org.apache.dubbo.config.nested.TripleConfig;
 
 import io.netty.incubator.codec.quic.QuicCodecBuilder;
@@ -30,43 +31,37 @@ final class Helper {
     @SuppressWarnings("unchecked")
     static <T extends QuicCodecBuilder<T>> T configCodec(QuicCodecBuilder<T> builder, URL url) {
         TripleConfig tripleConfig = ConfigManager.getProtocol(url).getTriple();
-        if (tripleConfig.getHttp3InitialMaxData() != null) {
-            builder.initialMaxData(tripleConfig.getHttp3InitialMaxData());
+        Http3Config config = tripleConfig.getHttp3();
+        if (config == null) {
+            config = new Http3Config();
         }
-        if (tripleConfig.getHttp3RecvQueueLen() != null && tripleConfig.getHttp3SendQueueLen() != null) {
-            builder.datagram(tripleConfig.getHttp3RecvQueueLen(), tripleConfig.getHttp3SendQueueLen());
-        }
-        if (tripleConfig.getHttp3InitialMaxStreamDataBidiLocal() != null) {
-            builder.initialMaxStreamDataBidirectionalLocal(tripleConfig.getHttp3InitialMaxStreamDataBidiLocal());
-        }
-        if (tripleConfig.getHttp3InitialMaxStreamDataBidiRemote() != null) {
-            builder.initialMaxStreamDataBidirectionalRemote(tripleConfig.getHttp3InitialMaxStreamDataBidiRemote());
-        }
-        if (tripleConfig.getHttp3InitialMaxStreamDataUni() != null) {
-            builder.initialMaxStreamDataUnidirectional(tripleConfig.getHttp3InitialMaxStreamDataUni());
-        }
-        if (tripleConfig.getHttp3InitialMaxStreamsBidi() != null) {
-            builder.initialMaxStreamsBidirectional(tripleConfig.getHttp3InitialMaxStreamsBidi());
-        }
-        if (tripleConfig.getHttp3InitialMaxStreamsUni() != null) {
-            builder.initialMaxStreamsUnidirectional(tripleConfig.getHttp3InitialMaxStreamsUni());
+        config.checkDefault();
+        builder.initialMaxData(config.getInitialMaxData())
+                .initialMaxStreamDataBidirectionalLocal(config.getInitialMaxStreamDataBidiLocal())
+                .initialMaxStreamDataBidirectionalRemote(config.getInitialMaxStreamDataBidiRemote())
+                .initialMaxStreamDataUnidirectional(config.getInitialMaxStreamDataUni())
+                .initialMaxStreamsBidirectional(config.getInitialMaxStreamsBidi())
+                .initialMaxStreamsUnidirectional(config.getInitialMaxStreamsUni());
+
+        if (config.getRecvQueueLen() != null && config.getSendQueueLen() != null) {
+            builder.datagram(config.getRecvQueueLen(), config.getSendQueueLen());
         }
-        if (tripleConfig.getHttp3MaxAckDelayExponent() != null) {
-            builder.ackDelayExponent(tripleConfig.getHttp3MaxAckDelayExponent());
+        if (config.getMaxAckDelayExponent() != null) {
+            builder.ackDelayExponent(config.getMaxAckDelayExponent());
         }
-        if (tripleConfig.getHttp3MaxAckDelay() != null) {
-            builder.maxAckDelay(tripleConfig.getHttp3MaxAckDelay(), MILLISECONDS);
+        if (config.getMaxAckDelay() != null) {
+            builder.maxAckDelay(config.getMaxAckDelay(), MILLISECONDS);
         }
-        if (tripleConfig.getHttp3DisableActiveMigration() != null) {
-            builder.activeMigration(tripleConfig.getHttp3DisableActiveMigration());
+        if (config.getDisableActiveMigration() != null) {
+            builder.activeMigration(config.getDisableActiveMigration());
         }
-        if (tripleConfig.getHttp3EnableHystart() != null) {
-            builder.hystart(tripleConfig.getHttp3EnableHystart());
+        if (config.getEnableHystart() != null) {
+            builder.hystart(config.getEnableHystart());
         }
-        if (tripleConfig.getHttp3CcAlgorithm() != null) {
-            if ("RENO".equalsIgnoreCase(tripleConfig.getHttp3CcAlgorithm())) {
+        if (config.getCcAlgorithm() != null) {
+            if ("RENO".equalsIgnoreCase(config.getCcAlgorithm())) {
                 builder.congestionControlAlgorithm(QuicCongestionControlAlgorithm.RENO);
-            } else if ("BBR".equalsIgnoreCase(tripleConfig.getHttp3CcAlgorithm())) {
+            } else if ("BBR".equalsIgnoreCase(config.getCcAlgorithm())) {
                 builder.congestionControlAlgorithm(QuicCongestionControlAlgorithm.BBR);
             }
         }
diff --git a/dubbo-remoting/dubbo-remoting-zookeeper-curator5/pom.xml b/dubbo-remoting/dubbo-remoting-zookeeper-curator5/pom.xml
index 5f57672ced..5b8510d6f8 100644
--- a/dubbo-remoting/dubbo-remoting-zookeeper-curator5/pom.xml
+++ b/dubbo-remoting/dubbo-remoting-zookeeper-curator5/pom.xml
@@ -46,11 +46,6 @@
       <scope>test</scope>
     </dependency>
 
-    <dependency>
-      <groupId>org.apache.dubbo</groupId>
-      <artifactId>dubbo-common</artifactId>
-      <version>${project.parent.version}</version>
-    </dependency>
     <dependency>
       <groupId>org.apache.curator</groupId>
       <artifactId>curator-framework</artifactId>
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Constants.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Constants.java
index 0e6100827d..9327ad6a7b 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Constants.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Constants.java
@@ -105,8 +105,8 @@ public interface Constants {
     String H2_SETTINGS_BUILTIN_SERVICE_INIT = "dubbo.tri.builtin.service.init";
     String H2_SETTINGS_PASS_THROUGH_STANDARD_HTTP_HEADERS = "dubbo.rpc.tri.pass-through-standard-http-headers";
 
-    String H3_SETTINGS_HTTP3_ENABLE = "dubbo.protocol.triple.enable-http3";
-    String H3_SETTINGS_SERVLET_ENABLE = "dubbo.protocol.triple.enable-servlet";
+    String H3_SETTINGS_HTTP3_ENABLE = "dubbo.protocol.triple.http3.enable";
+    String H3_SETTINGS_SERVLET_ENABLE = "dubbo.protocol.triple.servlet.enable";
 
     String ADAPTIVE_LOADBALANCE_ATTACHMENT_KEY = "lb_adaptive";
     String ADAPTIVE_LOADBALANCE_START_TIME = "adaptive_startTime";
diff --git a/dubbo-rpc/dubbo-rpc-triple/pom.xml b/dubbo-rpc/dubbo-rpc-triple/pom.xml
index aa29ea0bfe..cde7d597af 100644
--- a/dubbo-rpc/dubbo-rpc-triple/pom.xml
+++ b/dubbo-rpc/dubbo-rpc-triple/pom.xml
@@ -52,6 +52,12 @@
       <version>${project.parent.version}</version>
       <optional>true</optional>
     </dependency>
+    <dependency>
+      <groupId>org.apache.dubbo</groupId>
+      <artifactId>dubbo-cluster</artifactId>
+      <version>${project.parent.version}</version>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>io.netty</groupId>
       <artifactId>netty-codec-http2</artifactId>
@@ -100,6 +106,11 @@
       <artifactId>reactor-core</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.spockframework</groupId>
+      <artifactId>spock-core</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.apache.dubbo</groupId>
       <artifactId>dubbo-native</artifactId>
@@ -161,6 +172,40 @@
           <skip>true</skip>
         </configuration>
       </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>test-jar</goal>
+            </goals>
+            <phase>package</phase>
+            <configuration>
+              <skip>false</skip>
+              <includes>
+                <include>org/apache/dubbo/rpc/protocol/tri/rest/test/*</include>
+                <include>org/apache/dubbo/rpc/protocol/tri/rest/service/*</include>
+                <include>org/apache/dubbo/rpc/protocol/tri/test/*</include>
+                <include>META-INF/dubbo/**</include>
+              </includes>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.gmavenplus</groupId>
+        <artifactId>gmavenplus-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>compileTests</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
     </plugins>
   </build>
 </project>
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleConstant.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleConstant.java
index f36b78e6eb..0289e08f78 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleConstant.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleConstant.java
@@ -39,6 +39,5 @@ public class TripleConstant {
     public static final String HTTP_RESPONSE_KEY = "tri.http.response";
 
     public static final String TRIPLE_HANDLER_TYPE_REST = "rest";
-    public static final String TRIPLE_HANDLER_TYPE_HTTP = "http";
     public static final String TRIPLE_HANDLER_TYPE_GRPC = "grpc";
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpRequestHandlerMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpRequestHandlerMapping.java
deleted file mode 100644
index 2dbfea05fa..0000000000
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/HttpRequestHandlerMapping.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.rpc.protocol.tri.h12;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.extension.Activate;
-import org.apache.dubbo.remoting.http12.HttpRequest;
-import org.apache.dubbo.remoting.http12.HttpResponse;
-import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder;
-import org.apache.dubbo.remoting.http12.message.HttpMessageEncoder;
-import org.apache.dubbo.remoting.http12.message.MediaType;
-import org.apache.dubbo.remoting.http12.message.codec.CodecUtils;
-import org.apache.dubbo.rpc.Invoker;
-import org.apache.dubbo.rpc.PathResolver;
-import org.apache.dubbo.rpc.model.FrameworkModel;
-import org.apache.dubbo.rpc.protocol.tri.DescriptorUtils;
-import org.apache.dubbo.rpc.protocol.tri.TripleConstant;
-import org.apache.dubbo.rpc.protocol.tri.TripleHeaderEnum;
-import org.apache.dubbo.rpc.protocol.tri.TripleProtocol;
-import org.apache.dubbo.rpc.protocol.tri.route.RequestHandler;
-import org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping;
-
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-
-@Activate(order = -1000)
-public class HttpRequestHandlerMapping implements RequestHandlerMapping {
-
-    private final FrameworkModel frameworkModel;
-    private final PathResolver pathResolver;
-    private final CodecUtils codecUtils;
-
-    public HttpRequestHandlerMapping(FrameworkModel frameworkModel) {
-        this.frameworkModel = frameworkModel;
-        pathResolver = frameworkModel.getDefaultExtension(PathResolver.class);
-        codecUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CodecUtils.class);
-    }
-
-    @Override
-    public RequestHandler getRequestHandler(URL url, HttpRequest request, HttpResponse response) {
-        if (!supportContentType(request.contentType())) {
-            return null;
-        }
-
-        String uri = request.uri();
-        int index = uri.indexOf('/', 1);
-        if (index == -1) {
-            return null;
-        }
-        if (uri.indexOf('/', index + 1) != -1) {
-            return null;
-        }
-
-        String serviceName = uri.substring(1, index);
-        String version = request.header(TripleHeaderEnum.SERVICE_VERSION.getHeader());
-        String group = request.header(TripleHeaderEnum.SERVICE_GROUP.getHeader());
-        String key = URL.buildKey(serviceName, group, version);
-        Invoker<?> invoker = pathResolver.resolve(key);
-        if (invoker == null && TripleProtocol.RESOLVE_FALLBACK_TO_DEFAULT) {
-            invoker = pathResolver.resolve(URL.buildKey(serviceName, group, TripleConstant.DEFAULT_VERSION));
-            if (invoker == null) {
-                invoker = pathResolver.resolve(serviceName);
-                if (invoker == null) {
-                    return null;
-                }
-            }
-        }
-
-        RequestHandler handler = new RequestHandler(invoker);
-        handler.setHasStub(pathResolver.hasNativeStub(uri));
-        handler.setMethodName(uri.substring(index + 1));
-        handler.setServiceDescriptor(DescriptorUtils.findServiceDescriptor(invoker, serviceName, handler.isHasStub()));
-        determineHttpMessageCodec(handler, url, request);
-        return handler;
-    }
-
-    protected boolean supportContentType(String contentType) {
-        return true;
-    }
-
-    protected void determineHttpMessageCodec(RequestHandler handler, URL url, HttpRequest request) {
-        String mediaType = request.mediaType();
-        if (mediaType == null) {
-            mediaType = MediaType.APPLICATION_JSON.getName();
-            request.setContentType(mediaType);
-        }
-
-        Charset charset = request.charsetOrDefault();
-        HttpMessageDecoder decoder = codecUtils.determineHttpMessageDecoder(url, frameworkModel, mediaType);
-        HttpMessageEncoder encoder = codecUtils.determineHttpMessageEncoder(url, frameworkModel, mediaType);
-        if (!StandardCharsets.UTF_8.equals(charset)) {
-            decoder = new HttpMessageDecoderWrapper(charset, decoder);
-            encoder = new HttpMessageEncoderWrapper(charset, encoder);
-        }
-        handler.setHttpMessageDecoder(decoder);
-        handler.setHttpMessageEncoder(encoder);
-    }
-
-    protected final FrameworkModel getFrameworkModel() {
-        return frameworkModel;
-    }
-
-    @Override
-    public String getType() {
-        return TripleConstant.TRIPLE_HANDLER_TYPE_HTTP;
-    }
-}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/UnaryServerCallListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/UnaryServerCallListener.java
index 8c32c30031..7eb2704f6e 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/UnaryServerCallListener.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/UnaryServerCallListener.java
@@ -24,14 +24,14 @@ import org.apache.dubbo.rpc.TriRpcStatus;
 
 public class UnaryServerCallListener extends AbstractServerCallListener {
 
-    private boolean applyCustomizeException = false;
+    private final boolean applyCustomizeException;
 
     public UnaryServerCallListener(
-            RpcInvocation invocation, Invoker<?> invoker, StreamObserver<Object> responseObserver) {
+            RpcInvocation invocation,
+            Invoker<?> invoker,
+            StreamObserver<Object> responseObserver,
+            boolean applyCustomizeException) {
         super(invocation, invoker, responseObserver);
-    }
-
-    public void setApplyCustomizeException(boolean applyCustomizeException) {
         this.applyCustomizeException = applyCustomizeException;
     }
 
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java
index 3aa1bc0ac7..cbc2501065 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java
@@ -27,12 +27,14 @@ import org.apache.dubbo.remoting.http12.h2.H2StreamChannel;
 import org.apache.dubbo.remoting.http12.h2.Http2Header;
 import org.apache.dubbo.remoting.http12.h2.Http2ServerChannelObserver;
 import org.apache.dubbo.remoting.http12.h2.Http2TransportListener;
+import org.apache.dubbo.remoting.http12.message.MethodMetadata;
 import org.apache.dubbo.remoting.http12.message.StreamingDecoder;
 import org.apache.dubbo.rpc.RpcInvocation;
 import org.apache.dubbo.rpc.TriRpcStatus;
 import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.apache.dubbo.rpc.model.MethodDescriptor;
 import org.apache.dubbo.rpc.protocol.tri.DescriptorUtils;
+import org.apache.dubbo.rpc.protocol.tri.ReflectionPackableMethod;
 import org.apache.dubbo.rpc.protocol.tri.RpcInvocationBuildContext;
 import org.apache.dubbo.rpc.protocol.tri.compressor.DeCompressor;
 import org.apache.dubbo.rpc.protocol.tri.compressor.Identity;
@@ -135,6 +137,19 @@ public class GrpcHttp2ServerTransportListener extends GenericHttp2ServerTranspor
         return (GrpcStreamingDecoder) super.getStreamingDecoder();
     }
 
+    @Override
+    protected boolean applyCustomizeException() {
+        RpcInvocationBuildContext context = getContext();
+        if (context.isHasStub()) {
+            return false;
+        }
+        MethodMetadata methodMetadata = context.getMethodMetadata();
+        return ReflectionPackableMethod.needWrap(
+                context.getMethodDescriptor(),
+                methodMetadata.getActualRequestTypes(),
+                methodMetadata.getActualResponseType());
+    }
+
     @Override
     protected void onSettingMethodDescriptor(MethodDescriptor methodDescriptor) {
         GrpcCompositeCodec grpcCompositeCodec =
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcRequestHandlerMapping.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcRequestHandlerMapping.java
index 52894e0df8..c2e8d9d3ae 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcRequestHandlerMapping.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcRequestHandlerMapping.java
@@ -19,32 +19,71 @@ package org.apache.dubbo.rpc.protocol.tri.h12.grpc;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.Activate;
 import org.apache.dubbo.remoting.http12.HttpRequest;
+import org.apache.dubbo.remoting.http12.HttpResponse;
 import org.apache.dubbo.remoting.http12.message.HttpMessageCodec;
 import org.apache.dubbo.remoting.http12.message.MediaType;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.PathResolver;
 import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.apache.dubbo.rpc.protocol.tri.DescriptorUtils;
 import org.apache.dubbo.rpc.protocol.tri.TripleConstant;
-import org.apache.dubbo.rpc.protocol.tri.h12.HttpRequestHandlerMapping;
+import org.apache.dubbo.rpc.protocol.tri.TripleHeaderEnum;
+import org.apache.dubbo.rpc.protocol.tri.TripleProtocol;
 import org.apache.dubbo.rpc.protocol.tri.route.RequestHandler;
+import org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping;
 
 @Activate(order = -3000)
-public final class GrpcRequestHandlerMapping extends HttpRequestHandlerMapping {
+public final class GrpcRequestHandlerMapping implements RequestHandlerMapping {
 
     public static final GrpcCompositeCodecFactory CODEC_FACTORY = new GrpcCompositeCodecFactory();
 
+    private final FrameworkModel frameworkModel;
+    private final PathResolver pathResolver;
+
     public GrpcRequestHandlerMapping(FrameworkModel frameworkModel) {
-        super(frameworkModel);
+        this.frameworkModel = frameworkModel;
+        pathResolver = frameworkModel.getDefaultExtension(PathResolver.class);
     }
 
     @Override
-    protected boolean supportContentType(String contentType) {
-        return contentType != null && contentType.startsWith(MediaType.APPLICATION_GRPC.getName());
-    }
+    public RequestHandler getRequestHandler(URL url, HttpRequest request, HttpResponse response) {
+        String contentType = request.contentType();
+        if (contentType == null || !contentType.startsWith(MediaType.APPLICATION_GRPC.getName())) {
+            return null;
+        }
 
-    @Override
-    protected void determineHttpMessageCodec(RequestHandler handler, URL url, HttpRequest request) {
-        HttpMessageCodec codec = CODEC_FACTORY.createCodec(url, getFrameworkModel(), request.contentType());
+        String uri = request.uri();
+        int index = uri.indexOf('/', 1);
+        if (index == -1) {
+            return null;
+        }
+        if (uri.indexOf('/', index + 1) != -1) {
+            return null;
+        }
+
+        String serviceName = uri.substring(1, index);
+        String version = request.header(TripleHeaderEnum.SERVICE_VERSION.getHeader());
+        String group = request.header(TripleHeaderEnum.SERVICE_GROUP.getHeader());
+        String key = URL.buildKey(serviceName, group, version);
+        Invoker<?> invoker = pathResolver.resolve(key);
+        if (invoker == null && TripleProtocol.RESOLVE_FALLBACK_TO_DEFAULT) {
+            invoker = pathResolver.resolve(URL.buildKey(serviceName, group, TripleConstant.DEFAULT_VERSION));
+            if (invoker == null) {
+                invoker = pathResolver.resolve(serviceName);
+                if (invoker == null) {
+                    return null;
+                }
+            }
+        }
+
+        RequestHandler handler = new RequestHandler(invoker);
+        handler.setHasStub(pathResolver.hasNativeStub(uri));
+        handler.setMethodName(uri.substring(index + 1));
+        handler.setServiceDescriptor(DescriptorUtils.findServiceDescriptor(invoker, serviceName, handler.isHasStub()));
+        HttpMessageCodec codec = CODEC_FACTORY.createCodec(url, frameworkModel, request.contentType());
         handler.setHttpMessageDecoder(codec);
         handler.setHttpMessageEncoder(codec);
+        return handler;
     }
 
     @Override
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http1/DefaultHttp11ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http1/DefaultHttp11ServerTransportListener.java
index 5d445dcb3c..7df724d47c 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http1/DefaultHttp11ServerTransportListener.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http1/DefaultHttp11ServerTransportListener.java
@@ -126,7 +126,7 @@ public class DefaultHttp11ServerTransportListener
 
         public AutoCompleteUnaryServerCallListener(
                 RpcInvocation invocation, Invoker<?> invoker, StreamObserver<Object> responseObserver) {
-            super(invocation, invoker, responseObserver);
+            super(invocation, invoker, responseObserver, false);
         }
 
         @Override
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java
index 16e44fd033..32b16bb238 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java
@@ -29,7 +29,6 @@ import org.apache.dubbo.remoting.http12.h2.Http2TransportListener;
 import org.apache.dubbo.remoting.http12.message.DefaultListeningDecoder;
 import org.apache.dubbo.remoting.http12.message.DefaultStreamingDecoder;
 import org.apache.dubbo.remoting.http12.message.ListeningDecoder;
-import org.apache.dubbo.remoting.http12.message.MethodMetadata;
 import org.apache.dubbo.remoting.http12.message.StreamingDecoder;
 import org.apache.dubbo.remoting.http12.message.codec.JsonCodec;
 import org.apache.dubbo.rpc.CancellationContext;
@@ -39,7 +38,6 @@ import org.apache.dubbo.rpc.RpcInvocation;
 import org.apache.dubbo.rpc.executor.ExecutorSupport;
 import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.apache.dubbo.rpc.model.MethodDescriptor;
-import org.apache.dubbo.rpc.protocol.tri.ReflectionPackableMethod;
 import org.apache.dubbo.rpc.protocol.tri.RpcInvocationBuildContext;
 import org.apache.dubbo.rpc.protocol.tri.TripleProtocol;
 import org.apache.dubbo.rpc.protocol.tri.h12.AbstractServerTransportListener;
@@ -109,19 +107,8 @@ public class GenericHttp2ServerTransportListener extends AbstractServerTransport
         switch (methodDescriptor.getRpcType()) {
             case UNARY:
                 onUnary();
-                boolean applyCustomizeException = false;
-                if (!getContext().isHasStub()) {
-                    MethodMetadata methodMetadata = getContext().getMethodMetadata();
-                    applyCustomizeException = ReflectionPackableMethod.needWrap(
-                            methodDescriptor,
-                            methodMetadata.getActualRequestTypes(),
-                            methodMetadata.getActualResponseType());
-                }
                 onListenerStart();
-                UnaryServerCallListener unaryServerCallListener =
-                        startUnary(invocation, invoker, getServerChannelObserver());
-                unaryServerCallListener.setApplyCustomizeException(applyCustomizeException);
-                return unaryServerCallListener;
+                return startUnary(invocation, invoker, getServerChannelObserver());
             case SERVER_STREAM:
                 onListenerStart();
                 return startServerStreaming(invocation, invoker, getServerChannelObserver());
@@ -147,7 +134,7 @@ public class GenericHttp2ServerTransportListener extends AbstractServerTransport
 
     private UnaryServerCallListener startUnary(
             RpcInvocation invocation, Invoker<?> invoker, Http2ServerChannelObserver responseObserver) {
-        return new UnaryServerCallListener(invocation, invoker, responseObserver);
+        return new UnaryServerCallListener(invocation, invoker, responseObserver, applyCustomizeException());
     }
 
     private ServerStreamServerCallListener startServerStreaming(
@@ -218,6 +205,10 @@ public class GenericHttp2ServerTransportListener extends AbstractServerTransport
         return serverChannelObserver;
     }
 
+    protected boolean applyCustomizeException() {
+        return false;
+    }
+
     @Override
     public void close() throws Exception {
         getServerChannelObserver().close();
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 e1493f428d..2abcff1220 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
@@ -31,7 +31,7 @@ public enum Messages {
     NO_MORE_DATA_ALLOWED("No more data allowed after '{*...}' or '**' pattern segment for path ''{0}'' at index {1}"),
     CANNOT_COMBINE_PATHS("Cannot combine paths: ''{0}'' vs ''{1}''"),
     DUPLICATE_MAPPING("Duplicate mapping for ''{0}'': current={1}, exists={2}"),
-    AMBIGUOUS_MAPPING("Ambiguous mapping for ''{0}'': {{1}, {2}}"),
+    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."),
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java
index 7c512278bb..d267d58144 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java
@@ -29,6 +29,7 @@ public final class RestConstants {
     public static final String EXTENSION_KEY = "extension";
     public static final String EXTENSIONS_ATTRIBUTE_KEY = "restExtensionsAttributeKey";
 
+    public static final int DIALECT_BASIC = 0;
     public static final int DIALECT_SPRING_MVC = 1;
     public static final int DIALECT_JAXRS = 2;
 
@@ -40,6 +41,7 @@ public final class RestConstants {
     /* Request Attribute */
     public static final String BODY_ATTRIBUTE = HttpRequest.class.getName() + ".body";
     public static final String BODY_DECODER_ATTRIBUTE = HttpMessageDecoder.class.getName() + ".body";
+    public static final String SIG_ATTRIBUTE = RequestMapping.class.getName() + ".sig";
     public static final String MAPPING_ATTRIBUTE = RequestMapping.class.getName();
     public static final String HANDLER_ATTRIBUTE = HandlerMeta.class.getName();
     public static final String PATH_ATTRIBUTE = "org.springframework.web.util.UrlPathHelper.PATH";
@@ -49,9 +51,7 @@ public final class RestConstants {
             "org.springframework.web.servlet.HandlerMapping.producibleMediaTypes";
 
     /* Configuration Key */
-    public static final String CONFIG_PREFIX = "dubbo.rpc.rest.";
-    public static final String MAX_BODY_SIZE_KEY = CONFIG_PREFIX + "max-body-size";
-    public static final String MAX_RESPONSE_BODY_SIZE_KEY = CONFIG_PREFIX + "max-response-body-size";
+    public static final String CONFIG_PREFIX = "dubbo.protocol.triple.rest.";
     public static final String SUFFIX_PATTERN_MATCH_KEY = CONFIG_PREFIX + "suffix-pattern-match";
     public static final String TRAILING_SLASH_MATCH_KEY = CONFIG_PREFIX + "trailing-slash-match";
     public static final String CASE_SENSITIVE_MATCH_KEY = CONFIG_PREFIX + "case-sensitive-match";
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 448a2b7db4..1451c8d2d1 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
@@ -62,6 +62,10 @@ public final class RestHttpMessageCodec implements HttpMessageDecoder, HttpMessa
         charset = request.charsetOrDefault();
     }
 
+    public HttpMessageEncoder getMessageEncoder() {
+        return messageEncoder;
+    }
+
     @Override
     public Object decode(InputStream inputStream, Class<?> targetType, Charset charset) throws DecodeException {
         return decode(inputStream, new Class<?>[] {targetType}, charset);
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/CompositeArgumentResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/CompositeArgumentResolver.java
index 9a8690f611..69c50585bc 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/CompositeArgumentResolver.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/argument/CompositeArgumentResolver.java
@@ -74,4 +74,8 @@ public final class CompositeArgumentResolver implements ArgumentResolver {
 
         throw new IllegalStateException(Messages.ARGUMENT_COULD_NOT_RESOLVED.format(parameter.getDescription()));
     }
+
+    public ArgumentConverter getArgumentConverter() {
+        return argumentConverter;
+    }
 }
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 ee9fb15221..89dc6153f7 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
@@ -25,6 +25,9 @@ import org.apache.dubbo.common.utils.DateUtils;
 import org.apache.dubbo.common.utils.JsonUtils;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.remoting.http12.HttpCookie;
+import org.apache.dubbo.remoting.http12.HttpRequest.FileUpload;
+import org.apache.dubbo.remoting.http12.message.MediaType;
+import org.apache.dubbo.remoting.http12.message.codec.CodecUtils;
 import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.apache.dubbo.rpc.protocol.tri.rest.RestException;
 import org.apache.dubbo.rpc.protocol.tri.rest.RestParameterException;
@@ -93,6 +96,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.regex.Pattern;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.dubbo.common.utils.StringUtils.tokenizeToList;
 import static org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils.getActualGenericType;
 import static org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils.getActualType;
@@ -104,13 +108,16 @@ public class GeneralTypeConverter implements TypeConverter {
     private static final Logger LOGGER = LoggerFactory.getLogger(GeneralTypeConverter.class);
 
     private final ConverterUtil converterUtil;
+    private final CodecUtils codecUtils;
 
     public GeneralTypeConverter() {
         converterUtil = null;
+        codecUtils = null;
     }
 
     public GeneralTypeConverter(FrameworkModel frameworkModel) {
         converterUtil = frameworkModel.getBeanFactory().getOrRegisterBean(ConverterUtil.class);
+        codecUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CodecUtils.class);
     }
 
     @Override
@@ -255,7 +262,7 @@ public class GeneralTypeConverter implements TypeConverter {
                 case "java.lang.Class":
                     return TypeUtils.loadClass(str);
                 case "[B":
-                    return str.getBytes(StandardCharsets.UTF_8);
+                    return str.getBytes(UTF_8);
                 case "[C":
                     return str.toCharArray();
                 case "java.util.OptionalInt":
@@ -432,7 +439,7 @@ public class GeneralTypeConverter implements TypeConverter {
 
             switch (targetClass.getName()) {
                 case "java.lang.String":
-                    return new String(bytes, StandardCharsets.UTF_8);
+                    return new String(bytes, UTF_8);
                 case "java.lang.Double":
                 case "double":
                     return ByteBuffer.wrap(bytes).getDouble();
@@ -568,6 +575,11 @@ public class GeneralTypeConverter implements TypeConverter {
                     return StreamUtils.readBytes(is);
                 }
             }
+            if (source instanceof FileUpload) {
+                try (InputStream is = ((FileUpload) source).inputStream()) {
+                    return StreamUtils.readBytes(is);
+                }
+            }
             if (source instanceof Character) {
                 char c = (Character) source;
                 return new byte[] {(byte) (c >> 8), (byte) c};
@@ -910,8 +922,7 @@ public class GeneralTypeConverter implements TypeConverter {
                                     defCt = ct;
                                     break;
                                 case 1:
-                                    Class paramType = ct.getParameterTypes()[0];
-                                    if (paramType == int.class) {
+                                    if (ct.getParameterTypes()[0] == int.class) {
                                         return (Map) ct.newInstance(CollectionUtils.capacity(size));
                                     }
                                     break;
@@ -1078,10 +1089,15 @@ public class GeneralTypeConverter implements TypeConverter {
         return false;
     }
 
-    private static Object jsonToObject(String value, Type targetType) {
+    private Object jsonToObject(String value, Type targetType) {
         if (isMaybeJSON(value)) {
             try {
-                return JsonUtils.toJavaObject(value, targetType);
+                if (codecUtils == null || !(targetType instanceof Class)) {
+                    return JsonUtils.toJavaObject(value, targetType);
+                }
+                return codecUtils
+                        .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);
             }
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 90b02b68a9..d7401c57cf 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
@@ -38,18 +38,17 @@ public abstract class NamedValueArgumentResolverSupport {
 
     protected final Object resolve(NamedValueMeta meta, HttpRequest request, HttpResponse response) {
         Class<?> type = meta.type();
-        Object arg;
         if (type.isArray() || Collection.class.isAssignableFrom(type)) {
-            arg = resolveCollectionValue(meta, request, response);
-        } else if (Map.class.isAssignableFrom(type)) {
-            arg = resolveMapValue(meta, request, response);
-        } else {
-            arg = resolveValue(meta, request, response);
+            return resolveCollectionValue(meta, request, response);
         }
-
+        if (Map.class.isAssignableFrom(type)) {
+            return resolveMapValue(meta, request, response);
+        }
+        Object arg = resolveValue(meta, request, response);
         if (arg != null) {
-            return StringUtils.EMPTY_STRING.equals(arg) ? meta.defaultValue() : arg;
+            return filterValue(arg, meta);
         }
+
         arg = meta.defaultValue();
         if (arg != null) {
             return arg;
@@ -68,7 +67,7 @@ public abstract class NamedValueArgumentResolverSupport {
         Class<?> type = parameterMeta.getType();
         Type genericType = parameterMeta.getGenericType();
         if (type == Optional.class) {
-            Class<?> actualType = TypeUtils.getNestedType(genericType, 0);
+            Class<?> actualType = TypeUtils.getNestedActualType(genericType, 0);
             meta.setType(actualType == null ? Object.class : actualType);
         } else {
             meta.setType(type);
@@ -76,14 +75,22 @@ public abstract class NamedValueArgumentResolverSupport {
         if (type.isArray()) {
             meta.setNestedTypes(new Class<?>[] {type.getComponentType()});
         } else {
-            meta.setNestedTypes(TypeUtils.getNestedTypes(genericType));
+            meta.setNestedTypes(TypeUtils.getNestedActualTypes(genericType));
         }
         meta.setParameterMeta(parameterMeta);
         return meta;
     }
 
+    protected String emptyDefaultValue(NamedValueMeta meta) {
+        return meta.defaultValue();
+    }
+
     protected abstract Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response);
 
+    protected Object filterValue(Object value, NamedValueMeta meta) {
+        return value;
+    }
+
     protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) {
         return resolveValue(meta, request, response);
     }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java
index f3dbe3eb62..8b71b3eff9 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java
@@ -52,16 +52,16 @@ import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
 @Activate(group = CommonConstants.PROVIDER, order = 1000)
 public class CorsHeaderFilter extends RestHeaderFilterAdapter {
 
-    public static final String VARY = "Vary";
-    public static final String ORIGIN = "Origin";
-    public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
-    public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
-    public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
-    public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
-    public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
-    public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
-    public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
-    public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
+    public static final String VARY = "vary";
+    public static final String ORIGIN = "origin";
+    public static final String ACCESS_CONTROL_REQUEST_METHOD = "access-control-request-method";
+    public static final String ACCESS_CONTROL_REQUEST_HEADERS = "access-control-request-headers";
+    public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "access-control-allow-credentials";
+    public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "access-control-expose-headers";
+    public static final String ACCESS_CONTROL_MAX_AGE = "access-control-max-age";
+    public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "access-control-allow-origin";
+    public static final String ACCESS_CONTROL_ALLOW_METHODS = "access-control-allow-methods";
+    public static final String ACCESS_CONTROL_ALLOW_HEADERS = "access-control-allow-headers";
     public static final String SEP = ", ";
 
     @Override
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/ContentNegotiator.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/ContentNegotiator.java
index d31858ce72..e92b0bd564 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/ContentNegotiator.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/ContentNegotiator.java
@@ -87,6 +87,10 @@ public class ContentNegotiator {
         return null;
     }
 
+    public boolean supportExtension(String extension) {
+        return getMediaTypeByExtension(extension) != null;
+    }
+
     private String getSuitableMediaType(String name) {
         int index = name.indexOf('/');
         if (index == -1 || index == name.length() - 1) {
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 ffb3502778..17edec2101 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
@@ -16,12 +16,17 @@
  */
 package org.apache.dubbo.rpc.protocol.tri.rest.mapping;
 
-import org.apache.dubbo.common.utils.Assert;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.config.Configuration;
+import org.apache.dubbo.common.config.ConfigurationUtils;
+import org.apache.dubbo.config.nested.RestConfig;
 import org.apache.dubbo.remoting.http12.HttpRequest;
 import org.apache.dubbo.remoting.http12.message.MethodMetadata;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.apache.dubbo.rpc.model.MethodDescriptor;
+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.RestConstants;
@@ -32,53 +37,99 @@ import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ProducesConditio
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.HandlerMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.util.KeyString;
 import org.apache.dubbo.rpc.protocol.tri.rest.util.MethodWalker;
 import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils;
-import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import static org.apache.dubbo.rpc.protocol.tri.rest.Messages.AMBIGUOUS_MAPPING;
-import static org.apache.dubbo.rpc.protocol.tri.rest.Messages.DUPLICATE_MAPPING;
 
 public final class DefaultRequestMappingRegistry implements RequestMappingRegistry {
 
-    private final List<RequestMappingResolver> resolvers;
-
-    private final RadixTree<Registration> tree = new RadixTree<>();
+    private final FrameworkModel frameworkModel;
+    private final ContentNegotiator contentNegotiator;
     private final ReadWriteLock lock = new ReentrantReadWriteLock();
+    private final AtomicBoolean initialized = new AtomicBoolean();
+
+    private RestConfig restConfig;
+    private List<RequestMappingResolver> resolvers;
+    private RadixTree<Registration> tree;
 
     public DefaultRequestMappingRegistry(FrameworkModel frameworkModel) {
+        this.frameworkModel = frameworkModel;
+        contentNegotiator = frameworkModel.getBeanFactory().getOrRegisterBean(ContentNegotiator.class);
+    }
+
+    private void init() {
+        Configuration conf = ConfigurationUtils.getGlobalConfiguration(frameworkModel.defaultApplication());
+        restConfig = new RestConfig();
+        restConfig.setSuffixPatternMatch(conf.getBoolean(RestConstants.SUFFIX_PATTERN_MATCH_KEY, true));
+        restConfig.setTrailingSlashMatch(conf.getBoolean(RestConstants.TRAILING_SLASH_MATCH_KEY, true));
+        restConfig.setCaseSensitiveMatch(conf.getBoolean(RestConstants.CASE_SENSITIVE_MATCH_KEY, true));
+
         resolvers = frameworkModel.getActivateExtensions(RequestMappingResolver.class);
+        tree = new RadixTree<>(restConfig.isCaseSensitiveMatch());
     }
 
     @Override
     public void register(Invoker<?> invoker) {
-        Object service = invoker.getUrl().getServiceModel().getProxyObject();
+        if (tree == null) {
+            lock.writeLock().lock();
+            try {
+                if (initialized.compareAndSet(false, true)) {
+                    init();
+                }
+            } finally {
+                lock.writeLock().unlock();
+            }
+        }
+
+        URL url = invoker.getUrl();
+        Object service = url.getServiceModel().getProxyObject();
+        ServiceDescriptor sd = DescriptorUtils.getReflectionServiceDescriptor(url);
+        if (sd == null) {
+            return;
+        }
         new MethodWalker().walk(service.getClass(), (classes, consumer) -> {
             for (int i = 0, size = resolvers.size(); i < size; i++) {
                 RequestMappingResolver resolver = resolvers.get(i);
-                RestToolKit toolKit = resolver.getRestToolKit();
-                ServiceMeta serviceMeta = new ServiceMeta(classes, service, invoker.getUrl(), toolKit);
+                ServiceMeta serviceMeta = new ServiceMeta(classes, sd, service, url, resolver.getRestToolKit());
                 if (!resolver.accept(serviceMeta)) {
                     continue;
                 }
                 RequestMapping classMapping = resolver.resolve(serviceMeta);
                 consumer.accept((methods) -> {
-                    MethodMeta methodMeta = new MethodMeta(methods, serviceMeta);
+                    Method method = methods.get(0);
+                    MethodDescriptor md = sd.getMethod(method.getName(), method.getParameterTypes());
+                    MethodMeta methodMeta = new MethodMeta(methods, md, serviceMeta);
+                    if (!resolver.accept(methodMeta)) {
+                        return;
+                    }
                     RequestMapping methodMapping = resolver.resolve(methodMeta);
                     if (methodMapping == null) {
                         return;
                     }
+                    if (md == null) {
+                        if (!(sd instanceof ReflectionServiceDescriptor)) {
+                            return;
+                        }
+                        md = new ReflectionMethodDescriptor(method);
+                        ((ReflectionServiceDescriptor) sd).addMethod(md);
+                        methodMeta.setMethodDescriptor(md);
+                    }
                     if (classMapping != null) {
                         methodMapping = classMapping.combine(methodMapping);
                     }
-                    register0(methodMapping, buildHandlerMeta(invoker, methodMeta));
+                    methodMeta.initParameters();
+                    MethodMetadata methodMetadata = MethodMetadata.fromMethodDescriptor(md);
+                    register0(methodMapping, new HandlerMeta(invoker, methodMeta, methodMetadata, md, sd));
                 });
             }
         });
@@ -91,31 +142,13 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist
             registration.mapping = mapping;
             registration.meta = handler;
             for (PathExpression path : mapping.getPathCondition().getExpressions()) {
-                Registration exists = tree.addPath(path, registration);
-                if (exists != null) {
-                    throw new RestInitializeException(DUPLICATE_MAPPING, path.getPath(), mapping, exists.mapping);
-                }
+                tree.addPath(path, registration);
             }
         } finally {
             lock.writeLock().unlock();
         }
     }
 
-    private HandlerMeta buildHandlerMeta(Invoker<?> invoker, MethodMeta methodMeta) {
-        ServiceDescriptor serviceDescriptor = DescriptorUtils.getReflectionServiceDescriptor(invoker.getUrl());
-        String serviceInterface = invoker.getUrl().getServiceInterface();
-        Assert.notNull(serviceDescriptor, "ServiceDescriptor for [%s] can't be null", serviceInterface);
-        Method method = methodMeta.getMethod();
-        MethodDescriptor methodDescriptor = serviceDescriptor.getMethod(method.getName(), method.getParameterTypes());
-        Assert.notNull(methodDescriptor, "MethodDescriptor for [%s] can't be null", method);
-        return new HandlerMeta(
-                invoker,
-                methodMeta,
-                MethodMetadata.fromMethodDescriptor(methodDescriptor),
-                methodDescriptor,
-                serviceDescriptor);
-    }
-
     @Override
     public void unregister(Invoker<?> invoker) {
         lock.writeLock().lock();
@@ -128,6 +161,9 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist
 
     @Override
     public void destroy() {
+        if (tree == null) {
+            return;
+        }
         lock.writeLock().lock();
         try {
             tree.clear();
@@ -139,33 +175,48 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist
     public HandlerMeta lookup(HttpRequest request) {
         String path = PathUtils.normalize(request.rawPath());
         request.setAttribute(RestConstants.PATH_ATTRIBUTE, path);
-        List<Match<Registration>> matches = new ArrayList<>();
-        lock.readLock().lock();
-        try {
-            tree.match(path, matches);
-        } finally {
-            lock.readLock().unlock();
-        }
 
-        int size = matches.size();
-        if (size == 0) {
-            return null;
-        }
-        List<Candidate> candidates = new ArrayList<>(size);
-        for (int i = 0; i < size; i++) {
-            Match<Registration> match = matches.get(i);
-            RequestMapping mapping = match.getValue().mapping.match(request, match.getExpression());
-            if (mapping != null) {
-                Candidate candidate = new Candidate();
-                candidate.mapping = mapping;
-                candidate.meta = match.getValue().meta;
-                candidate.expression = match.getExpression();
-                candidate.variableMap = match.getVariableMap();
-                candidates.add(candidate);
+        List<Candidate> candidates = new ArrayList<>();
+        boolean cs = restConfig.isCaseSensitiveMatch();
+        tryMatch(request, new KeyString(path, cs), candidates);
+
+        if (candidates.isEmpty()) {
+            int end = path.length();
+
+            if (restConfig.isTrailingSlashMatch()) {
+                if (path.charAt(end - 1) == '/') {
+                    end--;
+                    tryMatch(request, new KeyString(path, end, cs), candidates);
+                }
+            }
+
+            if (candidates.isEmpty()) {
+                for (int i = end - 1; i >= 0; i--) {
+                    char ch = path.charAt(i);
+                    if (ch == '/') {
+                        break;
+                    }
+                    if (ch == '.' && restConfig.isSuffixPatternMatch()) {
+                        if (contentNegotiator.supportExtension(path.substring(i + 1, end))) {
+                            tryMatch(request, new KeyString(path, i, cs), candidates);
+                            if (!candidates.isEmpty()) {
+                                break;
+                            }
+                            end = i;
+                        }
+                    }
+                    if (ch == '~') {
+                        request.setAttribute(RestConstants.SIG_ATTRIBUTE, path.substring(i + 1, end));
+                        tryMatch(request, new KeyString(path, i, cs), candidates);
+                        if (!candidates.isEmpty()) {
+                            break;
+                        }
+                    }
+                }
             }
         }
 
-        size = candidates.size();
+        int size = candidates.size();
         if (size == 0) {
             return null;
         }
@@ -184,7 +235,8 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist
             Candidate first = candidates.get(0);
             Candidate second = candidates.get(1);
             if (first.mapping.compareTo(second.mapping, request) == 0) {
-                throw new RestInitializeException(AMBIGUOUS_MAPPING, path, first.mapping, second.mapping);
+                throw new RestInitializeException(
+                        AMBIGUOUS_MAPPING, path, first.mapping.getName(), second.mapping.getName());
             }
         }
 
@@ -206,8 +258,73 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist
         return handler;
     }
 
+    private void tryMatch(HttpRequest request, KeyString path, List<Candidate> candidates) {
+        List<Match<Registration>> matches = new ArrayList<>();
+
+        lock.readLock().lock();
+        try {
+            tree.match(path, matches);
+        } finally {
+            lock.readLock().unlock();
+        }
+
+        int size = matches.size();
+        if (size == 0) {
+            return;
+        }
+        for (int i = 0; i < size; i++) {
+            Match<Registration> match = matches.get(i);
+            RequestMapping mapping = match.getValue().mapping.match(request, match.getExpression());
+            if (mapping != null) {
+                Candidate candidate = new Candidate();
+                candidate.mapping = mapping;
+                candidate.meta = match.getValue().meta;
+                candidate.expression = match.getExpression();
+                candidate.variableMap = match.getVariableMap();
+                candidates.add(candidate);
+            }
+        }
+    }
+
     @Override
     public boolean exists(String path, String method) {
+        boolean cs = restConfig.isCaseSensitiveMatch();
+        if (tryExists(new KeyString(path, cs), method)) {
+            return true;
+        }
+
+        int end = path.length();
+        if (restConfig.isTrailingSlashMatch()) {
+            if (path.charAt(end - 1) == '/') {
+                end--;
+                if (tryExists(new KeyString(path, end, cs), method)) {
+                    return true;
+                }
+            }
+        }
+
+        for (int i = end - 1; i >= 0; i--) {
+            char ch = path.charAt(i);
+            if (ch == '/') {
+                break;
+            }
+            if (ch == '.' && restConfig.isSuffixPatternMatch()) {
+                if (contentNegotiator.supportExtension(path.substring(i + 1, end))) {
+                    if (tryExists(new KeyString(path, i, cs), method)) {
+                        return true;
+                    }
+                    end = i;
+                }
+            }
+            if (ch == '~') {
+                return tryExists(new KeyString(path, i, cs), method);
+            }
+        }
+
+        return false;
+    }
+
+    private boolean tryExists(KeyString path, String method) {
         List<Match<Registration>> matches = new ArrayList<>();
         lock.readLock().lock();
         try {
@@ -224,6 +341,7 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist
     }
 
     private static final class Registration {
+
         RequestMapping mapping;
         HandlerMeta meta;
 
@@ -245,6 +363,7 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist
     }
 
     private static final class Candidate {
+
         RequestMapping mapping;
         HandlerMeta meta;
         PathExpression expression;
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTree.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTree.java
index cc13a38439..7e45bced4e 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTree.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTree.java
@@ -20,6 +20,7 @@ import org.apache.dubbo.common.utils.Pair;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type;
+import org.apache.dubbo.rpc.protocol.tri.rest.util.KeyString;
 import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils;
 
 import java.util.ArrayList;
@@ -39,12 +40,22 @@ import java.util.function.Predicate;
  */
 public final class RadixTree<T> {
 
-    private final Map<String, List<Match<T>>> directPathMap = new HashMap<>();
+    private final Map<KeyString, List<Match<T>>> directPathMap = new HashMap<>();
     private final Node<T> root = new Node<>();
+    private final boolean caseSensitive;
+
+    public RadixTree(boolean caseSensitive) {
+        this.caseSensitive = caseSensitive;
+    }
+
+    public RadixTree() {
+        caseSensitive = true;
+    }
 
     public T addPath(PathExpression path, T value) {
         if (path.isDirect()) {
-            List<Match<T>> matches = directPathMap.computeIfAbsent(path.getPath(), k -> new ArrayList<>());
+            KeyString key = new KeyString(path.getPath(), caseSensitive);
+            List<Match<T>> matches = directPathMap.computeIfAbsent(key, k -> new ArrayList<>());
             for (int i = 0, len = matches.size(); i < len; i++) {
                 Match<T> match = matches.get(i);
                 if (match.getValue().equals(value)) {
@@ -73,11 +84,15 @@ public final class RadixTree<T> {
         return null;
     }
 
-    private static <T> Node<T> getChild(Node<T> current, PathSegment segment) {
+    public T addPath(String path, T value) {
+        return addPath(PathExpression.parse(PathUtils.normalize(path)), value);
+    }
+
+    private Node<T> getChild(Node<T> current, PathSegment segment) {
         Node<T> child;
         if (segment.getType() == Type.LITERAL) {
-            Map<Key, Node<T>> children = current.children;
-            Key key = new Key(segment.getValue());
+            Map<KeyString, Node<T>> children = current.children;
+            KeyString key = new KeyString(segment.getValue(), caseSensitive);
             child = children.get(key);
             if (child == null) {
                 child = new Node<>();
@@ -124,7 +139,7 @@ public final class RadixTree<T> {
     /**
      * Ensure that the path is normalized using {@link PathUtils#normalize(String)} before matching.
      */
-    public void match(String path, List<Match<T>> matches) {
+    public void match(KeyString path, List<Match<T>> matches) {
         List<Match<T>> directMatches = directPathMap.get(path);
         if (directMatches != null) {
             for (int i = 0, size = directMatches.size(); i < size; i++) {
@@ -136,7 +151,11 @@ public final class RadixTree<T> {
         matchRecursive(root, path, 1, new HashMap<>(), matches);
     }
 
-    public List<Match<T>> match(String path) {
+    public void match(String path, List<Match<T>> matches) {
+        match(new KeyString(path, caseSensitive), matches);
+    }
+
+    public List<Match<T>> match(KeyString path) {
         List<Match<T>> matches = directPathMap.get(path);
         if (matches != null) {
             return new ArrayList<>(matches);
@@ -147,10 +166,14 @@ public final class RadixTree<T> {
         return matches;
     }
 
+    public List<Match<T>> match(String path) {
+        return match(new KeyString(path, caseSensitive));
+    }
+
     private void matchRecursive(
-            Node<T> current, String path, int start, Map<String, String> variableMap, List<Match<T>> matches) {
+            Node<T> current, KeyString path, int start, Map<String, String> variableMap, List<Match<T>> matches) {
         int end = path.indexOf('/', start);
-        Node<T> node = current.children.get(new Key(path, start, end));
+        Node<T> node = current.children.get(new KeyString(path, start, end));
         if (node != null) {
             if (node.isLeaf()) {
                 addMatch(node, variableMap, matches);
@@ -235,67 +258,9 @@ public final class RadixTree<T> {
         }
     }
 
-    /**
-     * Zero-copy string key.
-     */
-    private static final class Key implements CharSequence {
-
-        private final String value;
-        private final int offset;
-        private final int length;
-
-        private Key(String value, int start, int end) {
-            this.value = value;
-            offset = start;
-            length = (end == -1 ? value.length() : end) - start;
-        }
-
-        public Key(String value) {
-            this.value = value;
-            offset = 0;
-            length = value.length();
-        }
-
-        @Override
-        public int length() {
-            return length;
-        }
-
-        @Override
-        public char charAt(int index) {
-            return value.charAt(offset + index);
-        }
-
-        @Override
-        public CharSequence subSequence(int start, int end) {
-            return value.substring(offset + start, offset + end);
-        }
-
-        @Override
-        public int hashCode() {
-            int h = 0;
-            for (int i = 0; i < length; i++) {
-                h = 31 * h + value.charAt(offset + i);
-            }
-            return h;
-        }
-
-        @Override
-        @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
-        public boolean equals(Object obj) {
-            Key other = (Key) obj;
-            return value.regionMatches(offset, other.value, other.offset, length);
-        }
-
-        @Override
-        public String toString() {
-            return value.substring(offset, length - offset);
-        }
-    }
-
     private static final class Node<T> {
 
-        private final Map<Key, Node<T>> children = new HashMap<>();
+        private final Map<KeyString, Node<T>> children = new HashMap<>();
         private final Map<PathSegment, Node<T>> fuzzyChildren = new HashMap<>();
         private final List<Pair<PathExpression, T>> values = new ArrayList<>();
 
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 3dd19f4f2b..bd84d96f3e 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
@@ -16,8 +16,10 @@
  */
 package org.apache.dubbo.rpc.protocol.tri.rest.mapping;
 
+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.rpc.protocol.tri.rest.RestConstants;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.Condition;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ConditionWrapper;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ConsumesCondition;
@@ -37,6 +39,7 @@ import static org.apache.dubbo.common.utils.ArrayUtils.isEmpty;
 public final class RequestMapping implements Condition<RequestMapping, HttpRequest> {
 
     private final String name;
+    private final String sig;
     private final PathCondition pathCondition;
     private final MethodsCondition methodsCondition;
     private final ParamsCondition paramsCondition;
@@ -51,6 +54,7 @@ public final class RequestMapping implements Condition<RequestMapping, HttpReque
 
     private RequestMapping(
             String name,
+            String sig,
             PathCondition pathCondition,
             MethodsCondition methodsCondition,
             ParamsCondition paramsCondition,
@@ -61,6 +65,7 @@ public final class RequestMapping implements Condition<RequestMapping, HttpReque
             CorsMeta cors,
             ResponseMeta response) {
         this.name = name;
+        this.sig = sig;
         this.pathCondition = pathCondition;
         this.methodsCondition = methodsCondition;
         this.paramsCondition = paramsCondition;
@@ -78,28 +83,31 @@ public final class RequestMapping implements Condition<RequestMapping, HttpReque
 
     @Override
     public RequestMapping combine(RequestMapping other) {
-        String name = this.name == null ? other.name : other.name == null ? this.name : this.name + "#" + other.name;
-        PathCondition paths = combine(pathCondition, other.pathCondition);
-        MethodsCondition methods = combine(methodsCondition, other.methodsCondition);
-        ParamsCondition params = combine(paramsCondition, other.paramsCondition);
-        HeadersCondition headers = combine(headersCondition, other.headersCondition);
-        ConsumesCondition consumes = combine(consumesCondition, other.consumesCondition);
-        ProducesCondition produces = combine(producesCondition, other.producesCondition);
-        ConditionWrapper custom = combine(customCondition, other.customCondition);
-        CorsMeta corsMeta = combine(this.cors, other.cors);
-        ResponseMeta response = ResponseMeta.combine(this.response, other.response);
         return new RequestMapping(
-                name, paths, methods, params, headers, consumes, produces, custom, corsMeta, response);
+                combineName(name, other.name, other.sig),
+                other.sig,
+                combine(pathCondition, other.pathCondition),
+                combine(methodsCondition, other.methodsCondition),
+                combine(paramsCondition, other.paramsCondition),
+                combine(headersCondition, other.headersCondition),
+                combine(consumesCondition, other.consumesCondition),
+                combine(producesCondition, other.producesCondition),
+                combine(customCondition, other.customCondition),
+                CorsMeta.combine(cors, other.cors),
+                ResponseMeta.combine(response, other.response));
     }
 
-    private <T extends Condition<T, HttpRequest>> T combine(T source, T other) {
-        return source == null ? other : other == null ? source : source.combine(other);
+    private String combineName(String name, String otherName, String otherSig) {
+        if (name == null) {
+            name = otherName;
+        } else if (otherName != null) {
+            name += '#' + otherName;
+        }
+        return otherSig == null ? name : name + '~' + otherSig;
     }
 
-    private CorsMeta combine(CorsMeta source, CorsMeta other) {
-        return source == null || source.isEmpty()
-                ? other == null || other.isEmpty() ? null : other.applyDefault()
-                : source.combine(other).applyDefault();
+    private <T extends Condition<T, HttpRequest>> T combine(T source, T other) {
+        return source == null ? other : other == null ? source : source.combine(other);
     }
 
     public RequestMapping match(HttpRequest request, PathExpression path) {
@@ -172,13 +180,25 @@ public final class RequestMapping implements Condition<RequestMapping, HttpReque
             }
         }
 
-        return new RequestMapping(name, paths, methods, params, headers, consumes, produces, custom, cors, response);
+        if (StringUtils.isNotEmpty(sig)) {
+            String rSig = request.attribute(RestConstants.SIG_ATTRIBUTE);
+            if (rSig != null && !rSig.equals(sig)) {
+                return null;
+            }
+        }
+
+        return new RequestMapping(
+                name, sig, paths, methods, params, headers, consumes, produces, custom, cors, response);
     }
 
     public String getName() {
         return name;
     }
 
+    public String getSig() {
+        return sig;
+    }
+
     public PathCondition getPathCondition() {
         return pathCondition;
     }
@@ -242,7 +262,21 @@ public final class RequestMapping implements Condition<RequestMapping, HttpReque
         }
         if (customCondition != null) {
             result = customCondition.compareTo(other.customCondition, request);
-            return result;
+            if (result != 0) {
+                return result;
+            }
+        }
+        if (sig != null && other.sig != null) {
+            int size = request.queryParameters().size();
+            int size1 = sig.length();
+            int size2 = other.sig.length();
+            if (size1 == size) {
+                if (size2 != size) {
+                    return -1;
+                }
+            } else if (size2 == size) {
+                return 1;
+            }
         }
         return 0;
     }
@@ -258,7 +292,8 @@ public final class RequestMapping implements Condition<RequestMapping, HttpReque
                     headersCondition,
                     consumesCondition,
                     producesCondition,
-                    customCondition);
+                    customCondition,
+                    sig);
             this.hashCode = hashCode;
         }
         return hashCode;
@@ -279,13 +314,15 @@ public final class RequestMapping implements Condition<RequestMapping, HttpReque
                 && Objects.equals(headersCondition, other.headersCondition)
                 && Objects.equals(consumesCondition, other.consumesCondition)
                 && Objects.equals(producesCondition, other.producesCondition)
-                && Objects.equals(customCondition, other.customCondition);
+                && Objects.equals(customCondition, other.customCondition)
+                && Objects.equals(sig, other.sig);
     }
 
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("RequestMapping{name='");
         sb.append(name).append('\'');
+        sb.append('\'');
         if (pathCondition != null) {
             sb.append(", pathCondition=").append(pathCondition);
         }
@@ -318,7 +355,9 @@ public final class RequestMapping implements Condition<RequestMapping, HttpReque
     }
 
     public static final class Builder {
+
         private String name;
+        private String sig;
         private String contextPath;
         private String[] paths;
         private String[] methods;
@@ -327,7 +366,7 @@ public final class RequestMapping implements Condition<RequestMapping, HttpReque
         private String[] consumes;
         private String[] produces;
         private Condition<?, HttpRequest> customCondition;
-        private CorsMeta corsMeta;
+        private CorsMeta cors;
         private Integer responseStatus;
         private String responseReason;
 
@@ -336,6 +375,11 @@ public final class RequestMapping implements Condition<RequestMapping, HttpReque
             return this;
         }
 
+        public Builder sig(String sig) {
+            this.sig = sig;
+            return this;
+        }
+
         public Builder contextPath(String contextPath) {
             this.contextPath = contextPath;
             return this;
@@ -376,8 +420,8 @@ public final class RequestMapping implements Condition<RequestMapping, HttpReque
             return this;
         }
 
-        public Builder cors(CorsMeta corsMeta) {
-            this.corsMeta = corsMeta;
+        public Builder cors(CorsMeta cors) {
+            this.cors = cors;
             return this;
         }
 
@@ -394,6 +438,7 @@ public final class RequestMapping implements Condition<RequestMapping, HttpReque
         public RequestMapping build() {
             return new RequestMapping(
                     name,
+                    sig,
                     isEmpty(paths) ? null : new PathCondition(contextPath, paths),
                     isEmpty(methods) ? null : new MethodsCondition(methods),
                     isEmpty(params) ? null : new ParamsCondition(params),
@@ -401,7 +446,7 @@ public final class RequestMapping implements Condition<RequestMapping, HttpReque
                     isEmpty(consumes) ? null : new ConsumesCondition(consumes),
                     isEmpty(produces) ? null : new ProducesCondition(produces),
                     customCondition,
-                    corsMeta == null ? null : corsMeta,
+                    cors == null ? null : cors,
                     responseStatus == null ? null : new ResponseMeta(responseStatus, responseReason));
         }
     }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingResolver.java
index 7c7b5f372e..803792f1a4 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingResolver.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RequestMappingResolver.java
@@ -33,5 +33,9 @@ public interface RequestMappingResolver {
 
     RequestMapping resolve(ServiceMeta serviceMeta);
 
+    default boolean accept(MethodMeta methodMeta) {
+        return true;
+    }
+
     RequestMapping resolve(MethodMeta methodMeta);
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java
index 2859401816..ead98873a0 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java
@@ -17,6 +17,7 @@
 package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition;
 
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type;
+import org.apache.dubbo.rpc.protocol.tri.rest.util.KeyString;
 
 import javax.annotation.Nonnull;
 
@@ -38,6 +39,10 @@ public final class PathExpression implements Comparable<PathExpression> {
         return new PathExpression(path, PathParser.parse(path));
     }
 
+    public static boolean match(@Nonnull String path, String value) {
+        return value != null && parse(path).match(value) != null;
+    }
+
     public String getPath() {
         return path;
     }
@@ -60,7 +65,7 @@ public final class PathExpression implements Comparable<PathExpression> {
             if (end != -1) {
                 start = end + 1;
                 end = path.indexOf('/', start);
-                if (segment.match(path, start, end, variableMap)) {
+                if (segment.match(new KeyString(path), start, end, variableMap)) {
                     continue;
                 }
             }
@@ -85,6 +90,9 @@ public final class PathExpression implements Comparable<PathExpression> {
     public int compareTo(PathExpression other) {
         int size = segments.length;
         int otherSize = other.segments.length;
+        if (isDirect() && other.isDirect()) {
+            return other.path.length() - path.length();
+        }
         for (int i = 0; i < size && i < otherSize; i++) {
             int result = segments[i].compareTo(other.segments[i]);
             if (result != 0) {
@@ -112,13 +120,21 @@ public final class PathExpression implements Comparable<PathExpression> {
 
     @Override
     public String toString() {
+        if (isDirect()) {
+            return path;
+        }
         StringBuilder sb = new StringBuilder(32);
         for (PathSegment segment : segments) {
             sb.append('/');
+            String value = segment.getValue();
             if (segment.getType() == Type.VARIABLE) {
-                sb.append('{').append(segment.getValue()).append('}');
+                if (value.isEmpty()) {
+                    sb.append('*');
+                } else {
+                    sb.append('{').append(value).append('}');
+                }
             } else {
-                sb.append(segment.getValue());
+                sb.append(value);
             }
         }
         return sb.toString();
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParser.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParser.java
index 30e978c21e..8add3bd99e 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParser.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParser.java
@@ -23,7 +23,6 @@ import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type;
 import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils;
 
-import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
@@ -33,7 +32,7 @@ import java.util.ListIterator;
  * <p>
  * <a href="https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-requestmapping.html#mvc-ann-requestmapping-uri-templates">Spring uri templates</a>
  * <br/>
- * <a href="https://docs.jboss.org/resteasy/docs/6.2.6.Final/userguide/html/ch04.html">Path and regular expression mappings</a>
+ * <a href="https://docs.jboss.org/resteasy/docs/6.2.7.Final/userguide/html/ch04.html">Path and regular expression mappings</a>
  * </p>
  */
 final class PathParser {
@@ -59,9 +58,6 @@ final class PathParser {
 
     private List<PathSegment> doParse(String path) {
         parseSegments(path);
-        if (segments.isEmpty()) {
-            return Collections.emptyList();
-        }
         transformSegments(segments, path);
         for (PathSegment segment : segments) {
             try {
@@ -96,11 +92,11 @@ final class PathParser {
                             appendSegment(Type.WILDCARD);
                             break;
                         case State.REGEX_VARIABLE_START:
-                            if (path.charAt(i - 1) == '^' && path.charAt(i - 2) == '[') {
-                                break;
+                            if (path.charAt(i - 1) != '^' || path.charAt(i - 2) != '[') {
+                                regexMulti = true;
                             }
-                            regexMulti = true;
-                            break;
+                            buf.append(c);
+                            continue;
                         case State.VARIABLE_START:
                         case State.WILDCARD_VARIABLE_START:
                             throw new PathParserException(Messages.MISSING_CLOSE_CAPTURE, path, i);
@@ -235,8 +231,6 @@ final class PathParser {
                 case State.REGEX_VARIABLE_START:
                 case State.WILDCARD_VARIABLE_START:
                     throw new PathParserException(Messages.MISSING_CLOSE_CAPTURE, path, len - 1);
-                case State.END:
-                    throw new PathParserException(Messages.NO_MORE_DATA_ALLOWED, path, len - 1);
                 default:
             }
         }
@@ -269,7 +263,9 @@ final class PathParser {
                                 prev = curr;
                                 break;
                             case PATTERN_MULTI:
-                                prev.setValue(prev.getValue() + '/');
+                                if (!".*".equals(prev.getValue())) {
+                                    prev.setValue(prev.getValue() + '/');
+                                }
                                 break;
                             default:
                         }
@@ -338,9 +334,10 @@ final class PathParser {
                             throw new PathParserException(Messages.ILLEGAL_DOUBLE_CAPTURE, path);
                         case PATTERN:
                         case PATTERN_MULTI:
-                            prev.addVariable(curr.getVariable());
+                            String var = curr.getVariable();
+                            prev.addVariable(var);
                             prev.setType(type);
-                            prev.setValue("(?<" + prev.getVariable() + ">[^/]+)" + value);
+                            prev.setValue("(?<" + prev.getVariable() + ">[^/]+)(?<" + var + '>' + value + ')');
                             iterator.remove();
                             continue;
                         default:
@@ -354,8 +351,13 @@ final class PathParser {
                             iterator.remove();
                             continue;
                         case WILDCARD_TAIL:
-                            prev.addVariable(curr.getVariable());
-                            prev.setValue(pValue + "(?<" + curr.getVariable() + ">.*)");
+                            if (curr.getVariables() == null) {
+                                prev.setValue(pValue + ".*");
+                            } else {
+                                prev.addVariable(curr.getVariable());
+                                prev.setValue(pValue + "(?<" + curr.getVariable() + ">.*)");
+                            }
+                            prev.setType(Type.PATTERN_MULTI);
                             iterator.remove();
                             continue;
                         case VARIABLE:
@@ -368,10 +370,15 @@ final class PathParser {
                             prev.setValue(pValue + "(?<" + curr.getVariable() + ">[^/]+)");
                             iterator.remove();
                             continue;
-                        case PATTERN:
                         case PATTERN_MULTI:
-                            prev.addVariable(curr.getVariable());
-                            prev.setValue(pValue + "(?<" + curr.getVariable() + '>' + value + ')');
+                            prev.setType(Type.PATTERN_MULTI);
+                        case PATTERN:
+                            if (curr.getVariables() == null) {
+                                prev.setValue(pValue + value);
+                            } else {
+                                prev.addVariable(curr.getVariable());
+                                prev.setValue(pValue + "(?<" + curr.getVariable() + '>' + value + ')');
+                            }
                             iterator.remove();
                             continue;
                         default:
@@ -424,7 +431,7 @@ final class PathParser {
                     sb.append("[^/]*");
                     break;
                 case '?':
-                    if (i > 0 && sb.charAt(i - 1) == '*') {
+                    if (i > 0 && wildcard.charAt(i - 1) == '*') {
                         continue;
                     }
                     sb.append("[^/]");
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java
index 4a05a20bf1..e31ccaebf1 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java
@@ -16,9 +16,9 @@
  */
 package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition;
 
-import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.rpc.protocol.tri.rest.Messages;
 import org.apache.dubbo.rpc.protocol.tri.rest.PathParserException;
+import org.apache.dubbo.rpc.protocol.tri.rest.util.KeyString;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -106,7 +106,7 @@ public final class PathSegment implements Comparable<PathSegment> {
         return type == Type.WILDCARD_TAIL || type == Type.PATTERN_MULTI;
     }
 
-    public boolean match(String path, int start, int end, Map<String, String> variableMap) {
+    public boolean match(KeyString path, int start, int end, Map<String, String> variableMap) {
         switch (type) {
             case SLASH:
             case LITERAL:
@@ -118,11 +118,11 @@ public final class PathSegment implements Comparable<PathSegment> {
                 return true;
             case VARIABLE:
                 if (variables != null) {
-                    variableMap.put(getVariable(), StringUtils.substring(path, start, end));
+                    variableMap.put(getVariable(), path.substring(start, end));
                 }
                 return true;
             case PATTERN:
-                return matchPattern(StringUtils.substring(path, start, end), variableMap);
+                return matchPattern(path.substring(start, end), variableMap);
             case PATTERN_MULTI:
                 return matchPattern(path.substring(start), variableMap);
             default:
@@ -130,9 +130,16 @@ public final class PathSegment implements Comparable<PathSegment> {
         }
     }
 
+    public boolean match(String path, int start, int end, Map<String, String> variableMap) {
+        return match(new KeyString(path), start, end, variableMap);
+    }
+
     private boolean matchPattern(String path, Map<String, String> variableMap) {
         Matcher matcher = getPattern().matcher(path);
         if (matcher.matches()) {
+            if (variables == null) {
+                return true;
+            }
             for (int i = 0, size = variables.size(); i < size; i++) {
                 String variable = variables.get(i);
                 variableMap.put(variable, matcher.group(variable));
@@ -183,7 +190,7 @@ public final class PathSegment implements Comparable<PathSegment> {
         }
         int size = variables == null ? 0 : variables.size();
         int otherSize = other.variables == null ? 0 : other.variables.size();
-        return size - otherSize;
+        return otherSize - size;
     }
 
     public enum Type {
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 393106e2d3..35592ce137 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
@@ -71,7 +71,7 @@ public abstract class AnnotationSupport {
         return result;
     }
 
-    public final AnnotationMeta getAnnotation(Class<? extends Annotation> annotationType) {
+    public final <A extends Annotation> AnnotationMeta<A> getAnnotation(Class<A> annotationType) {
         return cache.computeIfAbsent(Pair.of(annotationType, GET_KEY), k -> {
                     AnnotatedElement element = getAnnotatedElement();
                     Annotation annotation = element.getAnnotation(annotationType);
@@ -87,7 +87,7 @@ public abstract class AnnotationSupport {
         return annotationEnum.isPresent() ? getAnnotation(annotationEnum.type()) : null;
     }
 
-    public final boolean isAnnotated(Class<Annotation> annotationType) {
+    public final boolean isAnnotated(Class<? extends Annotation> annotationType) {
         return getAnnotation(annotationType) != null;
     }
 
@@ -95,7 +95,7 @@ public abstract class AnnotationSupport {
         return getAnnotation(annotationEnum) != null;
     }
 
-    public final AnnotationMeta getMergedAnnotation(Class<Annotation> annotationType) {
+    public final <A extends Annotation> AnnotationMeta<A> getMergedAnnotation(Class<A> annotationType) {
         return cache.computeIfAbsent(Pair.of(annotationType, GET_MERGED_KEY), k -> {
                     AnnotatedElement element = getAnnotatedElement();
                     Annotation[] annotations = element.getAnnotations();
@@ -117,7 +117,7 @@ public abstract class AnnotationSupport {
         return annotationEnum.isPresent() ? getMergedAnnotation(annotationEnum.type()) : null;
     }
 
-    public final boolean isMergedAnnotated(Class<Annotation> annotationType) {
+    public final boolean isMergedAnnotated(Class<? extends Annotation> annotationType) {
         return getMergedAnnotation(annotationType) != null;
     }
 
@@ -143,7 +143,7 @@ public abstract class AnnotationSupport {
         });
     }
 
-    public final AnnotationMeta findAnnotation(Class<Annotation> annotationType) {
+    public final <A extends Annotation> AnnotationMeta<A> findAnnotation(Class<A> annotationType) {
         return cache.computeIfAbsent(Pair.of(annotationType, FIND_KEY), k -> {
                     List<? extends AnnotatedElement> elements = getAnnotatedElements();
                     for (int i = 0, len = elements.size(); i < len; i++) {
@@ -162,15 +162,15 @@ public abstract class AnnotationSupport {
         return annotationEnum.isPresent() ? findAnnotation(annotationEnum.type()) : null;
     }
 
-    public final boolean isHierarchyAnnotated(Class<Annotation> annotationType) {
-        return getMergedAnnotation(annotationType) != null;
+    public final boolean isHierarchyAnnotated(Class<? extends Annotation> annotationType) {
+        return findAnnotation(annotationType) != null;
     }
 
     public final boolean isHierarchyAnnotated(AnnotationEnum annotationEnum) {
-        return getMergedAnnotation(annotationEnum) != null;
+        return findAnnotation(annotationEnum) != null;
     }
 
-    public final AnnotationMeta findMergedAnnotation(Class<Annotation> annotationType) {
+    public final <A extends Annotation> AnnotationMeta<A> findMergedAnnotation(Class<A> annotationType) {
         return cache.computeIfAbsent(Pair.of(annotationType, FIND_MERGED_KEY), k -> {
                     List<? extends AnnotatedElement> elements = getAnnotatedElements();
                     for (int i = 0, len = elements.size(); i < len; i++) {
@@ -196,7 +196,7 @@ public abstract class AnnotationSupport {
         return annotationEnum.isPresent() ? findMergedAnnotation(annotationEnum.type()) : null;
     }
 
-    public final boolean isMergedHierarchyAnnotated(Class<Annotation> annotationType) {
+    public final boolean isMergedHierarchyAnnotated(Class<? extends Annotation> annotationType) {
         return findMergedAnnotation(annotationType) != null;
     }
 
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
new file mode 100644
index 0000000000..ce8fd206e1
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/BeanMeta.java
@@ -0,0 +1,387 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta;
+
+import org.apache.dubbo.remoting.http12.rest.Param;
+import org.apache.dubbo.rpc.protocol.tri.rest.RestException;
+import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit;
+import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils;
+
+import javax.annotation.Nullable;
+
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Parameter;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public final class BeanMeta {
+
+    private final Map<String, FieldMeta> fields = new LinkedHashMap<>();
+    private final Map<String, SetMethodMeta> methods = new LinkedHashMap<>();
+    private final ConstructorMeta constructor;
+
+    public BeanMeta(RestToolKit toolKit, String prefix, Class<?> type) {
+        constructor = resolveConstructor(toolKit, prefix, type);
+        resolveFieldAndMethod(toolKit, prefix, type);
+    }
+
+    public BeanMeta(RestToolKit toolKit, Class<?> type) {
+        this(toolKit, null, type);
+    }
+
+    public Collection<FieldMeta> getFields() {
+        return fields.values();
+    }
+
+    public FieldMeta getField(String name) {
+        return fields.get(name);
+    }
+
+    public Collection<SetMethodMeta> getMethods() {
+        return methods.values();
+    }
+
+    public SetMethodMeta getMethod(String name) {
+        return methods.get(name);
+    }
+
+    public ConstructorMeta getConstructor() {
+        return constructor;
+    }
+
+    public Object newInstance() {
+        return constructor.newInstance();
+    }
+
+    public static ConstructorMeta resolveConstructor(RestToolKit toolKit, String prefix, Class<?> type) {
+        Constructor<?>[] constructors = type.getConstructors();
+        Constructor<?> ct = null;
+        if (constructors.length == 1) {
+            ct = constructors[0];
+        } else {
+            try {
+                ct = type.getDeclaredConstructor();
+            } catch (NoSuchMethodException ignored) {
+            }
+        }
+        if (ct == null) {
+            throw new IllegalArgumentException("No available default constructor found in " + type);
+        }
+        return new ConstructorMeta(toolKit, prefix, ct);
+    }
+
+    private void resolveFieldAndMethod(RestToolKit toolKit, String prefix, Class<?> type) {
+        if (type == Object.class) {
+            return;
+        }
+        for (Field field : type.getDeclaredFields()) {
+            int modifiers = field.getModifiers();
+            if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) {
+                continue;
+            }
+            if (field.getAnnotations().length == 0) {
+                continue;
+            }
+            if (!field.isAccessible()) {
+                field.setAccessible(true);
+            }
+            FieldMeta fieldMeta = new FieldMeta(toolKit, prefix, field);
+            fields.put(fieldMeta.getName(), fieldMeta);
+        }
+        for (Method method : type.getDeclaredMethods()) {
+            if (method.getParameterCount() != 1) {
+                continue;
+            }
+            int modifiers = method.getModifiers();
+            if ((modifiers & (Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.STATIC)) == Modifier.PUBLIC) {
+                Parameter parameter = method.getParameters()[0];
+                String name = method.getName();
+                if (name.length() > 3 && name.startsWith("set")) {
+                    String getMethodName = "get" + name.substring(3);
+                    Method getMethod = null;
+                    try {
+                        getMethod = type.getDeclaredMethod(getMethodName);
+                    } catch (NoSuchMethodException ignored) {
+                    }
+                    name = Character.toLowerCase(name.charAt(3)) + name.substring(4);
+                    SetMethodMeta methodMeta = new SetMethodMeta(toolKit, method, getMethod, parameter, prefix, name);
+                    methods.put(methodMeta.getName(), methodMeta);
+                }
+            }
+        }
+        resolveFieldAndMethod(toolKit, prefix, type.getSuperclass());
+    }
+
+    public static final class ConstructorMeta {
+
+        private final Constructor<?> constructor;
+        private final ConstructorParameterMeta[] parameters;
+
+        ConstructorMeta(RestToolKit toolKit, String prefix, Constructor<?> constructor) {
+            this.constructor = constructor;
+            parameters = initParameters(toolKit, prefix, constructor);
+        }
+
+        public ConstructorParameterMeta[] getParameters() {
+            return parameters;
+        }
+
+        private ConstructorParameterMeta[] initParameters(RestToolKit toolKit, String prefix, Constructor<?> ct) {
+            Parameter[] cps = ct.getParameters();
+            int len = cps.length;
+            ConstructorParameterMeta[] parameters = new ConstructorParameterMeta[len];
+            for (int i = 0; i < len; i++) {
+                parameters[i] = new ConstructorParameterMeta(toolKit, cps[i], prefix);
+            }
+            return parameters;
+        }
+
+        public Object newInstance(Object... args) {
+            try {
+                return constructor.newInstance(args);
+            } catch (Throwable t) {
+                throw RestException.wrap(t);
+            }
+        }
+    }
+
+    public static final class ConstructorParameterMeta extends ParameterMeta {
+
+        private final Parameter parameter;
+
+        ConstructorParameterMeta(RestToolKit toolKit, Parameter parameter, String prefix) {
+            super(toolKit, prefix, parameter.isNamePresent() ? parameter.getName() : null);
+            this.parameter = parameter;
+        }
+
+        @Override
+        protected AnnotatedElement getAnnotatedElement() {
+            return parameter;
+        }
+
+        @Override
+        public Class<?> getType() {
+            return parameter.getType();
+        }
+
+        @Override
+        public Type getGenericType() {
+            return parameter.getParameterizedType();
+        }
+
+        @Override
+        public String getDescription() {
+            return "ConstructorParameter{" + parameter + '}';
+        }
+    }
+
+    public abstract static class NestableParameterMeta extends ParameterMeta {
+
+        private NestableParameterMeta nestedMeta;
+        private String finalName;
+
+        public NestableParameterMeta(RestToolKit toolKit, String prefix, String name) {
+            super(toolKit, prefix, name);
+        }
+
+        @Nullable
+        @Override
+        public final String getName() {
+            String name = finalName;
+            if (name == null) {
+                AnnotationMeta<Param> param = findAnnotation(Param.class);
+                if (param != null) {
+                    name = param.getValue();
+                }
+                if (name == null || name.isEmpty()) {
+                    name = super.getName();
+                }
+                finalName = name;
+            }
+            return name;
+        }
+
+        public void setValue(Object bean, Object value) {}
+
+        public Object getValue(Object bean) {
+            return null;
+        }
+
+        public final NestableParameterMeta getNestedMeta() {
+            return nestedMeta;
+        }
+
+        protected final void initNestedMeta() {
+            Type nestedType = null;
+            Class<?> type = getType();
+            if (Map.class.isAssignableFrom(type)) {
+                nestedType = TypeUtils.getNestedGenericType(getGenericType(), 1);
+            } else if (Collection.class.isAssignableFrom(type)) {
+                nestedType = TypeUtils.getNestedGenericType(getGenericType(), 0);
+            } else if (type.isArray()) {
+                Type genericType = getGenericType();
+                if (genericType instanceof GenericArrayType) {
+                    nestedType = ((GenericArrayType) genericType).getGenericComponentType();
+                } else {
+                    nestedType = type.getComponentType();
+                }
+            }
+            nestedMeta = nestedType == null ? null : new NestedMeta(getToolKit(), nestedType);
+        }
+    }
+
+    public static final class FieldMeta extends NestableParameterMeta {
+
+        private final Field field;
+
+        FieldMeta(RestToolKit toolKit, String prefix, Field field) {
+            super(toolKit, prefix, field.getName());
+            this.field = field;
+            initNestedMeta();
+        }
+
+        @Override
+        public Class<?> getType() {
+            return field.getType();
+        }
+
+        @Override
+        public Type getGenericType() {
+            return field.getGenericType();
+        }
+
+        @Override
+        protected AnnotatedElement getAnnotatedElement() {
+            return field;
+        }
+
+        public void setValue(Object bean, Object value) {
+            try {
+                field.set(bean, value);
+            } catch (Throwable t) {
+                throw RestException.wrap(t);
+            }
+        }
+
+        public Object getValue(Object bean) {
+            try {
+                return field.get(bean);
+            } catch (Throwable t) {
+                throw RestException.wrap(t);
+            }
+        }
+
+        @Override
+        public String getDescription() {
+            return "FieldParameter{" + field + '}';
+        }
+    }
+
+    public static final class SetMethodMeta extends NestableParameterMeta {
+
+        private final Method method;
+        private final Method getMethod;
+        private final Parameter parameter;
+
+        SetMethodMeta(RestToolKit toolKit, Method m, Method gm, Parameter p, String prefix, String name) {
+            super(toolKit, prefix, name);
+            method = m;
+            getMethod = gm;
+            parameter = p;
+            initNestedMeta();
+        }
+
+        @Override
+        public Class<?> getType() {
+            return parameter.getType();
+        }
+
+        @Override
+        public Type getGenericType() {
+            return parameter.getParameterizedType();
+        }
+
+        @Override
+        protected AnnotatedElement getAnnotatedElement() {
+            return parameter;
+        }
+
+        public void setValue(Object bean, Object value) {
+            try {
+                method.invoke(bean, value);
+            } catch (Throwable t) {
+                throw RestException.wrap(t);
+            }
+        }
+
+        public Object getValue(Object bean) {
+            if (getMethod == null) {
+                return null;
+            }
+            try {
+                return getMethod.invoke(bean);
+            } catch (Throwable t) {
+                throw RestException.wrap(t);
+            }
+        }
+
+        @Override
+        public String getDescription() {
+            return "SetMethodParameter{" + method + '}';
+        }
+    }
+
+    public static final class NestedMeta extends NestableParameterMeta {
+
+        private final Class<?> type;
+        private final Type genericType;
+
+        NestedMeta(RestToolKit toolKit, Type genericType) {
+            super(toolKit, null, null);
+            type = TypeUtils.getActualType(genericType);
+            this.genericType = genericType;
+            initNestedMeta();
+        }
+
+        @Override
+        public Class<?> getType() {
+            return type;
+        }
+
+        @Override
+        public Type getGenericType() {
+            return genericType;
+        }
+
+        @Override
+        protected AnnotatedElement getAnnotatedElement() {
+            return null;
+        }
+
+        @Override
+        public String getDescription() {
+            return "NestedParameter{" + (genericType == null ? type : genericType) + '}';
+        }
+    }
+}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java
index d5545c5858..d16987ff85 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/CorsMeta.java
@@ -65,6 +65,12 @@ public class CorsMeta {
         return new Builder();
     }
 
+    public static CorsMeta combine(CorsMeta source, CorsMeta other) {
+        return source == null || source.isEmpty()
+                ? other == null || other.isEmpty() ? null : other.applyDefault()
+                : source.combine(other).applyDefault();
+    }
+
     public String[] getAllowedOrigins() {
         return allowedOrigins;
     }
@@ -105,24 +111,24 @@ public class CorsMeta {
     public CorsMeta applyDefault() {
         String[] allowedOriginArray = null;
         Pattern[] allowedOriginPatternArray = null;
-        if (this.allowedOrigins.length == 0) {
+        if (allowedOrigins.length == 0) {
             allowedOriginArray = new String[] {ANY_VALUE};
             allowedOriginPatternArray = new Pattern[] {null};
         }
 
         String[] allowedMethodArray = null;
-        if (this.allowedMethods.length == 0) {
+        if (allowedMethods.length == 0) {
             allowedMethodArray =
                     new String[] {HttpMethods.GET.name(), HttpMethods.HEAD.name(), HttpMethods.POST.name()};
         }
 
         String[] allowedHeaderArray = null;
-        if (this.allowedHeaders.length == 0) {
+        if (allowedHeaders.length == 0) {
             allowedHeaderArray = new String[] {ANY_VALUE};
         }
 
         Long maxAgeValue = null;
-        if (this.maxAge == null) {
+        if (maxAge == null) {
             maxAgeValue = 1800L;
         }
 
@@ -134,10 +140,10 @@ public class CorsMeta {
         }
 
         return new CorsMeta(
-                allowedOriginArray == null ? this.allowedOrigins : allowedOriginArray,
-                allowedOriginPatternArray == null ? this.allowedOriginsPatterns : allowedOriginPatternArray,
-                allowedMethodArray == null ? this.allowedMethods : allowedMethodArray,
-                allowedHeaderArray == null ? this.allowedHeaders : allowedHeaderArray,
+                allowedOriginArray == null ? allowedOrigins : allowedOriginArray,
+                allowedOriginPatternArray == null ? allowedOriginsPatterns : allowedOriginPatternArray,
+                allowedMethodArray == null ? allowedMethods : allowedMethodArray,
+                allowedHeaderArray == null ? allowedHeaders : allowedHeaderArray,
                 exposedHeaders,
                 allowCredentials,
                 maxAgeValue);
@@ -158,7 +164,7 @@ public class CorsMeta {
     }
 
     /**
-     * Merge two arrays of CORS config values, with the other array having higher priority.
+     * Merge two arrays of CORS config values, the other array having higher priority.
      */
     private static String[] combine(String[] source, String[] other) {
         if (other.length == 0) {
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 93ea6b2faf..ce3e2350d6 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,6 +16,8 @@
  */
 package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta;
 
+import org.apache.dubbo.rpc.model.MethodDescriptor;
+
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Method;
 import java.lang.reflect.Parameter;
@@ -27,18 +29,27 @@ public final class MethodMeta extends AnnotationSupport {
 
     private final List<Method> hierarchy;
     private final Method method;
-    private final ParameterMeta[] parameters;
+    private MethodDescriptor methodDescriptor;
+    private ParameterMeta[] parameters;
     private final ServiceMeta serviceMeta;
 
-    public MethodMeta(List<Method> hierarchy, ServiceMeta serviceMeta) {
+    public MethodMeta(List<Method> hierarchy, MethodDescriptor methodDescriptor, ServiceMeta serviceMeta) {
         super(serviceMeta.getToolKit());
         this.hierarchy = hierarchy;
-        method = hierarchy.get(0);
-        parameters = initParameters(method, hierarchy);
+        method = initMethod(hierarchy, methodDescriptor);
+        this.methodDescriptor = methodDescriptor;
         this.serviceMeta = serviceMeta;
     }
 
-    private ParameterMeta[] initParameters(Method method, List<Method> hierarchy) {
+    private Method initMethod(List<Method> hierarchy, MethodDescriptor methodDescriptor) {
+        Method method = null;
+        if (methodDescriptor != null) {
+            method = methodDescriptor.getMethod();
+        }
+        return method == null ? hierarchy.get(hierarchy.size() - 1) : method;
+    }
+
+    public void initParameters() {
         int count = method.getParameterCount();
         List<List<Parameter>> parameterHierarchies = new ArrayList<>(count);
         for (int i = 0, len = hierarchy.size(); i < len; i++) {
@@ -62,7 +73,7 @@ public final class MethodMeta extends AnnotationSupport {
             String parameterName = parameterNames == null ? null : parameterNames[i];
             parameters[i] = new MethodParameterMeta(parameterHierarchies.get(i), parameterName, i, this);
         }
-        return parameters;
+        this.parameters = parameters;
     }
 
     public List<Method> getHierarchy() {
@@ -73,6 +84,14 @@ public final class MethodMeta extends AnnotationSupport {
         return method;
     }
 
+    public MethodDescriptor getMethodDescriptor() {
+        return methodDescriptor;
+    }
+
+    public void setMethodDescriptor(MethodDescriptor methodDescriptor) {
+        this.methodDescriptor = methodDescriptor;
+    }
+
     public ParameterMeta[] getParameters() {
         return parameters;
     }
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 a6094410f8..845acf7d60 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,6 +16,9 @@
  */
 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;
@@ -45,6 +48,13 @@ 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/ParameterMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ParameterMeta.java
index 32779b34e0..2def707122 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
@@ -16,7 +16,6 @@
  */
 package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta;
 
-import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.remoting.http12.HttpRequest;
 import org.apache.dubbo.remoting.http12.HttpResponse;
 import org.apache.dubbo.rpc.protocol.tri.rest.Messages;
@@ -27,6 +26,7 @@ import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils;
 import javax.annotation.Nullable;
 
 import java.lang.reflect.Type;
+import java.util.Collection;
 import java.util.Optional;
 
 public abstract class ParameterMeta extends AnnotationSupport {
@@ -39,7 +39,7 @@ public abstract class ParameterMeta extends AnnotationSupport {
 
     protected ParameterMeta(RestToolKit toolKit, String prefix, String name) {
         super(toolKit);
-        this.prefix = StringUtils.isEmpty(prefix) ? null : prefix;
+        this.prefix = prefix;
         this.name = name;
     }
 
@@ -54,11 +54,12 @@ public abstract class ParameterMeta extends AnnotationSupport {
     }
 
     @Nullable
-    public final String getName() {
+    public String getName() {
         return name;
     }
 
-    public final String getRequiredName() {
+    public String getRequiredName() {
+        String name = getName();
         if (name == null) {
             throw new RestException(Messages.ARGUMENT_NAME_MISSING, getType());
         }
@@ -68,7 +69,10 @@ public abstract class ParameterMeta extends AnnotationSupport {
     public final boolean isSimple() {
         Boolean simple = this.simple;
         if (simple == null) {
-            simple = TypeUtils.isSimpleProperty(getActualType());
+            Class<?> type = Collection.class.isAssignableFrom(getType())
+                    ? TypeUtils.getNestedActualType(getGenericType(), 0)
+                    : getActualType();
+            simple = TypeUtils.isSimpleProperty(type);
             this.simple = simple;
         }
         return simple;
@@ -79,7 +83,7 @@ public abstract class ParameterMeta extends AnnotationSupport {
         if (type == null) {
             type = getType();
             if (type == Optional.class) {
-                type = TypeUtils.getNestedType(getGenericType(), 0);
+                type = TypeUtils.getNestedActualType(getGenericType(), 0);
                 if (type == null) {
                     type = Object.class;
                 }
@@ -101,6 +105,14 @@ public abstract class ParameterMeta extends AnnotationSupport {
         return getToolKit().bind(this, request, response);
     }
 
+    public boolean isSingle() {
+        return false;
+    }
+
+    public int getIndex() {
+        return -1;
+    }
+
     public String getDescription() {
         return name;
     }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ServiceMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ServiceMeta.java
index f9f68ca6f5..28d940205e 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ServiceMeta.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/meta/ServiceMeta.java
@@ -17,6 +17,7 @@
 package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.rpc.model.ServiceDescriptor;
 import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils;
 import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit;
 
@@ -30,14 +31,21 @@ public final class ServiceMeta extends AnnotationSupport {
     private final List<Class<?>> hierarchy;
     private final Class<?> type;
     private final Object service;
+    private final ServiceDescriptor serviceDescriptor;
     private final URL url;
     private final String contextPath;
 
     private List<MethodMeta> exceptionHandlers;
 
-    public ServiceMeta(Collection<Class<?>> hierarchy, Object service, URL url, RestToolKit toolKit) {
+    public ServiceMeta(
+            Collection<Class<?>> hierarchy,
+            ServiceDescriptor serviceDescriptor,
+            Object service,
+            URL url,
+            RestToolKit toolKit) {
         super(toolKit);
         this.hierarchy = new ArrayList<>(hierarchy);
+        this.serviceDescriptor = serviceDescriptor;
         type = this.hierarchy.get(0);
         this.service = service;
         this.url = url;
@@ -52,6 +60,10 @@ public final class ServiceMeta extends AnnotationSupport {
         return type;
     }
 
+    public ServiceDescriptor getServiceDescriptor() {
+        return serviceDescriptor;
+    }
+
     public Object getService() {
         return service;
     }
diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/Annotations.java
similarity index 78%
copy from dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java
copy to dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/Annotations.java
index be8b1c5865..5a1ef216aa 100644
--- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/Annotations.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/Annotations.java
@@ -14,40 +14,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.rpc.protocol.tri.rest.support.spring;
+package org.apache.dubbo.rpc.protocol.tri.rest.support.basic;
 
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationEnum;
 
 import java.lang.annotation.Annotation;
 
 public enum Annotations implements AnnotationEnum {
-    RequestMapping,
-    PathVariable,
-    MatrixVariable,
-    RequestParam,
-    RequestHeader,
-    CookieValue,
-    RequestAttribute,
-    RequestPart,
-    RequestBody,
-    ModelAttribute,
-    ResponseStatus,
-    CrossOrigin,
-    ExceptionHandler,
+    Mapping,
+    Param,
     Nonnull("javax.annotation.Nonnull");
 
     private final String className;
     private Class<Annotation> type;
 
-    Annotations() {
-        className = "org.springframework.web.bind.annotation." + name();
-    }
-
     Annotations(String className) {
         this.className = className;
     }
 
-    @Override
+    Annotations() {
+        className = "org.apache.dubbo.remoting.http12.rest." + name();
+    }
+
     public String className() {
         return className;
     }
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
new file mode 100644
index 0000000000..db39ea43f1
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRequestMappingResolver.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+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.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;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit;
+import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils;
+
+import java.lang.reflect.Method;
+
+@Activate
+public class BasicRequestMappingResolver implements RequestMappingResolver {
+
+    private final FrameworkModel frameworkModel;
+    private final RestToolKit toolKit;
+    private CorsMeta globalCorsMeta;
+
+    public BasicRequestMappingResolver(FrameworkModel frameworkModel) {
+        this.frameworkModel = frameworkModel;
+        toolKit = new BasicRestToolKit(frameworkModel);
+    }
+
+    @Override
+    public RestToolKit getRestToolKit() {
+        return toolKit;
+    }
+
+    @Override
+    public boolean accept(MethodMeta methodMeta) {
+        return methodMeta.getMethodDescriptor() != null || methodMeta.findAnnotation(Annotations.Mapping) != null;
+    }
+
+    @Override
+    public RequestMapping resolve(ServiceMeta serviceMeta) {
+        AnnotationMeta<?> mapping = serviceMeta.findAnnotation(Annotations.Mapping);
+        Builder builder = builder(mapping);
+
+        String[] paths = getPaths(mapping);
+        if (paths.length == 0) {
+            builder.path(serviceMeta.getServiceInterface());
+        } else {
+            builder.path(paths);
+        }
+
+        return builder.name(serviceMeta.getType().getSimpleName())
+                .contextPath(serviceMeta.getContextPath())
+                .build();
+    }
+
+    @Override
+    public RequestMapping resolve(MethodMeta methodMeta) {
+        Method method = methodMeta.getMethod();
+        AnnotationMeta<?> mapping = methodMeta.findAnnotation(Annotations.Mapping);
+        Builder builder = builder(mapping);
+
+        String[] paths = getPaths(mapping);
+        if (paths.length == 0) {
+            builder.path(method.getName()).sig(TypeUtils.buildSig(method));
+        } else {
+            builder.path(paths);
+        }
+
+        ServiceMeta serviceMeta = methodMeta.getServiceMeta();
+        if (globalCorsMeta == null) {
+            globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel);
+        }
+        return builder.name(method.getName())
+                .custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion()))
+                .cors(globalCorsMeta)
+                .build();
+    }
+
+    private Builder builder(AnnotationMeta<?> mapping) {
+        Builder builder = RequestMapping.builder();
+        if (mapping == null) {
+            return builder;
+        }
+        builder.method(mapping.getStringArray("method"))
+                .param(mapping.getStringArray("params"))
+                .header(mapping.getStringArray("headers"))
+                .consume(mapping.getStringArray("consumes"))
+                .produce(mapping.getStringArray("produces"));
+        return builder;
+    }
+
+    private static String[] getPaths(AnnotationMeta<?> mapping) {
+        if (mapping == null) {
+            return StringUtils.EMPTY_STRING_ARRAY;
+        }
+        String[] paths = mapping.getStringArray("path");
+        if (paths.length > 0) {
+            return paths;
+        }
+        return mapping.getValueArray();
+    }
+}
diff --git a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRestToolKit.java
similarity index 51%
copy from dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java
copy to dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRestToolKit.java
index fef61723e7..8aac208d8b 100644
--- a/dubbo-plugin/dubbo-rest-spring/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/spring/AbstractSpringArgumentResolver.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BasicRestToolKit.java
@@ -14,19 +14,31 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.rpc.protocol.tri.rest.support.spring;
+package org.apache.dubbo.rpc.protocol.tri.rest.support.basic;
 
-import org.apache.dubbo.rpc.protocol.tri.rest.argument.AbstractAnnotationBaseArgumentResolver;
-import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta;
-import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.NamedValueMeta;
+import org.apache.dubbo.remoting.http12.HttpRequest;
+import org.apache.dubbo.remoting.http12.HttpResponse;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants;
 import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.util.AbstractRestToolKit;
 
-import java.lang.annotation.Annotation;
+final class BasicRestToolKit extends AbstractRestToolKit {
 
-public abstract class AbstractSpringArgumentResolver extends AbstractAnnotationBaseArgumentResolver {
+    private final BeanArgumentBinder binder;
+
+    public BasicRestToolKit(FrameworkModel frameworkModel) {
+        super(frameworkModel);
+        binder = new BeanArgumentBinder(frameworkModel);
+    }
+
+    @Override
+    public int getDialect() {
+        return RestConstants.DIALECT_BASIC;
+    }
 
     @Override
-    protected NamedValueMeta createNamedValueMeta(ParameterMeta param, AnnotationMeta<Annotation> ann) {
-        return new NamedValueMeta(ann.getValue(), Helper.isRequired(ann), Helper.defaultValue(ann));
+    public Object bind(ParameterMeta parameter, HttpRequest request, HttpResponse response) {
+        return binder.bind(parameter, request, response);
     }
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BeanArgumentBinder.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BeanArgumentBinder.java
new file mode 100644
index 0000000000..93214b2bff
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/BeanArgumentBinder.java
@@ -0,0 +1,375 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.tri.rest.support.basic;
+
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.Pair;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.remoting.http12.HttpRequest;
+import org.apache.dubbo.remoting.http12.HttpResponse;
+import org.apache.dubbo.remoting.http12.rest.Param;
+import org.apache.dubbo.remoting.http12.rest.ParamType;
+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.CompositeArgumentResolver;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.ConstructorMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.FieldMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.NestableParameterMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.BeanMeta.SetMethodMeta;
+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.Array;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+final class BeanArgumentBinder {
+
+    private static final Map<Class<?>, BeanMeta> CACHE = CollectionUtils.newConcurrentHashMap();
+
+    private final CompositeArgumentResolver argumentResolver;
+
+    public BeanArgumentBinder(FrameworkModel frameworkModel) {
+        argumentResolver = frameworkModel.getBeanFactory().getOrRegisterBean(CompositeArgumentResolver.class);
+    }
+
+    public Object bind(ParameterMeta paramMeta, HttpRequest request, HttpResponse response) {
+        try {
+            BeanMeta beanMeta = getBeanMeta(paramMeta);
+            if (beanMeta == null) {
+                return null;
+            }
+
+            ConstructorMeta constructor = beanMeta.getConstructor();
+            ParameterMeta[] parameters = constructor.getParameters();
+            Object bean;
+            int len = parameters.length;
+            if (len == 0) {
+                bean = constructor.newInstance();
+            } else {
+                Object[] args = new Object[len];
+                for (int i = 0; i < len; i++) {
+                    ParameterMeta parameter = parameters[i];
+                    args[i] = parameter.isSimple() ? argumentResolver.resolve(parameter, request, response) : null;
+                }
+                bean = constructor.newInstance(args);
+            }
+
+            Node root = new Node(paramMeta.getName(), bean, beanMeta);
+            for (String paramName : request.parameterNames()) {
+                Node current = root;
+                List<String> parts = StringUtils.tokenizeToList(paramName, '.');
+                for (int i = 0, size = parts.size(); i < size; i++) {
+                    if (current == null) {
+                        break;
+                    }
+                    String name = parts.get(i);
+                    Pair<String, String> pair = parseKeyParam(name);
+                    if (pair == null) {
+                        current = current.getChild(name);
+                        if (i == 0 && current == null && name.equals(root.name)) {
+                            current = root;
+                        }
+                    } else {
+                        name = pair.getLeft();
+                        current = current.getChild(name);
+                        if (current == null) {
+                            break;
+                        }
+                        String key = pair.getValue();
+                        if (!key.isEmpty()) {
+                            if (Character.isDigit(key.charAt(0))) {
+                                try {
+                                    current = current.getChild(Long.parseLong(key));
+                                    continue;
+                                } catch (NumberFormatException ignored) {
+                                }
+                            }
+                            current = current.getChild(key);
+                        }
+                    }
+                }
+                if (current == null) {
+                    continue;
+                }
+                Class<?> type = current.paramMeta.getActualType();
+                Object value;
+                if (type.isArray() || Collection.class.isAssignableFrom(type)) {
+                    value = request.parameterValues(paramName);
+                } else {
+                    value = request.parameter(paramName);
+                }
+                //noinspection unchecked
+                current.setValue(argumentResolver.getArgumentConverter().convert(value, current.paramMeta));
+            }
+
+            for (FieldMeta fieldMeta : beanMeta.getFields()) {
+                resolveParam(fieldMeta, bean, request, response);
+            }
+
+            for (SetMethodMeta methodMeta : beanMeta.getMethods()) {
+                resolveParam(methodMeta, bean, request, response);
+            }
+
+            return bean;
+        } catch (Exception e) {
+            throw new RestException(e, Messages.ARGUMENT_BIND_ERROR, paramMeta.getName(), paramMeta.getType());
+        }
+    }
+
+    private void resolveParam(NestableParameterMeta meta, Object bean, HttpRequest request, HttpResponse response) {
+        AnnotationMeta<Param> param = meta.getAnnotation(Param.class);
+        if (param == null || param.getAnnotation().type() == ParamType.Param) {
+            return;
+        }
+        meta.setValue(bean, argumentResolver.resolve(meta, request, response));
+    }
+
+    private static BeanMeta getBeanMeta(ParameterMeta paramMeta) {
+        Class<?> type = paramMeta.getActualType();
+        if (paramMeta.isSimple() || Modifier.isAbstract(type.getModifiers())) {
+            return null;
+        }
+        return CACHE.computeIfAbsent(type, k -> new BeanMeta(paramMeta.getToolKit(), k));
+    }
+
+    /**
+     * See
+     * <p>
+     * <a href="https://docs.spring.io/spring-framework/reference/core/validation/beans-beans.html#beans-binding">Spring beans-binding</a>
+     */
+    private static Pair<String, String> parseKeyParam(String name) {
+        int len = name.length();
+        if (name.charAt(len - 1) == ']') {
+            int start = name.lastIndexOf('[');
+            if (start > -1) {
+                return Pair.of(name.substring(0, start), name.substring(start + 1, len - 1));
+            }
+        }
+        return null;
+    }
+
+    private static final class Node {
+
+        public final String name;
+        public NestableParameterMeta paramMeta;
+        public Consumer<Object> setter;
+        public Object value;
+        public Map<Object, Node> children;
+        public BeanMeta beanMeta;
+
+        public Node(String name, NestableParameterMeta paramMeta, Consumer<Object> setter) {
+            this.name = name;
+            this.paramMeta = paramMeta;
+            this.setter = setter;
+        }
+
+        public Node(String name, Object value, BeanMeta beanMeta) {
+            this.name = name;
+            this.value = value;
+            this.beanMeta = beanMeta;
+        }
+
+        @SuppressWarnings({"rawtypes", "unchecked"})
+        public Node getChild(String name) {
+            if (children != null) {
+                Node node = children.get(name);
+                if (node != null) {
+                    return node;
+                }
+            }
+
+            if (beanMeta == null) {
+                Class<?> type = paramMeta.getType();
+                if (Map.class.isAssignableFrom(type)) {
+                    return createChild(name, paramMeta.getNestedMeta(), v -> ((Map) value).put(name, v));
+                }
+                return null;
+            }
+
+            NestableParameterMeta methodMeta = beanMeta.getMethod(name);
+            if (methodMeta != null) {
+                return createChild(name, methodMeta, v -> methodMeta.setValue(value, v));
+            }
+
+            NestableParameterMeta fieldMeta = beanMeta.getField(name);
+            if (fieldMeta != null) {
+                return createChild(name, fieldMeta, v -> fieldMeta.setValue(value, v));
+            }
+
+            return null;
+        }
+
+        @SuppressWarnings({"rawtypes", "unchecked", "SuspiciousSystemArraycopy"})
+        public Node getChild(long num) {
+            if (children != null) {
+                Node node = children.get(num);
+                if (node != null) {
+                    return node;
+                }
+            }
+
+            NestableParameterMeta nestedMeta = paramMeta.getNestedMeta();
+            Class<?> type = paramMeta.getType();
+            Object childValue = null;
+            Node child;
+            if (List.class.isAssignableFrom(type)) {
+                int index = (int) num;
+                List list = (List) value;
+                child = new Node(name, nestedMeta, v -> {
+                    if (index < 0) {
+                        return;
+                    }
+                    while (list.size() <= index) {
+                        list.add(null);
+                    }
+                    list.set(index, v);
+                });
+                if (index < list.size()) {
+                    childValue = list.get(index);
+                }
+            } else if (type.isArray()) {
+                int index = (int) num;
+                int len = Array.getLength(value);
+                child = new Node(name, nestedMeta, v -> {
+                    if (num >= 0 && num < len) {
+                        Array.set(value, index, v);
+                        return;
+                    }
+                    int tail = index < 0 ? len : index;
+                    Object newArr = Array.newInstance(value.getClass().getComponentType(), tail + 1);
+                    System.arraycopy(value, 0, newArr, 0, len);
+                    Array.set(newArr, tail, v);
+                    setter.accept(newArr);
+                });
+                if (index < len) {
+                    childValue = Array.get(value, index);
+                }
+            } else if (Map.class.isAssignableFrom(type)) {
+                Class<?> keyType = TypeUtils.getNestedActualType(paramMeta.getGenericType(), 0);
+                Object key = TypeUtils.longToObject(num, keyType);
+                child = new Node(name, nestedMeta, v -> {
+                    ((Map) value).put(key, v);
+                });
+                childValue = ((Map) value).get(key);
+            } else {
+                return null;
+            }
+
+            if (childValue == null) {
+                childValue = createValue(nestedMeta.getType());
+                if (childValue == null) {
+                    if (nestedMeta.isSimple()) {
+                        return child;
+                    }
+                    BeanMeta beanMeta = getBeanMeta(nestedMeta);
+                    if (beanMeta == null) {
+                        return null;
+                    }
+                    child.beanMeta = beanMeta;
+                    childValue = beanMeta.newInstance();
+                }
+                child.setter.accept(childValue);
+            } else {
+                child.beanMeta = getBeanMeta(nestedMeta);
+            }
+
+            putChild(child, num, childValue);
+            return child;
+        }
+
+        public void setValue(Object value) {
+            setter.accept(value);
+        }
+
+        private Node createChild(String name, NestableParameterMeta paramMeta, Consumer<Object> setter) {
+            Class<?> type = paramMeta.getType();
+            if (type.isArray()) {
+                Consumer<Object> arraySetter = setter;
+                setter = v -> {
+                    arraySetter.accept(v);
+                    if (children != null) {
+                        children.get(name).value = v;
+                    }
+                };
+            }
+            Node child = new Node(name, paramMeta, setter);
+            Object childValue = paramMeta.getValue(value);
+            boolean created = false;
+            if (childValue == null) {
+                if (value instanceof Map) {
+                    childValue = ((Map<?, ?>) value).get(name);
+                    if (childValue != null) {
+                        child.beanMeta = getBeanMeta(paramMeta);
+                    }
+                }
+                if (childValue == null) {
+                    childValue = createValue(type);
+                    if (childValue == null) {
+                        if (paramMeta.isSimple()) {
+                            return child;
+                        }
+                    } else {
+                        created = true;
+                    }
+                }
+            }
+            if (childValue == null) {
+                BeanMeta beanMeta = getBeanMeta(paramMeta);
+                if (beanMeta == null) {
+                    return null;
+                }
+                child.beanMeta = beanMeta;
+                childValue = beanMeta.newInstance();
+                created = true;
+            }
+            if (created) {
+                setter.accept(childValue);
+            }
+            putChild(child, name, childValue);
+            return child;
+        }
+
+        private Object createValue(Class<?> type) {
+            if (Map.class.isAssignableFrom(type)) {
+                return TypeUtils.createMap(type);
+            }
+            if (Collection.class.isAssignableFrom(type)) {
+                return TypeUtils.createCollection(type);
+            }
+            if (type.isArray()) {
+                return Array.newInstance(type.getComponentType(), 1);
+            }
+            return null;
+        }
+
+        private void putChild(Node child, Object name, Object value) {
+            child.value = value;
+            if (children == null) {
+                children = new HashMap<>();
+            }
+            children.put(name, child);
+        }
+    }
+}
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
new file mode 100644
index 0000000000..ab4cbd97a6
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/FallbackArgumentResolver.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.tri.rest.support.basic;
+
+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.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.NamedValueMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ParameterMeta;
+import org.apache.dubbo.rpc.protocol.tri.rest.util.RequestUtils;
+import org.apache.dubbo.rpc.protocol.tri.rest.util.RestUtils;
+
+import java.util.List;
+import java.util.Map;
+
+@Activate(order = Integer.MAX_VALUE - 10000)
+public class FallbackArgumentResolver extends AbstractArgumentResolver {
+
+    @Override
+    public boolean accept(ParameterMeta param) {
+        return param.getToolKit().getDialect() == RestConstants.DIALECT_BASIC;
+    }
+
+    @Override
+    protected NamedValueMeta createNamedValueMeta(ParameterMeta param) {
+        return new NamedValueMeta(param.isAnnotated(Annotations.Nonnull), null);
+    }
+
+    @Override
+    protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) {
+        return doResolveValue(meta, true, request, response);
+    }
+
+    @Override
+    protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) {
+        return doResolveValue(meta, false, request, response);
+    }
+
+    protected Object doResolveValue(NamedValueMeta meta, boolean single, HttpRequest request, HttpResponse response) {
+        ParameterMeta parameter = meta.parameterMeta();
+        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) {
+                    List<?> list = (List<?>) body;
+                    int index = parameter.getIndex();
+                    if (index >= 0 && list.size() > index) {
+                        return list.get(index);
+                    }
+                } else if (body instanceof Map) {
+                    Object value = ((Map<?, ?>) body).get(meta.name());
+                    if (value != null) {
+                        return value;
+                    }
+                }
+            }
+        }
+        if (single) {
+            String value = request.parameter(meta.name());
+            if (parameter.isSimple() || RestUtils.isMaybeJSONObject(value)) {
+                return value;
+            }
+            return parameter.bind(request, response);
+        }
+        return request.parameterValues(meta.name());
+    }
+
+    @Override
+    protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) {
+        return resolveValue(meta, request, response);
+    }
+}
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
new file mode 100644
index 0000000000..52456c898c
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/ParamArgumentResolver.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.tri.rest.support.basic;
+
+import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.common.io.StreamUtils;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.remoting.http12.HttpCookie;
+import org.apache.dubbo.remoting.http12.HttpRequest;
+import org.apache.dubbo.remoting.http12.HttpRequest.FileUpload;
+import org.apache.dubbo.remoting.http12.HttpResponse;
+import org.apache.dubbo.remoting.http12.rest.Param;
+import org.apache.dubbo.remoting.http12.rest.ParamType;
+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.RestParameterException;
+import org.apache.dubbo.rpc.protocol.tri.rest.argument.AbstractAnnotationBaseArgumentResolver;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta;
+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;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Activate
+public class ParamArgumentResolver extends AbstractAnnotationBaseArgumentResolver {
+
+    @Override
+    protected NamedValueMeta createNamedValueMeta(ParameterMeta param, AnnotationMeta<Annotation> ann) {
+        String defaultValue = ann.getString("defaultValue");
+        if (Param.DEFAULT_NONE.equals(defaultValue)) {
+            defaultValue = null;
+        }
+        ParamType paramType = ann.getEnum("type");
+        return new ParamNamedValueMeta(ann.getValue(), ann.getBoolean("required"), defaultValue, paramType);
+    }
+
+    @Override
+    public Class<Annotation> accept() {
+        return Annotations.Param.type();
+    }
+
+    @Override
+    protected Object resolveValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) {
+        switch (((ParamNamedValueMeta) meta).paramType) {
+            case PathVariable:
+                return resolvePathVariable(meta, request);
+            case MatrixVariable:
+                return CollectionUtils.first(resolveMatrixVariable(meta, request));
+            case Param:
+                return request.parameter(meta.name());
+            case Header:
+                return request.header(meta.name());
+            case Cookie:
+                return request.cookie(meta.name());
+            case Attribute:
+                return request.attribute(meta.name());
+            case Part:
+                return request.part(meta.name());
+            case Body:
+                if (RequestUtils.isFormOrMultiPart(request)) {
+                    return request.formParameter(meta.name());
+                }
+                return RequestUtils.decodeBody(request, meta.type());
+        }
+        return null;
+    }
+
+    @Override
+    protected Object filterValue(Object value, NamedValueMeta meta) {
+        return StringUtils.EMPTY_STRING.equals(value) ? meta.defaultValue() : value;
+    }
+
+    @Override
+    protected Object resolveCollectionValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) {
+        switch (((ParamNamedValueMeta) meta).paramType) {
+            case PathVariable:
+                String value = resolvePathVariable(meta, request);
+                return value == null ? Collections.emptyList() : Collections.singletonList(value);
+            case MatrixVariable:
+                return resolveMatrixVariable(meta, request);
+            case Param:
+                return request.parameterValues(meta.name());
+            case Header:
+                return request.headerValues(meta.name());
+            case Cookie:
+                Collection<HttpCookie> cookies = request.cookies();
+                if (cookies.isEmpty()) {
+                    return Collections.emptyList();
+                }
+                String name = meta.name();
+                List<HttpCookie> result = new ArrayList<>(cookies.size());
+                for (HttpCookie cookie : cookies) {
+                    if (name.equals(cookie.name())) {
+                        result.add(cookie);
+                    }
+                }
+                return result;
+            case Attribute:
+                return request.attribute(meta.name());
+            case Part:
+                return request.parts();
+            case Body:
+                Class<?> type = meta.type();
+                if (type == byte[].class) {
+                    try {
+                        return StreamUtils.readBytes(request.inputStream());
+                    } catch (IOException e) {
+                        throw new RestException(e);
+                    }
+                }
+                if (RequestUtils.isFormOrMultiPart(request)) {
+                    return request.formParameterValues(meta.name());
+                }
+                return RequestUtils.decodeBody(request, meta.type());
+        }
+        return null;
+    }
+
+    @Override
+    protected Object resolveMapValue(NamedValueMeta meta, HttpRequest request, HttpResponse response) {
+        switch (((ParamNamedValueMeta) meta).paramType) {
+            case PathVariable:
+                String value = resolvePathVariable(meta, request);
+                return value == null ? Collections.emptyMap() : Collections.singletonMap(meta.name(), value);
+            case MatrixVariable:
+                return CollectionUtils.first(resolveMatrixVariable(meta, request));
+            case Param:
+                return RequestUtils.getParametersMap(request);
+            case Header:
+                return request.headers();
+            case Cookie:
+                Collection<HttpCookie> cookies = request.cookies();
+                if (cookies.isEmpty()) {
+                    return Collections.emptyMap();
+                }
+                Map<String, List<HttpCookie>> mapValue = CollectionUtils.newLinkedHashMap(cookies.size());
+                for (HttpCookie cookie : cookies) {
+                    mapValue.computeIfAbsent(cookie.name(), k -> new ArrayList<>())
+                            .add(cookie);
+                }
+                return mapValue;
+            case Attribute:
+                return request.attributes();
+            case Part:
+                Collection<FileUpload> parts = request.parts();
+                if (parts.isEmpty()) {
+                    return Collections.emptyMap();
+                }
+                Map<String, FileUpload> result = new LinkedHashMap<>(parts.size());
+                for (FileUpload part : parts) {
+                    result.put(part.name(), part);
+                }
+                return result;
+            case Body:
+                if (RequestUtils.isFormOrMultiPart(request)) {
+                    return RequestUtils.getFormParametersMap(request);
+                }
+                return RequestUtils.decodeBody(request, meta.type());
+        }
+        return null;
+    }
+
+    private static String resolvePathVariable(NamedValueMeta meta, HttpRequest request) {
+        Map<String, String> variableMap = request.attribute(RestConstants.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
+        if (variableMap == null) {
+            if (meta.required()) {
+                throw new RestParameterException(Messages.ARGUMENT_VALUE_MISSING, meta.name(), meta.type());
+            }
+            return null;
+        }
+        String value = variableMap.get(meta.name());
+        if (value == null) {
+            return null;
+        }
+        int index = value.indexOf(';');
+        return RequestUtils.decodeURL(index == -1 ? value : value.substring(0, index));
+    }
+
+    private static List<String> resolveMatrixVariable(NamedValueMeta meta, HttpRequest request) {
+        Map<String, String> variableMap = request.attribute(RestConstants.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
+        return RequestUtils.parseMatrixVariableValues(variableMap, meta.name());
+    }
+
+    private static class ParamNamedValueMeta extends NamedValueMeta {
+
+        private final ParamType paramType;
+
+        public 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/DefaultRestToolKit.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/AbstractRestToolKit.java
similarity index 86%
rename from dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/DefaultRestToolKit.java
rename to dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/AbstractRestToolKit.java
index 0d5a2a03f7..c35cd414d5 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/DefaultRestToolKit.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/AbstractRestToolKit.java
@@ -18,8 +18,6 @@ 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.remoting.http12.HttpRequest;
-import org.apache.dubbo.remoting.http12.HttpResponse;
 import org.apache.dubbo.rpc.model.FrameworkModel;
 import org.apache.dubbo.rpc.protocol.tri.rest.argument.GeneralTypeConverter;
 import org.apache.dubbo.rpc.protocol.tri.rest.argument.TypeConverter;
@@ -31,21 +29,16 @@ import java.lang.reflect.Method;
 import java.lang.reflect.Parameter;
 import java.util.Map;
 
-public class DefaultRestToolKit implements RestToolKit {
+public abstract class AbstractRestToolKit implements RestToolKit {
 
     protected final FrameworkModel frameworkModel;
     protected final TypeConverter typeConverter;
 
-    public DefaultRestToolKit(FrameworkModel frameworkModel) {
+    public AbstractRestToolKit(FrameworkModel frameworkModel) {
         this.frameworkModel = frameworkModel;
         typeConverter = frameworkModel.getBeanFactory().getOrRegisterBean(GeneralTypeConverter.class);
     }
 
-    @Override
-    public int getDialect() {
-        return 0;
-    }
-
     @Override
     public String resolvePlaceholders(String text) {
         return RestUtils.hasPlaceholder(text) ? getEnvironment().resolvePlaceholders(text) : text;
@@ -60,11 +53,6 @@ public class DefaultRestToolKit implements RestToolKit {
         return typeConverter.convert(value, parameter.getGenericType());
     }
 
-    @Override
-    public Object bind(ParameterMeta parameter, HttpRequest request, HttpResponse response) {
-        return null;
-    }
-
     @Override
     public String[] getParameterNames(Method method) {
         Parameter[] parameters = method.getParameters();
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/KeyString.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/KeyString.java
new file mode 100644
index 0000000000..2ddac3c70c
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/KeyString.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.tri.rest.util;
+
+/**
+ * Zero-copy string as map key.
+ */
+public final class KeyString implements CharSequence {
+
+    private final String value;
+    private final int offset;
+    private final int length;
+    private final boolean caseSensitive;
+
+    public KeyString(String value, int start, int end, boolean caseSensitive) {
+        this.value = value;
+        offset = start;
+        length = (end == -1 ? value.length() : end) - start;
+        this.caseSensitive = caseSensitive;
+    }
+
+    public KeyString(String value, int start, int end) {
+        this(value, start, end, true);
+    }
+
+    public KeyString(String value, int end, boolean caseSensitive) {
+        this.value = value;
+        offset = 0;
+        length = end == -1 ? value.length() : end;
+        this.caseSensitive = caseSensitive;
+    }
+
+    public KeyString(String value, int end) {
+        this(value, end, true);
+    }
+
+    public KeyString(String value, boolean caseSensitive) {
+        this.value = value;
+        offset = 0;
+        length = value.length();
+        this.caseSensitive = caseSensitive;
+    }
+
+    public KeyString(String value) {
+        this(value, false);
+    }
+
+    public KeyString(KeyString path, int start, int end) {
+        this(path.value, path.offset + start, path.offset + end, path.caseSensitive);
+    }
+
+    @Override
+    public int length() {
+        return length;
+    }
+
+    @Override
+    public char charAt(int index) {
+        return value.charAt(offset + index);
+    }
+
+    @Override
+    public CharSequence subSequence(int start, int end) {
+        return value.substring(offset + start, offset + end);
+    }
+
+    @Override
+    public int hashCode() {
+        int h = 0;
+        for (int i = 0; i < length; i++) {
+            h = 31 * h + (caseSensitive ? value.charAt(offset + i) : Character.toLowerCase(value.charAt(offset + i)));
+        }
+        return h;
+    }
+
+    @Override
+    @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
+    public boolean equals(Object obj) {
+        KeyString other = (KeyString) obj;
+        return value.regionMatches(!caseSensitive, offset, other.value, other.offset, length);
+    }
+
+    @Override
+    public String toString() {
+        return value.substring(offset, length - offset);
+    }
+
+    public int indexOf(char ch, int start) {
+        int index = value.indexOf(ch, offset + start);
+        return index == -1 ? -1 : index - offset;
+    }
+
+    public boolean regionMatches(int start, String value, int i, int length) {
+        return this.value.regionMatches(!caseSensitive, offset + start, value, i, length);
+    }
+
+    public String substring(int start) {
+        return value.substring(offset + start, offset + length);
+    }
+
+    public String substring(int start, int end) {
+        return value.substring(offset + start, offset + (end == -1 ? length : end));
+    }
+}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtils.java
index e6a6bf51c0..609446f826 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtils.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtils.java
@@ -22,6 +22,7 @@ import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.rpc.protocol.tri.rest.Messages;
 import org.apache.dubbo.rpc.protocol.tri.rest.PathParserException;
 import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression;
 
 import javax.annotation.Nonnull;
 
@@ -88,6 +89,12 @@ public final class PathUtils {
                 return path2;
             }
         } else if (path1.indexOf('{') == -1) {
+            if (path1.indexOf('*') != -1) {
+                if (PathExpression.parse(path1).match(path2) != null) {
+                    return path2;
+                }
+            }
+
             int starDotPos1 = path1.lastIndexOf("*.");
             if (starDotPos1 > -1) {
                 String ext1 = path1.substring(starDotPos1 + 1);
@@ -100,7 +107,7 @@ public final class PathUtils {
                     file2 = path2.substring(0, dotPos2);
                     ext2 = path2.substring(dotPos2);
                 }
-                boolean ext1All = ext1.equals(".*");
+                boolean ext1All = ext1.equals(".*") || ext1.isEmpty();
                 boolean ext2All = ext2.equals(".*") || ext2.isEmpty();
                 if (!ext1All && !ext2All) {
                     throw new PathParserException(Messages.CANNOT_COMBINE_PATHS, path1, path2);
@@ -240,8 +247,24 @@ public final class PathUtils {
             }
             end = i;
         }
+        switch (state) {
+            case State.DOT:
+                end--;
+                break;
+            case State.DOT_DOT:
+                if (buf == null) {
+                    buf = new StringBuilder(len);
+                }
+                if (end > 2) {
+                    buf.append(path, start, end - 2);
+                    buf.setLength(buf.lastIndexOf(RestConstants.SLASH) + 1);
+                    start = -1;
+                }
+                break;
+            default:
+        }
         if (buf == null) {
-            return path;
+            return start == -1 ? path : path.substring(start, end + 1);
         }
         if (start != -1) {
             buf.append(path, start, end + 1);
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 a91fbb3289..b75561a0ee 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,6 +25,9 @@ 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.net.URLDecoder;
 import java.net.URLEncoder;
@@ -41,6 +44,11 @@ public final class RequestUtils {
 
     private RequestUtils() {}
 
+    public static boolean isMultiPart(HttpRequest request) {
+        String contentType = request.contentType();
+        return contentType != null && contentType.startsWith(MediaType.MULTIPART_FORM_DATA.getName());
+    }
+
     public static boolean isFormOrMultiPart(HttpRequest request) {
         String contentType = request.contentType();
         if (contentType == null) {
@@ -168,12 +176,85 @@ public final class RequestUtils {
         }
         Object value = request.attribute(RestConstants.BODY_ATTRIBUTE);
         if (value == null) {
-            if (decoder.mediaType() == MediaType.TEXT_PLAIN) {
+            if (decoder.mediaType().isPureText()) {
                 type = String.class;
             }
-            value = decoder.decode(request.inputStream(), type, request.charsetOrDefault());
+            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;
+        }
+
+        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();
+                }
+            }
+
+            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;
+                } else {
+                    return null;
+                }
+                int available = input.available();
+                if (available != 0) {
+                    value = decoder.decode(input, type, request.charsetOrDefault());
+                }
+                request.setAttribute(RestConstants.BODY_ATTRIBUTE, value);
+            }
+            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];
+        }
+        return null;
+    }
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RestUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RestUtils.java
index a5e5331dc5..1155d3860a 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RestUtils.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/RestUtils.java
@@ -21,6 +21,7 @@ import org.apache.dubbo.common.lang.Prioritized;
 import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension;
 
 public final class RestUtils {
+
     private RestUtils() {}
 
     public static boolean hasPlaceholder(String text) {
@@ -50,6 +51,36 @@ public final class RestUtils {
         return false;
     }
 
+    public static boolean isMaybeJSONObject(String str) {
+        if (str == null) {
+            return false;
+        }
+        int i = 0, n = str.length();
+        if (n < 3) {
+            return false;
+        }
+        char expected = 0;
+        for (; i < n; i++) {
+            char c = str.charAt(i);
+            if (Character.isWhitespace(c)) {
+                continue;
+            }
+            if (c == '{') {
+                expected = '}';
+                break;
+            }
+            return false;
+        }
+        for (int j = n - 1; j > i; j--) {
+            char c = str.charAt(j);
+            if (Character.isWhitespace(c)) {
+                continue;
+            }
+            return c == expected;
+        }
+        return false;
+    }
+
     public static int getPriority(Object obj) {
         if (obj instanceof Prioritized) {
             int priority = ((Prioritized) obj).getPriority();
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/TypeUtils.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/TypeUtils.java
index 1e6181795d..fc77b1f10b 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/TypeUtils.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/TypeUtils.java
@@ -22,7 +22,10 @@ import org.apache.dubbo.common.utils.ConcurrentHashSet;
 
 import java.io.File;
 import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.lang.reflect.TypeVariable;
@@ -34,18 +37,34 @@ import java.nio.charset.Charset;
 import java.nio.file.Path;
 import java.time.ZoneId;
 import java.time.temporal.Temporal;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Currency;
 import java.util.Date;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Queue;
 import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
 import java.util.TimeZone;
+import java.util.TreeMap;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.regex.Pattern;
 
 public final class TypeUtils {
+
     private static final Set<Class<?>> SIMPLE_TYPES = new ConcurrentHashSet<>();
 
     static {
@@ -124,15 +143,15 @@ public final class TypeUtils {
     }
 
     public static Class<?> getSuperGenericType(Class<?> clazz, int index) {
-        Class<?> result = getNestedType(clazz.getGenericSuperclass(), index);
-        return result == null ? getNestedType(ArrayUtils.first(clazz.getGenericInterfaces()), index) : result;
+        Class<?> result = getNestedActualType(clazz.getGenericSuperclass(), index);
+        return result == null ? getNestedActualType(ArrayUtils.first(clazz.getGenericInterfaces()), index) : result;
     }
 
     public static Class<?> getSuperGenericType(Class<?> clazz) {
         return getSuperGenericType(clazz, 0);
     }
 
-    public static Class<?>[] getNestedTypes(Type type) {
+    public static Class<?>[] getNestedActualTypes(Type type) {
         if (type instanceof ParameterizedType) {
             Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments();
             int len = typeArgs.length;
@@ -145,7 +164,7 @@ public final class TypeUtils {
         return null;
     }
 
-    public static Class<?> getNestedType(Type type, int index) {
+    public static Class<?> getNestedActualType(Type type, int index) {
         if (type instanceof ParameterizedType) {
             Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments();
             if (index < typeArgs.length) {
@@ -175,6 +194,16 @@ public final class TypeUtils {
         return null;
     }
 
+    public static Type getNestedGenericType(Type type, int index) {
+        if (type instanceof ParameterizedType) {
+            Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments();
+            if (index < typeArgs.length) {
+                return getActualGenericType(typeArgs[index]);
+            }
+        }
+        return null;
+    }
+
     public static Type getActualGenericType(Type type) {
         if (type instanceof TypeVariable) {
             return ((TypeVariable<?>) type).getBounds()[0];
@@ -215,4 +244,137 @@ public final class TypeUtils {
         }
         return null;
     }
+
+    public static Object longToObject(long value, Class<?> targetClass) {
+        if (targetClass == Long.class) {
+            return value;
+        }
+        if (targetClass == Integer.class) {
+            return (int) value;
+        }
+        if (targetClass == Short.class) {
+            return (short) value;
+        }
+        if (targetClass == Character.class) {
+            return (char) value;
+        }
+        if (targetClass == Byte.class) {
+            return (byte) value;
+        }
+        return null;
+    }
+
+    @SuppressWarnings("rawtypes")
+    public static Collection createCollection(Class targetClass) {
+        if (targetClass.isInterface()) {
+            if (targetClass == List.class || targetClass == Collection.class) {
+                return new ArrayList<>();
+            }
+            if (targetClass == Set.class) {
+                return new HashSet<>();
+            }
+            if (targetClass == SortedSet.class) {
+                return new LinkedHashSet<>();
+            }
+            if (targetClass == Queue.class || targetClass == Deque.class) {
+                return new LinkedList<>();
+            }
+        } else if (Collection.class.isAssignableFrom(targetClass)) {
+            if (targetClass == ArrayList.class) {
+                return new ArrayList<>();
+            }
+            if (targetClass == LinkedList.class) {
+                return new LinkedList();
+            }
+            if (targetClass == HashSet.class) {
+                return new HashSet<>();
+            }
+            if (targetClass == LinkedHashSet.class) {
+                return new LinkedHashSet<>();
+            }
+            if (!Modifier.isAbstract(targetClass.getModifiers())) {
+                try {
+                    Constructor sizeCt = null;
+                    for (Constructor ct : targetClass.getConstructors()) {
+                        switch (ct.getParameterCount()) {
+                            case 0:
+                                return (Collection) ct.newInstance();
+                            case 1:
+                                if (ct.getParameterTypes()[0] == int.class) {
+                                    sizeCt = ct;
+                                }
+                                break;
+                            default:
+                        }
+                    }
+                    if (sizeCt != null) {
+                        return (Collection) sizeCt.newInstance(16);
+                    }
+                } catch (Exception ignored) {
+                }
+            }
+        }
+        throw new IllegalArgumentException("Unsupported collection type: " + targetClass.getName());
+    }
+
+    @SuppressWarnings("rawtypes")
+    public static Map createMap(Class targetClass) {
+        if (targetClass.isInterface()) {
+            if (targetClass == Map.class) {
+                return new HashMap<>();
+            }
+            if (targetClass == ConcurrentMap.class) {
+                return new ConcurrentHashMap<>();
+            }
+            if (SortedMap.class.isAssignableFrom(targetClass)) {
+                return new TreeMap<>();
+            }
+        } else if (Map.class.isAssignableFrom(targetClass)) {
+            if (targetClass == HashMap.class) {
+                return new HashMap<>();
+            }
+            if (targetClass == LinkedHashMap.class) {
+                return new LinkedHashMap<>();
+            }
+            if (targetClass == TreeMap.class) {
+                return new TreeMap<>();
+            }
+            if (targetClass == ConcurrentHashMap.class) {
+                return new ConcurrentHashMap<>();
+            }
+            if (!Modifier.isAbstract(targetClass.getModifiers())) {
+                try {
+                    Constructor sizeCt = null;
+                    for (Constructor ct : targetClass.getConstructors()) {
+                        if (Modifier.isPublic(ct.getModifiers())) {
+                            switch (ct.getParameterCount()) {
+                                case 0:
+                                    return (Map) ct.newInstance();
+                                case 1:
+                                    if (ct.getParameterTypes()[0] == int.class) {
+                                        sizeCt = ct;
+                                    }
+                                    break;
+                                default:
+                            }
+                        }
+                    }
+                    if (sizeCt != null) {
+                        return (Map) sizeCt.newInstance(16);
+                    }
+                } catch (Throwable ignored) {
+                }
+            }
+        }
+        throw new IllegalArgumentException("Unsupported map type: " + targetClass.getName());
+    }
+
+    public static String buildSig(Method method) {
+        StringBuilder sb = new StringBuilder(8);
+        for (Class<?> type : method.getParameterTypes()) {
+            String name = type.getName();
+            sb.append(name.charAt(name.lastIndexOf('.') + 1));
+        }
+        return sb.toString();
+    }
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver
index 3d2d4e5073..0486f00884 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver
@@ -1 +1,3 @@
 misc=org.apache.dubbo.rpc.protocol.tri.rest.argument.MiscArgumentResolver
+basic-param=org.apache.dubbo.rpc.protocol.tri.rest.support.basic.ParamArgumentResolver
+basic-fallback=org.apache.dubbo.rpc.protocol.tri.rest.support.basic.FallbackArgumentResolver
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver
new file mode 100644
index 0000000000..edb8a4f70f
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver
@@ -0,0 +1 @@
+basic=org.apache.dubbo.rpc.protocol.tri.rest.support.basic.BasicRequestMappingResolver
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping
index 3f44b63ea2..af94fe1071 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping
@@ -1,3 +1,2 @@
-http=org.apache.dubbo.rpc.protocol.tri.h12.HttpRequestHandlerMapping
 grpc=org.apache.dubbo.rpc.protocol.tri.h12.grpc.GrpcRequestHandlerMapping
-rest=org.apache.dubbo.rpc.protocol.tri.rest.mapping.RestRequestHandlerMapping
\ No newline at end of file
+rest=org.apache.dubbo.rpc.protocol.tri.rest.mapping.RestRequestHandlerMapping
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.groovy b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.groovy
new file mode 100644
index 0000000000..35bbe55031
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the 'License'); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.rpc.protocol.tri.rest.mapping
+
+import spock.lang.Specification
+
+class RadixTreeTest extends Specification {
+
+    def "Match"() {
+        given:
+            def tree = new RadixTree<String>()
+            tree.addPath('/a/*', 'abc')
+            tree.addPath('/a/{x}/d/e', 'bcd')
+            tree.addPath('/a/{v:.*}/e', 'bcd')
+        when:
+            def match = tree.match('/a/b/d/e')
+        then:
+            !match.empty
+    }
+
+    def "Clear"() {
+        given:
+            def tree = new RadixTree<String>()
+            tree.addPath('/a/*', 'abc')
+            tree.addPath('/a/{x}/d/e', 'bcd')
+            tree.addPath('/a/{v:.*}/e', 'bcd')
+        when:
+            tree.remove(s -> s in ['abc', 'bcd'])
+        then:
+            tree.empty
+    }
+}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpressionTest.groovy b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpressionTest.groovy
new file mode 100644
index 0000000000..cbcec0922e
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpressionTest.groovy
@@ -0,0 +1,261 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition
+
+import org.apache.dubbo.rpc.protocol.tri.rest.Messages
+import org.apache.dubbo.rpc.protocol.tri.rest.PathParserException
+
+import spock.lang.Specification
+
+import static org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression.parse
+import static org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type.LITERAL
+import static org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type.PATTERN
+import static org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type.PATTERN_MULTI
+import static org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type.VARIABLE
+import static org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type.WILDCARD
+import static org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type.WILDCARD_TAIL
+
+class PathExpressionTest extends Specification {
+
+    def "Parse"() {
+        given:
+            def pss = PathParser.parse(path)
+        expect:
+            assertPath(pss, rs)
+        where:
+            path                       | rs
+            null                       | [[LITERAL, '/']]
+            ''                         | [[LITERAL, '/']]
+            '/'                        | [[LITERAL, '/']]
+            '/a?*b'                    | [[PATTERN, 'a[^/]*b']]
+            '/a*?b'                    | [[PATTERN, 'a[^/]*b']]
+            '/a**b'                    | [[PATTERN, 'a[^/]*b']]
+            '/{param:o?e}'             | [[PATTERN, '(?<param>o?e)']]
+            '/{param: \\d+ }/name'     | [[PATTERN, '(?<param>\\d+)', 'param'], [LITERAL, 'name']]
+            '/{param:\\S\\W}'          | [[PATTERN_MULTI, '(?<param>\\S\\W)']]
+            '/{param:a{2}}'            | [[PATTERN, '(?<param>a{2})']]
+            '/{param:[^/]+}'           | [[PATTERN, '(?<param>[^/]+)', 'param']]
+            '/{param:[/]+}'            | [[PATTERN_MULTI, '(?<param>[/]+)', 'param']]
+            '/{one}{two:\\d+}'         | [[PATTERN, '(?<one>[^/]+)(?<two>\\d+)', 'one', 'two']]
+            '/{one:\\d+}{}'            | [[PATTERN, '(?<one>\\d+)[^/]+', 'one']]
+            '/{one:\\d+}**'            | [[PATTERN_MULTI, '(?<one>\\d+).*', 'one']]
+            '/{one:\\d+}{*two}'        | [[PATTERN_MULTI, '(?<one>\\d+)(?<two>.*)', 'one', 'two']]
+            '/{first}-{last}/b/{vv}/v' | [[PATTERN, '(?<first>[^/]+)-(?<last>[^/]+)', 'first', 'last'], 'b', [VARIABLE, 'vv'], 'v']
+    }
+
+    def "Parse failed"() {
+        when:
+            PathParser.parse(path)
+        then:
+            def e = thrown(PathParserException)
+            e.errorCode == code.name()
+        where:
+            path                | code
+            '/{param:[a}'       | Messages.REGEX_PATTERN_INVALID
+            '/{param:}'         | Messages.MISSING_REGEX_CONSTRAINT
+            '/{one}/{*/three'   | Messages.MISSING_CLOSE_CAPTURE
+            '/{one}/{two/three' | Messages.MISSING_CLOSE_CAPTURE
+            '/{o{ne}'           | Messages.ILLEGAL_NESTED_CAPTURE
+            '/{one}a}'          | Messages.MISSING_OPEN_CAPTURE
+            '/one/{*postfix}t'  | Messages.NO_MORE_DATA_ALLOWED
+            '/{one}{two}'       | Messages.ILLEGAL_DOUBLE_CAPTURE
+            '/{one}-{one}'      | Messages.DUPLICATE_CAPTURE_VARIABLE
+            '/{one}/{one'       | Messages.MISSING_CLOSE_CAPTURE
+    }
+
+    /**
+     * <a href="https://docs.jboss.org/resteasy/docs/6.2.7.Final/userguide/html/ch04.html">resteasy @Path</a>
+     */
+    def "ParseJaxrs"() {
+        given:
+            def pss = PathParser.parse(path)
+        expect:
+            assertPath(pss, rs)
+        where:
+            path                             | rs
+            '/library/books'                 | ['/library/books']
+            '/library/{isbn}'                | ['library', [VARIABLE, 'isbn']]
+            '/library/{isbn}/{type}'         | ['library', [VARIABLE, 'isbn'], [VARIABLE, 'type']]
+            '{var:\\d+}/stuff'               | [[PATTERN, '(?<var>\\d+)', 'var'], [LITERAL, 'stuff']]
+            '{var:.*}/stuff'                 | [[PATTERN_MULTI, '(?<var>.*)/stuff', 'var']]
+            '/aaa{param}bbb'                 | [[PATTERN, 'aaa(?<param>[^/]+)bbb', 'param']]
+            '/foo{name}-{zip}bar'            | [[PATTERN, 'foo(?<name>[^/]+)-(?<zip>[^/]+)bar', 'name', 'zip']]
+            '/aaa{param:b+}/{many:.*}/stuff' | [[PATTERN, 'aaa(?<param>b+)', 'param'], [PATTERN_MULTI, '(?<many>.*)/stuff', 'many']]
+    }
+
+    /**
+     * <a href="https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-requestmapping.html#mvc-ann-requestmapping-uri-templates">Spring uri templates</a>
+     */
+    def "ParseSpring"() {
+        given:
+            def pss = PathParser.parse(path)
+        expect:
+            assertPath(pss, rs)
+        where:
+            path                                                | rs
+            '/resources/ima?e.png'                              | ['resources', [PATTERN, 'ima[^/]e\\.png']]
+            '/resources/*.png'                                  | ['resources', [PATTERN, '[^/]*\\.png']]
+            '/resources/*/test.png'                             | ['resources', [VARIABLE, ''], 'test.png']
+            '/resources/**/test.png'                            | ['resources', [PATTERN_MULTI, '.*\\Qtest.png\\E']]
+            '/resources/**/*.png'                               | ['resources', [PATTERN_MULTI, '.*[^/]*\\.png']]
+            '/resources/**'                                     | ['resources', [WILDCARD_TAIL, '']]
+            '/resources/{**}'                                   | ['resources', [WILDCARD_TAIL, '*']]
+            '/resources/{*path}'                                | ['resources', [WILDCARD_TAIL, 'path', 'path']]
+            '/resources/*'                                      | ['resources', [VARIABLE, '']]
+            '/projects/{project}/versions'                      | ['projects', [VARIABLE, 'project'], 'versions']
+            '/projects/{project:[a-z]+}/versions'               | ['projects', [PATTERN, '(?<project>[a-z]+)', 'project'], 'versions']
+            /{name:[a-z-]+}-{version:\d\.\d\.\d}{ext:\.[a-z]+}/ | [[PATTERN, /(?<name>[a-z-]+)-(?<version>\d\.\d\.\d)(?<ext>\.[a-z]+)/]]
+    }
+
+    def assertPath(PathSegment[] pss, List rs) {
+        rs.eachWithIndex { s, i ->
+            assert pss.length > i
+            def ps = pss[i]
+            if (s instanceof String) {
+                assert ps.value == s
+            } else if (s instanceof List) {
+                assert ps.type == s[0]
+                assert ps.value == s[1]
+                for (j in 2..<s.size()) {
+                    assert ps.variables[j - 2] == s[j]
+                }
+            }
+        }
+    }
+
+    def "IsDirect"() {
+        expect:
+            parse(path).direct == result
+        where:
+            path              | result
+            '/library/books'  | true
+            '/library/{isbn}' | false
+            '/library/**'     | false
+    }
+
+    def "Match"() {
+        given:
+            def map = parse(path).match(value)
+        expect:
+            if (result instanceof Boolean) {
+                assert map != null == result
+            } else if (result instanceof Map) {
+                assert map == result
+            }
+        where:
+            path                             | value                         | result
+            '/library/books'                 | '/library/books'              | true
+            '/library/{isbn}'                | '/library/1-84356-028-3'      | [isbn: '1-84356-028-3']
+            '/library/{isbn}/{type}'         | '/library/1-84356-028-3/math' | [isbn: '1-84356-028-3', type: 'math']
+            '/{var:.*}/stuff'                | '/123/stuff'                  | [var: '123']
+            '/{var:.*}/stu'                  | '/123/stuff'                  | false
+            '/{var:.*}/stuff'                | '/123/abc/stuff'              | [var: '123/abc']
+            '/aaa{param}bbb'                 | '/aaac1cbbb'                  | [param: 'c1c']
+            '/foo{name}-{zip}bar'            | '/fooabc-123bar'              | [name: 'abc', zip: '123']
+            '/aaa{param:b+}/{many:.*}/stuff' | '/aaabbb/ccc/stuff'           | [param: 'bbb', many: 'ccc']
+            '/resources/*.png'               | '/resources/cat.png'          | true
+            '/resources/**/test.png'         | '/resources/abc/def/test.png' | true
+            '/resources/**'                  | '/resources/a/b/c'            | true
+            '/resources/{*path}'             | '/resources/a/b/c'            | [path: 'a/b/c']
+            '/resources/*'                   | '/resources/a'                | true
+            '/resources/*'                   | '/resources/a/b/c'            | true
+            '/{id:\\d+}'                     | '/123'                        | [id: '123']
+            '/{id:\\d+}'                     | '/one'                        | false
+            '/a?cd/ef'                       | '/abcd/ef'                    | true
+            '/a?cd/ef'                       | '/aaccd/ef'                   | false
+            '/a?cd/ef'                       | '/a/cd/ef'                    | false
+            '/a*cd/ef'                       | '/aaccd/ef'                   | true
+            '/a*cd/ef'                       | '/acd/ef'                     | true
+            '/a*cd/ef'                       | '/a/ccd/ef'                   | false
+            '/one/*/three'                   | '/one/two/three'              | true
+            '/one/*/three'                   | '/one/three'                  | false
+            '/one/**/three'                  | '/one/three'                  | true
+            '/one/**/*/three'                | '/one/three'                  | false
+            '/{one:\\d+}{*two}'              | '/123abc/def'                 | [one: '123', two: 'abc/def']
+            '/{one:\\d+}{two:.*}'            | '/123abc/def'                 | [one: '123', two: 'abc/def']
+    }
+
+    def "MatchTest"() {
+        expect:
+            parse(path).match(value) != null
+        where:
+            path           | value
+            '/resources/*' | '/resources/a/b/c'
+    }
+
+    def "CompareTo"() {
+        expect:
+            parse(path) <=> parse(other) == result
+        where:
+            path                    | other             | result
+            '/one'                  | '/one'            | 0
+            '/one'                  | '/two'            | 0
+            '/one/{two}/three'      | '/one/{t}/three'  | 0
+            '/one/{two}/three'      | '/one/*/three'    | -1
+            '/one/*/three'          | '/one/**/three'   | -1
+            '/one/two'              | '/one'            | -1
+            '/one/{two}'            | '/{one}/two'      | -1
+            '/one/{two}'            | '/one/{two:\\d+}' | -1
+            '/one/*'                | '/one/{two:\\d+}' | -1
+            '/one/{two:\\d+}/three' | '/one/**/three'   | -1
+    }
+
+    def "CompareTo with lookup path"() {
+        expect:
+            parse(path).compareTo(parse(other), lookupPath) == result
+        where:
+            path   | other  | lookupPath | result
+            '/one' | '/two' | '/one'     | -1
+            '/one' | '/two' | '/two'     | 1
+            '/one' | '/two' | '/three'   | 0
+    }
+
+    def "Equals"() {
+        expect:
+            Objects.equals(path, other) == result
+            if (result) {
+                path.hashCode() == other.hashCode()
+            }
+        where:
+            path          | other         | result
+            parse('/one') | parse('/one') | true
+            parse('/one') | parse('/two') | false
+            parse('/two') | null          | false
+            parse('/two') | '/two'        | false
+    }
+
+    @SuppressWarnings('ChangeToOperator')
+    def "Misc"() {
+        given:
+            def path = '/library/{isbn}/{type}'
+            def expr = parse(path)
+            def seg = expr.segments[1]
+        expect:
+            PathExpression.match(path, "/library/1-84356-028-3/math")
+            expr.path == path
+            expr.equals(expr)
+            expr.segments.length == 3
+            expr.toString() == path
+            seg.hashCode()
+            seg.equals(seg)
+            !seg.equals(null)
+            !seg.equals(expr.segments[2])
+            !new PathSegment(WILDCARD, '').match('', 0, 0, null)
+            seg.toString() == '{type=VARIABLE, value=isbn, variables=[isbn]}'
+    }
+}
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
new file mode 100644
index 0000000000..23692bc020
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/support/basic/RestProtocolTest.groovy
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.rpc.protocol.tri.rest.support.basic
+
+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.service.DemoServiceImpl
+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())
+    }
+
+    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'
+    }
+
+    def "argument test"() {
+        expect:
+            runner.post(path, body) == output
+        where:
+            path                | body                        | output
+            '/argTest'          | '["Sam","8"]'               | 'Sam is 8 years old'
+            '/argTest'          | '{"name": "Sam", "age": 8}' | 'Sam is 8 years old'
+            '/argTest?name=Sam' | '{"age": 8}'                | 'Sam is 8 years old'
+    }
+
+    def "bean argument test"() {
+        expect:
+            runner.post(path, body, Book.class).name == output
+        where:
+            path    | body                                      | output
+            '/buy'  | new Book(name: "Dubbo")                   | 'Dubbo'
+            '/buy'  | [new Book(name: "Dubbo")]                 | 'Dubbo'
+            '/buy2' | [new Book(name: "Dubbo"), 2]              | 'Dubbo'
+            '/buy2' | [book: new Book(name: "Dubbo"), count: 2] | 'Dubbo'
+    }
+
+    def "bean argument get test"() {
+        expect:
+            runner.get(path, Book.class).price == output
+        where:
+            path                                     | output
+            '/beanArgTest'                           | 0
+            '/beanArgTest?quote=5'                   | 5
+            '/beanArgTest?book={"price": 6}'         | 6
+            '/beanArgTest?book={"price": 6}&quote=5' | 5
+    }
+
+    def "bean argument post test"() {
+        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
+    }
+
+    def "advance bean argument get test"() {
+        expect:
+            runner.get(path) contains output
+        where:
+            path                                                                            | output
+            '/bean?id=1&name=sam'                                                           | '"id":1,"name":"sam"'
+            '/bean?user.id=1&user.name=sam'                                                 | '"id":1,"name":"sam"'
+            '/bean?name=sam&p=123&email=a@b.com'                                            | '"email":"a@b.com","name":"sam","phone":"123"'
+            '/bean?group.name=g1&group.owner.name=jack'                                     | '"group":{"id":0,"name":"g1","owner":{"name":"jack"'
+            '/bean?group.parent.parent.children[0].name=xx'                                 | '"group":{"id":0,"parent":{"id":0,"parent":{"children":[{"id":0,"name":"xx"}],"id":0}}}'
+            '/bean?group={"name":"g1","id":2}'                                              | '"group":{"id":2,"name":"g1"}'
+            '/bean?ids=3&ids=4'                                                             | '"ids":[3,4]'
+            '/bean?ids[]=3&ids[]=4'                                                         | '"ids":[3,4]'
+            '/bean?ids[1]=3&ids[2]=4'                                                       | '"ids":[0,3,4]'
+            '/bean?scores=3&scores=4'                                                       | '"scores":[3,4]'
+            '/bean?scores[]=3&scores[]=4'                                                   | '"scores":[3,4]'
+            '/bean?scores[1]=3&scores[2]=4'                                                 | '"scores":[null,3,4]'
+            '/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"}]'
+            '/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"}]'
+            '/bean?tagsB[0].name=e&tagsB[1].name=c&tagsB[1].value=d'                        | '"tagsB":[{"name":"e","value":"b"},{"name":"c","value":"d"}]'
+            '/bean?tagsC[0].name=e&tagsC[1].name=c&tagsC[1].value=d'                        | '"tagsC":[{"name":"e","value":"b"},{"name":"c","value":"d"}]'
+            '/bean?groupMaps[0].one.name=a&groupMaps[1].two.name=b'                         | '"groupMaps":[{"one":{"id":0,"name":"a"}},{"two":{"id":0,"name":"b"}}]'
+            '/bean?id=1&features.a=xx&features.b=2'                                         | '"features":{"a":"xx","b":"2"}'
+            '/bean?id=1&features[a]=xx&features[b]=2'                                       | '"features":{"a":"xx","b":"2"}'
+            '/bean?group.id=2&group.features.a=1&group.features.b=xx'                       | '"group":{"features":{"a":"1","b":"xx"},"id":2}'
+            '/bean?tagMap.a.name=a&tagMap.a.value=b&tagMap.b.name=c&tagMap.b.value=d'       | '"tagMap":{"a":{"name":"a","value":"b"},"b":{"name":"c","value":"d"}}'
+            '/bean?tagMapA.a.name=e&tagMapA.b.name=c&tagMapA.b.value=d'                     | '"tagMapA":{"a":{"name":"e","value":"b"},"b":{"name":"c","value":"d"}}'
+            '/bean?tagMapB[2].name=a&tagMapB[2].value=b&tagMapB[3].name=c'                  | '"tagMapB":{2:{"name":"a","value":"b"},3:{"name":"c"}}'
+            '/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 "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
+            '/argTest' | 'Sam is 8 years old'
+    }
+
+    def "override mapping test"() {
+        expect:
+            runner.get(path) == output
+        where:
+            path                          | output
+            '/say?name=sam&count=2'       | '2'
+            '/say?name=sam'               | '1'
+            '/say~SL'                     | '2'
+            '/say~S'                      | '1'
+            '/say~S?name=sam&count=2'     | '1'
+            '/say~S.yml?name=sam&count=2' | '1'
+    }
+
+    def "ambiguous mapping test"() {
+        expect:
+            runner.get(path) contains "Ambiguous mapping"
+        where:
+            path   | _
+            '/say' | _
+    }
+
+    def "no interface method test"() {
+        expect:
+            runner.get(path) contains output
+        where:
+            path                     | output
+            '/noInterface'           | 'ok'
+            '/noInterfaceAndMapping' | '404'
+    }
+
+    def "use interface argument name test"() {
+        expect:
+            runner.get(path) contains output
+        where:
+            path                    | output
+            '/argNameTest?name=Sam' | 'Sam'
+    }
+
+    def "pb server stream test"() {
+        expect:
+            runner.posts(path, body).size() == output
+        where:
+            path              | body               | output
+            '/pbServerStream' | '{"service": "3"}' | 3
+            '/pbServerStream' | '{}'               | 0
+    }
+
+    def "pb server stream get test"() {
+        expect:
+            runner.gets(path).size() == output
+        where:
+            path                                       | output
+            '/pbServerStream?request={"service": "3"}' | 3
+    }
+
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/test/BaseServiceTest.groovy
similarity index 57%
copy from dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java
copy to dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/test/BaseServiceTest.groovy
index c7ee0ba7dd..cf2df41cd6 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/test/BaseServiceTest.groovy
@@ -14,35 +14,36 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.config.nested;
 
-import java.io.Serializable;
+package org.apache.dubbo.rpc.protocol.tri.rest.test
 
-/**
- * Configuration for the propagation.
- */
-public class PropagationConfig implements Serializable {
+import org.apache.dubbo.rpc.protocol.tri.test.TestRunner
+import org.apache.dubbo.rpc.protocol.tri.test.TestRunnerBuilder
+
+import spock.lang.Shared
+import spock.lang.Specification
 
-    public static final String B3 = "B3";
+abstract class BaseServiceTest extends Specification {
 
-    public static final String W3C = "W3C";
+    @Shared
+    TestRunner runner
 
-    /**
-     * Tracing context propagation type.
-     */
-    private String type = W3C;
+    void setupSpec() {
+        TestRunnerBuilder build = TestRunnerBuilder.builder()
+        setupService(build)
+        runner = build.build()
+    }
 
-    public PropagationConfig() {}
+    void cleanupSpec() {
+        runner.destroy()
+    }
 
-    public PropagationConfig(String type) {
-        this.type = type;
+    void setup() {
     }
 
-    public String getType() {
-        return type;
+    void cleanup() {
     }
 
-    public void setType(String type) {
-        this.type = type;
+    void setupService(TestRunnerBuilder builder) {
     }
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtilsTest.groovy b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtilsTest.groovy
new file mode 100644
index 0000000000..6e4047d0e9
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/util/PathUtilsTest.groovy
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.rpc.protocol.tri.rest.util
+
+import org.apache.dubbo.common.URL
+import org.apache.dubbo.rpc.protocol.tri.rest.PathParserException
+
+import spock.lang.Specification
+
+class PathUtilsTest extends Specification {
+
+    def "GetContextPath"() {
+        expect:
+            PathUtils.getContextPath(URL.valueOf(url)) == result
+        where:
+            result | url
+            ''     | 'tri://127.0.0.1'
+            ''     | 'tri://127.0.0.1/'
+            ''     | 'tri://127.0.0.1/one'
+            ''     | 'tri://127.0.0.1/test.Demo?interface=test.DeMo'
+            'one'  | 'tri://127.0.0.1/one/test.Demo?interface=test.Demo'
+            'one'  | 'tri://127.0.0.1/one/?interface=test.Demo'
+    }
+
+    def "IsDirectPath"() {
+        expect:
+            PathUtils.isDirectPath(path) == result
+        where:
+            result | path
+            true   | '/one'
+            true   | '/one/{ab/c'
+            false  | '/one/*'
+            false  | '/one/**'
+            false  | '/one/a?c'
+            false  | '/one/{ab}/c'
+    }
+
+    def "Combine"() {
+        expect:
+            PathUtils.combine(path1, path2) == result
+        where:
+            path1        | path2          | result
+            '/hotels'    | ''             | '/hotels'
+            ''           | '/hotels'      | '/hotels'
+            '/hotels'    | '/bookings'    | '/hotels/bookings'
+            '/hotels'    | 'bookings'     | '/hotels/bookings'
+            '/hotels/'   | '/bookings'    | '/hotels/bookings'
+            '/hotels/'   | '/bookings/'   | '/hotels/bookings/'
+            '/hotels/*'  | '/bookings'    | '/hotels/bookings'
+            '/hotels/**' | '/bookings'    | '/hotels/**/bookings'
+            '/hotels'    | '{hotel}'      | '/hotels/{hotel}'
+            '/{hotels}'  | '/hotel'       | '/{hotels}/hotel'
+            '/hotels/*'  | '{hotel}'      | '/hotels/{hotel}'
+            '/hotels/**' | '{hotel}'      | '/hotels/**/{hotel}'
+            '/*.html'    | '/hotels.html' | '/hotels.html'
+            '/hotels.*'  | '/hotels.html' | '/hotels.html'
+            '/*.html'    | '/hotels'      | '/hotels.html'
+            '/'          | '/bookings'    | '/bookings'
+            '/hotels'    | '/'            | '/hotels/'
+    }
+
+    def "Combine failed"() {
+        when:
+            PathUtils.combine(path1, path2)
+        then:
+            thrown(PathParserException)
+        where:
+            path1     | path2
+            '/*.html' | '/*.txt'
+            '/*.html' | '/hotel.txt'
+    }
+
+    def "Normalize"() {
+        expect:
+            PathUtils.normalize(path) == result
+        where:
+            path                      | result
+            ''                        | '/'
+            '/'                       | '/'
+            '//'                      | '/'
+            '/.'                      | '/'
+            '/./'                     | '/'
+            '/./.'                    | '/'
+            '..'                      | '/..'
+            '/..'                     | '/..'
+            '../'                     | '/../'
+            '/../'                    | '/../'
+            'one'                     | '/one'
+            '.one'                    | '/.one'
+            '/one.two'                | '/one.two'
+            ' /one '                  | '/one'
+            '\t /one \t\n \r'         | '/one'
+            '/one#two'                | '/one'
+            '/one?two'                | '/one'
+            '/one'                    | '/one'
+            '/one/'                   | '/one/'
+            '/one//'                  | '/one/'
+            '/one/.'                  | '/one/'
+            'a/..'                    | '/'
+            '/one/..'                 | '/'
+            '/one/.../'               | '/one/.../'
+            '/one/../'                | '/'
+            '/one/../../'             | '/../'
+            '/one/../../../'          | '/../../'
+            '/./one/../../../'        | '/../../'
+            '/one/../two'             | '/two'
+            '/one/./two'              | '/one/two'
+            '/one/./two/..'           | '/one/'
+            '/one/./two/../'          | '/one/'
+            '/one/./two/../three'     | '/one/three'
+            '/one/./two/../three/'    | '/one/three/'
+            '/one/./two/../three/.'   | '/one/three/'
+            '/one/./two/../three/..'  | '/one/'
+            '/one/./two/../three/../' | '/one/'
+    }
+
+    def "Normalize with context path"() {
+        expect:
+            PathUtils.normalize(contextPath, path) == result
+        where:
+            contextPath | path   | result
+            ''          | ''     | '/'
+            ''          | '/'    | '/'
+            '/'         | ''     | '/'
+            ''          | '/one' | '/one'
+            '/one'      | ''     | '/one'
+            '/one'      | 'two'  | '/one/two'
+            'one'       | 'two'  | '/one/two'
+            '/one'      | '/two' | '/one/two'
+            '/one//'    | '/two' | '/one/two'
+    }
+}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.java
deleted file mode 100644
index bd3142c0ef..0000000000
--- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.rpc.protocol.tri.rest.mapping;
-
-import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathExpression;
-import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils;
-
-import java.util.List;
-
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-class RadixTreeTest {
-
-    @Test
-    void match() {
-        RadixTree<String> tree = new RadixTree<>();
-        tree.addPath(PathExpression.parse("/a/*"), "abc");
-        tree.addPath(PathExpression.parse("/a/{x}/d/e"), "acd");
-        tree.addPath(PathExpression.parse("/a/{v:.*}/e"), "acd");
-        List<RadixTree.Match<String>> match = tree.match("/a/b/d/e");
-        Assertions.assertFalse(match.isEmpty());
-    }
-
-    @Test
-    void match1() {
-        RadixTree<String> tree = new RadixTree<>();
-        tree.addPath(PathExpression.parse(PathUtils.normalize("")), "abc");
-        List<RadixTree.Match<String>> match = tree.match(PathUtils.normalize(""));
-        Assertions.assertFalse(match.isEmpty());
-    }
-
-    @Test
-    void clear() {
-        RadixTree<String> tree = new RadixTree<>();
-        tree.addPath(PathExpression.parse("/a/*"), "abc");
-        tree.addPath(PathExpression.parse("/a/{x}/d/e"), "acd");
-        tree.addPath(PathExpression.parse("/a/{v:.*}/e"), "acd");
-        tree.remove(s -> "abc".equals(s) || "acd".equals(s));
-        Assertions.assertTrue(tree.isEmpty());
-    }
-}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParserTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParserTest.java
deleted file mode 100644
index cb8616fc83..0000000000
--- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathParserTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition;
-
-import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils;
-
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-class PathParserTest {
-
-    @Test
-    void parse1() {
-        PathSegment[] parse = PathParser.parse("/{first}-{last}/b/{vv}/v");
-        Assertions.assertEquals(parse.length, 4);
-    }
-
-    @Test
-    void parse2() {
-        System.out.println(PathUtils.normalize("/../a/b"));
-        System.out.println(PathUtils.normalize("../a/b"));
-        System.out.println(PathUtils.normalize("./a/b"));
-        System.out.println(PathUtils.normalize("/a/b"));
-        System.out.println(PathUtils.normalize("a/b"));
-        System.out.println(PathUtils.normalize("a/../b"));
-        System.out.println(PathUtils.normalize("a/../../b"));
-        System.out.println(PathUtils.normalize("a/../../../b"));
-        System.out.println(PathUtils.normalize("  a/../../../b   \r"));
-    }
-
-    @Test
-    void parse3() {
-        System.out.println(PathUtils.normalize("/a/b/c"));
-    }
-}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/Book.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/Book.java
new file mode 100644
index 0000000000..fcfcadc5bb
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/Book.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.tri.rest.service;
+
+import java.util.Date;
+import java.util.Objects;
+
+public class Book {
+
+    private String name;
+    private int price;
+    private Date publishDate;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getPrice() {
+        return price;
+    }
+
+    public void setPrice(int price) {
+        this.price = price;
+    }
+
+    public Date getPublishDate() {
+        return publishDate;
+    }
+
+    public void setPublishDate(Date publishDate) {
+        this.publishDate = publishDate;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o instanceof Book) {
+            Book book = (Book) o;
+            return price == book.price
+                    && Objects.equals(name, book.name)
+                    && Objects.equals(publishDate, book.publishDate);
+        }
+        return false;
+    }
+}
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
new file mode 100644
index 0000000000..cb3cc8f1da
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoService.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+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.rpc.protocol.tri.rest.service.User.UserEx;
+
+import io.grpc.health.v1.HealthCheckRequest;
+import io.grpc.health.v1.HealthCheckResponse;
+
+@Mapping("/")
+public interface DemoService {
+
+    String hello(String name);
+
+    String argTest(String name, int age);
+
+    Book beanArgTest(Book book, Integer quote);
+
+    @Mapping("/bean")
+    UserEx advanceBeanArgTest(UserEx user);
+
+    Book buy(Book book);
+
+    @Mapping("/buy2")
+    Book buy(Book book, int count);
+
+    String say(String name, Long count);
+
+    String say(String name);
+
+    String argNameTest(String name);
+
+    void pbServerStream(HealthCheckRequest request, StreamObserver<HealthCheckResponse> responseObserver);
+}
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
new file mode 100644
index 0000000000..d17e71a00d
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/DemoServiceImpl.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.tri.rest.service;
+
+import org.apache.dubbo.common.stream.StreamObserver;
+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.UserEx;
+
+import io.grpc.health.v1.HealthCheckRequest;
+import io.grpc.health.v1.HealthCheckResponse;
+import io.grpc.health.v1.HealthCheckResponse.ServingStatus;
+
+public class DemoServiceImpl implements DemoService {
+
+    @Override
+    public String hello(String name) {
+        return "hello " + name;
+    }
+
+    @Override
+    public String argTest(String name, int age) {
+        return name + " is " + age + " years old";
+    }
+
+    @Override
+    public Book beanArgTest(Book book, Integer quote) {
+        if (book == null) {
+            book = new Book();
+        }
+        if (quote != null) {
+            book.setPrice(quote);
+        }
+        return book;
+    }
+
+    @Override
+    public UserEx advanceBeanArgTest(UserEx user) {
+        return user;
+    }
+
+    @Override
+    public Book buy(Book book) {
+        return book;
+    }
+
+    @Override
+    public Book buy(Book book, int count) {
+        return book;
+    }
+
+    @Override
+    public String say(String name, Long count) {
+        return "2";
+    }
+
+    @Override
+    public String say(String name) {
+        return "1";
+    }
+
+    @Mapping
+    public String noInterface() {
+        return "ok";
+    }
+
+    public String noInterfaceAndMapping() {
+        return "ok";
+    }
+
+    @Override
+    public String argNameTest(String name1) {
+        return name1;
+    }
+
+    @Override
+    public void pbServerStream(HealthCheckRequest request, StreamObserver<HealthCheckResponse> responseObserver) {
+        String service = request.getService();
+        if (StringUtils.isNotEmpty(service)) {
+            int count = Integer.parseInt(service);
+            for (int i = 0; i < count; i++) {
+                responseObserver.onNext(HealthCheckResponse.newBuilder()
+                        .setStatus(ServingStatus.SERVING)
+                        .build());
+            }
+        }
+        responseObserver.onCompleted();
+    }
+}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/User.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/User.java
new file mode 100644
index 0000000000..b74474220d
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/rest/service/User.java
@@ -0,0 +1,283 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.tri.rest.service;
+
+import org.apache.dubbo.remoting.http12.rest.Param;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class User {
+
+    private Long id;
+    private String name;
+    private Group group;
+    private long[] ids;
+    private List<Integer> scores;
+    private List<Tag> tags;
+    private Tag[] tagsA;
+    private List<Tag> tagsB = new ArrayList<>();
+    private Tag[] tagsC = new Tag[] {new Tag("a", "b")};
+    private List<Map<String, Group>> groupMaps;
+    private Map<String, String> features;
+    private Map<String, Tag> tagMap;
+    private Map<String, Tag> tagMapA = new HashMap<>();
+    private Map<Integer, Tag> tagMapB;
+    private Map<String, List<Group>> groupsMap;
+
+    public User() {
+        tagsB.add(new Tag("a", "b"));
+        tagMapA.put("a", new Tag("a", "b"));
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Group getGroup() {
+        return group;
+    }
+
+    public void setGroup(Group group) {
+        this.group = group;
+    }
+
+    public long[] getIds() {
+        return ids;
+    }
+
+    public void setIds(long[] ids) {
+        this.ids = ids;
+    }
+
+    public List<Integer> getScores() {
+        return scores;
+    }
+
+    public void setScores(List<Integer> scores) {
+        this.scores = scores;
+    }
+
+    public List<Tag> getTags() {
+        return tags;
+    }
+
+    public void setTags(List<Tag> tags) {
+        this.tags = tags;
+    }
+
+    public Tag[] getTagsA() {
+        return tagsA;
+    }
+
+    public void setTagsA(Tag[] tagsA) {
+        this.tagsA = tagsA;
+    }
+
+    public List<Tag> getTagsB() {
+        return tagsB;
+    }
+
+    public void setTagsB(List<Tag> tagsB) {
+        this.tagsB = tagsB;
+    }
+
+    public Tag[] getTagsC() {
+        return tagsC;
+    }
+
+    public void setTagsC(Tag[] tagsC) {
+        this.tagsC = tagsC;
+    }
+
+    public List<Map<String, Group>> getGroupMaps() {
+        return groupMaps;
+    }
+
+    public void setGroupMaps(List<Map<String, Group>> groupMaps) {
+        this.groupMaps = groupMaps;
+    }
+
+    public Map<String, String> getFeatures() {
+        return features;
+    }
+
+    public void setFeatures(Map<String, String> features) {
+        this.features = features;
+    }
+
+    public Map<String, Tag> getTagMap() {
+        return tagMap;
+    }
+
+    public void setTagMap(Map<String, Tag> tagMap) {
+        this.tagMap = tagMap;
+    }
+
+    public Map<String, Tag> getTagMapA() {
+        return tagMapA;
+    }
+
+    public void setTagMapA(Map<String, Tag> tagMapA) {
+        this.tagMapA = tagMapA;
+    }
+
+    public Map<Integer, Tag> getTagMapB() {
+        return tagMapB;
+    }
+
+    public void setTagMapB(Map<Integer, Tag> tagMapB) {
+        this.tagMapB = tagMapB;
+    }
+
+    public Map<String, List<Group>> getGroupsMap() {
+        return groupsMap;
+    }
+
+    public void setGroupsMap(Map<String, List<Group>> groupsMap) {
+        this.groupsMap = groupsMap;
+    }
+
+    public static class UserEx extends User {
+
+        private String email;
+
+        @Param(value = "p")
+        private String phone;
+
+        public UserEx(String email) {
+            this.email = email;
+        }
+
+        public String getEmail() {
+            return email;
+        }
+
+        public void setEmail(String email) {
+            this.email = email;
+        }
+
+        public String getPhone() {
+            return phone;
+        }
+
+        public void setPhone(String phone) {
+            this.phone = phone;
+        }
+    }
+
+    public static class Group {
+
+        private int id;
+        private String name;
+        private User owner;
+        private Group parent;
+        private List<Group> children;
+        private Map<String, String> features;
+
+        public int getId() {
+            return id;
+        }
+
+        public void setId(int id) {
+            this.id = id;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public User getOwner() {
+            return owner;
+        }
+
+        public void setOwner(User owner) {
+            this.owner = owner;
+        }
+
+        public Group getParent() {
+            return parent;
+        }
+
+        public void setParent(Group parent) {
+            this.parent = parent;
+        }
+
+        public List<Group> getChildren() {
+            return children;
+        }
+
+        public void setChildren(List<Group> children) {
+            this.children = children;
+        }
+
+        public Map<String, String> getFeatures() {
+            return features;
+        }
+
+        public void setFeatures(Map<String, String> features) {
+            this.features = features;
+        }
+    }
+
+    public static class Tag {
+
+        private String name;
+        private String value;
+
+        public Tag() {}
+
+        public Tag(String name, String value) {
+            this.name = name;
+            this.value = value;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public String getValue() {
+            return value;
+        }
+
+        public void setValue(String value) {
+            this.value = value;
+        }
+    }
+}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/MockH2StreamChannel.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/MockH2StreamChannel.java
new file mode 100644
index 0000000000..d6a31e6e41
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/MockH2StreamChannel.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.tri.test;
+
+import org.apache.dubbo.remoting.http12.HttpMetadata;
+import org.apache.dubbo.remoting.http12.HttpOutputMessage;
+import org.apache.dubbo.remoting.http12.h2.H2StreamChannel;
+import org.apache.dubbo.remoting.http12.h2.Http2OutputMessage;
+
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+public class MockH2StreamChannel implements H2StreamChannel {
+
+    private HttpMetadata httpMetadata;
+    private final List<OutputStream> bodies = new ArrayList<>();
+
+    @Override
+    public CompletableFuture<Void> writeHeader(HttpMetadata httpMetadata) {
+        this.httpMetadata = httpMetadata;
+        return CompletableFuture.completedFuture(null);
+    }
+
+    @Override
+    public CompletableFuture<Void> writeMessage(HttpOutputMessage httpOutputMessage) {
+        bodies.add(httpOutputMessage.getBody());
+        return CompletableFuture.completedFuture(null);
+    }
+
+    @Override
+    public SocketAddress remoteAddress() {
+        return InetSocketAddress.createUnresolved(TestProtocol.HOST, TestProtocol.PORT + 1);
+    }
+
+    @Override
+    public SocketAddress localAddress() {
+        return InetSocketAddress.createUnresolved(TestProtocol.HOST, TestProtocol.PORT);
+    }
+
+    @Override
+    public void flush() {}
+
+    @Override
+    public CompletableFuture<Void> writeResetFrame(long errorCode) {
+        return CompletableFuture.completedFuture(null);
+    }
+
+    @Override
+    public Http2OutputMessage newOutputMessage(boolean endStream) {
+        return new MockHttp2OutputMessage(endStream);
+    }
+
+    public HttpMetadata getHttpMetadata() {
+        return httpMetadata;
+    }
+
+    public List<OutputStream> getBodies() {
+        return bodies;
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/MockHttp2OutputMessage.java
similarity index 57%
copy from dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java
copy to dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/MockHttp2OutputMessage.java
index c7ee0ba7dd..18fd78158a 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/PropagationConfig.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/MockHttp2OutputMessage.java
@@ -14,35 +14,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.config.nested;
+package org.apache.dubbo.rpc.protocol.tri.test;
 
-import java.io.Serializable;
+import org.apache.dubbo.remoting.http12.h2.Http2OutputMessage;
 
-/**
- * Configuration for the propagation.
- */
-public class PropagationConfig implements Serializable {
-
-    public static final String B3 = "B3";
-
-    public static final String W3C = "W3C";
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
 
-    /**
-     * Tracing context propagation type.
-     */
-    private String type = W3C;
+public class MockHttp2OutputMessage implements Http2OutputMessage {
 
-    public PropagationConfig() {}
+    private final OutputStream outputStream;
+    private final boolean endStream;
 
-    public PropagationConfig(String type) {
-        this.type = type;
+    public MockHttp2OutputMessage(boolean endStream) {
+        outputStream = new ByteArrayOutputStream();
+        this.endStream = endStream;
     }
 
-    public String getType() {
-        return type;
+    @Override
+    public OutputStream getBody() {
+        return outputStream;
     }
 
-    public void setType(String type) {
-        this.type = type;
+    @Override
+    public boolean isEndStream() {
+        return endStream;
     }
 }
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestProtocol.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestProtocol.java
new file mode 100644
index 0000000000..e7c0d07225
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestProtocol.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.tri.test;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.rpc.Exporter;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.model.FrameworkModel;
+import org.apache.dubbo.rpc.protocol.AbstractExporter;
+import org.apache.dubbo.rpc.protocol.AbstractProtocol;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.DefaultRequestMappingRegistry;
+import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingRegistry;
+
+public class TestProtocol extends AbstractProtocol {
+
+    public static final String NAME = "test";
+    public static final String HOST = "127.0.0.1";
+    public static final int PORT = 8081;
+
+    private final RequestMappingRegistry mappingRegistry;
+
+    public TestProtocol(FrameworkModel frameworkModel) {
+        this.frameworkModel = frameworkModel;
+        mappingRegistry = frameworkModel.getBeanFactory().getOrRegisterBean(DefaultRequestMappingRegistry.class);
+    }
+
+    @Override
+    public int getDefaultPort() {
+        return PORT;
+    }
+
+    @Override
+    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
+        AbstractExporter<T> exporter = new AbstractExporter<T>(invoker) {
+            @Override
+            public void afterUnExport() {
+                mappingRegistry.unregister(invoker);
+            }
+        };
+
+        invokers.add(invoker);
+        mappingRegistry.register(invoker);
+        return exporter;
+    }
+
+    @Override
+    protected <T> Invoker<T> protocolBindingRefer(Class<T> type, URL url) throws RpcException {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java
new file mode 100644
index 0000000000..7a14a0f82f
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/test/TestRequest.java
@@ -0,0 +1,209 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.tri.test;
+
+import org.apache.dubbo.remoting.http12.HttpHeaderNames;
+import org.apache.dubbo.remoting.http12.HttpHeaders;
+import org.apache.dubbo.remoting.http12.HttpMethods;
+import org.apache.dubbo.remoting.http12.h2.Http2Header;
+import org.apache.dubbo.remoting.http12.h2.Http2Headers;
+import org.apache.dubbo.remoting.http12.h2.Http2MetadataFrame;
+import org.apache.dubbo.remoting.http12.message.MediaType;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@SuppressWarnings("UnusedReturnValue")
+public class TestRequest {
+
+    private final HttpHeaders headers = new HttpHeaders();
+    private final Map<String, String> cookies = new LinkedHashMap<>();
+    private final Map<String, Object> params = new LinkedHashMap<>();
+    private final Map<String, String> providerParams = new LinkedHashMap<>();
+    private List<Object> bodies;
+
+    public TestRequest(HttpMethods method, String path) {
+        setMethod(method);
+        setPath(path);
+    }
+
+    public TestRequest(String path) {
+        setPath(path);
+    }
+
+    public TestRequest() {}
+
+    public String getPath() {
+        return headers.getFirst(Http2Headers.PATH.getName());
+    }
+
+    public TestRequest setPath(String path) {
+        headers.set(Http2Headers.PATH.getName(), path);
... 1088 lines suppressed ...