You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by me...@apache.org on 2020/06/30 03:01:28 UTC

[dubbo] branch 2.7.8-dev updated: 2.7.8 Dev (#6386)

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

mercyblitz pushed a commit to branch 2.7.8-dev
in repository https://gitbox.apache.org/repos/asf/dubbo.git


The following commit(s) were added to refs/heads/2.7.8-dev by this push:
     new 88bd09f  2.7.8 Dev (#6386)
88bd09f is described below

commit 88bd09f1e97a3880178fecb9cb0f61ad0a4563f6
Author: Mercy Ma <me...@gmail.com>
AuthorDate: Tue Jun 30 11:01:18 2020 +0800

    2.7.8 Dev (#6386)
    
    * Polish apache/dubbo#6296 : Adding the new methods into MetadataReport to manipulate the exported URLs for service introspection
    
    * Polish apache/dubbo#6296 : Adding the new methods into MetadataReport to manipulate the exported URLs for service introspection
    
    * Polish apache/dubbo#6171 : [Feature] Introducing the composite implementation of MetadataService
    
    * Revert "fix wrong check of InvokerListener when export a service (fix issue_6269) (#6271)"
    
    This reverts commit 91989cae508f8482f31ac335879da4a5975661c8.
    
    * Revert "fix wrong check of InvokerListener when export a service (fix issue_6269) (#6271)"
    
    This reverts commit 91989cae508f8482f31ac335879da4a5975661c8.
    
    * Revert the MetadataReport
    
    * Polish apache/dubbo#6305 : [Refactor] ServiceConfig and ReferenceConfig publish the ServiceDefinition based on the Dubbo Event
    
    * Polish apache/dubbo#6198 : [Issue] Fixing NacosDynamicConfiguration#publishConfig bug
    
    * Polish apache/dubbo#6310 : Refactoring MetadataReport's methods
    
    * Polish apache/dubbo#6198 : [Issue] Fixing NacosDynamicConfiguration#publishConfig bug
    
    * Polish apache/dubbo#6198 : [Issue] Fixing NacosDynamicConfiguration#publishConfig bug
    
    * Polish apache/dubbo#6315 : [Refactor] Refactoring the implementation of MetadataReport based on The Config-Center infrastructure
    
    Deprecated List :
    
    - NacosMetadataReport
    - ZookeeperMetadataReport
    
    * Polish apache/dubbo#6315 : Refactoring by TreePathDynamicConfiguration
    
    * Polish apache/dubbo#6315 : Refactoring ConsulDynamicConfiguration by TreePathDynamicConfiguration
    
    * Polish apache/dubbo#6315 : Reset the config base path to be "metadata" for ConfigCenterBasedMetadataReportFactory
    
    * Polish apache/dubbo#6315 : Bugfix
    
    * Polish apache/dubbo#6315 : Bugfix
    
    * Polish apache/dubbo#6315 : Correct words
    
    * sync wait netty server to finish shutdown (#6281)
    
    * Polish apache/dubbo#6333 : [Refactor] Using mandatory implementation of Service Instance registration instead of the event
    
    * maybe we can remove null judge in this case (#6321)
    
    * update
    
    * update
    
    * Polish apache/dubbo#6336 : [Refactor] org.apache.dubbo.metadata.ServiceNameMapping
    
    * Polish apache/dubbo#6170 : [Feature] Introducing the externalized configuration for ServiceNameMapping
    
    * Polish apache/dubbo#6342 : [Enhancement] Introducing the composite ServiceNameMapping
    
    * Refactor
    
    * fix method name typo in JValidator.java (#6344)
    
    * [Dubbo-6340]fix application cannot exit when use consul registry (#6341)
    
    * fix application cannot exit when use consul registry
    
    * make consul registry suppor ACL (#6313)
    
    * make consul registry suppor ACL
    
    * Polish apache/dubbo#6172 : [Feature] Adding the "services" attribute methods into @DubboReference
    
    * Polish apache/dubbo#6173 : [Feature] Adding the "services" attribute into <dubbo:reference> element
    
    * Polish apache/dubbo#6346 : [Issue] Merging all subscribied URLs from the multiple services
    
    * Polish apache/dubbo#6346 : [Issue] Merging all subscribied URLs from the multiple services
    
    * fix publish null value when use consul config center (#6351)
    
    * fix publish null value when use consul config center
    
    * Polish apache/dubbo#6252
    
    * Polish apache/dubbo#6356 & apache/dubbo#6171
    
    * Polish apache/dubbo#6356 & apache/dubbo#6171
    
    * Polish apache/dubbo#6224 : Filter chain was not invoked with local calls since v2.7.6
    
    * Polish apache/dubbo#6322 : [Enhancement] Fix the issues of test-cases after refactoring
    
    * Polish apache/dubbo#6322 : [Enhancement] Fix the issues of test-cases after refactoring
    
    * Polish apache/dubbo#6322 : [Enhancement] Fix the issues of test-cases after refactoring
    
    * Polish apache/dubbo#6322 : Adding META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter
    
    * fix the priority of ListenableRouter were not effective (#6148)
    
    fixes #4822
    
    * Polish apache/dubbo#6322 : [Enhancement] Fix the issues of test-cases after refactoring
    
    * when the url is generic, the log level should be info (#6363)
    
    * Polish apache/dubbo#6322 : [Enhancement] Fix the issues of test-cases after refactoring
    
    * Polish apache/dubbo#6322 : [Enhancement] Fix the issues of test-cases after refactoring
    
    * Polish apache/dubbo#6322 : [Enhancement] Fix the issues of test-cases after refactoring
    
    * Polish apache/dubbo#6322 : [Enhancement] Fix the issues of test-cases after refactoring
    
    * fix NPE when check=false is set and provider is empty. (#6376)
    
    fixes #6228
    
    * Polish apache/dubbo#6322 : [Enhancement] Fix the issues of test-cases after refactoring
    
    * Polish apache/dubbo#6322 : [Enhancement] Fix the issues of test-cases after refactoring
    
    * Polish apache/dubbo#6322 : [Enhancement] Fix the issues of test-cases after refactoring
    
    * Polish apache/dubbo#6322 : [Enhancement] Fix the issues of test-cases after refactoring
    
    * fix #6306.  support TypeBuilder sort (#6365)
    
    * fix #6306. support TypeBuilder sort
    
    * fix #6306. support TypeBuilder sort
    
    * fix #6306. support TypeBuilder sort
    
    * remove unused import
    
    * add license for test file
    
    * Polish apache/dubbo#6322 : [Enhancement] Fix the issues of test-cases after refactoring
    
    * enhance ClusterInvoker & ExtensionLoader (#6343)
    
    - Introduce ClusterInvoker to better support multiple registries subscription
    - Wrapper sort and enable/disable
    - some small fixes
    
    * Polish apache/dubbo#6322 : [Enhancement] Fix the issues of test-cases after refactoring
    
    * Fixed the test-cases
    
    Co-authored-by: tswstarplanet <ts...@apache.org>
    Co-authored-by: Nine <ni...@gmail.com>
    Co-authored-by: 陈哈哈 <ch...@outlook.com>
    Co-authored-by: luoning810 <18...@163.com>
    Co-authored-by: cvictory <sh...@gmail.com>
    Co-authored-by: ken.lj <ke...@gmail.com>
---
 dubbo-all/pom.xml                                  |    8 +
 .../java/org/apache/dubbo/rpc/cluster/Cluster.java |   15 +-
 .../cluster/{Cluster.java => ClusterInvoker.java}  |   84 +-
 .../rpc/cluster/directory/AbstractDirectory.java   |    3 +-
 .../router/condition/config/ListenableRouter.java  |    2 +-
 .../cluster/router/mock/MockInvokersSelector.java  |    2 +-
 .../cluster/support/AbstractClusterInvoker.java    |    3 +-
 .../support/registry/ZoneAwareClusterInvoker.java  |   30 +-
 .../support/wrapper/MockClusterInvoker.java        |    8 +-
 .../support/AbstractClusterInvokerTest.java        |    2 +-
 .../configcenter/AbstractDynamicConfiguration.java |    2 +-
 .../file/FileSystemDynamicConfiguration.java       |   20 +
 .../dubbo/common/extension/ExtensionLoader.java    | 2069 ++++++++++----------
 .../org/apache/dubbo/common/extension/Wrapper.java |   22 +-
 .../extension/support/ActivateComparator.java      |    4 +-
 ...ivateComparator.java => WrapperComparator.java} |  232 +--
 .../org/apache/dubbo/common/utils/ArrayUtils.java  |   22 +
 .../org/apache/dubbo/config/ServiceConfigBase.java |   18 +-
 .../apache/dubbo/config/context/ConfigManager.java |    4 +-
 .../org/apache/dubbo/rpc/model/ConsumerModel.java  |    7 +-
 .../test/java/org/apache/dubbo/common/URLTest.java |   20 +-
 .../AbstractDynamicConfigurationTest.java          |   45 +
 .../file/FileSystemDynamicConfigurationTest.java   |   42 +-
 .../common/constants/CommonConstantsTest.java      |   35 +-
 .../apache/dubbo/common/utils/StringUtilsTest.java |   37 +-
 .../dubbo/config/context/ConfigManagerTest.java    |    5 +
 .../org/apache/dubbo/config/ReferenceConfig.java   |   19 +-
 .../config/bootstrap/builders/ServiceBuilder.java  |   18 +-
 .../dubbo/config/utils/ConfigValidationUtils.java  |    2 -
 ...g.apache.dubbo.metadata.MetadataServiceExporter |    2 +-
 .../org/apache/dubbo/config/ServiceConfigTest.java |   30 +-
 .../bootstrap/builders/ReferenceBuilderTest.java   |   15 +-
 .../bootstrap/builders/RegistryBuilderTest.java    |    2 +-
 .../bootstrap/builders/ServiceBuilderTest.java     |   32 +-
 .../PublishingServiceDefinitionListenerTest.java   |   94 +
 .../RemoteMetadataServiceExporterTest.java         |  106 +
 .../metadata/MetadataServiceExporterTest.java      |   34 +-
 .../spring/extension/SpringExtensionFactory.java   |    2 +-
 .../annotation/ReferenceBeanBuilderTest.java       |   33 +-
 .../validation/support/jvalidation/JValidator.java |    6 +-
 .../metadata/CompositeServiceNameMapping.java      |   25 +-
 .../metadata/definition/TypeDefinitionBuilder.java |   10 +-
 .../metadata/definition/builder/TypeBuilder.java   |    3 +-
 .../dubbo/metadata/report/MetadataReport.java      |   13 +-
 .../BaseApplicationMetadataIdentifier.java         |    9 +-
 .../report/support/AbstractMetadataReport.java     |  133 +-
 .../support/ConfigCenterBasedMetadataReport.java   |    9 +
 .../ConfigCenterBasedMetadataReportFactory.java    |    4 +-
 .../file/FileSystemMetadataReportFactory.java}     |   30 +-
 ...che.dubbo.metadata.report.MetadataReportFactory |    1 +
 .../metadata/CompositeServiceNameMappingTest.java  |  107 +
 .../dubbo/metadata/MetadataConstantsTest.java}     |   30 +-
 ...olverTest.java => MetadataServiceTypeTest.java} |   38 +-
 .../dubbo/metadata/URLRevisionResolverTest.java    |    2 +-
 .../metadata/definition/Test3TypeBuilder.java}     |   30 +-
 .../metadata/definition/TestTypeBuilder.java}      |   30 +-
 .../definition/TypeDefinitionBuilderTest.java}     |   31 +-
 .../report/identifier/KeyTypeEnumTest.java}        |   29 +-
 .../support/AbstractMetadataReportFactoryTest.java |    9 +-
 .../report/support/AbstractMetadataReportTest.java |  121 +-
 .../ConfigCenterBasedMetadataReportTest.java       |  155 ++
 ...e.dubbo.metadata.definition.builder.TypeBuilder |    2 +
 .../store/nacos/NacosMetadataReportTest.java       |  246 ---
 .../proxy/BaseMetadataServiceProxyFactory.java     |   29 +-
 .../CompositeMetadataServiceProxyFactory.java      |   58 +-
 .../registry/integration/RegistryDirectory.java    |    4 +-
 .../integration/RegistryInvokerWrapper.java        |   18 +-
 .../registry/integration/RegistryProtocol.java     |   22 +-
 .../client/DefaultServiceInstanceTest.java         |    9 +
 .../proxy/BaseMetadataServiceProxyFactoryTest.java |   78 +
 .../CompositeMetadataServiceProxyFactoryTest.java  |   96 +
 .../proxy/MetadataServiceProxyFactoryTest.java     |   49 +
 .../proxy/MyMetadataServiceProxyFactory.java}      |   25 +-
 ...ient.metadata.proxy.MetadataServiceProxyFactory |    2 +
 .../dubbo/registry/consul/ConsulRegistry.java      |   32 +-
 .../dubbo/registry/dubbo/RegistryProtocolTest.java |    5 +-
 .../remoting/transport/netty4/NettyServer.java     |    6 +-
 .../dubbo/rpc/protocol/ProtocolFilterWrapper.java  |    2 +
 .../rpc/protocol/ProtocolListenerWrapper.java      |    2 +
 79 files changed, 2711 insertions(+), 1929 deletions(-)

diff --git a/dubbo-all/pom.xml b/dubbo-all/pom.xml
index 5c2936e..050ae24 100644
--- a/dubbo-all/pom.xml
+++ b/dubbo-all/pom.xml
@@ -981,6 +981,14 @@
                                     </resource>
                                 </transformer>
 
+                                <!-- @since 2.7.8 -->
+                                <transformer
+                                        implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
+                                    <resource>
+                                        META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter
+                                    </resource>
+                                </transformer>
+
                             </transformers>
                             <filters>
                                 <filter>
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Cluster.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Cluster.java
index e5ecb15..948827a 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Cluster.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Cluster.java
@@ -17,7 +17,9 @@
 package org.apache.dubbo.rpc.cluster;
 
 import org.apache.dubbo.common.extension.Adaptive;
+import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.extension.SPI;
+import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.RpcException;
 import org.apache.dubbo.rpc.cluster.support.FailoverCluster;
@@ -29,8 +31,9 @@ import org.apache.dubbo.rpc.cluster.support.FailoverCluster;
  * <a href="http://en.wikipedia.org/wiki/Fault-tolerant_system">Fault-Tolerant</a>
  *
  */
-@SPI(FailoverCluster.NAME)
+@SPI(Cluster.DEFAULT)
 public interface Cluster {
+    String DEFAULT = FailoverCluster.NAME;
 
     /**
      * Merge the directory invokers to a virtual invoker.
@@ -43,4 +46,14 @@ public interface Cluster {
     @Adaptive
     <T> Invoker<T> join(Directory<T> directory) throws RpcException;
 
+    static Cluster getCluster(String name) {
+        return getCluster(name, true);
+    }
+
+    static Cluster getCluster(String name, boolean wrap) {
+        if (StringUtils.isEmpty(name)) {
+            name = Cluster.DEFAULT;
+        }
+        return ExtensionLoader.getExtensionLoader(Cluster.class).getExtension(name, wrap);
+    }
 }
\ No newline at end of file
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Cluster.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/ClusterInvoker.java
similarity index 52%
copy from dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Cluster.java
copy to dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/ClusterInvoker.java
index e5ecb15..4b9199e 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Cluster.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/ClusterInvoker.java
@@ -1,46 +1,38 @@
-/*
- * 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.cluster;
-
-import org.apache.dubbo.common.extension.Adaptive;
-import org.apache.dubbo.common.extension.SPI;
-import org.apache.dubbo.rpc.Invoker;
-import org.apache.dubbo.rpc.RpcException;
-import org.apache.dubbo.rpc.cluster.support.FailoverCluster;
-
-/**
- * Cluster. (SPI, Singleton, ThreadSafe)
- * <p>
- * <a href="http://en.wikipedia.org/wiki/Computer_cluster">Cluster</a>
- * <a href="http://en.wikipedia.org/wiki/Fault-tolerant_system">Fault-Tolerant</a>
- *
- */
-@SPI(FailoverCluster.NAME)
-public interface Cluster {
-
-    /**
-     * Merge the directory invokers to a virtual invoker.
-     *
-     * @param <T>
-     * @param directory
-     * @return cluster invoker
-     * @throws RpcException
-     */
-    @Adaptive
-    <T> Invoker<T> join(Directory<T> directory) throws RpcException;
-
-}
\ No newline at end of file
+/*
+ * 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.cluster;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.rpc.Invoker;
+
+/**
+ * This is the final Invoker type referenced by the RPC proxy on Consumer side.
+ * <p>
+ * A ClusterInvoker holds a group of normal invokers, stored in a Directory, mapping to one Registry.
+ * The ClusterInvoker implementation usually provides LB or HA policies, like FailoverClusterInvoker.
+ * <p>
+ * In multi-registry subscription scenario, the final ClusterInvoker will referr to several sub ClusterInvokers, with each
+ * sub ClusterInvoker representing one Registry. Take ZoneAwareClusterInvoker as an example, it is specially customized for
+ * multi-registry use cases: first, pick up one ClusterInvoker, then do LB inside the chose ClusterInvoker.
+ *
+ * @param <T>
+ */
+public interface ClusterInvoker<T> extends Invoker<T> {
+    URL getRegistryUrl();
+
+    Directory<T> getDirectory();
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java
index 814fb64..7984d7d 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java
@@ -60,8 +60,7 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
         }
 
         this.url = url.removeParameter(REFER_KEY).removeParameter(MONITOR_KEY);
-        this.consumerUrl = url.addParameters(StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY)))
-                .removeParameter(MONITOR_KEY);
+        this.consumerUrl = this.url.addParameters(StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY)));
 
         setRouterChain(routerChain);
     }
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableRouter.java
index 74dfce2..a3514c1 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableRouter.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableRouter.java
@@ -92,7 +92,7 @@ public abstract class ListenableRouter extends AbstractRouter implements Configu
 
     @Override
     public int getPriority() {
-        return DEFAULT_PRIORITY;
+        return priority;
     }
 
     @Override
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mock/MockInvokersSelector.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mock/MockInvokersSelector.java
index cfe1a70..49893b7 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mock/MockInvokersSelector.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mock/MockInvokersSelector.java
@@ -36,7 +36,7 @@ import static org.apache.dubbo.rpc.cluster.Constants.MOCK_PROTOCOL;
 public class MockInvokersSelector extends AbstractRouter {
 
     public static final String NAME = "MOCK_ROUTER";
-    private static final int MOCK_INVOKERS_DEFAULT_PRIORITY = Integer.MIN_VALUE;
+    private static final int MOCK_INVOKERS_DEFAULT_PRIORITY = -100;
 
     public MockInvokersSelector() {
         this.priority = MOCK_INVOKERS_DEFAULT_PRIORITY;
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvoker.java
index a4b9e92..b1e8a95 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvoker.java
@@ -30,6 +30,7 @@ import org.apache.dubbo.rpc.Result;
 import org.apache.dubbo.rpc.RpcContext;
 import org.apache.dubbo.rpc.RpcException;
 import org.apache.dubbo.rpc.RpcInvocation;
+import org.apache.dubbo.rpc.cluster.ClusterInvoker;
 import org.apache.dubbo.rpc.cluster.Directory;
 import org.apache.dubbo.rpc.cluster.LoadBalance;
 import org.apache.dubbo.rpc.support.RpcUtils;
@@ -49,7 +50,7 @@ import static org.apache.dubbo.rpc.cluster.Constants.DEFAULT_CLUSTER_STICKY;
 /**
  * AbstractClusterInvoker
  */
-public abstract class AbstractClusterInvoker<T> implements Invoker<T> {
+public abstract class AbstractClusterInvoker<T> implements ClusterInvoker<T> {
 
     private static final Logger logger = LoggerFactory.getLogger(AbstractClusterInvoker.class);
 
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/registry/ZoneAwareClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/registry/ZoneAwareClusterInvoker.java
index f9f2ae7..5585b35 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/registry/ZoneAwareClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/registry/ZoneAwareClusterInvoker.java
@@ -23,6 +23,7 @@ import org.apache.dubbo.rpc.Invocation;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Result;
 import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.cluster.ClusterInvoker;
 import org.apache.dubbo.rpc.cluster.Directory;
 import org.apache.dubbo.rpc.cluster.LoadBalance;
 import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
@@ -59,24 +60,23 @@ public class ZoneAwareClusterInvoker<T> extends AbstractClusterInvoker<T> {
     public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
         // First, pick the invoker (XXXClusterInvoker) that comes from the local registry, distinguish by a 'preferred' key.
         for (Invoker<T> invoker : invokers) {
-            // FIXME, the invoker is a cluster invoker representing one Registry, so it will automatically wrapped by MockClusterInvoker.
-            MockClusterInvoker<T> mockClusterInvoker = (MockClusterInvoker<T>) invoker;
-            if (mockClusterInvoker.isAvailable() && mockClusterInvoker.getRegistryUrl()
+            ClusterInvoker<T> clusterInvoker = (ClusterInvoker<T>) invoker;
+            if (clusterInvoker.isAvailable() && clusterInvoker.getRegistryUrl()
                     .getParameter(REGISTRY_KEY + "." + PREFERRED_KEY, false)) {
-                return mockClusterInvoker.invoke(invocation);
+                return clusterInvoker.invoke(invocation);
             }
         }
 
         // providers in the registry with the same zone
-        String zone = (String) invocation.getAttachment(REGISTRY_ZONE);
+        String zone = invocation.getAttachment(REGISTRY_ZONE);
         if (StringUtils.isNotEmpty(zone)) {
             for (Invoker<T> invoker : invokers) {
-                MockClusterInvoker<T> mockClusterInvoker = (MockClusterInvoker<T>) invoker;
-                if (mockClusterInvoker.isAvailable() && zone.equals(mockClusterInvoker.getRegistryUrl().getParameter(REGISTRY_KEY + "." + ZONE_KEY))) {
-                    return mockClusterInvoker.invoke(invocation);
+                ClusterInvoker<T> clusterInvoker = (ClusterInvoker<T>) invoker;
+                if (clusterInvoker.isAvailable() && zone.equals(clusterInvoker.getRegistryUrl().getParameter(REGISTRY_KEY + "." + ZONE_KEY))) {
+                    return clusterInvoker.invoke(invocation);
                 }
             }
-            String force = (String) invocation.getAttachment(REGISTRY_ZONE_FORCE);
+            String force = invocation.getAttachment(REGISTRY_ZONE_FORCE);
             if (StringUtils.isNotEmpty(force) && "true".equalsIgnoreCase(force)) {
                 throw new IllegalStateException("No registry instance in zone or no available providers in the registry, zone: "
                         + zone
@@ -93,12 +93,14 @@ public class ZoneAwareClusterInvoker<T> extends AbstractClusterInvoker<T> {
 
         // If none of the invokers has a preferred signal or is picked by the loadbalancer, pick the first one available.
         for (Invoker<T> invoker : invokers) {
-            MockClusterInvoker<T> mockClusterInvoker = (MockClusterInvoker<T>) invoker;
-            if (mockClusterInvoker.isAvailable()) {
-                return mockClusterInvoker.invoke(invocation);
+            ClusterInvoker<T> clusterInvoker = (ClusterInvoker<T>) invoker;
+            if (clusterInvoker.isAvailable()) {
+                return clusterInvoker.invoke(invocation);
             }
         }
-        throw new RpcException("No provider available in " + invokers);
+
+        //if none available,just pick one
+        return invokers.get(0).invoke(invocation);
     }
 
-}
+}
\ No newline at end of file
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvoker.java
index afdedde..90c43bd 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvoker.java
@@ -27,6 +27,7 @@ import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Result;
 import org.apache.dubbo.rpc.RpcException;
 import org.apache.dubbo.rpc.RpcInvocation;
+import org.apache.dubbo.rpc.cluster.ClusterInvoker;
 import org.apache.dubbo.rpc.cluster.Directory;
 import org.apache.dubbo.rpc.support.MockInvoker;
 
@@ -35,7 +36,7 @@ import java.util.List;
 import static org.apache.dubbo.rpc.Constants.MOCK_KEY;
 import static org.apache.dubbo.rpc.cluster.Constants.INVOCATION_NEED_MOCK;
 
-public class MockClusterInvoker<T> implements Invoker<T> {
+public class MockClusterInvoker<T> implements ClusterInvoker<T> {
 
     private static final Logger logger = LoggerFactory.getLogger(MockClusterInvoker.class);
 
@@ -58,6 +59,11 @@ public class MockClusterInvoker<T> implements Invoker<T> {
     }
 
     @Override
+    public Directory<T> getDirectory() {
+        return directory;
+    }
+
+    @Override
     public boolean isAvailable() {
         return directory.isAvailable();
     }
diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvokerTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvokerTest.java
index a27a70e..a6ec35b 100644
--- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvokerTest.java
+++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvokerTest.java
@@ -227,7 +227,7 @@ public class AbstractClusterInvokerTest {
     public void testCloseAvailablecheck() {
         LoadBalance lb = mock(LoadBalance.class);
         Map<String, String> queryMap = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
-        URL tmpUrl = url.addParameters(queryMap).removeParameter(MONITOR_KEY);
+        URL tmpUrl = url.addParameters(queryMap).removeParameter(REFER_KEY).removeParameter(MONITOR_KEY);
         given(lb.select(invokers, tmpUrl, invocation)).willReturn(invoker1);
         initlistsize5();
 
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfiguration.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfiguration.java
index bf1fa22..31c3684 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfiguration.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfiguration.java
@@ -236,7 +236,7 @@ public abstract class AbstractDynamicConfiguration implements DynamicConfigurati
                                                        int threadPoolSize,
                                                        long keepAliveTime) {
         return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, keepAliveTime,
-                TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new NamedThreadFactory(threadPoolPrefixName));
+                TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new NamedThreadFactory(threadPoolPrefixName, true));
     }
 
     protected static String getThreadPoolPrefixName(URL url) {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfiguration.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfiguration.java
index 5a1aefd..0c9178f 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfiguration.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfiguration.java
@@ -24,6 +24,7 @@ import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
 import org.apache.dubbo.common.config.configcenter.TreePathDynamicConfiguration;
 import org.apache.dubbo.common.function.ThrowableConsumer;
 import org.apache.dubbo.common.function.ThrowableFunction;
+import org.apache.dubbo.common.lang.ShutdownHookCallbacks;
 import org.apache.dubbo.common.utils.NamedThreadFactory;
 import org.apache.dubbo.common.utils.StringUtils;
 
@@ -146,6 +147,7 @@ public class FileSystemDynamicConfiguration extends TreePathDynamicConfiguration
         MODIFIERS = initWatchEventModifiers();
         DELAY = initDelay(MODIFIERS);
         WATCH_EVENTS_LOOP_THREAD_POOL = newWatchEventsLoopThreadPool();
+        registerDubboShutdownHook();
     }
 
     /**
@@ -230,6 +232,24 @@ public class FileSystemDynamicConfiguration extends TreePathDynamicConfiguration
         });
     }
 
+    /**
+     * Register the Dubbo ShutdownHook
+     *
+     * @since 2.7.8
+     */
+    private static void registerDubboShutdownHook() {
+        ShutdownHookCallbacks.INSTANCE.addCallback(() -> {
+            watchService.ifPresent(w -> {
+                try {
+                    w.close();
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            });
+            getWatchEventsLoopThreadPool().shutdown();
+        });
+    }
+
     private static boolean isProcessingWatchEvents() {
         return getWatchEventsLoopThreadPool().getActiveCount() > 0;
     }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java
index cf34202..922b04a 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java
@@ -1,1024 +1,1045 @@
-/*
- * 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.common.extension;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.context.Lifecycle;
-import org.apache.dubbo.common.extension.support.ActivateComparator;
-import org.apache.dubbo.common.lang.Prioritized;
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.common.utils.ArrayUtils;
-import org.apache.dubbo.common.utils.ClassUtils;
-import org.apache.dubbo.common.utils.CollectionUtils;
-import org.apache.dubbo.common.utils.ConcurrentHashSet;
-import org.apache.dubbo.common.utils.ConfigUtils;
-import org.apache.dubbo.common.utils.Holder;
-import org.apache.dubbo.common.utils.ReflectUtils;
-import org.apache.dubbo.common.utils.StringUtils;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.ServiceLoader;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.regex.Pattern;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.sort;
-import static java.util.ServiceLoader.load;
-import static java.util.stream.StreamSupport.stream;
-import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN;
-import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.REMOVE_VALUE_PREFIX;
-
-/**
- * {@link org.apache.dubbo.rpc.model.ApplicationModel}, {@code DubboBootstrap} and this class are
- * at present designed to be singleton or static (by itself totally static or uses some static fields).
- * So the instances returned from them are of process or classloader scope. If you want to support
- * multiple dubbo servers in a single process, you may need to refactor these three classes.
- * <p>
- * Load dubbo extensions
- * <ul>
- * <li>auto inject dependency extension </li>
- * <li>auto wrap extension in wrapper </li>
- * <li>default extension is an adaptive instance</li>
- * </ul>
- *
- * @see <a href="http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Service%20Provider">Service Provider in Java 5</a>
- * @see org.apache.dubbo.common.extension.SPI
- * @see org.apache.dubbo.common.extension.Adaptive
- * @see org.apache.dubbo.common.extension.Activate
- */
-public class ExtensionLoader<T> {
-
-    private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
-
-    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
-
-    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
-
-    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
-
-    private final Class<?> type;
-
-    private final ExtensionFactory objectFactory;
-
-    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
-
-    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
-
-    private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
-    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
-    private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
-    private volatile Class<?> cachedAdaptiveClass = null;
-    private String cachedDefaultName;
-    private volatile Throwable createAdaptiveInstanceError;
-
-    private Set<Class<?>> cachedWrapperClasses;
-
-    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<>();
-
-    private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
-
-    public static void setLoadingStrategies(LoadingStrategy... strategies) {
-        if (ArrayUtils.isNotEmpty(strategies)) {
-            ExtensionLoader.strategies = strategies;
-        }
-    }
-
-    /**
-     * Load all {@link Prioritized prioritized} {@link LoadingStrategy Loading Strategies} via {@link ServiceLoader}
-     *
-     * @return non-null
-     * @since 2.7.7
-     */
-    private static LoadingStrategy[] loadLoadingStrategies() {
-        return stream(load(LoadingStrategy.class).spliterator(), false)
-                .sorted()
-                .toArray(LoadingStrategy[]::new);
-    }
-
-    /**
-     * Get all {@link LoadingStrategy Loading Strategies}
-     *
-     * @return non-null
-     * @see LoadingStrategy
-     * @see Prioritized
-     * @since 2.7.7
-     */
-    public static List<LoadingStrategy> getLoadingStrategies() {
-        return asList(strategies);
-    }
-
-    private ExtensionLoader(Class<?> type) {
-        this.type = type;
-        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
-    }
-
-    private static <T> boolean withExtensionAnnotation(Class<T> type) {
-        return type.isAnnotationPresent(SPI.class);
-    }
-
-    @SuppressWarnings("unchecked")
-    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
-        if (type == null) {
-            throw new IllegalArgumentException("Extension type == null");
-        }
-        if (!type.isInterface()) {
-            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
-        }
-        if (!withExtensionAnnotation(type)) {
-            throw new IllegalArgumentException("Extension type (" + type +
-                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
-        }
-
-        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
-        if (loader == null) {
-            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
-            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
-        }
-        return loader;
-    }
-
-    // For testing purposes only
-    public static void resetExtensionLoader(Class type) {
-        ExtensionLoader loader = EXTENSION_LOADERS.get(type);
-        if (loader != null) {
-            // Remove all instances associated with this loader as well
-            Map<String, Class<?>> classes = loader.getExtensionClasses();
-            for (Map.Entry<String, Class<?>> entry : classes.entrySet()) {
-                EXTENSION_INSTANCES.remove(entry.getValue());
-            }
-            classes.clear();
-            EXTENSION_LOADERS.remove(type);
-        }
-    }
-
-    public static void destroyAll() {
-        EXTENSION_INSTANCES.forEach((_type, instance) -> {
-            if (instance instanceof Lifecycle) {
-                Lifecycle lifecycle = (Lifecycle) instance;
-                try {
-                    lifecycle.destroy();
-                } catch (Exception e) {
-                    logger.error("Error destroying extension " + lifecycle, e);
-                }
-            }
-        });
-    }
-
-    private static ClassLoader findClassLoader() {
-        return ClassUtils.getClassLoader(ExtensionLoader.class);
-    }
-
-    public String getExtensionName(T extensionInstance) {
-        return getExtensionName(extensionInstance.getClass());
-    }
-
-    public String getExtensionName(Class<?> extensionClass) {
-        getExtensionClasses();// load class
-        return cachedNames.get(extensionClass);
-    }
-
-    /**
-     * This is equivalent to {@code getActivateExtension(url, key, null)}
-     *
-     * @param url url
-     * @param key url parameter key which used to get extension point names
-     * @return extension list which are activated.
-     * @see #getActivateExtension(org.apache.dubbo.common.URL, String, String)
-     */
-    public List<T> getActivateExtension(URL url, String key) {
-        return getActivateExtension(url, key, null);
-    }
-
-    /**
-     * This is equivalent to {@code getActivateExtension(url, values, null)}
-     *
-     * @param url    url
-     * @param values extension point names
-     * @return extension list which are activated
-     * @see #getActivateExtension(org.apache.dubbo.common.URL, String[], String)
-     */
-    public List<T> getActivateExtension(URL url, String[] values) {
-        return getActivateExtension(url, values, null);
-    }
-
-    /**
-     * This is equivalent to {@code getActivateExtension(url, url.getParameter(key).split(","), null)}
-     *
-     * @param url   url
-     * @param key   url parameter key which used to get extension point names
-     * @param group group
-     * @return extension list which are activated.
-     * @see #getActivateExtension(org.apache.dubbo.common.URL, String[], String)
-     */
-    public List<T> getActivateExtension(URL url, String key, String group) {
-        String value = url.getParameter(key);
-        return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
-    }
-
-    /**
-     * Get activate extensions.
-     *
-     * @param url    url
-     * @param values extension point names
-     * @param group  group
-     * @return extension list which are activated
-     * @see org.apache.dubbo.common.extension.Activate
-     */
-    public List<T> getActivateExtension(URL url, String[] values, String group) {
-        List<T> activateExtensions = new ArrayList<>();
-        List<String> names = values == null ? new ArrayList<>(0) : asList(values);
-        if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
-            getExtensionClasses();
-            for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
-                String name = entry.getKey();
-                Object activate = entry.getValue();
-
-                String[] activateGroup, activateValue;
-
-                if (activate instanceof Activate) {
-                    activateGroup = ((Activate) activate).group();
-                    activateValue = ((Activate) activate).value();
-                } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
-                    activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
-                    activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
-                } else {
-                    continue;
-                }
-                if (isMatchGroup(group, activateGroup)
-                        && !names.contains(name)
-                        && !names.contains(REMOVE_VALUE_PREFIX + name)
-                        && isActive(activateValue, url)) {
-                    activateExtensions.add(getExtension(name));
-                }
-            }
-            activateExtensions.sort(ActivateComparator.COMPARATOR);
-        }
-        List<T> loadedExtensions = new ArrayList<>();
-        for (int i = 0; i < names.size(); i++) {
-            String name = names.get(i);
-            if (!name.startsWith(REMOVE_VALUE_PREFIX)
-                    && !names.contains(REMOVE_VALUE_PREFIX + name)) {
-                if (DEFAULT_KEY.equals(name)) {
-                    if (!loadedExtensions.isEmpty()) {
-                        activateExtensions.addAll(0, loadedExtensions);
-                        loadedExtensions.clear();
-                    }
-                } else {
-                    loadedExtensions.add(getExtension(name));
-                }
-            }
-        }
-        if (!loadedExtensions.isEmpty()) {
-            activateExtensions.addAll(loadedExtensions);
-        }
-        return activateExtensions;
-    }
-
-    private boolean isMatchGroup(String group, String[] groups) {
-        if (StringUtils.isEmpty(group)) {
-            return true;
-        }
-        if (groups != null && groups.length > 0) {
-            for (String g : groups) {
-                if (group.equals(g)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private boolean isActive(String[] keys, URL url) {
-        if (keys.length == 0) {
-            return true;
-        }
-        for (String key : keys) {
-            // @Active(value="key1:value1, key2:value2")
-            String keyValue = null;
-            if (key.contains(":")) {
-                String[] arr = key.split(":");
-                key = arr[0];
-                keyValue = arr[1];
-            }
-
-            for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {
-                String k = entry.getKey();
-                String v = entry.getValue();
-                if ((k.equals(key) || k.endsWith("." + key))
-                        && ((keyValue != null && keyValue.equals(v)) || (keyValue == null && ConfigUtils.isNotEmpty(v)))) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Get extension's instance. Return <code>null</code> if extension is not found or is not initialized. Pls. note
-     * that this method will not trigger extension load.
-     * <p>
-     * In order to trigger extension load, call {@link #getExtension(String)} instead.
-     *
-     * @see #getExtension(String)
-     */
-    @SuppressWarnings("unchecked")
-    public T getLoadedExtension(String name) {
-        if (StringUtils.isEmpty(name)) {
-            throw new IllegalArgumentException("Extension name == null");
-        }
-        Holder<Object> holder = getOrCreateHolder(name);
-        return (T) holder.get();
-    }
-
-    private Holder<Object> getOrCreateHolder(String name) {
-        Holder<Object> holder = cachedInstances.get(name);
-        if (holder == null) {
-            cachedInstances.putIfAbsent(name, new Holder<>());
-            holder = cachedInstances.get(name);
-        }
-        return holder;
-    }
-
-    /**
-     * Return the list of extensions which are already loaded.
-     * <p>
-     * Usually {@link #getSupportedExtensions()} should be called in order to get all extensions.
-     *
-     * @see #getSupportedExtensions()
-     */
-    public Set<String> getLoadedExtensions() {
-        return Collections.unmodifiableSet(new TreeSet<>(cachedInstances.keySet()));
-    }
-
-    public List<T> getLoadedExtensionInstances() {
-        List<T> instances = new ArrayList<>();
-        cachedInstances.values().forEach(holder -> instances.add((T) holder.get()));
-        return instances;
-    }
-
-    public Object getLoadedAdaptiveExtensionInstances() {
-        return cachedAdaptiveInstance.get();
-    }
-
-//    public T getPrioritizedExtensionInstance() {
-//        Set<String> supported = getSupportedExtensions();
-//
-//        Set<T> instances = new HashSet<>();
-//        Set<T> prioritized = new HashSet<>();
-//        for (String s : supported) {
-//
-//        }
-//
-//    }
-
-    /**
-     * Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException}
-     * will be thrown.
-     */
-    @SuppressWarnings("unchecked")
-    public T getExtension(String name) {
-        if (StringUtils.isEmpty(name)) {
-            throw new IllegalArgumentException("Extension name == null");
-        }
-        if ("true".equals(name)) {
-            return getDefaultExtension();
-        }
-        final Holder<Object> holder = getOrCreateHolder(name);
-        Object instance = holder.get();
-        if (instance == null) {
-            synchronized (holder) {
-                instance = holder.get();
-                if (instance == null) {
-                    instance = createExtension(name);
-                    holder.set(instance);
-                }
-            }
-        }
-        return (T) instance;
-    }
-
-    /**
-     * Get the extension by specified name if found, or {@link #getDefaultExtension() returns the default one}
-     *
-     * @param name the name of extension
-     * @return non-null
-     */
-    public T getOrDefaultExtension(String name) {
-        return containsExtension(name) ? getExtension(name) : getDefaultExtension();
-    }
-
-    /**
-     * Return default extension, return <code>null</code> if it's not configured.
-     */
-    public T getDefaultExtension() {
-        getExtensionClasses();
-        if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
-            return null;
-        }
-        return getExtension(cachedDefaultName);
-    }
-
-    public boolean hasExtension(String name) {
-        if (StringUtils.isEmpty(name)) {
-            throw new IllegalArgumentException("Extension name == null");
-        }
-        Class<?> c = this.getExtensionClass(name);
-        return c != null;
-    }
-
-    public Set<String> getSupportedExtensions() {
-        Map<String, Class<?>> clazzes = getExtensionClasses();
-        return Collections.unmodifiableSet(new TreeSet<>(clazzes.keySet()));
-    }
-
-    public Set<T> getSupportedExtensionInstances() {
-        List<T> instances = new LinkedList<>();
-        Set<String> supportedExtensions = getSupportedExtensions();
-        if (CollectionUtils.isNotEmpty(supportedExtensions)) {
-            for (String name : supportedExtensions) {
-                instances.add(getExtension(name));
-            }
-        }
-        // sort the Prioritized instances
-        sort(instances, Prioritized.COMPARATOR);
-        return new LinkedHashSet<>(instances);
-    }
-
-    /**
-     * Return default extension name, return <code>null</code> if not configured.
-     */
-    public String getDefaultExtensionName() {
-        getExtensionClasses();
-        return cachedDefaultName;
-    }
-
-    /**
-     * Register new extension via API
-     *
-     * @param name  extension name
-     * @param clazz extension class
-     * @throws IllegalStateException when extension with the same name has already been registered.
-     */
-    public void addExtension(String name, Class<?> clazz) {
-        getExtensionClasses(); // load classes
-
-        if (!type.isAssignableFrom(clazz)) {
-            throw new IllegalStateException("Input type " +
-                    clazz + " doesn't implement the Extension " + type);
-        }
-        if (clazz.isInterface()) {
-            throw new IllegalStateException("Input type " +
-                    clazz + " can't be interface!");
-        }
-
-        if (!clazz.isAnnotationPresent(Adaptive.class)) {
-            if (StringUtils.isBlank(name)) {
-                throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
-            }
-            if (cachedClasses.get().containsKey(name)) {
-                throw new IllegalStateException("Extension name " +
-                        name + " already exists (Extension " + type + ")!");
-            }
-
-            cachedNames.put(clazz, name);
-            cachedClasses.get().put(name, clazz);
-        } else {
-            if (cachedAdaptiveClass != null) {
-                throw new IllegalStateException("Adaptive Extension already exists (Extension " + type + ")!");
-            }
-
-            cachedAdaptiveClass = clazz;
-        }
-    }
-
-    /**
-     * Replace the existing extension via API
-     *
-     * @param name  extension name
-     * @param clazz extension class
-     * @throws IllegalStateException when extension to be placed doesn't exist
-     * @deprecated not recommended any longer, and use only when test
-     */
-    @Deprecated
-    public void replaceExtension(String name, Class<?> clazz) {
-        getExtensionClasses(); // load classes
-
-        if (!type.isAssignableFrom(clazz)) {
-            throw new IllegalStateException("Input type " +
-                    clazz + " doesn't implement Extension " + type);
-        }
-        if (clazz.isInterface()) {
-            throw new IllegalStateException("Input type " +
-                    clazz + " can't be interface!");
-        }
-
-        if (!clazz.isAnnotationPresent(Adaptive.class)) {
-            if (StringUtils.isBlank(name)) {
-                throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
-            }
-            if (!cachedClasses.get().containsKey(name)) {
-                throw new IllegalStateException("Extension name " +
-                        name + " doesn't exist (Extension " + type + ")!");
-            }
-
-            cachedNames.put(clazz, name);
-            cachedClasses.get().put(name, clazz);
-            cachedInstances.remove(name);
-        } else {
-            if (cachedAdaptiveClass == null) {
-                throw new IllegalStateException("Adaptive Extension doesn't exist (Extension " + type + ")!");
-            }
-
-            cachedAdaptiveClass = clazz;
-            cachedAdaptiveInstance.set(null);
-        }
-    }
-
-    @SuppressWarnings("unchecked")
-    public T getAdaptiveExtension() {
-        Object instance = cachedAdaptiveInstance.get();
-        if (instance == null) {
-            if (createAdaptiveInstanceError != null) {
-                throw new IllegalStateException("Failed to create adaptive instance: " +
-                        createAdaptiveInstanceError.toString(),
-                        createAdaptiveInstanceError);
-            }
-
-            synchronized (cachedAdaptiveInstance) {
-                instance = cachedAdaptiveInstance.get();
-                if (instance == null) {
-                    try {
-                        instance = createAdaptiveExtension();
-                        cachedAdaptiveInstance.set(instance);
-                    } catch (Throwable t) {
-                        createAdaptiveInstanceError = t;
-                        throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
-                    }
-                }
-            }
-        }
-
-        return (T) instance;
-    }
-
-    private IllegalStateException findException(String name) {
-        for (Map.Entry<String, IllegalStateException> entry : exceptions.entrySet()) {
-            if (entry.getKey().toLowerCase().contains(name.toLowerCase())) {
-                return entry.getValue();
-            }
-        }
-        StringBuilder buf = new StringBuilder("No such extension " + type.getName() + " by name " + name);
-
-
-        int i = 1;
-        for (Map.Entry<String, IllegalStateException> entry : exceptions.entrySet()) {
-            if (i == 1) {
-                buf.append(", possible causes: ");
-            }
-
-            buf.append("\r\n(");
-            buf.append(i++);
-            buf.append(") ");
-            buf.append(entry.getKey());
-            buf.append(":\r\n");
-            buf.append(StringUtils.toString(entry.getValue()));
-        }
-        return new IllegalStateException(buf.toString());
-    }
-
-    @SuppressWarnings("unchecked")
-    private T createExtension(String name) {
-        Class<?> clazz = getExtensionClasses().get(name);
-        if (clazz == null) {
-            throw findException(name);
-        }
-        try {
-            T instance = (T) EXTENSION_INSTANCES.get(clazz);
-            if (instance == null) {
-                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
-                instance = (T) EXTENSION_INSTANCES.get(clazz);
-            }
-            injectExtension(instance);
-            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
-            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
-                for (Class<?> wrapperClass : wrapperClasses) {
-                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
-                }
-            }
-            initExtension(instance);
-            return instance;
-        } catch (Throwable t) {
-            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
-                    type + ") couldn't be instantiated: " + t.getMessage(), t);
-        }
-    }
-
-    private boolean containsExtension(String name) {
-        return getExtensionClasses().containsKey(name);
-    }
-
-    private T injectExtension(T instance) {
-
-        if (objectFactory == null) {
-            return instance;
-        }
-
-        try {
-            for (Method method : instance.getClass().getMethods()) {
-                if (!isSetter(method)) {
-                    continue;
-                }
-                /**
-                 * Check {@link DisableInject} to see if we need auto injection for this property
-                 */
-                if (method.getAnnotation(DisableInject.class) != null) {
-                    continue;
-                }
-                Class<?> pt = method.getParameterTypes()[0];
-                if (ReflectUtils.isPrimitives(pt)) {
-                    continue;
-                }
-
-                try {
-                    String property = getSetterProperty(method);
-                    Object object = objectFactory.getExtension(pt, property);
-                    if (object != null) {
-                        method.invoke(instance, object);
-                    }
-                } catch (Exception e) {
-                    logger.error("Failed to inject via method " + method.getName()
-                            + " of interface " + type.getName() + ": " + e.getMessage(), e);
-                }
-
-            }
-        } catch (Exception e) {
-            logger.error(e.getMessage(), e);
-        }
-        return instance;
-    }
-
-    private void initExtension(T instance) {
-        if (instance instanceof Lifecycle) {
-            Lifecycle lifecycle = (Lifecycle) instance;
-            lifecycle.initialize();
-        }
-    }
-
-    /**
-     * get properties name for setter, for instance: setVersion, return "version"
-     * <p>
-     * return "", if setter name with length less than 3
-     */
-    private String getSetterProperty(Method method) {
-        return method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
-    }
-
-    /**
-     * return true if and only if:
-     * <p>
-     * 1, public
-     * <p>
-     * 2, name starts with "set"
-     * <p>
-     * 3, only has one parameter
-     */
-    private boolean isSetter(Method method) {
-        return method.getName().startsWith("set")
-                && method.getParameterTypes().length == 1
-                && Modifier.isPublic(method.getModifiers());
-    }
-
-    private Class<?> getExtensionClass(String name) {
-        if (type == null) {
-            throw new IllegalArgumentException("Extension type == null");
-        }
-        if (name == null) {
-            throw new IllegalArgumentException("Extension name == null");
-        }
-        return getExtensionClasses().get(name);
-    }
-
-    private Map<String, Class<?>> getExtensionClasses() {
-        Map<String, Class<?>> classes = cachedClasses.get();
-        if (classes == null) {
-            synchronized (cachedClasses) {
-                classes = cachedClasses.get();
-                if (classes == null) {
-                    classes = loadExtensionClasses();
-                    cachedClasses.set(classes);
-                }
-            }
-        }
-        return classes;
-    }
-
-    /**
-     * synchronized in getExtensionClasses
-     */
-    private Map<String, Class<?>> loadExtensionClasses() {
-        cacheDefaultExtensionName();
-
-        Map<String, Class<?>> extensionClasses = new HashMap<>();
-
-        for (LoadingStrategy strategy : strategies) {
-            loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
-            loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
-        }
-
-        return extensionClasses;
-    }
-
-    /**
-     * extract and cache default extension name if exists
-     */
-    private void cacheDefaultExtensionName() {
-        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
-        if (defaultAnnotation == null) {
-            return;
-        }
-
-        String value = defaultAnnotation.value();
-        if ((value = value.trim()).length() > 0) {
-            String[] names = NAME_SEPARATOR.split(value);
-            if (names.length > 1) {
-                throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
-                        + ": " + Arrays.toString(names));
-            }
-            if (names.length == 1) {
-                cachedDefaultName = names[0];
-            }
-        }
-    }
-
-    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
-        loadDirectory(extensionClasses, dir, type, false, false);
-    }
-
-    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
-                               boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
-        String fileName = dir + type;
-        try {
-            Enumeration<java.net.URL> urls = null;
-            ClassLoader classLoader = findClassLoader();
-
-            // try to load from ExtensionLoader's ClassLoader first
-            if (extensionLoaderClassLoaderFirst) {
-                ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
-                if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
-                    urls = extensionLoaderClassLoader.getResources(fileName);
-                }
-            }
-
-            if (urls == null || !urls.hasMoreElements()) {
-                if (classLoader != null) {
-                    urls = classLoader.getResources(fileName);
-                } else {
-                    urls = ClassLoader.getSystemResources(fileName);
-                }
-            }
-
-            if (urls != null) {
-                while (urls.hasMoreElements()) {
-                    java.net.URL resourceURL = urls.nextElement();
-                    loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
-                }
-            }
-        } catch (Throwable t) {
-            logger.error("Exception occurred when loading extension class (interface: " +
-                    type + ", description file: " + fileName + ").", t);
-        }
-    }
-
-    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
-                              java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
-        try {
-            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
-                String line;
-                while ((line = reader.readLine()) != null) {
-                    final int ci = line.indexOf('#');
-                    if (ci >= 0) {
-                        line = line.substring(0, ci);
-                    }
-                    line = line.trim();
-                    if (line.length() > 0) {
-                        try {
-                            String name = null;
-                            int i = line.indexOf('=');
-                            if (i > 0) {
-                                name = line.substring(0, i).trim();
-                                line = line.substring(i + 1).trim();
-                            }
-                            if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
-                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
-                            }
-                        } catch (Throwable t) {
-                            IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
-                            exceptions.put(line, e);
-                        }
-                    }
-                }
-            }
-        } catch (Throwable t) {
-            logger.error("Exception occurred when loading extension class (interface: " +
-                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
-        }
-    }
-
-    private boolean isExcluded(String className, String... excludedPackages) {
-        if (excludedPackages != null) {
-            for (String excludePackage : excludedPackages) {
-                if (className.startsWith(excludePackage + ".")) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
-                           boolean overridden) throws NoSuchMethodException {
-        if (!type.isAssignableFrom(clazz)) {
-            throw new IllegalStateException("Error occurred when loading extension class (interface: " +
-                    type + ", class line: " + clazz.getName() + "), class "
-                    + clazz.getName() + " is not subtype of interface.");
-        }
-        if (clazz.isAnnotationPresent(Adaptive.class)) {
-            cacheAdaptiveClass(clazz, overridden);
-        } else if (isWrapperClass(clazz)) {
-            cacheWrapperClass(clazz);
-        } else {
-            clazz.getConstructor();
-            if (StringUtils.isEmpty(name)) {
-                name = findAnnotationName(clazz);
-                if (name.length() == 0) {
-                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
-                }
-            }
-
-            String[] names = NAME_SEPARATOR.split(name);
-            if (ArrayUtils.isNotEmpty(names)) {
-                cacheActivateClass(clazz, names[0]);
-                for (String n : names) {
-                    cacheName(clazz, n);
-                    saveInExtensionClass(extensionClasses, clazz, n, overridden);
-                }
-            }
-        }
-    }
-
-    /**
-     * cache name
-     */
-    private void cacheName(Class<?> clazz, String name) {
-        if (!cachedNames.containsKey(clazz)) {
-            cachedNames.put(clazz, name);
-        }
-    }
-
-    /**
-     * put clazz in extensionClasses
-     */
-    private void saveInExtensionClass(Map<String, Class<?>> extensionClasses, Class<?> clazz, String name, boolean overridden) {
-        Class<?> c = extensionClasses.get(name);
-        if (c == null || overridden) {
-            extensionClasses.put(name, clazz);
-        } else if (c != clazz) {
-            String duplicateMsg = "Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName();
-            logger.error(duplicateMsg);
-            throw new IllegalStateException(duplicateMsg);
-        }
-    }
-
-    /**
-     * cache Activate class which is annotated with <code>Activate</code>
-     * <p>
-     * for compatibility, also cache class with old alibaba Activate annotation
-     */
-    private void cacheActivateClass(Class<?> clazz, String name) {
-        Activate activate = clazz.getAnnotation(Activate.class);
-        if (activate != null) {
-            cachedActivates.put(name, activate);
-        } else {
-            // support com.alibaba.dubbo.common.extension.Activate
-            com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
-            if (oldActivate != null) {
-                cachedActivates.put(name, oldActivate);
-            }
-        }
-    }
-
-    /**
-     * cache Adaptive class which is annotated with <code>Adaptive</code>
-     */
-    private void cacheAdaptiveClass(Class<?> clazz, boolean overridden) {
-        if (cachedAdaptiveClass == null || overridden) {
-            cachedAdaptiveClass = clazz;
-        } else if (!cachedAdaptiveClass.equals(clazz)) {
-            throw new IllegalStateException("More than 1 adaptive class found: "
-                    + cachedAdaptiveClass.getName()
-                    + ", " + clazz.getName());
-        }
-    }
-
-    /**
-     * cache wrapper class
-     * <p>
-     * like: ProtocolFilterWrapper, ProtocolListenerWrapper
-     */
-    private void cacheWrapperClass(Class<?> clazz) {
-        if (cachedWrapperClasses == null) {
-            cachedWrapperClasses = new ConcurrentHashSet<>();
-        }
-        cachedWrapperClasses.add(clazz);
-    }
-
-    /**
-     * test if clazz is a wrapper class
-     * <p>
-     * which has Constructor with given class type as its only argument
-     */
-    private boolean isWrapperClass(Class<?> clazz) {
-        try {
-            clazz.getConstructor(type);
-            return true;
-        } catch (NoSuchMethodException e) {
-            return false;
-        }
-    }
-
-    @SuppressWarnings("deprecation")
-    private String findAnnotationName(Class<?> clazz) {
-        org.apache.dubbo.common.Extension extension = clazz.getAnnotation(org.apache.dubbo.common.Extension.class);
-        if (extension != null) {
-            return extension.value();
-        }
-
-        String name = clazz.getSimpleName();
-        if (name.endsWith(type.getSimpleName())) {
-            name = name.substring(0, name.length() - type.getSimpleName().length());
-        }
-        return name.toLowerCase();
-    }
-
-    @SuppressWarnings("unchecked")
-    private T createAdaptiveExtension() {
-        try {
-            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
-        } catch (Exception e) {
-            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
-        }
-    }
-
-    private Class<?> getAdaptiveExtensionClass() {
-        getExtensionClasses();
-        if (cachedAdaptiveClass != null) {
-            return cachedAdaptiveClass;
-        }
-        return cachedAdaptiveClass = createAdaptiveExtensionClass();
-    }
-
-    private Class<?> createAdaptiveExtensionClass() {
-        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
-        ClassLoader classLoader = findClassLoader();
-        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
-        return compiler.compile(code, classLoader);
-    }
-
-    @Override
-    public String toString() {
-        return this.getClass().getName() + "[" + type.getName() + "]";
-    }
-
-}
+/*
+ * 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.common.extension;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.context.Lifecycle;
+import org.apache.dubbo.common.extension.support.ActivateComparator;
+import org.apache.dubbo.common.extension.support.WrapperComparator;
+import org.apache.dubbo.common.lang.Prioritized;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.ArrayUtils;
+import org.apache.dubbo.common.utils.ClassUtils;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.ConcurrentHashSet;
+import org.apache.dubbo.common.utils.ConfigUtils;
+import org.apache.dubbo.common.utils.Holder;
+import org.apache.dubbo.common.utils.ReflectUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.regex.Pattern;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.sort;
+import static java.util.ServiceLoader.load;
+import static java.util.stream.StreamSupport.stream;
+import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN;
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.REMOVE_VALUE_PREFIX;
+
+/**
+ * {@link org.apache.dubbo.rpc.model.ApplicationModel}, {@code DubboBootstrap} and this class are
+ * at present designed to be singleton or static (by itself totally static or uses some static fields).
+ * So the instances returned from them are of process or classloader scope. If you want to support
+ * multiple dubbo servers in a single process, you may need to refactor these three classes.
+ * <p>
+ * Load dubbo extensions
+ * <ul>
+ * <li>auto inject dependency extension </li>
+ * <li>auto wrap extension in wrapper </li>
+ * <li>default extension is an adaptive instance</li>
+ * </ul>
+ *
+ * @see <a href="http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Service%20Provider">Service Provider in Java 5</a>
+ * @see org.apache.dubbo.common.extension.SPI
+ * @see org.apache.dubbo.common.extension.Adaptive
+ * @see org.apache.dubbo.common.extension.Activate
+ */
+public class ExtensionLoader<T> {
+
+    private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
+
+    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
+
+    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
+
+    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
+
+    private final Class<?> type;
+
+    private final ExtensionFactory objectFactory;
+
+    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
+
+    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
+
+    private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
+    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
+    private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
+    private volatile Class<?> cachedAdaptiveClass = null;
+    private String cachedDefaultName;
+    private volatile Throwable createAdaptiveInstanceError;
+
+    private Set<Class<?>> cachedWrapperClasses;
+
+    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<>();
+
+    private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
+
+    public static void setLoadingStrategies(LoadingStrategy... strategies) {
+        if (ArrayUtils.isNotEmpty(strategies)) {
+            ExtensionLoader.strategies = strategies;
+        }
+    }
+
+    /**
+     * Load all {@link Prioritized prioritized} {@link LoadingStrategy Loading Strategies} via {@link ServiceLoader}
+     *
+     * @return non-null
+     * @since 2.7.7
+     */
+    private static LoadingStrategy[] loadLoadingStrategies() {
+        return stream(load(LoadingStrategy.class).spliterator(), false)
+                .sorted()
+                .toArray(LoadingStrategy[]::new);
+    }
+
+    /**
+     * Get all {@link LoadingStrategy Loading Strategies}
+     *
+     * @return non-null
+     * @see LoadingStrategy
+     * @see Prioritized
+     * @since 2.7.7
+     */
+    public static List<LoadingStrategy> getLoadingStrategies() {
+        return asList(strategies);
+    }
+
+    private ExtensionLoader(Class<?> type) {
+        this.type = type;
+        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
+    }
+
+    private static <T> boolean withExtensionAnnotation(Class<T> type) {
+        return type.isAnnotationPresent(SPI.class);
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
+        if (type == null) {
+            throw new IllegalArgumentException("Extension type == null");
+        }
+        if (!type.isInterface()) {
+            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
+        }
+        if (!withExtensionAnnotation(type)) {
+            throw new IllegalArgumentException("Extension type (" + type +
+                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
+        }
+
+        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
+        if (loader == null) {
+            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
+            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
+        }
+        return loader;
+    }
+
+    // For testing purposes only
+    public static void resetExtensionLoader(Class type) {
+        ExtensionLoader loader = EXTENSION_LOADERS.get(type);
+        if (loader != null) {
+            // Remove all instances associated with this loader as well
+            Map<String, Class<?>> classes = loader.getExtensionClasses();
+            for (Map.Entry<String, Class<?>> entry : classes.entrySet()) {
+                EXTENSION_INSTANCES.remove(entry.getValue());
+            }
+            classes.clear();
+            EXTENSION_LOADERS.remove(type);
+        }
+    }
+
+    public static void destroyAll() {
+        EXTENSION_INSTANCES.forEach((_type, instance) -> {
+            if (instance instanceof Lifecycle) {
+                Lifecycle lifecycle = (Lifecycle) instance;
+                try {
+                    lifecycle.destroy();
+                } catch (Exception e) {
+                    logger.error("Error destroying extension " + lifecycle, e);
+                }
+            }
+        });
+    }
+
+    private static ClassLoader findClassLoader() {
+        return ClassUtils.getClassLoader(ExtensionLoader.class);
+    }
+
+    public String getExtensionName(T extensionInstance) {
+        return getExtensionName(extensionInstance.getClass());
+    }
+
+    public String getExtensionName(Class<?> extensionClass) {
+        getExtensionClasses();// load class
+        return cachedNames.get(extensionClass);
+    }
+
+    /**
+     * This is equivalent to {@code getActivateExtension(url, key, null)}
+     *
+     * @param url url
+     * @param key url parameter key which used to get extension point names
+     * @return extension list which are activated.
+     * @see #getActivateExtension(org.apache.dubbo.common.URL, String, String)
+     */
+    public List<T> getActivateExtension(URL url, String key) {
+        return getActivateExtension(url, key, null);
+    }
+
+    /**
+     * This is equivalent to {@code getActivateExtension(url, values, null)}
+     *
+     * @param url    url
+     * @param values extension point names
+     * @return extension list which are activated
+     * @see #getActivateExtension(org.apache.dubbo.common.URL, String[], String)
+     */
+    public List<T> getActivateExtension(URL url, String[] values) {
+        return getActivateExtension(url, values, null);
+    }
+
+    /**
+     * This is equivalent to {@code getActivateExtension(url, url.getParameter(key).split(","), null)}
+     *
+     * @param url   url
+     * @param key   url parameter key which used to get extension point names
+     * @param group group
+     * @return extension list which are activated.
+     * @see #getActivateExtension(org.apache.dubbo.common.URL, String[], String)
+     */
+    public List<T> getActivateExtension(URL url, String key, String group) {
+        String value = url.getParameter(key);
+        return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
+    }
+
+    /**
+     * Get activate extensions.
+     *
+     * @param url    url
+     * @param values extension point names
+     * @param group  group
+     * @return extension list which are activated
+     * @see org.apache.dubbo.common.extension.Activate
+     */
+    public List<T> getActivateExtension(URL url, String[] values, String group) {
+        List<T> activateExtensions = new ArrayList<>();
+        List<String> names = values == null ? new ArrayList<>(0) : asList(values);
+        if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
+            getExtensionClasses();
+            for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
+                String name = entry.getKey();
+                Object activate = entry.getValue();
+
+                String[] activateGroup, activateValue;
+
+                if (activate instanceof Activate) {
+                    activateGroup = ((Activate) activate).group();
+                    activateValue = ((Activate) activate).value();
+                } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
+                    activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
+                    activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
+                } else {
+                    continue;
+                }
+                if (isMatchGroup(group, activateGroup)
+                        && !names.contains(name)
+                        && !names.contains(REMOVE_VALUE_PREFIX + name)
+                        && isActive(activateValue, url)) {
+                    activateExtensions.add(getExtension(name));
+                }
+            }
+            activateExtensions.sort(ActivateComparator.COMPARATOR);
+        }
+        List<T> loadedExtensions = new ArrayList<>();
+        for (int i = 0; i < names.size(); i++) {
+            String name = names.get(i);
+            if (!name.startsWith(REMOVE_VALUE_PREFIX)
+                    && !names.contains(REMOVE_VALUE_PREFIX + name)) {
+                if (DEFAULT_KEY.equals(name)) {
+                    if (!loadedExtensions.isEmpty()) {
+                        activateExtensions.addAll(0, loadedExtensions);
+                        loadedExtensions.clear();
+                    }
+                } else {
+                    loadedExtensions.add(getExtension(name));
+                }
+            }
+        }
+        if (!loadedExtensions.isEmpty()) {
+            activateExtensions.addAll(loadedExtensions);
+        }
+        return activateExtensions;
+    }
+
+    private boolean isMatchGroup(String group, String[] groups) {
+        if (StringUtils.isEmpty(group)) {
+            return true;
+        }
+        if (groups != null && groups.length > 0) {
+            for (String g : groups) {
+                if (group.equals(g)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private boolean isActive(String[] keys, URL url) {
+        if (keys.length == 0) {
+            return true;
+        }
+        for (String key : keys) {
+            // @Active(value="key1:value1, key2:value2")
+            String keyValue = null;
+            if (key.contains(":")) {
+                String[] arr = key.split(":");
+                key = arr[0];
+                keyValue = arr[1];
+            }
+
+            for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {
+                String k = entry.getKey();
+                String v = entry.getValue();
+                if ((k.equals(key) || k.endsWith("." + key))
+                        && ((keyValue != null && keyValue.equals(v)) || (keyValue == null && ConfigUtils.isNotEmpty(v)))) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Get extension's instance. Return <code>null</code> if extension is not found or is not initialized. Pls. note
+     * that this method will not trigger extension load.
+     * <p>
+     * In order to trigger extension load, call {@link #getExtension(String)} instead.
+     *
+     * @see #getExtension(String)
+     */
+    @SuppressWarnings("unchecked")
+    public T getLoadedExtension(String name) {
+        if (StringUtils.isEmpty(name)) {
+            throw new IllegalArgumentException("Extension name == null");
+        }
+        Holder<Object> holder = getOrCreateHolder(name);
+        return (T) holder.get();
+    }
+
+    private Holder<Object> getOrCreateHolder(String name) {
+        Holder<Object> holder = cachedInstances.get(name);
+        if (holder == null) {
+            cachedInstances.putIfAbsent(name, new Holder<>());
+            holder = cachedInstances.get(name);
+        }
+        return holder;
+    }
+
+    /**
+     * Return the list of extensions which are already loaded.
+     * <p>
+     * Usually {@link #getSupportedExtensions()} should be called in order to get all extensions.
+     *
+     * @see #getSupportedExtensions()
+     */
+    public Set<String> getLoadedExtensions() {
+        return Collections.unmodifiableSet(new TreeSet<>(cachedInstances.keySet()));
+    }
+
+    public List<T> getLoadedExtensionInstances() {
+        List<T> instances = new ArrayList<>();
+        cachedInstances.values().forEach(holder -> instances.add((T) holder.get()));
+        return instances;
+    }
+
+    public Object getLoadedAdaptiveExtensionInstances() {
+        return cachedAdaptiveInstance.get();
+    }
+
+//    public T getPrioritizedExtensionInstance() {
+//        Set<String> supported = getSupportedExtensions();
+//
+//        Set<T> instances = new HashSet<>();
+//        Set<T> prioritized = new HashSet<>();
+//        for (String s : supported) {
+//
+//        }
+//
+//    }
+
+    /**
+     * Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException}
+     * will be thrown.
+     */
+    @SuppressWarnings("unchecked")
+    public T getExtension(String name) {
+        return getExtension(name, true);
+    }
+
+    public T getExtension(String name, boolean wrap) {
+        if (StringUtils.isEmpty(name)) {
+            throw new IllegalArgumentException("Extension name == null");
+        }
+        if ("true".equals(name)) {
+            return getDefaultExtension();
+        }
+        final Holder<Object> holder = getOrCreateHolder(name);
+        Object instance = holder.get();
+        if (instance == null) {
+            synchronized (holder) {
+                instance = holder.get();
+                if (instance == null) {
+                    instance = createExtension(name, wrap);
+                    holder.set(instance);
+                }
+            }
+        }
+        return (T) instance;
+    }
+
+    /**
+     * Get the extension by specified name if found, or {@link #getDefaultExtension() returns the default one}
+     *
+     * @param name the name of extension
+     * @return non-null
+     */
+    public T getOrDefaultExtension(String name) {
+        return containsExtension(name) ? getExtension(name) : getDefaultExtension();
+    }
+
+    /**
+     * Return default extension, return <code>null</code> if it's not configured.
+     */
+    public T getDefaultExtension() {
+        getExtensionClasses();
+        if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
+            return null;
+        }
+        return getExtension(cachedDefaultName);
+    }
+
+    public boolean hasExtension(String name) {
+        if (StringUtils.isEmpty(name)) {
+            throw new IllegalArgumentException("Extension name == null");
+        }
+        Class<?> c = this.getExtensionClass(name);
+        return c != null;
+    }
+
+    public Set<String> getSupportedExtensions() {
+        Map<String, Class<?>> clazzes = getExtensionClasses();
+        return Collections.unmodifiableSet(new TreeSet<>(clazzes.keySet()));
+    }
+
+    public Set<T> getSupportedExtensionInstances() {
+        List<T> instances = new LinkedList<>();
+        Set<String> supportedExtensions = getSupportedExtensions();
+        if (CollectionUtils.isNotEmpty(supportedExtensions)) {
+            for (String name : supportedExtensions) {
+                instances.add(getExtension(name));
+            }
+        }
+        // sort the Prioritized instances
+        sort(instances, Prioritized.COMPARATOR);
+        return new LinkedHashSet<>(instances);
+    }
+
+    /**
+     * Return default extension name, return <code>null</code> if not configured.
+     */
+    public String getDefaultExtensionName() {
+        getExtensionClasses();
+        return cachedDefaultName;
+    }
+
+    /**
+     * Register new extension via API
+     *
+     * @param name  extension name
+     * @param clazz extension class
+     * @throws IllegalStateException when extension with the same name has already been registered.
+     */
+    public void addExtension(String name, Class<?> clazz) {
+        getExtensionClasses(); // load classes
+
+        if (!type.isAssignableFrom(clazz)) {
+            throw new IllegalStateException("Input type " +
+                    clazz + " doesn't implement the Extension " + type);
+        }
+        if (clazz.isInterface()) {
+            throw new IllegalStateException("Input type " +
+                    clazz + " can't be interface!");
+        }
+
+        if (!clazz.isAnnotationPresent(Adaptive.class)) {
+            if (StringUtils.isBlank(name)) {
+                throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
+            }
+            if (cachedClasses.get().containsKey(name)) {
+                throw new IllegalStateException("Extension name " +
+                        name + " already exists (Extension " + type + ")!");
+            }
+
+            cachedNames.put(clazz, name);
+            cachedClasses.get().put(name, clazz);
+        } else {
+            if (cachedAdaptiveClass != null) {
+                throw new IllegalStateException("Adaptive Extension already exists (Extension " + type + ")!");
+            }
+
+            cachedAdaptiveClass = clazz;
+        }
+    }
+
+    /**
+     * Replace the existing extension via API
+     *
+     * @param name  extension name
+     * @param clazz extension class
+     * @throws IllegalStateException when extension to be placed doesn't exist
+     * @deprecated not recommended any longer, and use only when test
+     */
+    @Deprecated
+    public void replaceExtension(String name, Class<?> clazz) {
+        getExtensionClasses(); // load classes
+
+        if (!type.isAssignableFrom(clazz)) {
+            throw new IllegalStateException("Input type " +
+                    clazz + " doesn't implement Extension " + type);
+        }
+        if (clazz.isInterface()) {
+            throw new IllegalStateException("Input type " +
+                    clazz + " can't be interface!");
+        }
+
+        if (!clazz.isAnnotationPresent(Adaptive.class)) {
+            if (StringUtils.isBlank(name)) {
+                throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
+            }
+            if (!cachedClasses.get().containsKey(name)) {
+                throw new IllegalStateException("Extension name " +
+                        name + " doesn't exist (Extension " + type + ")!");
+            }
+
+            cachedNames.put(clazz, name);
+            cachedClasses.get().put(name, clazz);
+            cachedInstances.remove(name);
+        } else {
+            if (cachedAdaptiveClass == null) {
+                throw new IllegalStateException("Adaptive Extension doesn't exist (Extension " + type + ")!");
+            }
+
+            cachedAdaptiveClass = clazz;
+            cachedAdaptiveInstance.set(null);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public T getAdaptiveExtension() {
+        Object instance = cachedAdaptiveInstance.get();
+        if (instance == null) {
+            if (createAdaptiveInstanceError != null) {
+                throw new IllegalStateException("Failed to create adaptive instance: " +
+                        createAdaptiveInstanceError.toString(),
+                        createAdaptiveInstanceError);
+            }
+
+            synchronized (cachedAdaptiveInstance) {
+                instance = cachedAdaptiveInstance.get();
+                if (instance == null) {
+                    try {
+                        instance = createAdaptiveExtension();
+                        cachedAdaptiveInstance.set(instance);
+                    } catch (Throwable t) {
+                        createAdaptiveInstanceError = t;
+                        throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
+                    }
+                }
+            }
+        }
+
+        return (T) instance;
+    }
+
+    private IllegalStateException findException(String name) {
+        for (Map.Entry<String, IllegalStateException> entry : exceptions.entrySet()) {
+            if (entry.getKey().toLowerCase().contains(name.toLowerCase())) {
+                return entry.getValue();
+            }
+        }
+        StringBuilder buf = new StringBuilder("No such extension " + type.getName() + " by name " + name);
+
+
+        int i = 1;
+        for (Map.Entry<String, IllegalStateException> entry : exceptions.entrySet()) {
+            if (i == 1) {
+                buf.append(", possible causes: ");
+            }
+
+            buf.append("\r\n(");
+            buf.append(i++);
+            buf.append(") ");
+            buf.append(entry.getKey());
+            buf.append(":\r\n");
+            buf.append(StringUtils.toString(entry.getValue()));
+        }
+        return new IllegalStateException(buf.toString());
+    }
+
+    @SuppressWarnings("unchecked")
+    private T createExtension(String name, boolean wrap) {
+        Class<?> clazz = getExtensionClasses().get(name);
+        if (clazz == null) {
+            throw findException(name);
+        }
+        try {
+            T instance = (T) EXTENSION_INSTANCES.get(clazz);
+            if (instance == null) {
+                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
+                instance = (T) EXTENSION_INSTANCES.get(clazz);
+            }
+            injectExtension(instance);
+
+
+            if (wrap) {
+
+                List<Class<?>> wrapperClassesList = new ArrayList<>();
+                if (cachedWrapperClasses != null) {
+                    wrapperClassesList.addAll(cachedWrapperClasses);
+                    wrapperClassesList.sort(WrapperComparator.COMPARATOR);
+                    Collections.reverse(wrapperClassesList);
+                }
+
+                if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
+                    for (Class<?> wrapperClass : wrapperClassesList) {
+                        Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
+                        if (wrapper == null
+                                || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
+                            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
+                        }
+                    }
+                }
+            }
+
+            initExtension(instance);
+            return instance;
+        } catch (Throwable t) {
+            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
+                    type + ") couldn't be instantiated: " + t.getMessage(), t);
+        }
+    }
+
+    private boolean containsExtension(String name) {
+        return getExtensionClasses().containsKey(name);
+    }
+
+    private T injectExtension(T instance) {
+
+        if (objectFactory == null) {
+            return instance;
+        }
+
+        try {
+            for (Method method : instance.getClass().getMethods()) {
+                if (!isSetter(method)) {
+                    continue;
+                }
+                /**
+                 * Check {@link DisableInject} to see if we need auto injection for this property
+                 */
+                if (method.getAnnotation(DisableInject.class) != null) {
+                    continue;
+                }
+                Class<?> pt = method.getParameterTypes()[0];
+                if (ReflectUtils.isPrimitives(pt)) {
+                    continue;
+                }
+
+                try {
+                    String property = getSetterProperty(method);
+                    Object object = objectFactory.getExtension(pt, property);
+                    if (object != null) {
+                        method.invoke(instance, object);
+                    }
+                } catch (Exception e) {
+                    logger.error("Failed to inject via method " + method.getName()
+                            + " of interface " + type.getName() + ": " + e.getMessage(), e);
+                }
+
+            }
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+        }
+        return instance;
+    }
+
+    private void initExtension(T instance) {
+        if (instance instanceof Lifecycle) {
+            Lifecycle lifecycle = (Lifecycle) instance;
+            lifecycle.initialize();
+        }
+    }
+
+    /**
+     * get properties name for setter, for instance: setVersion, return "version"
+     * <p>
+     * return "", if setter name with length less than 3
+     */
+    private String getSetterProperty(Method method) {
+        return method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
+    }
+
+    /**
+     * return true if and only if:
+     * <p>
+     * 1, public
+     * <p>
+     * 2, name starts with "set"
+     * <p>
+     * 3, only has one parameter
+     */
+    private boolean isSetter(Method method) {
+        return method.getName().startsWith("set")
+                && method.getParameterTypes().length == 1
+                && Modifier.isPublic(method.getModifiers());
+    }
+
+    private Class<?> getExtensionClass(String name) {
+        if (type == null) {
+            throw new IllegalArgumentException("Extension type == null");
+        }
+        if (name == null) {
+            throw new IllegalArgumentException("Extension name == null");
+        }
+        return getExtensionClasses().get(name);
+    }
+
+    private Map<String, Class<?>> getExtensionClasses() {
+        Map<String, Class<?>> classes = cachedClasses.get();
+        if (classes == null) {
+            synchronized (cachedClasses) {
+                classes = cachedClasses.get();
+                if (classes == null) {
+                    classes = loadExtensionClasses();
+                    cachedClasses.set(classes);
+                }
+            }
+        }
+        return classes;
+    }
+
+    /**
+     * synchronized in getExtensionClasses
+     */
+    private Map<String, Class<?>> loadExtensionClasses() {
+        cacheDefaultExtensionName();
+
+        Map<String, Class<?>> extensionClasses = new HashMap<>();
+
+        for (LoadingStrategy strategy : strategies) {
+            loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
+            loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
+        }
+
+        return extensionClasses;
+    }
+
+    /**
+     * extract and cache default extension name if exists
+     */
+    private void cacheDefaultExtensionName() {
+        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
+        if (defaultAnnotation == null) {
+            return;
+        }
+
+        String value = defaultAnnotation.value();
+        if ((value = value.trim()).length() > 0) {
+            String[] names = NAME_SEPARATOR.split(value);
+            if (names.length > 1) {
+                throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
+                        + ": " + Arrays.toString(names));
+            }
+            if (names.length == 1) {
+                cachedDefaultName = names[0];
+            }
+        }
+    }
+
+    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
+        loadDirectory(extensionClasses, dir, type, false, false);
+    }
+
+    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
+                               boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
+        String fileName = dir + type;
+        try {
+            Enumeration<java.net.URL> urls = null;
+            ClassLoader classLoader = findClassLoader();
+
+            // try to load from ExtensionLoader's ClassLoader first
+            if (extensionLoaderClassLoaderFirst) {
+                ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
+                if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
+                    urls = extensionLoaderClassLoader.getResources(fileName);
+                }
+            }
+
+            if (urls == null || !urls.hasMoreElements()) {
+                if (classLoader != null) {
+                    urls = classLoader.getResources(fileName);
+                } else {
+                    urls = ClassLoader.getSystemResources(fileName);
+                }
+            }
+
+            if (urls != null) {
+                while (urls.hasMoreElements()) {
+                    java.net.URL resourceURL = urls.nextElement();
+                    loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
+                }
+            }
+        } catch (Throwable t) {
+            logger.error("Exception occurred when loading extension class (interface: " +
+                    type + ", description file: " + fileName + ").", t);
+        }
+    }
+
+    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
+                              java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
+        try {
+            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    final int ci = line.indexOf('#');
+                    if (ci >= 0) {
+                        line = line.substring(0, ci);
+                    }
+                    line = line.trim();
+                    if (line.length() > 0) {
+                        try {
+                            String name = null;
+                            int i = line.indexOf('=');
+                            if (i > 0) {
+                                name = line.substring(0, i).trim();
+                                line = line.substring(i + 1).trim();
+                            }
+                            if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
+                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
+                            }
+                        } catch (Throwable t) {
+                            IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
+                            exceptions.put(line, e);
+                        }
+                    }
+                }
+            }
+        } catch (Throwable t) {
+            logger.error("Exception occurred when loading extension class (interface: " +
+                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
+        }
+    }
+
+    private boolean isExcluded(String className, String... excludedPackages) {
+        if (excludedPackages != null) {
+            for (String excludePackage : excludedPackages) {
+                if (className.startsWith(excludePackage + ".")) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
+                           boolean overridden) throws NoSuchMethodException {
+        if (!type.isAssignableFrom(clazz)) {
+            throw new IllegalStateException("Error occurred when loading extension class (interface: " +
+                    type + ", class line: " + clazz.getName() + "), class "
+                    + clazz.getName() + " is not subtype of interface.");
+        }
+        if (clazz.isAnnotationPresent(Adaptive.class)) {
+            cacheAdaptiveClass(clazz, overridden);
+        } else if (isWrapperClass(clazz)) {
+            cacheWrapperClass(clazz);
+        } else {
+            clazz.getConstructor();
+            if (StringUtils.isEmpty(name)) {
+                name = findAnnotationName(clazz);
+                if (name.length() == 0) {
+                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
+                }
+            }
+
+            String[] names = NAME_SEPARATOR.split(name);
+            if (ArrayUtils.isNotEmpty(names)) {
+                cacheActivateClass(clazz, names[0]);
+                for (String n : names) {
+                    cacheName(clazz, n);
+                    saveInExtensionClass(extensionClasses, clazz, n, overridden);
+                }
+            }
+        }
+    }
+
+    /**
+     * cache name
+     */
+    private void cacheName(Class<?> clazz, String name) {
+        if (!cachedNames.containsKey(clazz)) {
+            cachedNames.put(clazz, name);
+        }
+    }
+
+    /**
+     * put clazz in extensionClasses
+     */
+    private void saveInExtensionClass(Map<String, Class<?>> extensionClasses, Class<?> clazz, String name, boolean overridden) {
+        Class<?> c = extensionClasses.get(name);
+        if (c == null || overridden) {
+            extensionClasses.put(name, clazz);
+        } else if (c != clazz) {
+            String duplicateMsg = "Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName();
+            logger.error(duplicateMsg);
+            throw new IllegalStateException(duplicateMsg);
+        }
+    }
+
+    /**
+     * cache Activate class which is annotated with <code>Activate</code>
+     * <p>
+     * for compatibility, also cache class with old alibaba Activate annotation
+     */
+    private void cacheActivateClass(Class<?> clazz, String name) {
+        Activate activate = clazz.getAnnotation(Activate.class);
+        if (activate != null) {
+            cachedActivates.put(name, activate);
+        } else {
+            // support com.alibaba.dubbo.common.extension.Activate
+            com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
+            if (oldActivate != null) {
+                cachedActivates.put(name, oldActivate);
+            }
+        }
+    }
+
+    /**
+     * cache Adaptive class which is annotated with <code>Adaptive</code>
+     */
+    private void cacheAdaptiveClass(Class<?> clazz, boolean overridden) {
+        if (cachedAdaptiveClass == null || overridden) {
+            cachedAdaptiveClass = clazz;
+        } else if (!cachedAdaptiveClass.equals(clazz)) {
+            throw new IllegalStateException("More than 1 adaptive class found: "
+                    + cachedAdaptiveClass.getName()
+                    + ", " + clazz.getName());
+        }
+    }
+
+    /**
+     * cache wrapper class
+     * <p>
+     * like: ProtocolFilterWrapper, ProtocolListenerWrapper
+     */
+    private void cacheWrapperClass(Class<?> clazz) {
+        if (cachedWrapperClasses == null) {
+            cachedWrapperClasses = new ConcurrentHashSet<>();
+        }
+        cachedWrapperClasses.add(clazz);
+    }
+
+    /**
+     * test if clazz is a wrapper class
+     * <p>
+     * which has Constructor with given class type as its only argument
+     */
+    private boolean isWrapperClass(Class<?> clazz) {
+        try {
+            clazz.getConstructor(type);
+            return true;
+        } catch (NoSuchMethodException e) {
+            return false;
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private String findAnnotationName(Class<?> clazz) {
+        org.apache.dubbo.common.Extension extension = clazz.getAnnotation(org.apache.dubbo.common.Extension.class);
+        if (extension != null) {
+            return extension.value();
+        }
+
+        String name = clazz.getSimpleName();
+        if (name.endsWith(type.getSimpleName())) {
+            name = name.substring(0, name.length() - type.getSimpleName().length());
+        }
+        return name.toLowerCase();
+    }
+
+    @SuppressWarnings("unchecked")
+    private T createAdaptiveExtension() {
+        try {
+            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
+        } catch (Exception e) {
+            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
+        }
+    }
+
+    private Class<?> getAdaptiveExtensionClass() {
+        getExtensionClasses();
+        if (cachedAdaptiveClass != null) {
+            return cachedAdaptiveClass;
+        }
+        return cachedAdaptiveClass = createAdaptiveExtensionClass();
+    }
+
+    private Class<?> createAdaptiveExtensionClass() {
+        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
+        ClassLoader classLoader = findClassLoader();
+        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
+        return compiler.compile(code, classLoader);
+    }
+
+    @Override
+    public String toString() {
+        return this.getClass().getName() + "[" + type.getName() + "]";
+    }
+
+}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/Wrapper.java
old mode 100755
new mode 100644
similarity index 61%
copy from dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
copy to dubbo-common/src/main/java/org/apache/dubbo/common/extension/Wrapper.java
index d7022bd..670aae6
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/Wrapper.java
@@ -14,28 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.metadata.definition.builder;
-
-import org.apache.dubbo.common.extension.SPI;
-import org.apache.dubbo.metadata.definition.model.TypeDefinition;
-
-import java.lang.reflect.Type;
-import java.util.Map;
+package org.apache.dubbo.common.extension;
 
 /**
- * 2015/1/27.
+ * The annotated class will only work as a wrapper when the condition matches.
  */
-@SPI
-public interface TypeBuilder {
+public @interface Wrapper {
 
     /**
-     * Whether the build accept the type or class passed in.
+     * the extension names that need to be wrapped.
      */
-    boolean accept(Type type, Class<?> clazz);
+    String[] matches() default {};
 
     /**
-     * Build type definition with the type or class.
+     * the extension names that need to be excluded.
      */
-    TypeDefinition build(Type type, Class<?> clazz, Map<Class<?>, TypeDefinition> typeCache);
-
+    String[] mismatches() default {};
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/ActivateComparator.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/ActivateComparator.java
index 1114804..9ad1aaf 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/ActivateComparator.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/ActivateComparator.java
@@ -75,10 +75,8 @@ public class ActivateComparator implements Comparator<Object> {
                 }
             }
         }
-        int n1 = a1 == null ? 0 : a1.order;
-        int n2 = a2 == null ? 0 : a2.order;
         // never return 0 even if n1 equals n2, otherwise, o1 and o2 will override each other in collection like HashSet
-        return n1 > n2 ? 1 : -1;
+        return a1.order > a2.order ? 1 : -1;
     }
 
     private Class<?> findSpi(Class clazz) {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/ActivateComparator.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/WrapperComparator.java
similarity index 53%
copy from dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/ActivateComparator.java
copy to dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/WrapperComparator.java
index 1114804..ead4a4f 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/ActivateComparator.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/WrapperComparator.java
@@ -1,137 +1,95 @@
-/*
- * 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.common.extension.support;
-
-import org.apache.dubbo.common.extension.Activate;
-import org.apache.dubbo.common.extension.ExtensionLoader;
-import org.apache.dubbo.common.extension.SPI;
-import org.apache.dubbo.common.utils.ArrayUtils;
-
-import java.util.Arrays;
-import java.util.Comparator;
-
-/**
- * OrderComparator
- */
-public class ActivateComparator implements Comparator<Object> {
-
-    public static final Comparator<Object> COMPARATOR = new ActivateComparator();
-
-    @Override
-    public int compare(Object o1, Object o2) {
-        if (o1 == null && o2 == null) {
-            return 0;
-        }
-        if (o1 == null) {
-            return -1;
-        }
-        if (o2 == null) {
-            return 1;
-        }
-        if (o1.equals(o2)) {
-            return 0;
-        }
-
-        Class<?> inf = findSpi(o1.getClass());
-
-        ActivateInfo a1 = parseActivate(o1.getClass());
-        ActivateInfo a2 = parseActivate(o2.getClass());
-
-        if ((a1.applicableToCompare() || a2.applicableToCompare()) && inf != null) {
-            ExtensionLoader<?> extensionLoader = ExtensionLoader.getExtensionLoader(inf);
-            if (a1.applicableToCompare()) {
-                String n2 = extensionLoader.getExtensionName(o2.getClass());
-                if (a1.isLess(n2)) {
-                    return -1;
-                }
-
-                if (a1.isMore(n2)) {
-                    return 1;
-                }
-            }
-
-            if (a2.applicableToCompare()) {
-                String n1 = extensionLoader.getExtensionName(o1.getClass());
-                if (a2.isLess(n1)) {
-                    return 1;
-                }
-
-                if (a2.isMore(n1)) {
-                    return -1;
-                }
-            }
-        }
-        int n1 = a1 == null ? 0 : a1.order;
-        int n2 = a2 == null ? 0 : a2.order;
-        // never return 0 even if n1 equals n2, otherwise, o1 and o2 will override each other in collection like HashSet
-        return n1 > n2 ? 1 : -1;
-    }
-
-    private Class<?> findSpi(Class clazz) {
-        if (clazz.getInterfaces().length == 0) {
-            return null;
-        }
-
-        for (Class<?> intf : clazz.getInterfaces()) {
-            if (intf.isAnnotationPresent(SPI.class)) {
-                return intf;
-            } else {
-                Class result = findSpi(intf);
-                if (result != null) {
-                    return result;
-                }
-            }
-        }
-
-        return null;
-    }
-
-    private ActivateInfo parseActivate(Class<?> clazz) {
-        ActivateInfo info = new ActivateInfo();
-        if (clazz.isAnnotationPresent(Activate.class)) {
-            Activate activate = clazz.getAnnotation(Activate.class);
-            info.before = activate.before();
-            info.after = activate.after();
-            info.order = activate.order();
-        } else {
-            com.alibaba.dubbo.common.extension.Activate activate = clazz.getAnnotation(
-                    com.alibaba.dubbo.common.extension.Activate.class);
-            info.before = activate.before();
-            info.after = activate.after();
-            info.order = activate.order();
-        }
-        return info;
-    }
-
-    private static class ActivateInfo {
-        private String[] before;
-        private String[] after;
-        private int order;
-
-        private boolean applicableToCompare() {
-            return ArrayUtils.isNotEmpty(before) || ArrayUtils.isNotEmpty(after);
-        }
-
-        private boolean isLess(String name) {
-            return Arrays.asList(before).contains(name);
-        }
-
-        private boolean isMore(String name) {
-            return Arrays.asList(after).contains(name);
-        }
-    }
-}
+/*
+ * 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.common.extension.support;
+
+import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.common.extension.SPI;
+
+import java.util.Comparator;
+
+/**
+ * OrderComparator
+ */
+public class WrapperComparator implements Comparator<Object> {
+
+    public static final Comparator<Object> COMPARATOR = new WrapperComparator();
+
+    @Override
+    public int compare(Object o1, Object o2) {
+        if (o1 == null && o2 == null) {
+            return 0;
+        }
+        if (o1 == null) {
+            return -1;
+        }
+        if (o2 == null) {
+            return 1;
+        }
+        if (o1.equals(o2)) {
+            return 0;
+        }
+
+        Class clazz1 = (Class) o1;
+        Class clazz2 = (Class) o2;
+
+        Class<?> inf = findSpi(clazz1);
+
+        OrderInfo a1 = parseOrder(clazz1);
+        OrderInfo a2 = parseOrder(clazz2);
+
+        int n1 = a1 == null ? 0 : a1.order;
+        int n2 = a2 == null ? 0 : a2.order;
+        // never return 0 even if n1 equals n2, otherwise, o1 and o2 will override each other in collection like HashSet
+        return n1 > n2 ? 1 : -1;
+    }
+
+    private Class<?> findSpi(Class clazz) {
+        if (clazz.getInterfaces().length == 0) {
+            return null;
+        }
+
+        for (Class<?> intf : clazz.getInterfaces()) {
+            if (intf.isAnnotationPresent(SPI.class)) {
+                return intf;
+            } else {
+                Class result = findSpi(intf);
+                if (result != null) {
+                    return result;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private OrderInfo parseOrder(Class<?> clazz) {
+        OrderInfo info = new OrderInfo();
+        if (clazz.isAnnotationPresent(Activate.class)) {
+            Activate activate = clazz.getAnnotation(Activate.class);
+            info.order = activate.order();
+        } else if (clazz.isAnnotationPresent(com.alibaba.dubbo.common.extension.Activate.class)) {
+            com.alibaba.dubbo.common.extension.Activate activate = clazz.getAnnotation(
+                    com.alibaba.dubbo.common.extension.Activate.class);
+            info.order = activate.order();
+        }
+        return info;
+    }
+
+    private static class OrderInfo {
+        private int order;
+    }
+}
\ No newline at end of file
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ArrayUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ArrayUtils.java
index a0b62bd..5be000d 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ArrayUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ArrayUtils.java
@@ -44,4 +44,26 @@ public final class ArrayUtils {
     public static boolean isNotEmpty(final Object[] array) {
         return !isEmpty(array);
     }
+
+    public static boolean contains(final String[] array, String valueToFind) {
+        return indexOf(array, valueToFind, 0) != -1;
+    }
+
+    public static int indexOf(String[] array, String valueToFind, int startIndex) {
+        if (isEmpty(array) || valueToFind == null) {
+            return -1;
+        } else {
+            if (startIndex < 0) {
+                startIndex = 0;
+            }
+
+            for (int i = startIndex; i < array.length; ++i) {
+                if (valueToFind.equals(array[i])) {
+                    return i;
+                }
+            }
+
+            return -1;
+        }
+    }
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java b/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java
index 24f5176..f1b14d6 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java
@@ -362,15 +362,15 @@ public abstract class ServiceConfigBase<T> extends AbstractServiceConfig {
         }
     }
 
-    @Override
-    public void setMock(String mock) {
-        throw new IllegalArgumentException("mock doesn't support on provider side");
-    }
-
-    @Override
-    public void setMock(Object mock) {
-        throw new IllegalArgumentException("mock doesn't support on provider side");
-    }
+//    @Override
+//    public void setMock(String mock) {
+//        throw new IllegalArgumentException("mock doesn't support on provider side");
+//    }
+//
+//    @Override
+//    public void setMock(Object mock) {
+//        throw new IllegalArgumentException("mock doesn't support on provider side");
+//    }
 
     public ServiceMetadata getServiceMetadata() {
         return serviceMetadata;
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java b/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java
index 07647bf..4bc1660 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java
@@ -67,10 +67,10 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
 
     public static final String NAME = "config";
 
-    private final Map<String, Map<String, AbstractConfig>> configsCache = newMap();
-
     private final ReadWriteLock lock = new ReentrantReadWriteLock();
 
+    final Map<String, Map<String, AbstractConfig>> configsCache = newMap();
+
     public ConfigManager() {
     }
 
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ConsumerModel.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ConsumerModel.java
index 9dca2e8..e49888d 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ConsumerModel.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ConsumerModel.java
@@ -138,7 +138,12 @@ public class ConsumerModel {
     }
 
     public void initMethodModels() {
-        Class[] interfaceList = serviceMetadata.getTarget().getClass().getInterfaces();
+        Class[] interfaceList = null;
+        if (proxyObject == null) {
+            interfaceList = new Class[]{referenceConfig.getActualInterface()};
+        } else {
+            interfaceList = proxyObject.getClass().getInterfaces();
+        }
         for (Class interfaceClass : interfaceList) {
             for (Method method : interfaceClass.getMethods()) {
                 methodModels.put(method, new ConsumerMethodModel(method));
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java
index db5f57b..dc96595 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java
@@ -25,6 +25,7 @@ import java.io.File;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Predicate;
 
 import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.CoreMatchers.equalTo;
@@ -68,10 +69,10 @@ public class URLTest {
 
     private void assertURLStrDecoder(URL url) {
         String fullURLStr = url.toFullString();
-        URL newUrl =  URLStrParser.parseEncodedStr(URL.encode(fullURLStr));
+        URL newUrl = URLStrParser.parseEncodedStr(URL.encode(fullURLStr));
         assertEquals(URL.valueOf(fullURLStr), newUrl);
 
-        URL newUrl2 =  URLStrParser.parseDecodedStr(fullURLStr);
+        URL newUrl2 = URLStrParser.parseDecodedStr(fullURLStr);
         assertEquals(URL.valueOf(fullURLStr), newUrl2);
     }
 
@@ -874,4 +875,19 @@ public class URLTest {
         url = URL.valueOf("dubbo://10.20.130.230:20880/path");
         assertURLStrDecoder(url);
     }
+
+
+    /**
+     * Test {@link URL#getParameters(Predicate)} method
+     *
+     * @since 2.7.8
+     */
+    @Test
+    public void testGetParameters() {
+        URL url = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&group=group&version=1.0.0");
+        Map<String, String> parameters = url.getParameters(i -> "version".equals(i));
+        String version = parameters.get("version");
+        assertEquals(1, parameters.size());
+        assertEquals("1.0.0", version);
+    }
 }
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfigurationTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfigurationTest.java
index 772727b..7605a3c 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfigurationTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfigurationTest.java
@@ -28,10 +28,13 @@ import java.util.concurrent.TimeUnit;
 import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.DEFAULT_THREAD_POOL_KEEP_ALIVE_TIME;
 import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.DEFAULT_THREAD_POOL_PREFIX;
 import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.DEFAULT_THREAD_POOL_SIZE;
+import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.GROUP_PARAM_NAME;
 import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.PARAM_NAME_PREFIX;
 import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.THREAD_POOL_KEEP_ALIVE_TIME_PARAM_NAME;
 import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.THREAD_POOL_PREFIX_PARAM_NAME;
 import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.THREAD_POOL_SIZE_PARAM_NAME;
+import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.TIMEOUT_PARAM_NAME;
+import static org.apache.dubbo.common.config.configcenter.DynamicConfiguration.DEFAULT_GROUP;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -75,6 +78,10 @@ public class AbstractDynamicConfigurationTest {
         assertEquals("dubbo.config-center.thread-pool.keep-alive-time", THREAD_POOL_KEEP_ALIVE_TIME_PARAM_NAME);
         assertEquals(1, DEFAULT_THREAD_POOL_SIZE);
         assertEquals(60 * 1000, DEFAULT_THREAD_POOL_KEEP_ALIVE_TIME);
+
+        // @since 2.7.8
+        assertEquals("dubbo.config-center.group", GROUP_PARAM_NAME);
+        assertEquals("dubbo.config-center.timeout", TIMEOUT_PARAM_NAME);
     }
 
     @Test
@@ -158,4 +165,42 @@ public class AbstractDynamicConfigurationTest {
     public void testClose() throws Exception {
         configuration.close();
     }
+
+    /**
+     * Test {@link AbstractDynamicConfiguration#getGroup()} and
+     * {@link AbstractDynamicConfiguration#getDefaultGroup()} methods
+     *
+     * @since 2.7.8
+     */
+    @Test
+    public void testGetGroupAndGetDefaultGroup() {
+        assertEquals(configuration.getGroup(), configuration.getDefaultGroup());
+        assertEquals(DEFAULT_GROUP, configuration.getDefaultGroup());
+    }
+
+    /**
+     * Test {@link AbstractDynamicConfiguration#getTimeout()} and
+     * {@link AbstractDynamicConfiguration#getDefaultTimeout()} methods
+     *
+     * @since 2.7.8
+     */
+    @Test
+    public void testGetTimeoutAndGetDefaultTimeout() {
+        assertEquals(configuration.getTimeout(), configuration.getDefaultTimeout());
+        assertEquals(-1L, configuration.getDefaultTimeout());
+    }
+
+    /**
+     * Test {@link AbstractDynamicConfiguration#removeConfig(String, String)} and
+     * {@link AbstractDynamicConfiguration#doRemoveConfig(String, String)} methods
+     *
+     * @since 2.7.8
+     */
+    @Test
+    public void testRemoveConfigAndDoRemoveConfig() throws Exception {
+        String key = null;
+        String group = null;
+        assertEquals(configuration.removeConfig(key, group), configuration.doRemoveConfig(key, group));
+        assertFalse(configuration.removeConfig(key, group));
+    }
 }
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfigurationTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfigurationTest.java
index c471bb1..80282c1 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfigurationTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfigurationTest.java
@@ -17,6 +17,8 @@
 package org.apache.dubbo.common.config.configcenter.file;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
 
 import org.apache.commons.io.FileUtils;
 import org.junit.jupiter.api.AfterEach;
@@ -24,15 +26,17 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import java.io.File;
+import java.util.TreeSet;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import static java.util.Arrays.asList;
 import static org.apache.commons.io.FileUtils.deleteQuietly;
 import static org.apache.dubbo.common.URL.valueOf;
 import static org.apache.dubbo.common.config.configcenter.DynamicConfiguration.DEFAULT_GROUP;
 import static org.apache.dubbo.common.config.configcenter.file.FileSystemDynamicConfiguration.CONFIG_CENTER_DIR_PARAM_NAME;
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -41,6 +45,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  */
 public class FileSystemDynamicConfigurationTest {
 
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
     private FileSystemDynamicConfiguration configuration;
 
     private static final String KEY = "abc-def-ghi";
@@ -53,11 +59,11 @@ public class FileSystemDynamicConfigurationTest {
         rootDirectory.mkdirs();
         URL url = valueOf("dubbo://127.0.0.1:20880").addParameter(CONFIG_CENTER_DIR_PARAM_NAME, rootDirectory.getAbsolutePath());
         configuration = new FileSystemDynamicConfiguration(url);
-        deleteQuietly(configuration.getRootDirectory());
     }
 
     @AfterEach
     public void destroy() throws Exception {
+        deleteQuietly(configuration.getRootDirectory());
         configuration.close();
     }
 
@@ -73,9 +79,6 @@ public class FileSystemDynamicConfigurationTest {
         assertEquals(ThreadPoolExecutor.class, configuration.getWorkersThreadPool().getClass());
         assertEquals(1, (configuration.getWorkersThreadPool()).getCorePoolSize());
         assertEquals(1, (configuration.getWorkersThreadPool()).getMaximumPoolSize());
-        assertNotNull(configuration.getWatchEventsLoopThreadPool());
-        assertEquals(1, (configuration.getWatchEventsLoopThreadPool()).getCorePoolSize());
-        assertEquals(1, (configuration.getWatchEventsLoopThreadPool()).getMaximumPoolSize());
 
         if (configuration.isBasedPoolingWatchService()) {
             assertEquals(2, configuration.getDelay());
@@ -103,7 +106,7 @@ public class FileSystemDynamicConfigurationTest {
 
             processedEvent.set(true);
             assertEquals(KEY, event.getKey());
-            System.out.printf("[%s] " + event + "\n", Thread.currentThread().getName());
+            logger.info(String.format("[%s] " + event + "\n", Thread.currentThread().getName()));
         });
 
 
@@ -127,7 +130,7 @@ public class FileSystemDynamicConfigurationTest {
         configuration.addListener("test", "test", event -> {
             processedEvent.set(true);
             assertEquals("test", event.getKey());
-            System.out.printf("[%s] " + event + "\n", Thread.currentThread().getName());
+            logger.info(String.format("[%s] " + event + "\n", Thread.currentThread().getName()));
         });
         processedEvent.set(false);
         configuration.publishConfig("test", "test", "TEST");
@@ -148,4 +151,29 @@ public class FileSystemDynamicConfigurationTest {
             Thread.sleep(1 * 1000L);
         }
     }
+
+    @Test
+    public void testRemoveConfig() throws Exception {
+
+        assertTrue(configuration.publishConfig(KEY, DEFAULT_GROUP, "A"));
+
+        assertEquals("A", FileUtils.readFileToString(configuration.configFile(KEY, DEFAULT_GROUP), configuration.getEncoding()));
+
+        assertTrue(configuration.removeConfig(KEY, DEFAULT_GROUP));
+
+        assertFalse(configuration.configFile(KEY, DEFAULT_GROUP).exists());
+
+    }
+
+    @Test
+    public void testGetConfigKeys() throws Exception {
+
+        assertTrue(configuration.publishConfig("A", DEFAULT_GROUP, "A"));
+
+        assertTrue(configuration.publishConfig("B", DEFAULT_GROUP, "B"));
+
+        assertTrue(configuration.publishConfig("C", DEFAULT_GROUP, "C"));
+
+        assertEquals(new TreeSet(asList("A", "B", "C")), configuration.getConfigKeys(DEFAULT_GROUP));
+    }
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/constants/CommonConstantsTest.java
similarity index 50%
copy from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java
copy to dubbo-common/src/test/java/org/apache/dubbo/common/constants/CommonConstantsTest.java
index 487078e..bc85dfb 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/constants/CommonConstantsTest.java
@@ -14,37 +14,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.metadata;
+package org.apache.dubbo.common.constants;
 
 import org.junit.jupiter.api.Test;
 
-import java.util.Arrays;
-import java.util.Collection;
-
-import static java.util.Arrays.asList;
-import static org.apache.dubbo.metadata.URLRevisionResolver.UNKNOWN_REVISION;
+import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR_CHAR;
+import static org.apache.dubbo.common.constants.CommonConstants.COMPOSITE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_SERVICE_NAME_MAPPING_PROPERTIES_PATH;
+import static org.apache.dubbo.common.constants.CommonConstants.SERVICE_NAME_MAPPING_PROPERTIES_FILE_KEY;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 /**
- * {@link URLRevisionResolver} Test
+ * {@link CommonConstants} Test-Cases
  *
- * @since 2.7.5
+ * @since 2.7.8
  */
-public class URLRevisionResolverTest {
-
-    private static final String URL = "dubbo://192.168.0.102:20881/org.apache.dubbo.metadata.URLRevisionResolverTest";
-
-    private final URLRevisionResolver resolver = new URLRevisionResolver();
+public class CommonConstantsTest {
 
     @Test
-    public void testResolve() {
-        String revision = resolver.resolve(Arrays.<String>asList());
-        assertEquals(UNKNOWN_REVISION, revision);
-
-        revision = resolver.resolve((Collection<String>) null);
-        assertEquals(UNKNOWN_REVISION, revision);
-
-        revision = resolver.resolve(asList(URL));
-        assertEquals("2ca0638f189ce569", revision);
+    public void test() {
+        assertEquals(',', COMMA_SEPARATOR_CHAR);
+        assertEquals("composite", COMPOSITE_METADATA_STORAGE_TYPE);
+        assertEquals("service-name-mapping.properties-path", SERVICE_NAME_MAPPING_PROPERTIES_FILE_KEY);
+        assertEquals("META-INF/dubbo/service-name-mapping.properties", DEFAULT_SERVICE_NAME_MAPPING_PROPERTIES_PATH);
     }
 }
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringUtilsTest.java
index 8422c78..0d38e6c 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringUtilsTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringUtilsTest.java
@@ -24,10 +24,15 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
+import static java.util.Arrays.asList;
 import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
+import static org.apache.dubbo.common.utils.CollectionUtils.ofSet;
+import static org.apache.dubbo.common.utils.StringUtils.splitToList;
+import static org.apache.dubbo.common.utils.StringUtils.splitToSet;
 import static org.apache.dubbo.common.utils.StringUtils.toCommaDelimitedString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsString;
@@ -243,16 +248,32 @@ public class StringUtilsTest {
     public void testSplitToList() throws Exception {
         String str = "d,1,2,4";
 
-        assertEquals(4, StringUtils.splitToList(str, ',').size());
-        assertEquals(Arrays.asList(str.split(",")), StringUtils.splitToList(str, ','));
+        assertEquals(4, splitToList(str, ',').size());
+        assertEquals(asList(str.split(",")), splitToList(str, ','));
 
-        assertEquals(1, StringUtils.splitToList(str, 'a').size());
-        assertEquals(Arrays.asList(str.split("a")), StringUtils.splitToList(str, 'a'));
+        assertEquals(1, splitToList(str, 'a').size());
+        assertEquals(asList(str.split("a")), splitToList(str, 'a'));
 
-        assertEquals(0, StringUtils.splitToList("", 'a').size());
-        assertEquals(0, StringUtils.splitToList(null, 'a').size());
+        assertEquals(0, splitToList("", 'a').size());
+        assertEquals(0, splitToList(null, 'a').size());
     }
 
+    /**
+     * Test {@link StringUtils#splitToSet(String, char, boolean)}
+     *
+     * @since 2.7.8
+     */
+    @Test
+    public void testSplitToSet() {
+        String value = "1# 2#3 #4#3";
+        Set<String> values = splitToSet(value, '#', false);
+        assertEquals(ofSet("1", " 2", "3 ", "4", "3"), values);
+
+        values = splitToSet(value, '#', true);
+        assertEquals(ofSet("1", "2", "3", "4"), values);
+    }
+
+
     @Test
     public void testTranslate() throws Exception {
         String s = "16314";
@@ -372,6 +393,10 @@ public class StringUtilsTest {
         assertEquals(0, illegalMap.size());
     }
 
+    /**
+     * Test {@link StringUtils#toCommaDelimitedString(String, String...)}
+     * @since 2.7.8
+     */
     @Test
     public void testToCommaDelimitedString() {
         String value = toCommaDelimitedString(null);
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/config/context/ConfigManagerTest.java b/dubbo-common/src/test/java/org/apache/dubbo/config/context/ConfigManagerTest.java
index ac2b416..24c4f00 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/config/context/ConfigManagerTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/config/context/ConfigManagerTest.java
@@ -53,6 +53,11 @@ public class ConfigManagerTest {
     }
 
     @Test
+    public void testDestroy() {
+        assertTrue(configManager.configsCache.isEmpty());
+    }
+
+    @Test
     public void testDefaultValues() {
         // assert single
         assertFalse(configManager.getApplication().isPresent());
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java
index 101a6d2..570f7a4 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java
@@ -58,8 +58,8 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
-import static java.util.Collections.unmodifiableList;
 import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
 import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR;
@@ -75,7 +75,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.SEMICOLON_SPLIT_
 import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.SUBSCRIBED_SERVICE_NAMES_KEY;
 import static org.apache.dubbo.common.utils.NetUtils.isInvalidLocalHost;
-import static org.apache.dubbo.common.utils.StringUtils.splitToList;
+import static org.apache.dubbo.common.utils.StringUtils.splitToSet;
 import static org.apache.dubbo.config.Constants.DUBBO_IP_TO_REGISTRY;
 import static org.apache.dubbo.registry.Constants.REGISTER_IP_KEY;
 import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL;
@@ -178,8 +178,8 @@ public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
      * @since 2.7.8
      */
     @Parameter(excluded = true)
-    public List<String> getSubscribedServices() {
-        return unmodifiableList(splitToList(getServices(), COMMA_SEPARATOR_CHAR));
+    public Set<String> getSubscribedServices() {
+        return splitToSet(getServices(), COMMA_SEPARATOR_CHAR);
     }
 
     /**
@@ -365,11 +365,14 @@ public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
                 }
                 if (registryURL != null) { // registry url is available
                     // for multi-subscription scenario, use 'zone-aware' policy by default
-                    URL u = registryURL.addParameterIfAbsent(CLUSTER_KEY, ZoneAwareCluster.NAME);
-                    // The invoker wrap relation would be like: ZoneAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker
-                    invoker = CLUSTER.join(new StaticDirectory(u, invokers));
+                    String cluster = registryURL.getParameter(CLUSTER_KEY, ZoneAwareCluster.NAME);
+                    // The invoker wrap sequence would be: ZoneAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker
+                    invoker = Cluster.getCluster(cluster, false).join(new StaticDirectory(registryURL, invokers));
                 } else { // not a registry url, must be direct invoke.
-                    invoker = CLUSTER.join(new StaticDirectory(invokers));
+                    String cluster = CollectionUtils.isNotEmpty(invokers)
+                            ? (invokers.get(0).getUrl() != null ? invokers.get(0).getUrl().getParameter(CLUSTER_KEY, ZoneAwareCluster.NAME) : Cluster.DEFAULT)
+                            : Cluster.DEFAULT;
+                    invoker = Cluster.getCluster(cluster).join(new StaticDirectory(invokers));
                 }
             }
         }
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ServiceBuilder.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ServiceBuilder.java
index b7df8a4..cfea580 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ServiceBuilder.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ServiceBuilder.java
@@ -128,15 +128,15 @@ public class ServiceBuilder<U> extends AbstractServiceBuilder<ServiceConfig, Ser
         return getThis();
     }
 
-    @Override
-    public ServiceBuilder<U> mock(String mock) {
-        throw new IllegalArgumentException("mock doesn't support on provider side");
-    }
-
-    @Override
-    public ServiceBuilder<U> mock(Boolean mock) {
-        throw new IllegalArgumentException("mock doesn't support on provider side");
-    }
+//    @Override
+//    public ServiceBuilder<U> mock(String mock) {
+//        throw new IllegalArgumentException("mock doesn't support on provider side");
+//    }
+
+//    @Override
+//    public ServiceBuilder<U> mock(Boolean mock) {
+//        throw new IllegalArgumentException("mock doesn't support on provider side");
+//    }
 
     public ServiceConfig<U> build() {
         ServiceConfig<U> serviceConfig = new ServiceConfig<>();
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/utils/ConfigValidationUtils.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/utils/ConfigValidationUtils.java
index ba8b9a7..2e46fa2 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/utils/ConfigValidationUtils.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/utils/ConfigValidationUtils.java
@@ -97,7 +97,6 @@ import static org.apache.dubbo.config.Constants.CONTEXTPATH_KEY;
 import static org.apache.dubbo.config.Constants.DUBBO_IP_TO_REGISTRY;
 import static org.apache.dubbo.config.Constants.ENVIRONMENT;
 import static org.apache.dubbo.config.Constants.LAYER_KEY;
-import static org.apache.dubbo.config.Constants.LISTENER_KEY;
 import static org.apache.dubbo.config.Constants.NAME;
 import static org.apache.dubbo.config.Constants.ORGANIZATION;
 import static org.apache.dubbo.config.Constants.OWNER;
@@ -297,7 +296,6 @@ public class ConfigValidationUtils {
         checkExtension(ProxyFactory.class, PROXY_KEY, config.getProxy());
         checkExtension(Cluster.class, CLUSTER_KEY, config.getCluster());
         checkMultiExtension(Filter.class, FILE_KEY, config.getFilter());
-        checkMultiExtension(InvokerListener.class, LISTENER_KEY, config.getListener());
         checkNameHasSymbol(LAYER_KEY, config.getLayer());
 
         List<MethodConfig> methods = config.getMethods();
diff --git a/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter
index d084137..1b843b6 100644
--- a/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter
+++ b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter
@@ -1,3 +1,3 @@
 # since 2.7.8
-default = org.apache.dubbo.config.metadata.ConfigurableMetadataServiceExporter
+local = org.apache.dubbo.config.metadata.ConfigurableMetadataServiceExporter
 remote = org.apache.dubbo.config.metadata.RemoteMetadataServiceExporter
\ No newline at end of file
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java
index c1f42e2..4d157ff 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java
@@ -239,21 +239,21 @@ public class ServiceConfigTest {
         });
     }
 
-    @Test
-    public void testMock() throws Exception {
-        Assertions.assertThrows(IllegalArgumentException.class, () -> {
-            ServiceConfig service = new ServiceConfig();
-            service.setMock("true");
-        });
-    }
-
-    @Test
-    public void testMock2() throws Exception {
-        Assertions.assertThrows(IllegalArgumentException.class, () -> {
-            ServiceConfig service = new ServiceConfig();
-            service.setMock(true);
-        });
-    }
+//    @Test
+//    public void testMock() throws Exception {
+//        Assertions.assertThrows(IllegalArgumentException.class, () -> {
+//            ServiceConfig service = new ServiceConfig();
+//            service.setMock("true");
+//        });
+//    }
+//
+//    @Test
+//    public void testMock2() throws Exception {
+//        Assertions.assertThrows(IllegalArgumentException.class, () -> {
+//            ServiceConfig service = new ServiceConfig();
+//            service.setMock(true);
+//        });
+//    }
 
     @Test
     public void testApplicationInUrl() {
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ReferenceBuilderTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ReferenceBuilderTest.java
index 210d92e..4d11ef4 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ReferenceBuilderTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ReferenceBuilderTest.java
@@ -26,6 +26,8 @@ import org.junit.jupiter.api.Test;
 
 import java.util.Collections;
 
+import static org.apache.dubbo.common.utils.CollectionUtils.ofSet;
+
 class ReferenceBuilderTest {
 
     @Test
@@ -95,8 +97,15 @@ class ReferenceBuilderTest {
         MethodConfig method = new MethodConfig();
 
         ReferenceBuilder<DemoService> builder = new ReferenceBuilder<>();
-        builder.id("id").interfaceClass(DemoService.class).protocol("protocol").client("client").url("url")
-                .consumer(consumer).addMethod(method);
+        builder.id("id")
+                .interfaceClass(DemoService.class)
+                .protocol("protocol")
+                .client("client")
+                .url("url")
+                .consumer(consumer)
+                .addMethod(method)
+                // introduced since 2.7.8
+                .services("test-service", "test-service2");
 
         ReferenceConfig config = builder.build();
         ReferenceConfig config2 = builder.build();
@@ -107,6 +116,8 @@ class ReferenceBuilderTest {
         Assertions.assertEquals("client", config.getClient());
         Assertions.assertEquals("url", config.getUrl());
         Assertions.assertEquals(consumer, config.getConsumer());
+        Assertions.assertEquals("test-service,test-service2", config.getServices());
+        Assertions.assertEquals(ofSet("test-service", "test-service2"), config.getSubscribedServices());
         Assertions.assertTrue(config.getMethods().contains(method));
         Assertions.assertEquals(1, config.getMethods().size());
         Assertions.assertNotSame(config, config2);
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/RegistryBuilderTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/RegistryBuilderTest.java
index 24c4a4d..b125547 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/RegistryBuilderTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/RegistryBuilderTest.java
@@ -220,7 +220,7 @@ class RegistryBuilderTest {
                 .transporter("transporter").server("server").client("client").cluster("cluster").group("group")
                 .version("version").timeout(1000).session(2000).file("file").wait(Integer.valueOf(10)).isCheck(true)
                 .isDynamic(false).register(true).subscribe(false).isDefault(true).simplified(false).extraKeys("A")
-                .appendParameter("default.num", "one").id("id").prefix("prefix");
+                .parameter("default.num", "one").id("id").prefix("prefix");
 
         RegistryConfig config = builder.build();
         RegistryConfig config2 = builder.build();
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ServiceBuilderTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ServiceBuilderTest.java
index 5b07c29..ae07c24 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ServiceBuilderTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ServiceBuilderTest.java
@@ -91,22 +91,22 @@ class ServiceBuilderTest {
             builder.generic("illegal").build();
         });
     }
-
-    @Test
-    public void Mock() throws Exception {
-        Assertions.assertThrows(IllegalArgumentException.class, () -> {
-            ServiceBuilder builder = new ServiceBuilder();
-            builder.mock("true");
-        });
-    }
-
-    @Test
-    public void Mock1() throws Exception {
-        Assertions.assertThrows(IllegalArgumentException.class, () -> {
-            ServiceBuilder builder = new ServiceBuilder();
-            builder.mock(true);
-        });
-    }
+//
+//    @Test
+//    public void Mock() throws Exception {
+//        Assertions.assertThrows(IllegalArgumentException.class, () -> {
+//            ServiceBuilder builder = new ServiceBuilder();
+//            builder.mock("true");
+//        });
+//    }
+//
+//    @Test
+//    public void Mock1() throws Exception {
+//        Assertions.assertThrows(IllegalArgumentException.class, () -> {
+//            ServiceBuilder builder = new ServiceBuilder();
+//            builder.mock(true);
+//        });
+//    }
 
     @Test
     void build() {
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java
new file mode 100644
index 0000000..67a562d
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.event.listener;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.RegistryConfig;
+import org.apache.dubbo.config.ServiceConfig;
+import org.apache.dubbo.config.bootstrap.EchoService;
+import org.apache.dubbo.config.bootstrap.EchoServiceImpl;
+import org.apache.dubbo.config.context.ConfigManager;
+import org.apache.dubbo.config.event.ServiceConfigExportedEvent;
+import org.apache.dubbo.metadata.WritableMetadataService;
+import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import com.google.gson.Gson;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.PID_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY;
+import static org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder.buildFullDefinition;
+import static org.apache.dubbo.remoting.Constants.BIND_IP_KEY;
+import static org.apache.dubbo.remoting.Constants.BIND_PORT_KEY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link PublishingServiceDefinitionListener} Test-Cases
+ *
+ * @since 2.7.8
+ */
+public class PublishingServiceDefinitionListenerTest {
+
+    private WritableMetadataService writableMetadataService;
+
+    @BeforeEach
+    public void init() {
+        String metadataType = DEFAULT_METADATA_STORAGE_TYPE;
+        ConfigManager configManager = ApplicationModel.getConfigManager();
+        ApplicationConfig applicationConfig = new ApplicationConfig("dubbo-demo-provider");
+        applicationConfig.setMetadataType(metadataType);
+        configManager.setApplication(applicationConfig);
+        this.writableMetadataService = WritableMetadataService.getExtension(metadataType);
+    }
+
+    @AfterEach
+    public void reset() {
+        ApplicationModel.reset();
+    }
+
+    /**
+     * Test {@link ServiceConfigExportedEvent} arising
+     */
+    @Test
+    public void testOnServiceConfigExportedEvent() {
+        ServiceConfig<EchoService> serviceConfig = new ServiceConfig<>();
+        serviceConfig.setInterface(EchoService.class);
+        serviceConfig.setRef(new EchoServiceImpl());
+        serviceConfig.setRegistry(new RegistryConfig("N/A"));
+        serviceConfig.export();
+
+        String serviceDefinition = writableMetadataService.getServiceDefinition(EchoService.class.getName());
+
+        List<URL> exportedUrls = serviceConfig.getExportedUrls();
+
+        FullServiceDefinition fullServiceDefinition = buildFullDefinition(
+                serviceConfig.getInterfaceClass(),
+                exportedUrls.get(0)
+                        .removeParameters(PID_KEY, TIMESTAMP_KEY, BIND_IP_KEY, BIND_PORT_KEY, TIMESTAMP_KEY)
+                        .getParameters()
+        );
+
+        assertEquals(serviceDefinition, new Gson().toJson(fullServiceDefinition));
+    }
+}
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/RemoteMetadataServiceExporterTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/RemoteMetadataServiceExporterTest.java
new file mode 100644
index 0000000..e4d13a1
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/RemoteMetadataServiceExporterTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.metadata;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.metadata.MetadataServiceExporter;
+import org.apache.dubbo.metadata.WritableMetadataService;
+import org.apache.dubbo.metadata.report.MetadataReportInstance;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.service.EchoService;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static java.util.Arrays.asList;
+import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.COMPOSITE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
+import static org.apache.dubbo.metadata.MetadataServiceExporter.getExtension;
+import static org.apache.dubbo.metadata.report.support.Constants.SYNC_REPORT_KEY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link RemoteMetadataServiceExporter} Test-Cases
+ *
+ * @since 2.7.8
+ */
+public class RemoteMetadataServiceExporterTest {
+
+    private static final URL METADATA_REPORT_URL = URL.valueOf("file://")
+            .addParameter(APPLICATION_KEY, "test")
+            .addParameter(SYNC_REPORT_KEY, "true");
+
+    private static final Class<EchoService> INTERFACE_CLASS = EchoService.class;
+
+    private static final String INTERFACE_NAME = INTERFACE_CLASS.getName();
+
+    private static final String APP_NAME = "test-service";
+
+    private static final URL BASE_URL = URL
+            .valueOf("dubbo://127.0.0.1:20880")
+            .setPath(INTERFACE_NAME)
+            .addParameter(APPLICATION_KEY, APP_NAME)
+            .addParameter(SIDE_KEY, "provider");
+
+    private final MetadataServiceExporter exporter = getExtension(REMOTE_METADATA_STORAGE_TYPE);
+
+    private WritableMetadataService writableMetadataService;
+
+    @BeforeEach
+    public void init() {
+        ApplicationModel.getConfigManager().setApplication(new ApplicationConfig(APP_NAME));
+        MetadataReportInstance.init(METADATA_REPORT_URL);
+        writableMetadataService = WritableMetadataService.getDefaultExtension();
+        writableMetadataService.exportURL(BASE_URL);
+    }
+
+    @AfterEach
+    public void reset() {
+        ApplicationModel.reset();
+    }
+
+    @Test
+    public void testType() {
+        assertEquals(RemoteMetadataServiceExporter.class, exporter.getClass());
+    }
+
+    @Test
+    public void testSupports() {
+        assertTrue(exporter.supports(REMOTE_METADATA_STORAGE_TYPE));
+        assertTrue(exporter.supports(COMPOSITE_METADATA_STORAGE_TYPE));
+        assertFalse(exporter.supports(DEFAULT_METADATA_STORAGE_TYPE));
+    }
+
+    @Test
+    public void testExportAndUnexport() {
+        assertFalse(exporter.isExported());
+        assertEquals(exporter, exporter.export());
+        assertTrue(exporter.isExported());
+
+        assertEquals(asList(BASE_URL), exporter.getExportedURLs());
+
+        assertEquals(exporter, exporter.unexport());
+        assertFalse(exporter.isExported());
+    }
+}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java
similarity index 53%
copy from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java
copy to dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java
index 487078e..8096276 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java
@@ -18,33 +18,25 @@ package org.apache.dubbo.metadata;
 
 import org.junit.jupiter.api.Test;
 
-import java.util.Arrays;
-import java.util.Collection;
-
-import static java.util.Arrays.asList;
-import static org.apache.dubbo.metadata.URLRevisionResolver.UNKNOWN_REVISION;
+import static org.apache.dubbo.common.constants.CommonConstants.COMPOSITE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
- * {@link URLRevisionResolver} Test
+ * {@link MetadataServiceExporter} Test-Cases
  *
- * @since 2.7.5
+ * @since 2.7.8
  */
-public class URLRevisionResolverTest {
-
-    private static final String URL = "dubbo://192.168.0.102:20881/org.apache.dubbo.metadata.URLRevisionResolverTest";
-
-    private final URLRevisionResolver resolver = new URLRevisionResolver();
+public class MetadataServiceExporterTest {
 
     @Test
-    public void testResolve() {
-        String revision = resolver.resolve(Arrays.<String>asList());
-        assertEquals(UNKNOWN_REVISION, revision);
-
-        revision = resolver.resolve((Collection<String>) null);
-        assertEquals(UNKNOWN_REVISION, revision);
-
-        revision = resolver.resolve(asList(URL));
-        assertEquals("2ca0638f189ce569", revision);
+    public void test() {
+        MetadataServiceExporter exporter = MetadataServiceExporter.getExtension(null);
+        assertEquals(exporter, MetadataServiceExporter.getDefaultExtension());
+        assertTrue(exporter.supports(DEFAULT_METADATA_STORAGE_TYPE));
+        assertTrue(exporter.supports(REMOTE_METADATA_STORAGE_TYPE));
+        assertTrue(exporter.supports(COMPOSITE_METADATA_STORAGE_TYPE));
     }
 }
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/extension/SpringExtensionFactory.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/extension/SpringExtensionFactory.java
index a35645d..c7b6e77 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/extension/SpringExtensionFactory.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/extension/SpringExtensionFactory.java
@@ -72,7 +72,7 @@ public class SpringExtensionFactory implements ExtensionFactory {
             }
         }
 
-        logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());
+        //logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());
 
         return null;
     }
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceBeanBuilderTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceBeanBuilderTest.java
index a86e5a9..60837e2 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceBeanBuilderTest.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceBeanBuilderTest.java
@@ -17,11 +17,11 @@
 package org.apache.dubbo.config.spring.beans.factory.annotation;
 
 
+import org.apache.dubbo.config.annotation.DubboReference;
 import org.apache.dubbo.config.annotation.Reference;
 import org.apache.dubbo.config.spring.ReferenceBean;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 
-import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -29,6 +29,8 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
@@ -36,6 +38,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
+import static org.apache.dubbo.common.utils.CollectionUtils.ofSet;
 import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;
 import static org.springframework.util.ReflectionUtils.findField;
 
@@ -43,6 +46,7 @@ import static org.springframework.util.ReflectionUtils.findField;
  * {@link ReferenceBeanBuilder} Test
  *
  * @see ReferenceBeanBuilder
+ * @see DubboReference
  * @see Reference
  * @since 2.6.4
  */
@@ -50,17 +54,7 @@ import static org.springframework.util.ReflectionUtils.findField;
 @ContextConfiguration(classes = ReferenceBeanBuilderTest.class)
 public class ReferenceBeanBuilderTest {
 
-    @Before
-    public void setUp() {
-        ApplicationModel.reset();
-    }
-
-    @After
-    public void tearDown() {
-        ApplicationModel.reset();
-    }
-
-    @Reference(
+    @DubboReference(
             interfaceClass = CharSequence.class,
             interfaceName = "java.lang.CharSequence",
             version = "1.0.0", group = "TEST_GROUP", url = "dubbo://localhost:12345",
@@ -75,7 +69,11 @@ public class ReferenceBeanBuilderTest {
             timeout = 3, cache = "cache", filter = {"echo", "generic", "accesslog"},
             listener = {"deprecated"}, parameters = {"n1=v1  ", "n2 = v2 ", "  n3 =   v3  "},
             application = "application",
-            module = "module", consumer = "consumer", monitor = "monitor", registry = {"registry"}
+            module = "module", consumer = "consumer", monitor = "monitor", registry = {"registry"},
+            // @since 2.7.3
+            id = "reference",
+            // @since 2.7.8
+            services = {"service1", "service2", "service3", "service2", "service1"}
     )
     private static final Object TEST_FIELD = new Object();
 
@@ -89,8 +87,9 @@ public class ReferenceBeanBuilderTest {
 
     @Test
     public void testBuild() throws Exception {
-        Reference reference = findAnnotation(findField(getClass(), "TEST_FIELD"), Reference.class);
-        ReferenceBeanBuilder beanBuilder = ReferenceBeanBuilder.create(reference, context.getClassLoader(), context);
+        DubboReference reference = findAnnotation(findField(getClass(), "TEST_FIELD"), DubboReference.class);
+        AnnotationAttributes attributes = AnnotationUtils.getAnnotationAttributes(reference, false, false);
+        ReferenceBeanBuilder beanBuilder = ReferenceBeanBuilder.create(attributes, context);
         beanBuilder.interfaceClass(CharSequence.class);
         ReferenceBean referenceBean = beanBuilder.build();
         Assert.assertEquals(CharSequence.class, referenceBean.getInterfaceClass());
@@ -99,7 +98,7 @@ public class ReferenceBeanBuilderTest {
         Assert.assertEquals("dubbo://localhost:12345", referenceBean.getUrl());
         Assert.assertEquals("client", referenceBean.getClient());
         Assert.assertEquals(true, referenceBean.isGeneric());
-        Assert.assertNull(referenceBean.isInjvm());
+        Assert.assertTrue(referenceBean.isInjvm());
         Assert.assertEquals(false, referenceBean.isCheck());
         Assert.assertFalse(referenceBean.isInit());
         Assert.assertEquals(true, referenceBean.getLazy());
@@ -126,6 +125,8 @@ public class ReferenceBeanBuilderTest {
         Assert.assertEquals("cache", referenceBean.getCache());
         Assert.assertEquals("echo,generic,accesslog", referenceBean.getFilter());
         Assert.assertEquals("deprecated", referenceBean.getListener());
+        Assert.assertEquals("reference", referenceBean.getId());
+        Assert.assertEquals(ofSet("service1", "service2", "service3"), referenceBean.getSubscribedServices());
 
         // parameters
         Map<String, String> parameters = new HashMap<String, String>();
diff --git a/dubbo-filter/dubbo-filter-validation/src/main/java/org/apache/dubbo/validation/support/jvalidation/JValidator.java b/dubbo-filter/dubbo-filter-validation/src/main/java/org/apache/dubbo/validation/support/jvalidation/JValidator.java
index 8bfe7b2..e3978c6 100644
--- a/dubbo-filter/dubbo-filter-validation/src/main/java/org/apache/dubbo/validation/support/jvalidation/JValidator.java
+++ b/dubbo-filter/dubbo-filter-validation/src/main/java/org/apache/dubbo/validation/support/jvalidation/JValidator.java
@@ -186,7 +186,7 @@ public class JValidator implements Validator {
     private static String generateMethodParameterClassName(Class<?> clazz, Method method) {
         StringBuilder builder = new StringBuilder().append(clazz.getName())
                 .append("_")
-                .append(toUpperMethoName(method.getName()))
+                .append(toUpperMethodName(method.getName()))
                 .append("Parameter");
 
         Class<?>[] parameterTypes = method.getParameterTypes();
@@ -211,7 +211,7 @@ public class JValidator implements Validator {
         return false;
     }
 
-    private static String toUpperMethoName(String methodName) {
+    private static String toUpperMethodName(String methodName) {
         return methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
     }
 
@@ -292,7 +292,7 @@ public class JValidator implements Validator {
 
     private Class methodClass(String methodName) {
         Class<?> methodClass = null;
-        String methodClassName = clazz.getName() + "$" + toUpperMethoName(methodName);
+        String methodClassName = clazz.getName() + "$" + toUpperMethodName(methodName);
         Class cached = methodClassMap.get(methodClassName);
         if (cached != null) {
             return cached == clazz ? null : cached;
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/CompositeServiceNameMapping.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/CompositeServiceNameMapping.java
index 00ab4cc..9ad130b 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/CompositeServiceNameMapping.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/CompositeServiceNameMapping.java
@@ -45,17 +45,10 @@ public class CompositeServiceNameMapping implements ServiceNameMapping {
         if (this.serviceNameMappings == null) {
             synchronized (this) {
                 if (this.serviceNameMappings == null) {
-                    Set<ServiceNameMapping> serviceNameMappings = getExtensionLoader(ServiceNameMapping.class)
-                            .getSupportedExtensionInstances();
+                    Set<ServiceNameMapping> serviceNameMappings = loadAllServiceNameMappings();
 
-                    Iterator<ServiceNameMapping> iterator = serviceNameMappings.iterator();
+                    removeSelf(serviceNameMappings);
 
-                    while (iterator.hasNext()) {
-                        ServiceNameMapping serviceNameMapping = iterator.next();
-                        if (this.getClass().equals(serviceNameMapping.getClass())) {
-                            iterator.remove(); // Exclude self
-                        }
-                    }
                     this.serviceNameMappings = new LinkedList<>(serviceNameMappings);
                 }
             }
@@ -63,6 +56,20 @@ public class CompositeServiceNameMapping implements ServiceNameMapping {
         return this.serviceNameMappings;
     }
 
+    private Set<ServiceNameMapping> loadAllServiceNameMappings() {
+        return getExtensionLoader(ServiceNameMapping.class).getSupportedExtensionInstances();
+    }
+
+    private void removeSelf(Set<ServiceNameMapping> serviceNameMappings) {
+        Iterator<ServiceNameMapping> iterator = serviceNameMappings.iterator();
+        while (iterator.hasNext()) {
+            ServiceNameMapping serviceNameMapping = iterator.next();
+            if (this.getClass().equals(serviceNameMapping.getClass())) {
+                iterator.remove(); // Remove self
+            }
+        }
+    }
+
     @Override
     public void map(URL exportedURL) {
         List<ServiceNameMapping> serviceNameMappings = getServiceNameMappings();
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilder.java
index d1275e7..fe99d2e 100755
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilder.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilder.java
@@ -28,6 +28,7 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import static org.apache.dubbo.common.utils.ClassUtils.isSimpleType;
 
@@ -36,15 +37,12 @@ import static org.apache.dubbo.common.utils.ClassUtils.isSimpleType;
  */
 public class TypeDefinitionBuilder {
     private static final Logger logger = LoggerFactory.getLogger(TypeDefinitionBuilder.class);
-    private static final List<TypeBuilder> BUILDERS;
+    static final List<TypeBuilder> BUILDERS;
 
     static {
-        List<TypeBuilder> builders = new ArrayList<>();
         ExtensionLoader<TypeBuilder> extensionLoader = ExtensionLoader.getExtensionLoader(TypeBuilder.class);
-        for (String extensionName : extensionLoader.getSupportedExtensions()) {
-            builders.add(extensionLoader.getExtension(extensionName));
-        }
-        BUILDERS = builders;
+        Set<TypeBuilder> tbs = extensionLoader.getSupportedExtensionInstances();
+        BUILDERS = new ArrayList<>(tbs);
     }
 
     public static TypeDefinition build(Type type, Class<?> clazz, Map<Class<?>, TypeDefinition> typeCache) {
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
index d7022bd..57673fe 100755
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
@@ -17,6 +17,7 @@
 package org.apache.dubbo.metadata.definition.builder;
 
 import org.apache.dubbo.common.extension.SPI;
+import org.apache.dubbo.common.lang.Prioritized;
 import org.apache.dubbo.metadata.definition.model.TypeDefinition;
 
 import java.lang.reflect.Type;
@@ -26,7 +27,7 @@ import java.util.Map;
  * 2015/1/27.
  */
 @SPI
-public interface TypeBuilder {
+public interface TypeBuilder extends Prioritized {
 
     /**
      * Whether the build accept the type or class passed in.
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReport.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReport.java
index cc254b1..068d3a0 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReport.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReport.java
@@ -27,9 +27,9 @@ import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
 
 import com.google.gson.Gson;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
 
@@ -38,9 +38,11 @@ import static java.util.Collections.emptySortedSet;
 import static org.apache.dubbo.rpc.model.ApplicationModel.getName;
 
 /**
+ * The interface to report the metadata
  *
+ * @see AutoCloseable since 2.7.8
  */
-public interface MetadataReport {
+public interface MetadataReport extends AutoCloseable {
 
     void storeProviderMetadata(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition);
 
@@ -69,9 +71,9 @@ public interface MetadataReport {
         return emptyList();
     }
 
-    void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Set<String> urls);
+    void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Collection<String> urls);
 
-    List<String> getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier);
+    Collection<String> getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier);
 
     String getServiceDefinition(MetadataIdentifier metadataIdentifier);
 
@@ -154,4 +156,5 @@ public interface MetadataReport {
     default String getExportedURLsContent(String serviceName, String exportedServicesRevision) {
         return null;
     }
-}
+
+}
\ No newline at end of file
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/BaseApplicationMetadataIdentifier.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/BaseApplicationMetadataIdentifier.java
index 9e6b76b..f70678b 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/BaseApplicationMetadataIdentifier.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/BaseApplicationMetadataIdentifier.java
@@ -17,6 +17,7 @@
 package org.apache.dubbo.metadata.report.identifier;
 
 import static org.apache.dubbo.common.constants.CommonConstants.PATH_SEPARATOR;
+import static org.apache.dubbo.common.utils.PathUtils.buildPath;
 import static org.apache.dubbo.metadata.MetadataConstants.DEFAULT_PATH_TAG;
 import static org.apache.dubbo.metadata.MetadataConstants.KEY_SEPARATOR;
 
@@ -36,9 +37,7 @@ public class BaseApplicationMetadataIdentifier {
     }
 
     String getIdentifierKey(String... params) {
-
-        return application
-                + joinParams(KEY_SEPARATOR, params);
+        return application + joinParams(KEY_SEPARATOR, params);
     }
 
     private String joinParams(String joinChar, String... params) {
@@ -58,9 +57,7 @@ public class BaseApplicationMetadataIdentifier {
     }
 
     private String getFilePathKey(String pathTag, String... params) {
-        return pathTag
-                + application
-                + joinParams(PATH_SEPARATOR, params);
+        return buildPath(pathTag, application, joinParams(PATH_SEPARATOR, params));
     }
 
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java
index 1d39401..f3b90db 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java
@@ -44,6 +44,7 @@ import java.nio.channels.FileChannel;
 import java.nio.channels.FileLock;
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -52,7 +53,6 @@ import java.util.Set;
 import java.util.SortedSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ThreadLocalRandom;
@@ -61,11 +61,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
+import static java.util.concurrent.Executors.newScheduledThreadPool;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;
 import static org.apache.dubbo.common.constants.CommonConstants.FILE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
 import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
+import static org.apache.dubbo.common.utils.StringUtils.replace;
 import static org.apache.dubbo.metadata.report.support.Constants.CYCLE_REPORT_KEY;
 import static org.apache.dubbo.metadata.report.support.Constants.DEFAULT_METADATA_REPORT_CYCLE_REPORT;
 import static org.apache.dubbo.metadata.report.support.Constants.DEFAULT_METADATA_REPORT_RETRY_PERIOD;
@@ -86,24 +90,52 @@ public abstract class AbstractMetadataReport implements MetadataReport {
     // Log output
     protected final Logger logger = LoggerFactory.getLogger(getClass());
 
-    // Local disk cache, where the special key value.registries records the list of metadata centers, and the others are the list of notified service providers
-    final Properties properties = new Properties();
-    private final ExecutorService reportCacheExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("DubboSaveMetadataReport", true));
+    private final AtomicBoolean initialized = new AtomicBoolean(false);
+
     final Map<MetadataIdentifier, Object> allMetadataReports = new ConcurrentHashMap<>(4);
 
-    private final AtomicLong lastCacheChanged = new AtomicLong();
     final Map<MetadataIdentifier, Object> failedReports = new ConcurrentHashMap<>(4);
+
     private URL reportURL;
     boolean syncReport;
+
     // Local disk cache file
-    File file;
-    private AtomicBoolean initialized = new AtomicBoolean(false);
-    public MetadataReportRetry metadataReportRetry;
+    File localCacheFile;
+    // Local disk cache, where the special key value.registries records the list of metadata centers, and the others are the list of notified service providers
+    final Properties properties = new Properties();
+
+    private final AtomicLong lastCacheChanged = new AtomicLong();
+
+    // ThreadPoolExecutors
+    private final ExecutorService reportCacheExecutor;
+
+    public final MetadataReportRetry metadataReportRetry;
+
+    private final ScheduledExecutorService cycleReportExecutor;
 
     public AbstractMetadataReport(URL reportServerURL) {
         setUrl(reportServerURL);
+
+        this.localCacheFile = initializeLocalCacheFile(reportServerURL);
+        loadProperties();
+        syncReport = reportServerURL.getParameter(SYNC_REPORT_KEY, false);
+        metadataReportRetry = new MetadataReportRetry(reportServerURL.getParameter(RETRY_TIMES_KEY, DEFAULT_METADATA_REPORT_RETRY_TIMES),
+                reportServerURL.getParameter(RETRY_PERIOD_KEY, DEFAULT_METADATA_REPORT_RETRY_PERIOD));
+        this.reportCacheExecutor = newSingleThreadExecutor(new NamedThreadFactory("DubboSaveMetadataReport", true));
+        this.cycleReportExecutor = newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboMetadataReportTimer", true));
+        // cycle report the data switch
+        if (reportServerURL.getParameter(CYCLE_REPORT_KEY, DEFAULT_METADATA_REPORT_CYCLE_REPORT)) {
+            cycleReportExecutor.scheduleAtFixedRate(this::publishAll, calculateStartTime(), ONE_DAY_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    private File initializeLocalCacheFile(URL reportServerURL) {
         // Start file save timer
-        String defaultFilename = System.getProperty("user.home") + "/.dubbo/dubbo-metadata-" + reportServerURL.getParameter(APPLICATION_KEY) + "-" + reportServerURL.getAddress().replaceAll(":", "-") + ".cache";
+        String defaultFilename = System.getProperty("user.home") +
+                "/.dubbo/dubbo-metadata-" +
+                reportServerURL.getParameter(APPLICATION_KEY) + "-" +
+                replace(reportServerURL.getAddress(), ":", "-") +
+                ".cache";
         String filename = reportServerURL.getParameter(FILE_KEY, defaultFilename);
         File file = null;
         if (ConfigUtils.isNotEmpty(filename)) {
@@ -118,16 +150,7 @@ public abstract class AbstractMetadataReport implements MetadataReport {
                 file.delete();
             }
         }
-        this.file = file;
-        loadProperties();
-        syncReport = reportServerURL.getParameter(SYNC_REPORT_KEY, false);
-        metadataReportRetry = new MetadataReportRetry(reportServerURL.getParameter(RETRY_TIMES_KEY, DEFAULT_METADATA_REPORT_RETRY_TIMES),
-                reportServerURL.getParameter(RETRY_PERIOD_KEY, DEFAULT_METADATA_REPORT_RETRY_PERIOD));
-        // cycle report the data switch
-        if (reportServerURL.getParameter(CYCLE_REPORT_KEY, DEFAULT_METADATA_REPORT_CYCLE_REPORT)) {
-            ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboMetadataReportTimer", true));
-            scheduler.scheduleAtFixedRate(this::publishAll, calculateStartTime(), ONE_DAY_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
-        }
+        return file;
     }
 
     public URL getUrl() {
@@ -145,12 +168,12 @@ public abstract class AbstractMetadataReport implements MetadataReport {
         if (version < lastCacheChanged.get()) {
             return;
         }
-        if (file == null) {
+        if (localCacheFile == null) {
             return;
         }
         // Save
         try {
-            File lockfile = new File(file.getAbsolutePath() + ".lock");
+            File lockfile = new File(localCacheFile.getAbsolutePath() + ".lock");
             if (!lockfile.exists()) {
                 lockfile.createNewFile();
             }
@@ -158,14 +181,14 @@ public abstract class AbstractMetadataReport implements MetadataReport {
                  FileChannel channel = raf.getChannel()) {
                 FileLock lock = channel.tryLock();
                 if (lock == null) {
-                    throw new IOException("Can not lock the metadataReport cache file " + file.getAbsolutePath() + ", ignore and retry later, maybe multi java process use the file, please config: dubbo.metadata.file=xxx.properties");
+                    throw new IOException("Can not lock the metadataReport cache file " + localCacheFile.getAbsolutePath() + ", ignore and retry later, maybe multi java process use the file, please config: dubbo.metadata.file=xxx.properties");
                 }
                 // Save
                 try {
-                    if (!file.exists()) {
-                        file.createNewFile();
+                    if (!localCacheFile.exists()) {
+                        localCacheFile.createNewFile();
                     }
-                    try (FileOutputStream outputFile = new FileOutputStream(file)) {
+                    try (FileOutputStream outputFile = new FileOutputStream(localCacheFile)) {
                         properties.store(outputFile, "Dubbo metadataReport Cache");
                     }
                 } finally {
@@ -183,20 +206,20 @@ public abstract class AbstractMetadataReport implements MetadataReport {
     }
 
     void loadProperties() {
-        if (file != null && file.exists()) {
-            try (InputStream in = new FileInputStream(file)) {
+        if (localCacheFile != null && localCacheFile.exists()) {
+            try (InputStream in = new FileInputStream(localCacheFile)) {
                 properties.load(in);
                 if (logger.isInfoEnabled()) {
-                    logger.info("Load service store file " + file + ", data: " + properties);
+                    logger.info("Load service store file " + localCacheFile + ", data: " + properties);
                 }
             } catch (Throwable e) {
-                logger.warn("Failed to load service store file " + file, e);
+                logger.warn("Failed to load service store file " + localCacheFile, e);
             }
         }
     }
 
     private void saveProperties(MetadataIdentifier metadataIdentifier, String value, boolean add, boolean sync) {
-        if (file == null) {
+        if (localCacheFile == null) {
             return;
         }
 
@@ -318,7 +341,7 @@ public abstract class AbstractMetadataReport implements MetadataReport {
     }
 
     @Override
-    public void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Set<String> urls) {
+    public void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Collection<String> urls) {
         if (syncReport) {
             doSaveSubscriberData(subscriberMetadataIdentifier, new Gson().toJson(urls));
         } else {
@@ -328,7 +351,7 @@ public abstract class AbstractMetadataReport implements MetadataReport {
 
 
     @Override
-    public List<String> getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier) {
+    public Set<String> getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier) {
         String content = doGetSubscribedURLs(subscriberMetadataIdentifier);
         Type setType = new TypeToken<SortedSet<String>>() {
         }.getType();
@@ -392,7 +415,7 @@ public abstract class AbstractMetadataReport implements MetadataReport {
     class MetadataReportRetry {
         protected final Logger logger = LoggerFactory.getLogger(getClass());
 
-        final ScheduledExecutorService retryExecutor = Executors.newScheduledThreadPool(0, new NamedThreadFactory("DubboMetadataReportRetryTimer", true));
+        final ScheduledExecutorService retryExecutor = newScheduledThreadPool(0, new NamedThreadFactory("DubboMetadataReportRetryTimer", true));
         volatile ScheduledFuture retryScheduledFuture;
         final AtomicInteger retryCounter = new AtomicInteger(0);
         // retry task schedule period
@@ -435,8 +458,10 @@ public abstract class AbstractMetadataReport implements MetadataReport {
         }
 
         void cancelRetryTask() {
-            retryScheduledFuture.cancel(false);
-            retryExecutor.shutdown();
+            if (retryScheduledFuture != null) {
+                retryScheduledFuture.cancel(false);
+            }
+            shutdown(retryExecutor);
         }
     }
 
@@ -451,6 +476,13 @@ public abstract class AbstractMetadataReport implements MetadataReport {
         doSaveSubscriberData(subscriberMetadataIdentifier, encodedUrlList);
     }
 
+    @Override
+    public final void close() throws Exception {
+        this.shutdownThreadPoolExecutors();
+        this.clearCache();
+        doClose();
+    }
+
     protected abstract void doStoreProviderMetadata(MetadataIdentifier providerMetadataIdentifier, String serviceDefinitions);
 
     protected abstract void doStoreConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, String serviceParameterString);
@@ -465,4 +497,35 @@ public abstract class AbstractMetadataReport implements MetadataReport {
 
     protected abstract String doGetSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier);
 
+    /**
+     * Close other resources
+     *
+     * @since 2.7.8
+     */
+    protected void doClose() throws Exception {
+
+    }
+
+    private void clearCache() {
+        this.properties.clear();
+        this.allMetadataReports.clear();
+        this.failedReports.clear();
+        this.localCacheFile.delete();
+    }
+
+    private void shutdownThreadPoolExecutors() {
+        this.metadataReportRetry.cancelRetryTask();
+        shutdown(this.reportCacheExecutor);
+        shutdown(cycleReportExecutor);
+    }
+
+    private static void shutdown(ExecutorService executorService) {
+        if (executorService == null) {
+            return;
+        }
+        if (!executorService.isShutdown()) {
+            executorService.shutdown();
+        }
+    }
+
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReport.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReport.java
index 6111fb1..d962ca5 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReport.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReport.java
@@ -123,6 +123,11 @@ public class ConfigCenterBasedMetadataReport extends AbstractMetadataReport {
         dynamicConfiguration.publishConfig(key, group, value);
     }
 
+    protected String getMetadata(ServiceMetadataIdentifier metadataIdentifier) {
+        String key = getKey(metadataIdentifier);
+        return dynamicConfiguration.getConfig(key, group);
+    }
+
     protected String getMetadata(MetadataIdentifier metadataIdentifier) {
         String key = getKey(metadataIdentifier);
         return dynamicConfiguration.getConfig(key, group);
@@ -150,4 +155,8 @@ public class ConfigCenterBasedMetadataReport extends AbstractMetadataReport {
     protected String getKey(MetadataIdentifier metadataIdentifier) {
         return metadataIdentifier.getUniqueKey(keyType);
     }
+
+    protected void doClose() throws Exception {
+        this.dynamicConfiguration.close();
+    }
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReportFactory.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReportFactory.java
index 4ca3a7e..5b0f780 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReportFactory.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReportFactory.java
@@ -47,7 +47,7 @@ public abstract class ConfigCenterBasedMetadataReportFactory implements Metadata
     private static final String URL_PATH = MetadataReport.class.getName();
 
     // Registry Collection Map<metadataAddress, MetadataReport>
-    private static final Map<String, MetadataReport> metadataReportCache = new ConcurrentHashMap<String, MetadataReport>();
+    private static final Map<String, ConfigCenterBasedMetadataReport> metadataReportCache = new ConcurrentHashMap();
 
     private final KeyTypeEnum keyType;
 
@@ -59,7 +59,7 @@ public abstract class ConfigCenterBasedMetadataReportFactory implements Metadata
     }
 
     @Override
-    public MetadataReport getMetadataReport(URL url) {
+    public ConfigCenterBasedMetadataReport getMetadataReport(URL url) {
 
         url = url.setPath(URL_PATH).removeParameters(EXPORT_KEY, REFER_KEY);
 
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/file/FileSystemMetadataReportFactory.java
old mode 100755
new mode 100644
similarity index 59%
copy from dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
copy to dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/file/FileSystemMetadataReportFactory.java
index d7022bd..c4cc487
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/file/FileSystemMetadataReportFactory.java
@@ -14,28 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.metadata.definition.builder;
+package org.apache.dubbo.metadata.report.support.file;
 
-import org.apache.dubbo.common.extension.SPI;
-import org.apache.dubbo.metadata.definition.model.TypeDefinition;
-
-import java.lang.reflect.Type;
-import java.util.Map;
+import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum;
+import org.apache.dubbo.metadata.report.support.ConfigCenterBasedMetadataReportFactory;
 
 /**
- * 2015/1/27.
+ * The implementation of {@link ConfigCenterBasedMetadataReportFactory} based on File System
+ *
+ * @see ConfigCenterBasedMetadataReportFactory
+ * @since 2.7.8
  */
-@SPI
-public interface TypeBuilder {
-
-    /**
-     * Whether the build accept the type or class passed in.
-     */
-    boolean accept(Type type, Class<?> clazz);
-
-    /**
-     * Build type definition with the type or class.
-     */
-    TypeDefinition build(Type type, Class<?> clazz, Map<Class<?>, TypeDefinition> typeCache);
+public class FileSystemMetadataReportFactory extends ConfigCenterBasedMetadataReportFactory {
 
+    public FileSystemMetadataReportFactory() {
+        super(KeyTypeEnum.PATH);
+    }
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory b/dubbo-metadata/dubbo-metadata-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory
new file mode 100644
index 0000000..b686fce
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory
@@ -0,0 +1 @@
+file = org.apache.dubbo.metadata.report.support.file.FileSystemMetadataReportFactory
\ No newline at end of file
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/CompositeServiceNameMappingTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/CompositeServiceNameMappingTest.java
new file mode 100644
index 0000000..48243f8
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/CompositeServiceNameMappingTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.metadata;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.config.configcenter.file.FileSystemDynamicConfiguration;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.service.EchoService;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+
+import static java.util.Collections.singleton;
+import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
+import static org.apache.dubbo.common.constants.RegistryConstants.SUBSCRIBED_SERVICE_NAMES_KEY;
+import static org.apache.dubbo.common.utils.CollectionUtils.ofSet;
+import static org.apache.dubbo.metadata.DynamicConfigurationServiceNameMapping.buildGroup;
+import static org.apache.dubbo.rpc.model.ApplicationModel.getApplicationConfig;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * {@link CompositeServiceNameMapping} Test
+ *
+ * @since 2.7.8
+ */
+public class CompositeServiceNameMappingTest {
+
+    private static final URL BASE_URL = URL.valueOf("dubbo://127.0.0.1:20880")
+            .setPath(EchoService.class.getName())
+            .addParameter(GROUP_KEY, "default")
+            .addParameter(VERSION_KEY, "1.0.0");
+
+    private static final String APP_NAME = "test-service";
+
+    private ServiceNameMapping serviceNameMapping;
+
+    private FileSystemDynamicConfiguration dynamicConfiguration;
+
+    @BeforeEach
+    public void init() {
+        serviceNameMapping = ServiceNameMapping.getDefaultExtension();
+        dynamicConfiguration = new FileSystemDynamicConfiguration();
+        ApplicationModel.getConfigManager().setApplication(new ApplicationConfig(APP_NAME));
+        ApplicationModel.getEnvironment().setDynamicConfiguration(dynamicConfiguration);
+    }
+
+    @AfterEach
+    public void reset() {
+        FileUtils.deleteQuietly(dynamicConfiguration.getRootDirectory());
+        ApplicationModel.reset();
+    }
+
+    @Test
+    public void testType() {
+        assertEquals(CompositeServiceNameMapping.class, serviceNameMapping.getClass());
+    }
+
+    @Test
+    public void testMap() {
+        serviceNameMapping.map(BASE_URL);
+        assertNotNull(dynamicConfiguration.getConfig(APP_NAME,
+                buildGroup(BASE_URL.getServiceInterface(), null, null, null)));
+    }
+
+    @Test
+    public void testGet() {
+        serviceNameMapping.map(BASE_URL);
+        Set<String> serviceNames = serviceNameMapping.get(BASE_URL);
+        assertEquals(singleton(APP_NAME), serviceNames);
+
+        getApplicationConfig().setName("service1");
+        serviceNameMapping.map(BASE_URL);
+        serviceNames = serviceNameMapping.get(BASE_URL);
+        assertEquals(ofSet(APP_NAME, "service1"), serviceNames);
+
+        serviceNames = serviceNameMapping.get(BASE_URL
+                .setPath("com.acme.Interface1")
+                .removeParameter(VERSION_KEY)
+        );
+        assertEquals(singleton("Service1"), serviceNames);
+
+        serviceNames = serviceNameMapping.get(BASE_URL.addParameter(SUBSCRIBED_SERVICE_NAMES_KEY, "s1 , s2 , s3 "));
+        assertEquals(ofSet("s1", "s2", "s3"), serviceNames);
+    }
+}
+
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/MetadataConstantsTest.java
old mode 100755
new mode 100644
similarity index 59%
copy from dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
copy to dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/MetadataConstantsTest.java
index d7022bd..d0c201b
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/MetadataConstantsTest.java
@@ -14,28 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.metadata.definition.builder;
+package org.apache.dubbo.metadata;
 
-import org.apache.dubbo.common.extension.SPI;
-import org.apache.dubbo.metadata.definition.model.TypeDefinition;
+import org.junit.jupiter.api.Test;
 
-import java.lang.reflect.Type;
-import java.util.Map;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 /**
- * 2015/1/27.
+ * {@link MetadataConstants} Test-Cases
+ *
+ * @since 2.7.8
  */
-@SPI
-public interface TypeBuilder {
-
-    /**
-     * Whether the build accept the type or class passed in.
-     */
-    boolean accept(Type type, Class<?> clazz);
-
-    /**
-     * Build type definition with the type or class.
-     */
-    TypeDefinition build(Type type, Class<?> clazz, Map<Class<?>, TypeDefinition> typeCache);
+public class MetadataConstantsTest {
 
+    @Test
+    public void testConstants() {
+        assertEquals("exported-urls", MetadataConstants.EXPORTED_URLS_TAG);
+        assertEquals("subscribed-urls", MetadataConstants.SUBSCRIBED_URLS_TAG);
+    }
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceTypeTest.java
similarity index 53%
copy from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java
copy to dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceTypeTest.java
index 487078e..11eec1d 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceTypeTest.java
@@ -18,33 +18,31 @@ package org.apache.dubbo.metadata;
 
 import org.junit.jupiter.api.Test;
 
-import java.util.Arrays;
-import java.util.Collection;
-
-import static java.util.Arrays.asList;
-import static org.apache.dubbo.metadata.URLRevisionResolver.UNKNOWN_REVISION;
+import static org.apache.dubbo.metadata.MetadataServiceType.COMPOSITE;
+import static org.apache.dubbo.metadata.MetadataServiceType.DEFAULT;
+import static org.apache.dubbo.metadata.MetadataServiceType.REMOTE;
+import static org.apache.dubbo.metadata.MetadataServiceType.getOrDefault;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 /**
- * {@link URLRevisionResolver} Test
+ * {@link MetadataServiceType} Test-Cases
  *
- * @since 2.7.5
+ * @since 2.7.8
  */
-public class URLRevisionResolverTest {
-
-    private static final String URL = "dubbo://192.168.0.102:20881/org.apache.dubbo.metadata.URLRevisionResolverTest";
-
-    private final URLRevisionResolver resolver = new URLRevisionResolver();
+public class MetadataServiceTypeTest {
 
     @Test
-    public void testResolve() {
-        String revision = resolver.resolve(Arrays.<String>asList());
-        assertEquals(UNKNOWN_REVISION, revision);
-
-        revision = resolver.resolve((Collection<String>) null);
-        assertEquals(UNKNOWN_REVISION, revision);
+    public void testGetValue() {
+        assertEquals("local", DEFAULT.getValue());
+        assertEquals("remote", REMOTE.getValue());
+        assertEquals("composite", COMPOSITE.getValue());
+    }
 
-        revision = resolver.resolve(asList(URL));
-        assertEquals("2ca0638f189ce569", revision);
+    @Test
+    public void testGetOrDefault() {
+        assertEquals(DEFAULT, getOrDefault("local"));
+        assertEquals(REMOTE, getOrDefault("remote"));
+        assertEquals(COMPOSITE, getOrDefault("composite"));
+        assertEquals(DEFAULT, getOrDefault("others"));
     }
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java
index 487078e..b36ebcb 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java
@@ -34,7 +34,7 @@ public class URLRevisionResolverTest {
 
     private static final String URL = "dubbo://192.168.0.102:20881/org.apache.dubbo.metadata.URLRevisionResolverTest";
 
-    private final URLRevisionResolver resolver = new URLRevisionResolver();
+    private final URLRevisionResolver resolver = URLRevisionResolver.INSTANCE;
 
     @Test
     public void testResolve() {
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/Test3TypeBuilder.java
old mode 100755
new mode 100644
similarity index 63%
copy from dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
copy to dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/Test3TypeBuilder.java
index d7022bd..075a69e
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/Test3TypeBuilder.java
@@ -14,28 +14,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.metadata.definition.builder;
+package org.apache.dubbo.metadata.definition;
 
-import org.apache.dubbo.common.extension.SPI;
+import org.apache.dubbo.metadata.definition.builder.TypeBuilder;
 import org.apache.dubbo.metadata.definition.model.TypeDefinition;
 
 import java.lang.reflect.Type;
 import java.util.Map;
 
 /**
- * 2015/1/27.
+ * test for sort
  */
-@SPI
-public interface TypeBuilder {
+public class Test3TypeBuilder implements TypeBuilder {
+    // it is smaller than the implements of TypeBuilder
+    public int getPriority(){
+        return 10;
+    }
 
-    /**
-     * Whether the build accept the type or class passed in.
-     */
-    boolean accept(Type type, Class<?> clazz);
-
-    /**
-     * Build type definition with the type or class.
-     */
-    TypeDefinition build(Type type, Class<?> clazz, Map<Class<?>, TypeDefinition> typeCache);
+    @Override
+    public boolean accept(Type type, Class<?> clazz) {
+        return false;
+    }
 
+    @Override
+    public TypeDefinition build(Type type, Class<?> clazz, Map<Class<?>, TypeDefinition> typeCache) {
+        return null;
+    }
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/TestTypeBuilder.java
old mode 100755
new mode 100644
similarity index 63%
copy from dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
copy to dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/TestTypeBuilder.java
index d7022bd..bf7e9c6
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/TestTypeBuilder.java
@@ -14,28 +14,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.metadata.definition.builder;
+package org.apache.dubbo.metadata.definition;
 
-import org.apache.dubbo.common.extension.SPI;
+import org.apache.dubbo.metadata.definition.builder.TypeBuilder;
 import org.apache.dubbo.metadata.definition.model.TypeDefinition;
 
 import java.lang.reflect.Type;
 import java.util.Map;
 
 /**
- * 2015/1/27.
+ * test for sort
  */
-@SPI
-public interface TypeBuilder {
+public class TestTypeBuilder implements TypeBuilder {
+    // it is smaller than the implements of TypeBuilder
+    public int getPriority(){
+        return -3;
+    }
 
-    /**
-     * Whether the build accept the type or class passed in.
-     */
-    boolean accept(Type type, Class<?> clazz);
-
-    /**
-     * Build type definition with the type or class.
-     */
-    TypeDefinition build(Type type, Class<?> clazz, Map<Class<?>, TypeDefinition> typeCache);
+    @Override
+    public boolean accept(Type type, Class<?> clazz) {
+        return false;
+    }
 
+    @Override
+    public TypeDefinition build(Type type, Class<?> clazz, Map<Class<?>, TypeDefinition> typeCache) {
+        return null;
+    }
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilderTest.java
old mode 100755
new mode 100644
similarity index 58%
copy from dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
copy to dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilderTest.java
index d7022bd..11d03a1
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilderTest.java
@@ -14,28 +14,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.metadata.definition.builder;
+package org.apache.dubbo.metadata.definition;
 
-import org.apache.dubbo.common.extension.SPI;
-import org.apache.dubbo.metadata.definition.model.TypeDefinition;
+import org.apache.dubbo.metadata.definition.builder.TypeBuilder;
 
-import java.lang.reflect.Type;
-import java.util.Map;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
 
-/**
- * 2015/1/27.
- */
-@SPI
-public interface TypeBuilder {
-
-    /**
-     * Whether the build accept the type or class passed in.
-     */
-    boolean accept(Type type, Class<?> clazz);
+public class TypeDefinitionBuilderTest {
 
-    /**
-     * Build type definition with the type or class.
-     */
-    TypeDefinition build(Type type, Class<?> clazz, Map<Class<?>, TypeDefinition> typeCache);
+    @Test
+    public void testSortTypeBuilder(){
+        TypeBuilder tb = TypeDefinitionBuilder.BUILDERS.get(0);
+        Assertions.assertTrue(tb instanceof TestTypeBuilder);
 
+        tb = TypeDefinitionBuilder.BUILDERS.get(TypeDefinitionBuilder.BUILDERS.size()-1);
+        Assertions.assertTrue(tb instanceof Test3TypeBuilder);
+    }
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/identifier/KeyTypeEnumTest.java
old mode 100755
new mode 100644
similarity index 60%
copy from dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
copy to dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/identifier/KeyTypeEnumTest.java
index d7022bd..c4dae1f
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/identifier/KeyTypeEnumTest.java
@@ -14,28 +14,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.metadata.definition.builder;
+package org.apache.dubbo.metadata.report.identifier;
 
-import org.apache.dubbo.common.extension.SPI;
-import org.apache.dubbo.metadata.definition.model.TypeDefinition;
+import org.junit.jupiter.api.Test;
 
-import java.lang.reflect.Type;
-import java.util.Map;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 /**
- * 2015/1/27.
+ * {@link KeyTypeEnum} Test-Cases
+ *
+ * @since 2.7.8
  */
-@SPI
-public interface TypeBuilder {
+public class KeyTypeEnumTest {
 
     /**
-     * Whether the build accept the type or class passed in.
+     * {@link KeyTypeEnum#build(String, String...)}
      */
-    boolean accept(Type type, Class<?> clazz);
-
-    /**
-     * Build type definition with the type or class.
-     */
-    TypeDefinition build(Type type, Class<?> clazz, Map<Class<?>, TypeDefinition> typeCache);
-
+    @Test
+    public void testBuild() {
+        assertEquals("/A/B/C", KeyTypeEnum.PATH.build("/A", "/B", "C"));
+        assertEquals("A:B:C", KeyTypeEnum.UNIQUE_KEY.build("A", "B", "C"));
+    }
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportFactoryTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportFactoryTest.java
index 30d4ad1..3aa5c02 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportFactoryTest.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportFactoryTest.java
@@ -28,9 +28,9 @@ import com.alibaba.fastjson.JSON;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -65,7 +65,7 @@ public class AbstractMetadataReportFactoryTest {
 
                 @Override
                 public void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier,
-                                               Set<String> urls) {
+                                               Collection<String> urls) {
 
                 }
 
@@ -84,6 +84,11 @@ public class AbstractMetadataReportFactoryTest {
                     store.put(consumerMetadataIdentifier.getIdentifierKey(), JSON.toJSONString(serviceParameterMap));
                 }
 
+                @Override
+                public void close() throws Exception {
+
+                }
+
                 Map<String, String> store = new ConcurrentHashMap<>();
 
 
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportTest.java
index 9d4267f..c5325a1 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportTest.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportTest.java
@@ -18,14 +18,18 @@ package org.apache.dubbo.metadata.report.support;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.config.ApplicationConfig;
 import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
 import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
+import org.apache.dubbo.metadata.report.MetadataReport;
 import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum;
 import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
 import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier;
 import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import com.google.gson.Gson;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -35,10 +39,15 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.SortedSet;
 import java.util.concurrent.ConcurrentHashMap;
 
+import static java.util.Collections.emptySet;
 import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;
 import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
  *
@@ -52,17 +61,25 @@ public class AbstractMetadataReportTest {
     public void before() {
         URL url = URL.valueOf("zookeeper://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.TestService?version=1.0.0&application=vic");
         abstractMetadataReport = new NewMetadataReport(url);
+        // set the simple name of current class as the application name
+        ApplicationModel.getConfigManager().setApplication(new ApplicationConfig(getClass().getSimpleName()));
+    }
+
+    @AfterEach
+    public void reset() {
+        // reset
+        ApplicationModel.reset();
     }
 
     @Test
     public void testGetProtocol() {
         URL url = URL.valueOf("dubbo://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.TestService?version=1.0.0&application=vic&side=provider");
         String protocol = abstractMetadataReport.getProtocol(url);
-        Assertions.assertEquals(protocol, "provider");
+        assertEquals(protocol, "provider");
 
         URL url2 = URL.valueOf("consumer://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.TestService?version=1.0.0&application=vic");
         String protocol2 = abstractMetadataReport.getProtocol(url2);
-        Assertions.assertEquals(protocol2, "consumer");
+        assertEquals(protocol2, "consumer");
     }
 
     @Test
@@ -93,7 +110,7 @@ public class AbstractMetadataReportTest {
         URL singleUrl = URL.valueOf("redis://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.metadata.store.InterfaceNameTestService?version=1.0.0&application=singleTest");
         NewMetadataReport singleMetadataReport = new NewMetadataReport(singleUrl);
 
-        Assertions.assertFalse(singleMetadataReport.file.exists());
+        Assertions.assertFalse(singleMetadataReport.localCacheFile.exists());
 
         String interfaceName = "org.apache.dubbo.metadata.store.InterfaceNameTestService";
         String version = "1.0.0";
@@ -102,8 +119,8 @@ public class AbstractMetadataReportTest {
         MetadataIdentifier providerMetadataIdentifier = storePrivider(singleMetadataReport, interfaceName, version, group, application);
 
         Thread.sleep(2000);
-        Assertions.assertTrue(singleMetadataReport.file.exists());
-        Assertions.assertTrue(singleMetadataReport.properties.containsKey(providerMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY)));
+        assertTrue(singleMetadataReport.localCacheFile.exists());
+        assertTrue(singleMetadataReport.properties.containsKey(providerMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY)));
     }
 
     @Test
@@ -117,22 +134,22 @@ public class AbstractMetadataReportTest {
         retryReport.metadataReportRetry.retryPeriod = 400L;
         URL url = URL.valueOf("dubbo://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.TestService?version=1.0.0&application=vic");
         Assertions.assertNull(retryReport.metadataReportRetry.retryScheduledFuture);
-        Assertions.assertEquals(0, retryReport.metadataReportRetry.retryCounter.get());
-        Assertions.assertTrue(retryReport.store.isEmpty());
-        Assertions.assertTrue(retryReport.failedReports.isEmpty());
+        assertEquals(0, retryReport.metadataReportRetry.retryCounter.get());
+        assertTrue(retryReport.store.isEmpty());
+        assertTrue(retryReport.failedReports.isEmpty());
 
 
         storePrivider(retryReport, interfaceName, version, group, application);
         Thread.sleep(150);
 
-        Assertions.assertTrue(retryReport.store.isEmpty());
+        assertTrue(retryReport.store.isEmpty());
         Assertions.assertFalse(retryReport.failedReports.isEmpty());
         Assertions.assertNotNull(retryReport.metadataReportRetry.retryScheduledFuture);
         Thread.sleep(2000L);
-        Assertions.assertTrue(retryReport.metadataReportRetry.retryCounter.get() != 0);
-        Assertions.assertTrue(retryReport.metadataReportRetry.retryCounter.get() >= 3);
+        assertTrue(retryReport.metadataReportRetry.retryCounter.get() != 0);
+        assertTrue(retryReport.metadataReportRetry.retryCounter.get() >= 3);
         Assertions.assertFalse(retryReport.store.isEmpty());
-        Assertions.assertTrue(retryReport.failedReports.isEmpty());
+        assertTrue(retryReport.failedReports.isEmpty());
     }
 
     @Test
@@ -152,8 +169,8 @@ public class AbstractMetadataReportTest {
         Assertions.assertFalse(retryReport.metadataReportRetry.retryScheduledFuture.isCancelled());
         Assertions.assertFalse(retryReport.metadataReportRetry.retryExecutor.isShutdown());
         Thread.sleep(1000L);
-        Assertions.assertTrue(retryReport.metadataReportRetry.retryScheduledFuture.isCancelled());
-        Assertions.assertTrue(retryReport.metadataReportRetry.retryExecutor.isShutdown());
+        assertTrue(retryReport.metadataReportRetry.retryScheduledFuture.isCancelled());
+        assertTrue(retryReport.metadataReportRetry.retryExecutor.isShutdown());
 
     }
 
@@ -185,42 +202,42 @@ public class AbstractMetadataReportTest {
     @Test
     public void testPublishAll() throws ClassNotFoundException, InterruptedException {
 
-        Assertions.assertTrue(abstractMetadataReport.store.isEmpty());
-        Assertions.assertTrue(abstractMetadataReport.allMetadataReports.isEmpty());
+        assertTrue(abstractMetadataReport.store.isEmpty());
+        assertTrue(abstractMetadataReport.allMetadataReports.isEmpty());
         String interfaceName = "org.apache.dubbo.metadata.store.InterfaceNameTestService";
         String version = "1.0.0";
         String group = null;
         String application = "vic";
         MetadataIdentifier providerMetadataIdentifier1 = storePrivider(abstractMetadataReport, interfaceName, version, group, application);
         Thread.sleep(1000);
-        Assertions.assertEquals(abstractMetadataReport.allMetadataReports.size(), 1);
-        Assertions.assertTrue(((FullServiceDefinition) abstractMetadataReport.allMetadataReports.get(providerMetadataIdentifier1)).getParameters().containsKey("testPKey"));
+        assertEquals(abstractMetadataReport.allMetadataReports.size(), 1);
+        assertTrue(((FullServiceDefinition) abstractMetadataReport.allMetadataReports.get(providerMetadataIdentifier1)).getParameters().containsKey("testPKey"));
 
         MetadataIdentifier providerMetadataIdentifier2 = storePrivider(abstractMetadataReport, interfaceName, version + "_2", group + "_2", application);
         Thread.sleep(1000);
-        Assertions.assertEquals(abstractMetadataReport.allMetadataReports.size(), 2);
-        Assertions.assertTrue(((FullServiceDefinition) abstractMetadataReport.allMetadataReports.get(providerMetadataIdentifier2)).getParameters().containsKey("testPKey"));
-        Assertions.assertEquals(((FullServiceDefinition) abstractMetadataReport.allMetadataReports.get(providerMetadataIdentifier2)).getParameters().get("version"), version + "_2");
+        assertEquals(abstractMetadataReport.allMetadataReports.size(), 2);
+        assertTrue(((FullServiceDefinition) abstractMetadataReport.allMetadataReports.get(providerMetadataIdentifier2)).getParameters().containsKey("testPKey"));
+        assertEquals(((FullServiceDefinition) abstractMetadataReport.allMetadataReports.get(providerMetadataIdentifier2)).getParameters().get("version"), version + "_2");
 
         Map<String, String> tmpMap = new HashMap<>();
         tmpMap.put("testKey", "value");
         MetadataIdentifier consumerMetadataIdentifier = storeConsumer(abstractMetadataReport, interfaceName, version + "_3", group + "_3", application, tmpMap);
         Thread.sleep(1000);
-        Assertions.assertEquals(abstractMetadataReport.allMetadataReports.size(), 3);
+        assertEquals(abstractMetadataReport.allMetadataReports.size(), 3);
 
         Map tmpMapResult = (Map) abstractMetadataReport.allMetadataReports.get(consumerMetadataIdentifier);
-        Assertions.assertEquals(tmpMapResult.get("testPKey"), "9090");
-        Assertions.assertEquals(tmpMapResult.get("testKey"), "value");
-        Assertions.assertEquals(3, abstractMetadataReport.store.size());
+        assertEquals(tmpMapResult.get("testPKey"), "9090");
+        assertEquals(tmpMapResult.get("testKey"), "value");
+        assertEquals(3, abstractMetadataReport.store.size());
 
         abstractMetadataReport.store.clear();
 
-        Assertions.assertEquals(0, abstractMetadataReport.store.size());
+        assertEquals(0, abstractMetadataReport.store.size());
 
         abstractMetadataReport.publishAll();
         Thread.sleep(200);
 
-        Assertions.assertEquals(3, abstractMetadataReport.store.size());
+        assertEquals(3, abstractMetadataReport.store.size());
 
         String v = abstractMetadataReport.store.get(providerMetadataIdentifier1.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
         Gson gson = new Gson();
@@ -244,11 +261,53 @@ public class AbstractMetadataReportTest {
             long t = abstractMetadataReport.calculateStartTime() + System.currentTimeMillis();
             Calendar c = Calendar.getInstance();
             c.setTimeInMillis(t);
-            Assertions.assertTrue(c.get(Calendar.HOUR_OF_DAY) >= 2);
-            Assertions.assertTrue(c.get(Calendar.HOUR_OF_DAY) <= 6);
+            assertTrue(c.get(Calendar.HOUR_OF_DAY) >= 2);
+            assertTrue(c.get(Calendar.HOUR_OF_DAY) <= 6);
         }
     }
 
+    /**
+     * Test {@link MetadataReport#saveExportedURLs(String, String, String)} method
+     *
+     * @since 2.7.8
+     */
+    @Test
+    public void testSaveExportedURLs() {
+        String serviceName = null;
+        String exportedServiceRevision = null;
+        String exportedURLsContent = null;
+        SortedSet<String> exportedURLs = null;
+        // Default methods return true
+        assertTrue(abstractMetadataReport.saveExportedURLs(exportedURLs));
+        assertTrue(abstractMetadataReport.saveExportedURLs(exportedServiceRevision, exportedURLs));
+        assertTrue(abstractMetadataReport.saveExportedURLs(serviceName, exportedServiceRevision, exportedURLs));
+        assertTrue(abstractMetadataReport.saveExportedURLs(serviceName, exportedServiceRevision, exportedURLsContent));
+    }
+
+    /**
+     * Test {@link MetadataReport#getExportedURLs(String, String)} method
+     *
+     * @since 2.7.8
+     */
+    @Test
+    public void testGetExportedURLs() {
+        String serviceName = null;
+        String exportedServiceRevision = null;
+        assertEquals(emptySet(), abstractMetadataReport.getExportedURLs(serviceName, exportedServiceRevision));
+    }
+
+    /**
+     * Test {@link MetadataReport#getExportedURLsContent(String, String)} method
+     *
+     * @since 2.7.8
+     */
+    @Test
+    public void testGetExportedURLsContent() {
+        String serviceName = null;
+        String exportedServiceRevision = null;
+        assertNull(abstractMetadataReport.getExportedURLsContent(serviceName, exportedServiceRevision));
+    }
+
     private FullServiceDefinition toServiceDefinition(String v) {
         Gson gson = new Gson();
         FullServiceDefinition data = gson.fromJson(v, FullServiceDefinition.class);
@@ -256,8 +315,8 @@ public class AbstractMetadataReportTest {
     }
 
     private void checkParam(Map<String, String> map, String application, String version) {
-        Assertions.assertEquals(map.get("application"), application);
-        Assertions.assertEquals(map.get("version"), version);
+        assertEquals(map.get("application"), application);
+        assertEquals(map.get("version"), version);
     }
 
     private Map<String, String> queryUrlToMap(String urlQuery) {
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReportTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReportTest.java
new file mode 100644
index 0000000..32205b9
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReportTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.metadata.report.support;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.metadata.URLRevisionResolver;
+import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
+import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
+import org.apache.dubbo.metadata.report.MetadataReport;
+import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
+import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier;
+import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
+import org.apache.dubbo.metadata.report.support.file.FileSystemMetadataReportFactory;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.service.EchoService;
+
+import com.google.gson.Gson;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.toSet;
+import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
+import static org.apache.dubbo.metadata.report.support.Constants.SYNC_REPORT_KEY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * {@link ConfigCenterBasedMetadataReport} Test-Cases
+ *
+ * @since 2.7.8
+ */
+public class ConfigCenterBasedMetadataReportTest {
+
+    private static final URL REPORT_SERVER_URL = URL.valueOf("file://")
+            .addParameter(APPLICATION_KEY, "test")
+            .addParameter(SYNC_REPORT_KEY, "true");
+
+    private static final Class<EchoService> INTERFACE_CLASS = EchoService.class;
+
+    private static final String INTERFACE_NAME = INTERFACE_CLASS.getName();
+
+    private static final String APP_NAME = "test-service";
+
+    private static final URL BASE_URL = URL
+            .valueOf("dubbo://127.0.0.1:20880")
+            .setPath(INTERFACE_NAME)
+            .addParameter(APPLICATION_KEY, APP_NAME)
+            .addParameter(SIDE_KEY, "provider");
+
+    private ConfigCenterBasedMetadataReport metadataReport;
+
+    @BeforeEach
+    public void init() {
+        ApplicationModel.getConfigManager().setApplication(new ApplicationConfig("test-service"));
+        this.metadataReport = new FileSystemMetadataReportFactory().getMetadataReport(REPORT_SERVER_URL);
+    }
+
+    @AfterEach
+    public void reset() throws Exception {
+        ApplicationModel.reset();
+        this.metadataReport.close();
+    }
+
+    /**
+     * Test {@link MetadataReport#storeProviderMetadata(MetadataIdentifier, ServiceDefinition)} and
+     * {@link MetadataReport#getServiceDefinition(MetadataIdentifier)}
+     */
+    @Test
+    public void testStoreProviderMetadataAndGetServiceDefinition() {
+        MetadataIdentifier metadataIdentifier = new MetadataIdentifier(BASE_URL);
+        ServiceDefinition serviceDefinition = ServiceDefinitionBuilder.buildFullDefinition(INTERFACE_CLASS, BASE_URL.getParameters());
+        metadataReport.storeProviderMetadata(metadataIdentifier, serviceDefinition);
+        String serviceDefinitionJSON = metadataReport.getServiceDefinition(metadataIdentifier);
+        assertEquals(serviceDefinitionJSON, new Gson().toJson(serviceDefinition));
+    }
+
+    /**
+     * Test {@link MetadataReport#storeConsumerMetadata(MetadataIdentifier, Map)} and
+     * {@link MetadataReport#getServiceDefinition(MetadataIdentifier)}
+     */
+    @Test
+    public void testStoreConsumerMetadata() {
+        MetadataIdentifier metadataIdentifier = new MetadataIdentifier(BASE_URL);
+        metadataReport.storeConsumerMetadata(metadataIdentifier, BASE_URL.getParameters());
+        String parametersJSON = metadataReport.getServiceDefinition(metadataIdentifier);
+        assertEquals(parametersJSON, new Gson().toJson(BASE_URL.getParameters()));
+    }
+
+    /**
+     * Test {@link MetadataReport#saveServiceMetadata(ServiceMetadataIdentifier, URL)} and
+     * {@link MetadataReport#removeServiceMetadata(ServiceMetadataIdentifier)}
+     */
+    @Test
+    public void testSaveServiceMetadataAndRemoveServiceMetadata() {
+        ServiceMetadataIdentifier metadataIdentifier = new ServiceMetadataIdentifier(BASE_URL);
+        metadataReport.saveServiceMetadata(metadataIdentifier, BASE_URL);
+        String metadata = metadataReport.getMetadata(metadataIdentifier);
+        assertEquals(URL.encode(BASE_URL.toFullString()), metadata);
+        metadataReport.removeServiceMetadata(metadataIdentifier);
+        assertNull(metadataReport.getMetadata(metadataIdentifier));
+    }
+
+    /**
+     * Test {@link MetadataReport#saveSubscribedData(SubscriberMetadataIdentifier, Collection)} and
+     * {@link MetadataReport#getSubscribedURLs(SubscriberMetadataIdentifier)}
+     */
+    @Test
+    public void testSaveSubscribedDataAndGetSubscribedURLs() {
+        SubscriberMetadataIdentifier metadataIdentifier = new SubscriberMetadataIdentifier(BASE_URL);
+        Set<String> urls = singleton(BASE_URL).stream().map(URL::toIdentityString).collect(toSet());
+        metadataReport.saveSubscribedData(metadataIdentifier, urls);
+        Collection<String> subscribedURLs = metadataReport.getSubscribedURLs(metadataIdentifier);
+        assertEquals(1, subscribedURLs.size());
+        assertEquals(urls, subscribedURLs);
+    }
+
+    /**
+     * Test {@link MetadataReport#saveExportedURLs(SortedSet)},
+     * {@link MetadataReport#getExportedURLsContent(String, String)} and
+     * {@link MetadataReport#getExportedURLs(String, String)}
+     */
+    @Test
+    public void testSaveExportedURLsAndGetExportedURLs() {
+        SortedSet<String> urls = singleton(BASE_URL).stream().map(URL::toIdentityString).collect(TreeSet::new, Set::add, Set::addAll);
+        metadataReport.saveExportedURLs(urls);
+
+        URLRevisionResolver urlRevisionResolver = URLRevisionResolver.INSTANCE;
+        String revision = urlRevisionResolver.resolve(urls);
+        assertEquals(urls, metadataReport.getExportedURLs(APP_NAME, revision));
+    }
+}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.definition.builder.TypeBuilder b/dubbo-metadata/dubbo-metadata-api/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.definition.builder.TypeBuilder
new file mode 100644
index 0000000..1b1b7d2
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.definition.builder.TypeBuilder
@@ -0,0 +1,2 @@
+test=org.apache.dubbo.metadata.definition.TestTypeBuilder
+test3=org.apache.dubbo.metadata.definition.Test3TypeBuilder
diff --git a/dubbo-metadata/dubbo-metadata-report-nacos/src/test/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReportTest.java b/dubbo-metadata/dubbo-metadata-report-nacos/src/test/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReportTest.java
deleted file mode 100644
index 0b36bfb..0000000
--- a/dubbo-metadata/dubbo-metadata-report-nacos/src/test/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReportTest.java
+++ /dev/null
@@ -1,246 +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.metadata.store.nacos;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.utils.NetUtils;
-import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
-import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
-import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum;
-import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
-import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier;
-import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
-
-import com.alibaba.nacos.api.config.ConfigService;
-import com.alibaba.nacos.api.exception.NacosException;
-import com.google.gson.Gson;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;
-import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
-
-//FIXME: waiting for embedded Nacos suport, then we can open the switch.
-@Disabled("https://github.com/alibaba/nacos/issues/1188")
-public class NacosMetadataReportTest {
-
-    private static final String SESSION_TIMEOUT_KEY = "session";
-
-    private static final String TEST_SERVICE = "org.apache.dubbo.metadata.store.nacos.NacosMetadata4TstService";
-
-    private NacosMetadataReport nacosMetadataReport;
-
-    private NacosMetadataReportFactory nacosMetadataReportFactory;
-
-    private ConfigService configService;
-
-    private static final String NACOS_GROUP = "metadata_test";
-
-    /**
-     * timeout(ms) for nacos session
-     */
-    private static final int SESSION_TIMEOUT = 15 * 1000;
-
-    /**
-     * timeout(ms) for query operation on nacos
-     */
-    private static final int NACOS_READ_TIMEOUT = 5 * 1000;
-
-    /**
-     * interval(ms) to make nacos cache refresh
-     */
-    private static final int INTERVAL_TO_MAKE_NACOS_REFRESH = 1000;
-
-    /**
-     * version for test
-     */
-    private static final String VERSION = "1.0.0";
-
-    /**
-     * group for test
-     */
-    private static final String METADATA_GROUP = null;
-
-    /**
-     * application name for test
-     */
-    private static final String APPLICATION_NAME = "nacos-metdata-report-test";
-
-    /**
-     * revision for test
-     */
-    private static final String REVISION = "90980";
-
-    /**
-     * protocol for test
-     */
-    private static final String PROTOCOL = "xxx";
-
-    @BeforeEach
-    public void setUp() {
-        URL url = URL.valueOf("nacos://127.0.0.1:8848?group=" + NACOS_GROUP)
-                .addParameter(SESSION_TIMEOUT_KEY, SESSION_TIMEOUT);
-        nacosMetadataReportFactory = new NacosMetadataReportFactory();
-        this.nacosMetadataReport = (NacosMetadataReport) nacosMetadataReportFactory.getMetadataReport(url);
-    }
-
-    @AfterEach
-    public void tearDown() throws Exception {
-    }
-
-
-    @Test
-    public void testStoreProvider() throws Exception {
-        MetadataIdentifier providerIdentifier =
-                storeProvider(nacosMetadataReport, TEST_SERVICE, VERSION, METADATA_GROUP, APPLICATION_NAME);
-        String serverContent = configService.getConfig(providerIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP, NACOS_READ_TIMEOUT);
-        Assertions.assertNotNull(serverContent);
-
-        Gson gson = new Gson();
-        FullServiceDefinition fullServiceDefinition = gson.fromJson(serverContent, FullServiceDefinition.class);
-        Assertions.assertEquals(fullServiceDefinition.getParameters().get("paramTest"), "nacosTest");
-
-        //Clear test data
-        configService.removeConfig(providerIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP);
-    }
-
-    @Test
-    public void testStoreConsumer() throws Exception {
-        MetadataIdentifier consumerIdentifier = storeConsumer(nacosMetadataReport, TEST_SERVICE, VERSION, METADATA_GROUP, APPLICATION_NAME);
-
-        String serverContent = configService.getConfig(consumerIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP, NACOS_READ_TIMEOUT);
-        Assertions.assertNotNull(serverContent);
-        Assertions.assertEquals(serverContent, "{\"paramConsumerTest\":\"nacosConsumer\"}");
-
-        //clear test data
-        configService.removeConfig(consumerIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP);
-    }
-
-    @Test
-    public void testDoSaveServiceMetadata() throws Exception {
-        URL url = URL.valueOf("xxx://" + NetUtils.getLocalAddress().getHostName() + ":4444/" + TEST_SERVICE +
-                "?paramTest=nacosTest&version=" + VERSION + "&application="
-                + APPLICATION_NAME + (METADATA_GROUP == null ? "" : "&group=" + METADATA_GROUP));
-        ServiceMetadataIdentifier serviceMetadataIdentifier = new ServiceMetadataIdentifier(TEST_SERVICE, VERSION,
-                METADATA_GROUP, "provider", REVISION, PROTOCOL);
-        nacosMetadataReport.doSaveMetadata(serviceMetadataIdentifier, url);
-        Thread.sleep(INTERVAL_TO_MAKE_NACOS_REFRESH);
-        String serviceMetaData = configService.getConfig(serviceMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP, NACOS_READ_TIMEOUT);
-        Assertions.assertNotNull(serviceMetaData);
-        Assertions.assertEquals(serviceMetaData, URL.encode(url.toFullString()));
-
-        //clear test data
-        configService.removeConfig(serviceMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP);
-    }
-
-    @Test
-    public void testDoRemoveServiceMetadata() throws Exception {
-        URL url = URL.valueOf("xxx://" + NetUtils.getLocalAddress().getHostName() + ":4444/" + TEST_SERVICE +
-                "?paramTest=nacosTest&version=" + VERSION + "&application="
-                + APPLICATION_NAME + (METADATA_GROUP == null ? "" : "&group=" + METADATA_GROUP));
-        ServiceMetadataIdentifier serviceMetadataIdentifier = new ServiceMetadataIdentifier(TEST_SERVICE, VERSION,
-                METADATA_GROUP, "provider", REVISION, PROTOCOL);
-        nacosMetadataReport.doSaveMetadata(serviceMetadataIdentifier, url);
-        Thread.sleep(INTERVAL_TO_MAKE_NACOS_REFRESH);
-        String serviceMetaData = configService.getConfig(serviceMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP, NACOS_READ_TIMEOUT);
-        Assertions.assertNotNull(serviceMetaData);
-
-        nacosMetadataReport.doRemoveMetadata(serviceMetadataIdentifier);
-        Thread.sleep(INTERVAL_TO_MAKE_NACOS_REFRESH);
-        serviceMetaData = configService.getConfig(serviceMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP, NACOS_READ_TIMEOUT);
-        Assertions.assertNull(serviceMetaData);
-    }
-
-    @Test
-    public void testDoGetExportedURLs() throws InterruptedException, NacosException {
-        URL url = URL.valueOf("xxx://" + NetUtils.getLocalAddress().getHostName() + ":4444/" + TEST_SERVICE +
-                "?paramTest=nacosTest&version=" + VERSION + "&application="
-                + APPLICATION_NAME + (METADATA_GROUP == null ? "" : "&group=" + METADATA_GROUP));
-        ServiceMetadataIdentifier serviceMetadataIdentifier = new ServiceMetadataIdentifier(TEST_SERVICE, VERSION,
-                METADATA_GROUP, "provider", REVISION, PROTOCOL);
-
-        nacosMetadataReport.doSaveMetadata(serviceMetadataIdentifier, url);
-        Thread.sleep(INTERVAL_TO_MAKE_NACOS_REFRESH);
-
-        List<String> exportedURLs = nacosMetadataReport.doGetExportedURLs(serviceMetadataIdentifier);
-        Assertions.assertTrue(exportedURLs.size() == 1);
-
-        String exportedUrl = exportedURLs.get(0);
-        Assertions.assertNotNull(exportedUrl);
-        Assertions.assertEquals(exportedUrl, url.toFullString());
-
-        //clear test data
-        configService.removeConfig(serviceMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP);
-    }
-
-    @Test
-    public void testDoSaveSubscriberData() throws InterruptedException, NacosException {
-        URL url = URL.valueOf("xxx://" + NetUtils.getLocalAddress().getHostName() + ":4444/" + TEST_SERVICE +
-                "?paramTest=nacosTest&version=" + VERSION + "&application="
-                + APPLICATION_NAME + (METADATA_GROUP == null ? "" : "&group=" + METADATA_GROUP));
-        SubscriberMetadataIdentifier subscriberMetadataIdentifier = new SubscriberMetadataIdentifier(APPLICATION_NAME, REVISION);
-        Gson gson = new Gson();
-        String urlListJsonString = gson.toJson(Arrays.asList(url));
-        nacosMetadataReport.doSaveSubscriberData(subscriberMetadataIdentifier, urlListJsonString);
-        Thread.sleep(INTERVAL_TO_MAKE_NACOS_REFRESH);
-
-        String subscriberMetadata = configService.getConfig(subscriberMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP, NACOS_READ_TIMEOUT);
-        Assertions.assertNotNull(subscriberMetadata);
-        Assertions.assertEquals(subscriberMetadata, urlListJsonString);
-
-        //clear test data
-        configService.removeConfig(subscriberMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP);
-
-    }
-
-    private MetadataIdentifier storeProvider(NacosMetadataReport nacosMetadataReport, String interfaceName, String version,
-                                             String group, String application)
-            throws ClassNotFoundException, InterruptedException {
-        URL url = URL.valueOf("xxx://" + NetUtils.getLocalAddress().getHostName() + ":4444/" + interfaceName +
-                "?paramTest=nacosTest&version=" + version + "&application="
-                + application + (group == null ? "" : "&group=" + group));
-
-        MetadataIdentifier providerMetadataIdentifier =
-                new MetadataIdentifier(interfaceName, version, group, PROVIDER_SIDE, application);
-        Class interfaceClass = Class.forName(interfaceName);
-        FullServiceDefinition fullServiceDefinition =
-                ServiceDefinitionBuilder.buildFullDefinition(interfaceClass, url.getParameters());
-
-        nacosMetadataReport.storeProviderMetadata(providerMetadataIdentifier, fullServiceDefinition);
-        Thread.sleep(INTERVAL_TO_MAKE_NACOS_REFRESH);
-        return providerMetadataIdentifier;
-    }
-
-    private MetadataIdentifier storeConsumer(NacosMetadataReport nacosMetadataReport, String interfaceName,
-                                             String version, String group, String application) throws InterruptedException {
-        MetadataIdentifier consumerIdentifier = new MetadataIdentifier(interfaceName, version, group, CONSUMER_SIDE, application);
-        Map<String, String> tmp = new HashMap<>();
-        tmp.put("paramConsumerTest", "nacosConsumer");
-        nacosMetadataReport.storeConsumerMetadata(consumerIdentifier, tmp);
-        Thread.sleep(INTERVAL_TO_MAKE_NACOS_REFRESH);
-        return consumerIdentifier;
-    }
-
-}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactory.java
index e0e70ea..d599891 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactory.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactory.java
@@ -18,10 +18,11 @@ package org.apache.dubbo.registry.client.metadata.proxy;
 
 import org.apache.dubbo.metadata.MetadataService;
 import org.apache.dubbo.registry.client.ServiceInstance;
-import org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getExportedServicesRevision;
 
 /**
  * base class for remote and local implementations.
@@ -30,12 +31,28 @@ import java.util.Map;
  */
 abstract class BaseMetadataServiceProxyFactory implements MetadataServiceProxyFactory {
 
-    private final Map<String, MetadataService> proxies = new HashMap<>();
+    private final ConcurrentMap<String, MetadataService> proxiesCache = new ConcurrentHashMap<>();
 
     public final MetadataService getProxy(ServiceInstance serviceInstance) {
-        return proxies.computeIfAbsent(serviceInstance.getServiceName() + "##" +
-                ServiceInstanceMetadataUtils.getExportedServicesRevision(serviceInstance), id -> createProxy(serviceInstance));
+        return proxiesCache.computeIfAbsent(createProxyCacheKey(serviceInstance), id -> createProxy(serviceInstance));
+    }
+
+    /**
+     * Create the cache key of the proxy of {@link MetadataService}
+     *
+     * @param serviceInstance {@link ServiceInstance}
+     * @return non-null
+     * @since 2.7.8
+     */
+    protected String createProxyCacheKey(ServiceInstance serviceInstance) {
+        return serviceInstance.getServiceName() + "#" + getExportedServicesRevision(serviceInstance);
     }
 
+    /**
+     * Create the instance proxy of {@link MetadataService}
+     *
+     * @param serviceInstance {@link ServiceInstance}
+     * @return non-null
+     */
     protected abstract MetadataService createProxy(ServiceInstance serviceInstance);
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/CompositeMetadataServiceProxyFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/CompositeMetadataServiceProxyFactory.java
index 51a64d7..f8c95d6 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/CompositeMetadataServiceProxyFactory.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/CompositeMetadataServiceProxyFactory.java
@@ -24,6 +24,7 @@ import org.apache.dubbo.registry.client.ServiceInstance;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.util.List;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 import static java.lang.String.format;
@@ -35,10 +36,12 @@ import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoad
  *
  * @since 2.7.8
  */
-public class CompositeMetadataServiceProxyFactory implements MetadataServiceProxyFactory {
+public class CompositeMetadataServiceProxyFactory extends BaseMetadataServiceProxyFactory {
+
+    private static final Logger logger = LoggerFactory.getLogger(CompositeMetadataServiceProxyFactory.class);
 
     @Override
-    public MetadataService getProxy(ServiceInstance serviceInstance) {
+    public MetadataService createProxy(ServiceInstance serviceInstance) {
         MetadataService metadataService = (MetadataService) newProxyInstance(
                 getClass().getClassLoader(),
                 new Class[]{MetadataService.class},
@@ -49,25 +52,58 @@ public class CompositeMetadataServiceProxyFactory implements MetadataServiceProx
 
     static class MetadataServiceInvocationHandler implements InvocationHandler {
 
-        private final List<MetadataService> metadataServices;
+        private final ServiceInstance serviceInstance;
+
+        private final MetadataServiceProxyFactory excluded;
 
-        private final Logger logger = LoggerFactory.getLogger(getClass());
+        private volatile List<MetadataService> metadataServices;
 
         MetadataServiceInvocationHandler(ServiceInstance serviceInstance,
                                          MetadataServiceProxyFactory excluded) {
-            this.metadataServices = initMetadataServices(serviceInstance, excluded);
+            this.serviceInstance = serviceInstance;
+            this.excluded = excluded;
         }
 
-        private List<MetadataService> initMetadataServices(ServiceInstance serviceInstance,
-                                                           MetadataServiceProxyFactory excluded) {
+        private List<MetadataService> loadMetadataServices() {
             return getExtensionLoader(MetadataServiceProxyFactory.class)
                     .getSupportedExtensionInstances()
                     .stream()
-                    .filter(factory -> !factory.equals(excluded))
-                    .map(factory -> factory.getProxy(serviceInstance))
+                    .filter(this::isRequiredFactory)
+                    .map(this::getProxy)
+                    .filter(Objects::nonNull)
                     .collect(Collectors.toList());
         }
 
+        private List<MetadataService> getMetadataServices() {
+            if (metadataServices == null) {
+                metadataServices = loadMetadataServices();
+                if (metadataServices.isEmpty()) {
+                    throw new IllegalStateException(format("No valid proxy of %s can't be loaded.",
+                            MetadataService.class.getName()));
+                }
+            }
+            return metadataServices;
+        }
+
+        private boolean isRequiredFactory(MetadataServiceProxyFactory factory) {
+            return !factory.equals(excluded);
+        }
+
+        private MetadataService getProxy(MetadataServiceProxyFactory factory) {
+            MetadataService metadataService = null;
+            try {
+                metadataService = factory.getProxy(serviceInstance);
+            } catch (Exception e) {
+                if (logger.isErrorEnabled()) {
+                    logger.error(format("The proxy of %s can't be gotten by %s [from : %s].",
+                            MetadataService.class.getName(),
+                            factory.getClass().getName(),
+                            serviceInstance.toString()));
+                }
+            }
+            return metadataService;
+        }
+
         @Override
         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 
@@ -77,7 +113,7 @@ public class CompositeMetadataServiceProxyFactory implements MetadataServiceProx
 
             Object result = null;
 
-            for (MetadataService metadataService : metadataServices) {
+            for (MetadataService metadataService : getMetadataServices()) {
                 try {
                     result = method.invoke(metadataService, args);
                     if (result != null) {
@@ -85,7 +121,7 @@ public class CompositeMetadataServiceProxyFactory implements MetadataServiceProx
                     }
                 } catch (Exception e) {
                     if (logger.isErrorEnabled()) {
-                        logger.error(format("MetadataService[type : %s] executes failed", metadataService.getClass().getName()), e);
+                        logger.error(format("MetadataService[type : %s] executes failed.", metadataService.getClass().getName()), e);
                     }
                 }
             }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
index 736108f..01bde1d 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
@@ -748,7 +748,7 @@ public class RegistryDirectory<T> extends AbstractDirectory<T> implements Notify
         @Override
         protected void notifyOverrides() {
             // to notify configurator/router changes
-            directory.refreshInvoker(Collections.emptyList());
+            directory.refreshOverrideAndInvoker(Collections.emptyList());
         }
     }
 
@@ -769,7 +769,7 @@ public class RegistryDirectory<T> extends AbstractDirectory<T> implements Notify
 
         @Override
         protected void notifyOverrides() {
-            listeners.forEach(listener -> listener.refreshInvoker(Collections.emptyList()));
+            listeners.forEach(listener -> listener.refreshOverrideAndInvoker(Collections.emptyList()));
         }
     }
 
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryInvokerWrapper.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryInvokerWrapper.java
index 735ef18..b7e03c4 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryInvokerWrapper.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryInvokerWrapper.java
@@ -22,18 +22,17 @@ import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Result;
 import org.apache.dubbo.rpc.RpcException;
 import org.apache.dubbo.rpc.cluster.Cluster;
+import org.apache.dubbo.rpc.cluster.ClusterInvoker;
 
-class RegistryInvokerWrapper<T> implements Invoker<T> {
+class RegistryInvokerWrapper<T> implements ClusterInvoker<T> {
     private RegistryDirectory<T> directory;
     private Cluster cluster;
     private Invoker<T> invoker;
-    private URL url;
 
-    public RegistryInvokerWrapper(RegistryDirectory<T> directory, Cluster cluster, Invoker<T> invoker, URL url) {
+    public RegistryInvokerWrapper(RegistryDirectory<T> directory, Cluster cluster, Invoker<T> invoker) {
         this.directory = directory;
         this.cluster = cluster;
         this.invoker = invoker;
-        this.url = url;
     }
 
     @Override
@@ -48,11 +47,7 @@ class RegistryInvokerWrapper<T> implements Invoker<T> {
 
     @Override
     public URL getUrl() {
-        return url;
-    }
-
-    public void setUrl(URL url) {
-        this.url = url;
+        return invoker.getUrl();
     }
 
     public void setInvoker(Invoker<T> invoker) {
@@ -76,4 +71,9 @@ class RegistryInvokerWrapper<T> implements Invoker<T> {
     public void destroy() {
         invoker.destroy();
     }
+
+    @Override
+    public URL getRegistryUrl() {
+        return directory.getUrl();
+    }
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java
index 76da303..9a4acc8 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java
@@ -43,6 +43,7 @@ import org.apache.dubbo.rpc.RpcException;
 import org.apache.dubbo.rpc.cluster.Cluster;
 import org.apache.dubbo.rpc.cluster.Configurator;
 import org.apache.dubbo.rpc.cluster.governance.GovernanceRuleRepository;
+import org.apache.dubbo.rpc.cluster.support.MergeableCluster;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.ProviderModel;
 import org.apache.dubbo.rpc.protocol.InvokerWrapper;
@@ -132,7 +133,6 @@ public class RegistryProtocol implements Protocol {
     //To solve the problem of RMI repeated exposure port conflicts, the services that have been exposed are no longer exposed.
     //providerurl <--> exporter
     private final ConcurrentMap<String, ExporterChangeableWrapper<?>> bounds = new ConcurrentHashMap<>();
-    private Cluster cluster;
     private Protocol protocol;
     private RegistryFactory registryFactory;
     private ProxyFactory proxyFactory;
@@ -152,10 +152,6 @@ public class RegistryProtocol implements Protocol {
         }
     }
 
-    public void setCluster(Cluster cluster) {
-        this.cluster = cluster;
-    }
-
     public void setProtocol(Protocol protocol) {
         this.protocol = protocol;
     }
@@ -221,12 +217,13 @@ public class RegistryProtocol implements Protocol {
         // register stated url on provider model
         registerStatedUrl(registryUrl, registeredProviderUrl, register);
 
-        // Deprecated! Subscribe to override rules in 2.6.x or before.
-        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
 
         exporter.setRegisterUrl(registeredProviderUrl);
         exporter.setSubscribeUrl(overrideSubscribeUrl);
 
+        // Deprecated! Subscribe to override rules in 2.6.x or before.
+        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
+
         notifyExport(exporter);
         //Ensure that a new exporter instance is returned every time export
         return new DestroyableExporter<>(exporter);
@@ -449,14 +446,12 @@ public class RegistryProtocol implements Protocol {
         String group = qs.get(GROUP_KEY);
         if (group != null && group.length() > 0) {
             if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
-                return doRefer(getMergeableCluster(), registry, type, url);
+                return doRefer(Cluster.getCluster(MergeableCluster.NAME), registry, type, url);
             }
         }
-        return doRefer(cluster, registry, type, url);
-    }
 
-    private Cluster getMergeableCluster() {
-        return ExtensionLoader.getExtensionLoader(Cluster.class).getExtension("mergeable");
+        Cluster cluster = Cluster.getCluster(qs.get(CLUSTER_KEY));
+        return doRefer(cluster, registry, type, url);
     }
 
     private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
@@ -479,7 +474,7 @@ public class RegistryProtocol implements Protocol {
             return invoker;
         }
 
-        RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker, subscribeUrl);
+        RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker);
         for (RegistryProtocolListener listener : listeners) {
             listener.onRefer(this, registryInvokerWrapper);
         }
@@ -504,7 +499,6 @@ public class RegistryProtocol implements Protocol {
         directory.subscribe(toSubscribeUrl(newSubscribeUrl));
 
         invokerWrapper.setInvoker(invokerWrapper.getCluster().join(directory));
-        invokerWrapper.setUrl(newSubscribeUrl);
     }
 
     private static URL toSubscribeUrl(URL url) {
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/DefaultServiceInstanceTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/DefaultServiceInstanceTest.java
index e55b223..85b63de 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/DefaultServiceInstanceTest.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/DefaultServiceInstanceTest.java
@@ -20,8 +20,11 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import static java.lang.String.valueOf;
+import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URLS_PROPERTY_NAME;
+import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -64,4 +67,10 @@ public class DefaultServiceInstanceTest {
         assertFalse(instance.isHealthy());
         assertFalse(instance.getMetadata().isEmpty());
     }
+
+    @Test
+    public void testGetMetadata() {
+        assertNotNull(instance.getMetadata(METADATA_SERVICE_URLS_PROPERTY_NAME));
+        assertNotNull(instance.getMetadata(METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME));
+    }
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactoryTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactoryTest.java
new file mode 100644
index 0000000..6aca4c6
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactoryTest.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.registry.client.metadata.proxy;
+
+import org.apache.dubbo.metadata.MetadataService;
+import org.apache.dubbo.registry.client.DefaultServiceInstance;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.lang.String.valueOf;
+import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.EXPORTED_SERVICES_REVISION_PROPERTY_NAME;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * {@link BaseMetadataServiceProxyFactory} Test-Cases
+ *
+ * @since 2.7.8
+ */
+public class BaseMetadataServiceProxyFactoryTest {
+
+    private MyMetadataServiceProxyFactory factory;
+
+    private DefaultServiceInstance instance;
+
+    @BeforeEach
+    public void init() {
+        factory = new MyMetadataServiceProxyFactory();
+        instance = createServiceInstance();
+    }
+
+    private DefaultServiceInstance createServiceInstance() {
+        DefaultServiceInstance serviceInstance = new DefaultServiceInstance(valueOf(System.nanoTime()), "A", "127.0.0.1", 8080);
+        Map<String, String> metadata = new HashMap<>();
+        metadata.put(EXPORTED_SERVICES_REVISION_PROPERTY_NAME, "X");
+        serviceInstance.setMetadata(metadata);
+        return serviceInstance;
+    }
+
+    @Test
+    public void testCreateProxyCacheKey() {
+        assertEquals("A#X", factory.createProxyCacheKey(instance));
+    }
+
+    @Test
+    public void testCreateProxy() {
+        MetadataService metadataService = factory.createProxy(instance);
+        MetadataService metadataService2 = factory.createProxy(instance);
+        assertNotSame(metadataService, metadataService2);
+    }
+
+    @Test
+    public void testGetProxy() {
+        MetadataService metadataService = factory.getProxy(instance);
+        MetadataService metadataService2 = factory.getProxy(instance);
+        assertSame(metadataService, metadataService2);
+    }
+
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/CompositeMetadataServiceProxyFactoryTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/CompositeMetadataServiceProxyFactoryTest.java
new file mode 100644
index 0000000..ddd78e0
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/CompositeMetadataServiceProxyFactoryTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.registry.client.metadata.proxy;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.metadata.MetadataService;
+import org.apache.dubbo.metadata.report.MetadataReportInstance;
+import org.apache.dubbo.registry.client.DefaultServiceInstance;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedSet;
+
+import static java.lang.String.valueOf;
+import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.COMPOSITE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.metadata.report.support.Constants.SYNC_REPORT_KEY;
+import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.EXPORTED_SERVICES_REVISION_PROPERTY_NAME;
+import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link CompositeMetadataServiceProxyFactory} Test-Cases
+ *
+ * @since 2.7.8
+ */
+public class CompositeMetadataServiceProxyFactoryTest {
+
+    private static final URL METADATA_REPORT_URL = URL.valueOf("file://")
+            .addParameter(APPLICATION_KEY, "test")
+            .addParameter(SYNC_REPORT_KEY, "true");
+
+    private static final String APP_NAME = "test-service";
+
+    private MetadataServiceProxyFactory factory;
+
+    private DefaultServiceInstance instance;
+
+    @BeforeEach
+    public void init() {
+        ApplicationModel.getConfigManager().setApplication(new ApplicationConfig(APP_NAME));
+        MetadataReportInstance.init(METADATA_REPORT_URL);
+        factory = MetadataServiceProxyFactory.getExtension(COMPOSITE_METADATA_STORAGE_TYPE);
+        instance = createServiceInstance();
+    }
+
+    @AfterEach
+    public void reset() throws Exception {
+        ApplicationModel.reset();
+        MetadataReportInstance.getMetadataReport().close();
+    }
+
+    private DefaultServiceInstance createServiceInstance() {
+        DefaultServiceInstance serviceInstance = new DefaultServiceInstance(valueOf(System.nanoTime()), "A", "127.0.0.1", 8080);
+        Map<String, String> metadata = new HashMap<>();
+        metadata.put(EXPORTED_SERVICES_REVISION_PROPERTY_NAME, "X");
+        metadata.put(METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME, "{\"dubbo\":{\"application\":\"dubbo-provider-demo\",\"deprecated\":\"false\",\"group\":\"dubbo-provider-demo\",\"version\":\"1.0.0\",\"timestamp\":\"1564845042651\",\"dubbo\":\"2.0.2\",\"host\":\"192.168.0.102\",\"port\":\"20880\"}}");
+        serviceInstance.setMetadata(metadata);
+        return serviceInstance;
+    }
+
+    @Test
+    public void testGetProxy() {
+        MetadataService metadataService = factory.getProxy(instance);
+        MetadataService metadataService2 = factory.getProxy(instance);
+        assertSame(metadataService, metadataService2);
+    }
+
+    @Test
+    public void testGetExportedURLs() {
+        MetadataService metadataService = factory.getProxy(instance);
+        SortedSet<String> exportedURLs = metadataService.getExportedURLs();
+        assertTrue(exportedURLs.isEmpty());
+    }
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/MetadataServiceProxyFactoryTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/MetadataServiceProxyFactoryTest.java
new file mode 100644
index 0000000..2c31c22
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/MetadataServiceProxyFactoryTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.registry.client.metadata.proxy;
+
+import org.junit.jupiter.api.Test;
+
+import static org.apache.dubbo.common.constants.CommonConstants.COMPOSITE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.registry.client.metadata.proxy.MetadataServiceProxyFactory.getDefaultExtension;
+import static org.apache.dubbo.registry.client.metadata.proxy.MetadataServiceProxyFactory.getExtension;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link MetadataServiceProxyFactory} Test-Cases
+ *
+ * @since 2.7.8
+ */
+public class MetadataServiceProxyFactoryTest {
+
+    @Test
+    public void testExtension() {
+        MetadataServiceProxyFactory defaultFactory = getDefaultExtension();
+        MetadataServiceProxyFactory factory = getExtension(DEFAULT_METADATA_STORAGE_TYPE);
+        assertEquals(defaultFactory, factory);
+
+        assertEquals(MyMetadataServiceProxyFactory.class, factory.getClass());
+
+        factory = getExtension(REMOTE_METADATA_STORAGE_TYPE);
+        assertEquals(RemoteMetadataServiceProxyFactory.class, factory.getClass());
+
+        factory = getExtension(COMPOSITE_METADATA_STORAGE_TYPE);
+        assertEquals(CompositeMetadataServiceProxyFactory.class, factory.getClass());
+    }
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ExportingMetadataServiceListener.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/MyMetadataServiceProxyFactory.java
similarity index 53%
rename from dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ExportingMetadataServiceListener.java
rename to dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/MyMetadataServiceProxyFactory.java
index 15faea1..86247e1 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ExportingMetadataServiceListener.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/MyMetadataServiceProxyFactory.java
@@ -14,29 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.registry.client.event.listener;
+package org.apache.dubbo.registry.client.metadata.proxy;
 
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.event.EventListener;
 import org.apache.dubbo.metadata.MetadataService;
-import org.apache.dubbo.registry.client.event.ServiceInstancePreRegisteredEvent;
+import org.apache.dubbo.metadata.store.InMemoryWritableMetadataService;
+import org.apache.dubbo.registry.client.ServiceInstance;
 
-/**
- * An {@link EventListener} of {@link ServiceInstancePreRegisteredEvent} for
- * Exporting the {@link MetadataService}
- *
- * @see EventListener
- * @see ServiceInstancePreRegisteredEvent
- * @see MetadataService
- * @since 2.7.8
- */
-public class ExportingMetadataServiceListener implements EventListener<ServiceInstancePreRegisteredEvent> {
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
+public class MyMetadataServiceProxyFactory extends BaseMetadataServiceProxyFactory {
 
     @Override
-    public void onEvent(ServiceInstancePreRegisteredEvent event) {
-
+    protected MetadataService createProxy(ServiceInstance serviceInstance) {
+        return new InMemoryWritableMetadataService();
     }
 }
diff --git a/dubbo-registry/dubbo-registry-api/src/test/resources/META-INF/dubbo/org.apache.dubbo.registry.client.metadata.proxy.MetadataServiceProxyFactory b/dubbo-registry/dubbo-registry-api/src/test/resources/META-INF/dubbo/org.apache.dubbo.registry.client.metadata.proxy.MetadataServiceProxyFactory
new file mode 100644
index 0000000..6d2b9dd
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/test/resources/META-INF/dubbo/org.apache.dubbo.registry.client.metadata.proxy.MetadataServiceProxyFactory
@@ -0,0 +1,2 @@
+# Override "local" implementation
+local=org.apache.dubbo.registry.client.metadata.proxy.MyMetadataServiceProxyFactory
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulRegistry.java b/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulRegistry.java
index d649076..990646a 100644
--- a/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulRegistry.java
+++ b/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulRegistry.java
@@ -45,8 +45,8 @@ import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
@@ -65,6 +65,7 @@ import static org.apache.dubbo.registry.consul.AbstractConsulRegistry.DEREGISTER
 import static org.apache.dubbo.registry.consul.AbstractConsulRegistry.SERVICE_TAG;
 import static org.apache.dubbo.registry.consul.AbstractConsulRegistry.URL_META_KEY;
 import static org.apache.dubbo.registry.consul.AbstractConsulRegistry.WATCH_TIMEOUT;
+import static org.apache.dubbo.rpc.Constants.TOKEN_KEY;
 
 /**
  * registry center implementation for consul
@@ -78,15 +79,20 @@ public class ConsulRegistry extends FailbackRegistry {
             new NamedThreadFactory("dubbo-consul-notifier", true));
     private ConcurrentMap<URL, ConsulNotifier> notifiers = new ConcurrentHashMap<>();
     private ScheduledExecutorService ttlConsulCheckExecutor;
+    /**
+     * The ACL token
+     */
+    private String token;
 
 
     public ConsulRegistry(URL url) {
         super(url);
+        token = url.getParameter(TOKEN_KEY, (String) null);
         String host = url.getHost();
         int port = url.getPort() != 0 ? url.getPort() : DEFAULT_PORT;
         client = new ConsulClient(host, port);
         checkPassInterval = url.getParameter(CHECK_PASS_INTERVAL, DEFAULT_CHECK_PASS_INTERVAL);
-        ttlConsulCheckExecutor = Executors.newSingleThreadScheduledExecutor();
+        ttlConsulCheckExecutor = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("Ttl-Consul-Check-Executor", true));
         ttlConsulCheckExecutor.scheduleAtFixedRate(this::checkPass, checkPassInterval / 8,
                 checkPassInterval / 8, TimeUnit.MILLISECONDS);
     }
@@ -102,7 +108,11 @@ public class ConsulRegistry extends FailbackRegistry {
 
     @Override
     public void doRegister(URL url) {
-        client.agentServiceRegister(buildService(url));
+        if (token == null) {
+            client.agentServiceRegister(buildService(url));
+        } else {
+            client.agentServiceRegister(buildService(url), token);
+        }
     }
 
     @Override
@@ -116,7 +126,11 @@ public class ConsulRegistry extends FailbackRegistry {
 
     @Override
     public void doUnregister(URL url) {
-        client.agentServiceDeregister(buildId(url));
+        if (token == null) {
+            client.agentServiceDeregister(buildId(url));
+        } else {
+            client.agentServiceDeregister(buildId(url), token);
+        }
     }
 
     @Override
@@ -198,12 +212,16 @@ public class ConsulRegistry extends FailbackRegistry {
         for (URL url : getRegistered()) {
             String checkId = buildId(url);
             try {
-                client.agentCheckPass("service:" + checkId);
+                if (token == null) {
+                    client.agentCheckPass("service:" + checkId);
+                } else {
+                    client.agentCheckPass("service:" + checkId, null, token);
+                }
                 if (logger.isDebugEnabled()) {
                     logger.debug("check pass for url: " + url + " with check id: " + checkId);
                 }
             } catch (Throwable t) {
-                logger.warn("fail to check pass for url: " + url + ", check id is: " + checkId);
+                logger.warn("fail to check pass for url: " + url + ", check id is: " + checkId, t);
             }
         }
     }
@@ -213,6 +231,7 @@ public class ConsulRegistry extends FailbackRegistry {
                 .setTag(SERVICE_TAG)
                 .setQueryParams(new QueryParams(watchTimeout, index))
                 .setPassing(true)
+                .setToken(token)
                 .build();
         return client.getHealthServices(service, request);
     }
@@ -220,6 +239,7 @@ public class ConsulRegistry extends FailbackRegistry {
     private Response<Map<String, List<String>>> getAllServices(long index, int watchTimeout) {
         CatalogServicesRequest request = CatalogServicesRequest.newBuilder()
                 .setQueryParams(new QueryParams(watchTimeout, index))
+                .setToken(token)
                 .build();
         return client.getCatalogServices(request);
     }
diff --git a/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryProtocolTest.java b/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryProtocolTest.java
index 7167d9c..8d08b3d 100644
--- a/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryProtocolTest.java
+++ b/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryProtocolTest.java
@@ -30,7 +30,6 @@ import org.apache.dubbo.rpc.Invocation;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Protocol;
 import org.apache.dubbo.rpc.Result;
-import org.apache.dubbo.rpc.cluster.support.FailfastCluster;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.rpc.model.ServiceDescriptor;
 import org.apache.dubbo.rpc.protocol.AbstractInvoker;
@@ -86,7 +85,7 @@ public class RegistryProtocolTest {
     public void testExportUrlNull() {
         Assertions.assertThrows(IllegalArgumentException.class, () -> {
             RegistryProtocol registryProtocol = getRegistryProtocol();
-            registryProtocol.setCluster(new FailfastCluster());
+//            registryProtocol.setCluster(new FailfastCluster());
 
             Protocol dubboProtocol = DubboProtocol.getDubboProtocol();
             registryProtocol.setProtocol(dubboProtocol);
@@ -99,7 +98,7 @@ public class RegistryProtocolTest {
     @Test
     public void testExport() {
         RegistryProtocol registryProtocol = getRegistryProtocol();
-        registryProtocol.setCluster(new FailfastCluster());
+//        registryProtocol.setCluster(new FailfastCluster());
         registryProtocol.setRegistryFactory(ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension());
 
         Protocol dubboProtocol = DubboProtocol.getDubboProtocol();
diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServer.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServer.java
index d4ca4b3..b301a78 100644
--- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServer.java
+++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServer.java
@@ -16,7 +16,6 @@
  */
 package org.apache.dubbo.remoting.transport.netty4;
 
-import io.netty.channel.socket.SocketChannel;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
@@ -37,6 +36,7 @@ import io.netty.channel.ChannelFuture;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelOption;
 import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
 import io.netty.handler.timeout.IdleStateHandler;
 
 import java.net.InetSocketAddress;
@@ -149,8 +149,8 @@ public class NettyServer extends AbstractServer implements RemotingServer {
         }
         try {
             if (bootstrap != null) {
-                bossGroup.shutdownGracefully();
-                workerGroup.shutdownGracefully();
+                bossGroup.shutdownGracefully().syncUninterruptibly();
+                workerGroup.shutdownGracefully().syncUninterruptibly();
             }
         } catch (Throwable e) {
             logger.warn(e.getMessage(), e);
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolFilterWrapper.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolFilterWrapper.java
index 4cbe836..74ac50c 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolFilterWrapper.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolFilterWrapper.java
@@ -18,6 +18,7 @@ package org.apache.dubbo.rpc.protocol;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.extension.Activate;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.utils.UrlUtils;
 import org.apache.dubbo.rpc.Exporter;
@@ -38,6 +39,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.SERVICE_FILTER_K
 /**
  * ListenerProtocol
  */
+@Activate(order = 100)
 public class ProtocolFilterWrapper implements Protocol {
 
     private final Protocol protocol;
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolListenerWrapper.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolListenerWrapper.java
index a28906f..af943c3 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolListenerWrapper.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolListenerWrapper.java
@@ -17,6 +17,7 @@
 package org.apache.dubbo.rpc.protocol;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.Activate;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.utils.UrlUtils;
 import org.apache.dubbo.rpc.Exporter;
@@ -38,6 +39,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.INVOKER_LISTENER
 /**
  * ListenerProtocol
  */
+@Activate(order = 200)
 public class ProtocolListenerWrapper implements Protocol {
 
     private final Protocol protocol;