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/07/02 09:08:38 UTC

[dubbo] branch master updated: 2.7.8 Rlease (#6398)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 4aaaea4  2.7.8 Rlease (#6398)
4aaaea4 is described below

commit 4aaaea43ae75bee99837183c5be37ca2cb942741
Author: Mercy Ma <me...@gmail.com>
AuthorDate: Thu Jul 2 17:08:09 2020 +0800

    2.7.8 Rlease (#6398)
    
    * 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
    
    * 2.7.8 service introspection (#6300)
    
    * 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
    
    * 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
    
    * 2.7.8 service introspection update (#6308)
    
    * 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#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
    
    * 2.7.8 service introspection (#6317)
    
    * 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
    
    * Polish apache/dubbo#6333 : [Refactor] Using mandatory implementation of Service Instance registration instead of the event
    
    * Polish apache/dubbo#6336 : [Refactor] org.apache.dubbo.metadata.ServiceNameMapping
    
    * 2.7.8 service introspection (#6337)
    
    * 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
    
    * Polish apache/dubbo#6333 : [Refactor] Using mandatory implementation of Service Instance registration instead of the event
    
    * 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
    
    * 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
    
    * 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
    
    * 2.7.8 service introspection (#6366)
    
    * 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
    
    * Polish apache/dubbo#6333 : [Refactor] Using mandatory implementation of Service Instance registration instead of the event
    
    * 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
    
    * 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
    
    * 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
    
    * 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
    
    * 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
    
    * 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
    
    * 2.7.8 Test Cases (#6384)
    
    * Polish apache/dubbo#6152
    
    * Polish apache/dubbo#6174 : [Enhancement] Registering the common beans for Spring XML meta-configuration
    
    * Polish apache/dubbo#6174 : Fixing the test-cases
    
    * Polish apache/dubbo#6174 : Fixing the test-cases
    
    * Add the test-case for multiple services with MethodConfigs
    
    * Fixed the test-cases
    
    * 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>
    
    * Fixes the issue of merging code in Github
    
    * Polish apache/dubbo#6389 : [Issue] Resolving the issues with ConsulServiceDiscovery
    
    * Fixes the test-cases
    
    * Fixes the test-cases
    
    * Fixes 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 +
 .../org/apache/dubbo/rpc/cluster/Configurator.java |    2 +-
 .../src/main/java/org/apache/dubbo/common/URL.java |   59 +-
 .../configcenter/AbstractDynamicConfiguration.java |  103 +-
 .../AbstractDynamicConfigurationFactory.java       |    2 +-
 .../config/configcenter/DynamicConfiguration.java  |   10 +
 .../configcenter/TreePathDynamicConfiguration.java |  171 ++
 .../file/FileSystemDynamicConfiguration.java       |  150 +-
 .../dubbo/common/constants/CommonConstants.java    |   29 +
 .../dubbo/common/constants/RegistryConstants.java  |    2 +
 .../org/apache/dubbo/common/convert/Converter.java |   17 +
 .../convert/multiple/MultiValueConverter.java      |   28 +
 .../org/apache/dubbo/common/utils/PathUtils.java   |    7 +-
 .../utils/StringConstantFieldValuePredicate.java   |   67 +
 .../org/apache/dubbo/common/utils/StringUtils.java |   57 +-
 .../dubbo/config/annotation/DubboReference.java    |    9 +
 .../apache/dubbo/config/context/ConfigManager.java |   13 +-
 .../java/org/apache/dubbo/event/EventListener.java |    2 +-
 .../test/java/org/apache/dubbo/common/URLTest.java | 1777 ++++++++++----------
 .../AbstractDynamicConfigurationTest.java          |   60 +-
 .../file/FileSystemDynamicConfigurationTest.java   |   45 +-
 .../common/constants/CommonConstantsTest.java      |   32 +-
 .../convert/ConverterTest.java}                    |   40 +-
 .../convert/StringToBooleanConverterTest.java      |    5 +-
 .../convert/StringToCharArrayConverterTest.java    |    5 +-
 .../convert/StringToCharacterConverterTest.java    |    5 +-
 .../convert/StringToDoubleConverterTest.java       |    5 +-
 .../convert/StringToFloatConverterTest.java        |    5 +-
 .../convert/StringToIntegerConverterTest.java      |    5 +-
 .../convert/StringToLongConverterTest.java         |    5 +-
 .../convert/StringToOptionalConverterTest.java     |    5 +-
 .../convert/StringToShortConverterTest.java        |    5 +-
 .../convert/StringToStringConverterTest.java       |    5 +-
 .../convert/multiple/MultiValueConverterTest.java  |   72 +
 .../multiple/StringToArrayConverterTest.java       |    4 +-
 .../StringToBlockingDequeConverterTest.java        |    4 +-
 .../StringToBlockingQueueConverterTest.java        |    4 +-
 .../multiple/StringToCollectionConverterTest.java  |    5 +-
 .../multiple/StringToDequeConverterTest.java       |    4 +-
 .../multiple/StringToListConverterTest.java        |    4 +-
 .../StringToNavigableSetConverterTest.java         |    6 +-
 .../multiple/StringToQueueConverterTest.java       |    3 +-
 .../convert/multiple/StringToSetConverterTest.java |    3 +-
 .../multiple/StringToSortedSetConverterTest.java   |    6 +-
 .../StringToTransferQueueConverterTest.java        |    6 +-
 .../apache/dubbo/common/utils/PojoUtilsTest.java   |    2 +-
 .../StringConstantFieldValuePredicateTest.java     |   37 +-
 .../apache/dubbo/common/utils/StringUtilsTest.java |   73 +-
 .../dubbo/config/context/ConfigManagerTest.java    |    7 +-
 .../org/apache/dubbo/event/EchoEventListener2.java |    2 +-
 .../java/org/apache/dubbo/config/ConfigTest.java   |    4 +-
 .../apache/dubbo/config/ReferenceConfigTest.java   |    4 +-
 dubbo-config/dubbo-config-api/pom.xml              |   31 +
 .../org/apache/dubbo/config/ReferenceConfig.java   |   68 +-
 .../org/apache/dubbo/config/ServiceConfig.java     |   18 +-
 .../dubbo/config/bootstrap/DubboBootstrap.java     |  196 ++-
 .../bootstrap/builders/ReferenceBuilder.java       |   22 +
 .../config/bootstrap/builders/RegistryBuilder.java |   10 +
 .../PublishingServiceDefinitionListener.java       |   74 +
 .../event/listener/ServiceNameMappingListener.java |    8 +-
 .../metadata/AbstractMetadataServiceExporter.java  |  150 ++
 .../ConfigurableMetadataServiceExporter.java       |   69 +-
 .../metadata/RemoteMetadataServiceExporter.java    |   79 +
 .../dubbo/config/utils/ConfigValidationUtils.java  |    7 +-
 .../internal/org.apache.dubbo.event.EventListener  |    4 +-
 ...g.apache.dubbo.metadata.MetadataServiceExporter |    3 +
 .../apache/dubbo/config/ReferenceConfigTest.java   |    5 +-
 ...va => ConsulDubboServiceConsumerBootstrap.java} |   10 +-
 ...va => ConsulDubboServiceProviderBootstrap.java} |   10 +-
 .../NacosDubboServiceConsumerBootstrap.java        |   21 +-
 .../NacosDubboServiceProviderBootstrap.java        |   20 +-
 .../ZookeeperDubboServiceConsumerBootstrap.java    |   13 +-
 .../ZookeeperDubboServiceProviderBootstrap.java    |   11 +-
 .../bootstrap/builders/ReferenceBuilderTest.java   |   15 +-
 .../bootstrap/builders/RegistryBuilderTest.java    |    2 +-
 .../PublishingServiceDefinitionListenerTest.java   |   94 ++
 .../RemoteMetadataServiceExporterTest.java         |  106 ++
 .../config/url/ExporterSideConfigUrlTest.java      |    5 +-
 .../metadata/MetadataServiceExporterTest.java      |   38 +-
 dubbo-config/dubbo-config-spring/pom.xml           |   21 +
 .../ReferenceAnnotationBeanPostProcessor.java      |   92 +-
 .../schema/AnnotationBeanDefinitionParser.java     |   10 +-
 .../spring/schema/DubboBeanDefinitionParser.java   |   45 +-
 .../src/main/resources/META-INF/dubbo.xsd          |   17 +-
 .../ReferenceAnnotationBeanPostProcessorTest.java  |   13 +
 .../annotation/ReferenceBeanBuilderTest.java       |   29 +-
 .../ServiceAnnotationBeanPostProcessorTest.java    |   13 +
 .../annotation/ServiceClassPostProcessorTest.java  |   13 +
 .../MultipleServicesWithMethodConfigsTest.java}    |   30 +-
 .../DubboComponentScanRegistrarTest.java           |    4 +-
 .../spring/context/annotation/EnableDubboTest.java |    4 +-
 .../consumer/test/TestConsumerConfiguration.java   |    5 +-
 .../properties/DefaultDubboConfigBinderTest.java   |   13 +
 .../dubbo/config/spring/issues/Issue6252Test.java  |   50 +
 .../ZookeeperDubboSpringConsumerBootstrap.java     |   53 +
 .../ZookeeperDubboSpringConsumerXmlBootstrap.java  |   32 +-
 .../ZookeeperDubboSpringProviderBootstrap.java     |   60 +
 .../spring/schema/DubboNamespaceHandlerTest.java   |    4 +-
 .../config/spring/schema/GenericServiceTest.java   |   13 +
 .../resources/META-INF/issue-6252-test.properties  |   11 +
 .../zookeeper-dubbb-consumer.properties            |   14 +
 .../zookeeper-dubbb-provider.properties            |   10 +
 .../zookeeper-dubbo-consumer.xml                   |   34 +
 .../spring/multiple-services-with-methods.xml      |   45 +
 .../consul/ConsulDynamicConfiguration.java         |  123 +-
 .../consul/ConsulDynamicConfigurationTest.java     |   26 +-
 .../support/nacos/NacosDynamicConfiguration.java   |   64 +-
 .../zookeeper/ZookeeperDynamicConfiguration.java   |   68 +-
 .../ZookeeperDynamicConfigurationTest.java         |    4 +-
 .../metadata/CompositeServiceNameMapping.java      |   96 ++
 .../DynamicConfigurationServiceNameMapping.java    |   41 +-
 .../apache/dubbo/metadata/MetadataConstants.java   |   25 +-
 .../org/apache/dubbo/metadata/MetadataService.java |    7 +-
 .../dubbo/metadata/MetadataServiceExporter.java    |   41 +-
 .../apache/dubbo/metadata/MetadataServiceType.java |   72 +
 .../org/apache/dubbo/metadata/MetadataUtil.java    |   49 -
 .../metadata/ParameterizedServiceNameMapping.java} |   31 +-
 .../metadata/PropertiesFileServiceNameMapping.java |  148 ++
 .../metadata/ReadOnlyServiceNameMapping.java}      |   31 +-
 .../apache/dubbo/metadata/ServiceNameMapping.java  |   34 +-
 .../dubbo}/metadata/URLRevisionResolver.java       |   69 +-
 .../dubbo/metadata/WritableMetadataService.java    |    8 +-
 .../dubbo/metadata/report/MetadataReport.java      |  132 +-
 .../BaseApplicationMetadataIdentifier.java         |    9 +-
 .../metadata/report/identifier/KeyTypeEnum.java    |   40 +-
 .../report/support/AbstractMetadataReport.java     |  133 +-
 .../support/ConfigCenterBasedMetadataReport.java   |  162 ++
 .../ConfigCenterBasedMetadataReportFactory.java    |   86 +
 .../file/FileSystemMetadataReportFactory.java}     |   21 +-
 .../AbstractAbstractWritableMetadataService.java   |  100 ++
 .../store/BaseWritableMetadataService.java         |   73 -
 .../store/InMemoryWritableMetadataService.java     |   62 +-
 .../store/RemoteWritableMetadataService.java       |  173 +-
 .../RemoteWritableMetadataServiceDelegate.java     |  100 --
 .../org.apache.dubbo.metadata.ServiceNameMapping   |   10 +-
 ...g.apache.dubbo.metadata.WritableMetadataService |    2 +-
 ...che.dubbo.metadata.report.MetadataReportFactory |    1 +
 .../metadata/CompositeServiceNameMappingTest.java  |  107 ++
 ...DynamicConfigurationServiceNameMappingTest.java |   25 +-
 .../InMemoryWritableMetadataServiceTest.java       |   16 +-
 .../dubbo/metadata/MetadataConstantsTest.java}     |   23 +-
 .../dubbo/metadata/MetadataServiceTypeTest.java    |   37 +-
 .../ParameterizedServiceNameMappingTest.java       |   67 +
 .../PropertiesFileServiceNameMappingTest.java      |   60 +
 .../dubbo/metadata/ServiceNameMappingTest.java     |  121 ++
 .../dubbo}/metadata/URLRevisionResolverTest.java   |   21 +-
 .../report/identifier/KeyTypeEnumTest.java}        |   20 +-
 .../support/AbstractMetadataReportFactoryTest.java |    9 +-
 .../report/support/AbstractMetadataReportTest.java |  121 +-
 .../ConfigCenterBasedMetadataReportTest.java       |  155 ++
 .../store/InMemoryWritableMetadataServiceTest.java |   20 +-
 .../RemoteWritableMetadataServiceDelegateTest.java |  216 ---
 ...java => RemoteWritableMetadataServiceTest.java} |   24 +-
 .../META-INF/dubbo/service-name-mapping.properties |    3 +
 .../dubbo-metadata-report-consul/pom.xml           |    8 +-
 .../store/consul/ConsulMetadataReport.java         |    4 +
 .../store/consul/ConsulMetadataReportFactory.java  |   13 +-
 dubbo-metadata/dubbo-metadata-report-nacos/pom.xml |    9 +-
 .../metadata/store/nacos/NacosMetadataReport.java  |  131 +-
 .../store/nacos/NacosMetadataReportFactory.java    |   13 +-
 .../store/nacos/NacosMetadataReportTest.java       |  247 ---
 .../dubbo-metadata-report-zookeeper/pom.xml        |    2 +-
 .../store/zookeeper/ZookeeperMetadataReport.java   |   29 +
 .../zookeeper/ZookeeperMetadataReportFactory.java  |   22 +-
 .../zookeeper/ZookeeperMetadataReportTest.java     |  553 +++---
 .../registry/client/ServiceDiscoveryRegistry.java  |  143 +-
 .../dubbo/registry/client/ServiceInstance.java     |   22 +
 .../CustomizableServiceInstanceListener.java       |    2 +
 ...ExportedServicesRevisionMetadataCustomizer.java |    1 +
 .../metadata/RefreshServiceMetadataCustomizer.java |   47 -
 .../metadata/ServiceInstanceMetadataUtils.java     |    4 +-
 .../StandardMetadataServiceURLBuilder.java         |    1 +
 ...bscribedServicesRevisionMetadataCustomizer.java |    1 +
 .../proxy/BaseMetadataServiceProxyFactory.java     |   32 +-
 .../CompositeMetadataServiceProxyFactory.java      |  133 ++
 .../proxy/MetadataServiceProxyFactory.java         |    1 -
 .../metadata/proxy/RemoteMetadataServiceProxy.java |   52 +-
 .../internal/org.apache.dubbo.event.EventListener  |    6 +-
 ...dubbo.registry.client.ServiceInstanceCustomizer |    3 +-
 ...ient.metadata.proxy.MetadataServiceProxyFactory |    3 +-
 .../client/DefaultServiceInstanceTest.java         |    9 +
 .../proxy/BaseMetadataServiceProxyFactoryTest.java |   78 +
 .../CompositeMetadataServiceProxyFactoryTest.java  |   96 ++
 .../proxy/MetadataServiceProxyFactoryTest.java     |   49 +
 .../proxy/MyMetadataServiceProxyFactory.java       |   17 +-
 ...ient.metadata.proxy.MetadataServiceProxyFactory |    2 +
 .../dubbo/registry/consul/ConsulParameter.java     |   87 +
 .../registry/consul/ConsulServiceDiscovery.java    |   89 +-
 .../consul/ConsulServiceDiscoveryTest.java         |   16 +-
 .../registry/eureka/EurekaServiceDiscovery.java    |    5 +-
 .../remoting/transport/netty4/NettyServer.java     |    2 +-
 .../java/org/apache/dubbo/rpc/RpcContextTest.java  |    2 -
 .../rpc/protocol/dubbo/ArgumentCallbackTest.java   |   19 +-
 193 files changed, 6424 insertions(+), 3329 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/Configurator.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Configurator.java
index 436f0dc..2a69f6e 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Configurator.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Configurator.java
@@ -27,9 +27,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
-import static org.apache.dubbo.rpc.cluster.Constants.PRIORITY_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL;
+import static org.apache.dubbo.rpc.cluster.Constants.PRIORITY_KEY;
 
 /**
  * Configurator. (SPI, Prototype, ThreadSafe)
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java b/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java
index e605643..6ab3a89 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java
@@ -35,10 +35,12 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
 
 import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
@@ -55,6 +57,8 @@ import static org.apache.dubbo.common.constants.CommonConstants.PORT_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.USERNAME_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
+import static org.apache.dubbo.common.convert.Converter.convertIfPossible;
+import static org.apache.dubbo.common.utils.StringUtils.isBlank;
 
 /**
  * URL - Uniform Resource Locator (Immutable, ThreadSafe)
@@ -566,6 +570,24 @@ class URL implements Serializable {
         return parameters;
     }
 
+    /**
+     * Get the parameters to be selected(filtered)
+     *
+     * @param nameToSelect the {@link Predicate} to select the parameter name
+     * @return non-null {@link Map}
+     * @since 2.7.8
+     */
+    public Map<String, String> getParameters(Predicate<String> nameToSelect) {
+        Map<String, String> selectedParameters = new LinkedHashMap<>();
+        for (Map.Entry<String, String> entry : getParameters().entrySet()) {
+            String name = entry.getKey();
+            if (nameToSelect.test(name)) {
+                selectedParameters.put(name, entry.getValue());
+            }
+        }
+        return Collections.unmodifiableMap(selectedParameters);
+    }
+
     public Map<String, Map<String, String>> getMethodParameters() {
         return methodParameters;
     }
@@ -601,6 +623,41 @@ class URL implements Serializable {
         return Arrays.asList(strArray);
     }
 
+    /**
+     * Get parameter
+     *
+     * @param key       the key of parameter
+     * @param valueType the type of parameter value
+     * @param <T>       the type of parameter value
+     * @return get the parameter if present, or <code>null</code>
+     * @since 2.7.8
+     */
+    public <T> T getParameter(String key, Class<T> valueType) {
+        return getParameter(key, valueType, null);
+    }
+
+    /**
+     * Get parameter
+     *
+     * @param key          the key of parameter
+     * @param valueType    the type of parameter value
+     * @param defaultValue the default value if parameter is absent
+     * @param <T>          the type of parameter value
+     * @return get the parameter if present, or <code>defaultValue</code> will be used.
+     * @since 2.7.8
+     */
+    public <T> T getParameter(String key, Class<T> valueType, T defaultValue) {
+        String value = getParameter(key);
+        T result = null;
+        if (!isBlank(value)) {
+            result = convertIfPossible(value, valueType);
+        }
+        if (result == null) {
+            result = defaultValue;
+        }
+        return result;
+    }
+
     private Map<String, Number> getNumbers() {
         // concurrent initialization is tolerant
         if (numbers == null) {
@@ -1415,7 +1472,7 @@ class URL implements Serializable {
 
     private void append(StringBuilder target, String parameterName, boolean first) {
         String parameterValue = this.getParameter(parameterName);
-        if (!StringUtils.isBlank(parameterValue)) {
+        if (!isBlank(parameterValue)) {
             if (!first) {
                 target.append(":");
             }
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 6e85982..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
@@ -20,6 +20,7 @@ import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.NamedThreadFactory;
+import org.apache.dubbo.common.utils.StringUtils;
 
 import java.util.concurrent.Callable;
 import java.util.concurrent.Future;
@@ -27,6 +28,9 @@ import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
+import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
+
 /**
  * The abstract implementation of {@link DynamicConfiguration}
  *
@@ -47,6 +51,20 @@ public abstract class AbstractDynamicConfiguration implements DynamicConfigurati
      */
     public static final String THREAD_POOL_KEEP_ALIVE_TIME_PARAM_NAME = PARAM_NAME_PREFIX + "thread-pool.keep-alive-time";
 
+    /**
+     * The parameter name of group for config-center
+     *
+     * @since 2.7.8
+     */
+    public static final String GROUP_PARAM_NAME = PARAM_NAME_PREFIX + GROUP_KEY;
+
+    /**
+     * The parameter name of timeout for config-center
+     *
+     * @since 2.7.8
+     */
+    public static final String TIMEOUT_PARAM_NAME = PARAM_NAME_PREFIX + TIMEOUT_KEY;
+
     public static final int DEFAULT_THREAD_POOL_SIZE = 1;
 
     /**
@@ -64,28 +82,31 @@ public abstract class AbstractDynamicConfiguration implements DynamicConfigurati
      */
     private final ThreadPoolExecutor workersThreadPool;
 
-    public AbstractDynamicConfiguration() {
-        this(DEFAULT_THREAD_POOL_PREFIX, DEFAULT_THREAD_POOL_SIZE, DEFAULT_THREAD_POOL_KEEP_ALIVE_TIME);
-    }
+    private final String group;
+
+    private final long timeout;
 
     public AbstractDynamicConfiguration(URL url) {
-        this(getThreadPoolPrefixName(url), getThreadPoolSize(url), getThreadPoolKeepAliveTime(url));
+        this(getThreadPoolPrefixName(url), getThreadPoolSize(url), getThreadPoolKeepAliveTime(url), getGroup(url),
+                getTimeout(url));
     }
 
     public AbstractDynamicConfiguration(String threadPoolPrefixName,
                                         int threadPoolSize,
-                                        long keepAliveTime) {
+                                        long keepAliveTime,
+                                        String group,
+                                        long timeout) {
         this.workersThreadPool = initWorkersThreadPool(threadPoolPrefixName, threadPoolSize, keepAliveTime);
+        this.group = group;
+        this.timeout = timeout;
     }
 
     @Override
     public void addListener(String key, String group, ConfigurationListener listener) {
-
     }
 
     @Override
     public void removeListener(String key, String group, ConfigurationListener listener) {
-
     }
 
     @Override
@@ -107,6 +128,29 @@ public abstract class AbstractDynamicConfiguration implements DynamicConfigurati
         }
     }
 
+    @Override
+    public boolean removeConfig(String key, String group) {
+        return execute(() -> doRemoveConfig(key, group), -1L);
+    }
+
+    /**
+     * @return the default group
+     * @since 2.7.8
+     */
+    @Override
+    public String getDefaultGroup() {
+        return getGroup();
+    }
+
+    /**
+     * @return the default timeout
+     * @since 2.7.8
+     */
+    @Override
+    public long getDefaultTimeout() {
+        return getTimeout();
+    }
+
     /**
      * Get the content of configuration in the specified key and group
      *
@@ -125,6 +169,17 @@ public abstract class AbstractDynamicConfiguration implements DynamicConfigurati
     protected abstract void doClose() throws Exception;
 
     /**
+     * Remove the config in the specified key and group
+     *
+     * @param key   the key
+     * @param group the group
+     * @return If successful, return <code>true</code>, or <code>false</code>
+     * @throws Exception
+     * @since 2.7.8
+     */
+    protected abstract boolean doRemoveConfig(String key, String group) throws Exception;
+
+    /**
      * Executes the {@link Runnable} with the specified timeout
      *
      * @param task    the {@link Runnable task}
@@ -181,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) {
@@ -216,4 +271,36 @@ public abstract class AbstractDynamicConfiguration implements DynamicConfigurati
         }
         return defaultValue;
     }
+
+
+    protected String getGroup() {
+        return group;
+    }
+
+    protected long getTimeout() {
+        return timeout;
+    }
+
+    /**
+     * Get the group from {@link URL the specified connection URL}
+     *
+     * @param url {@link URL the specified connection URL}
+     * @return non-null
+     * @since 2.7.8
+     */
+    protected static String getGroup(URL url) {
+        String group = getParameter(url, GROUP_PARAM_NAME, null);
+        return StringUtils.isBlank(group) ? getParameter(url, GROUP_KEY, DEFAULT_GROUP) : group;
+    }
+
+    /**
+     * Get the timeout from {@link URL the specified connection URL}
+     *
+     * @param url {@link URL the specified connection URL}
+     * @return non-null
+     * @since 2.7.8
+     */
+    protected static long getTimeout(URL url) {
+        return getParameter(url, TIMEOUT_PARAM_NAME, -1L);
+    }
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfigurationFactory.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfigurationFactory.java
index eb35b9b..db53e73 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfigurationFactory.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfigurationFactory.java
@@ -35,7 +35,7 @@ public abstract class AbstractDynamicConfigurationFactory implements DynamicConf
 
     @Override
     public final DynamicConfiguration getDynamicConfiguration(URL url) {
-        String key = url == null ? DEFAULT_KEY : url.getAddress();
+        String key = url == null ? DEFAULT_KEY : url.toServiceString();
         return dynamicConfigurations.computeIfAbsent(key, k -> createDynamicConfiguration(url));
     }
 
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/DynamicConfiguration.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/DynamicConfiguration.java
index 5cda5bf..0b57396 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/DynamicConfiguration.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/DynamicConfiguration.java
@@ -232,4 +232,14 @@ public interface DynamicConfiguration extends Configuration, AutoCloseable {
     static String getRuleKey(URL url) {
         return url.getColonSeparatedKey();
     }
+
+    /**
+     * @param key   the key to represent a configuration
+     * @param group the group where the key belongs to
+     * @return <code>true</code> if success, or <code>false</code>
+     * @since 2.7.8
+     */
+    default boolean removeConfig(String key, String group) {
+        return true;
+    }
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/TreePathDynamicConfiguration.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/TreePathDynamicConfiguration.java
new file mode 100644
index 0000000..cbcb3a2
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/TreePathDynamicConfiguration.java
@@ -0,0 +1,171 @@
+/*
+ * 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.config.configcenter;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.config.configcenter.file.FileSystemDynamicConfiguration;
+import org.apache.dubbo.common.utils.PathUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+
+import java.util.Collection;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import static java.util.Collections.emptySortedSet;
+import static java.util.Collections.unmodifiableSortedSet;
+import static org.apache.dubbo.common.config.configcenter.Constants.CONFIG_NAMESPACE_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.PATH_SEPARATOR;
+import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty;
+import static org.apache.dubbo.common.utils.PathUtils.buildPath;
+
+/**
+ * An abstract implementation of {@link DynamicConfiguration} is like "tree-structure" path :
+ * <ul>
+ *     <li>{@link FileSystemDynamicConfiguration "file"}</li>
+ *     <li>{@link org.apache.dubbo.configcenter.support.zookeeper.ZookeeperDynamicConfiguration "zookeeper"}</li>
+ *     <li>{@link org.apache.dubbo.configcenter.consul.ConsulDynamicConfiguration "consul"}</li>
+ * </ul>
+ *
+ * @see DynamicConfiguration
+ * @see AbstractDynamicConfiguration
+ * @since 2.7.8
+ */
+public abstract class TreePathDynamicConfiguration extends AbstractDynamicConfiguration {
+
+    /**
+     * The parameter name of URL for the config base path
+     */
+    public static final String CONFIG_BASE_PATH_PARAM_NAME = PARAM_NAME_PREFIX + "base-path";
+
+    /**
+     * The default value of parameter of URL for the config base path
+     */
+    public static final String DEFAULT_CONFIG_BASE_PATH = "/config";
+
+    private final String rootPath;
+
+    public TreePathDynamicConfiguration(URL url) {
+        super(url);
+        this.rootPath = getRootPath(url);
+    }
+
+    public TreePathDynamicConfiguration(String rootPath,
+                                        String threadPoolPrefixName,
+                                        int threadPoolSize,
+                                        long keepAliveTime,
+                                        String group,
+                                        long timeout) {
+        super(threadPoolPrefixName, threadPoolSize, keepAliveTime, group, timeout);
+        this.rootPath = rootPath;
+    }
+
+    @Override
+    protected final String doGetConfig(String key, String group) throws Exception {
+        String pathKey = buildPathKey(group, key);
+        return doGetConfig(pathKey);
+    }
+
+    @Override
+    public final boolean publishConfig(String key, String group, String content) {
+        String pathKey = buildPathKey(group, key);
+        return execute(() -> doPublishConfig(pathKey, content), getDefaultTimeout());
+    }
+
+    @Override
+    protected final boolean doRemoveConfig(String key, String group) throws Exception {
+        String pathKey = buildPathKey(group, key);
+        return doRemoveConfig(pathKey);
+    }
+
+    @Override
+    public final void addListener(String key, String group, ConfigurationListener listener) {
+        String pathKey = buildPathKey(group, key);
+        doAddListener(pathKey, listener);
+    }
+
+    @Override
+    public final void removeListener(String key, String group, ConfigurationListener listener) {
+        String pathKey = buildPathKey(group, key);
+        doRemoveListener(pathKey, listener);
+    }
+
+    @Override
+    public final SortedSet<String> getConfigKeys(String group) throws UnsupportedOperationException {
+        String groupPath = buildGroupPath(group);
+        Collection<String> configKeys = doGetConfigKeys(groupPath);
+        return isEmpty(configKeys) ? emptySortedSet() : unmodifiableSortedSet(new TreeSet<>(configKeys));
+    }
+
+    protected abstract boolean doPublishConfig(String pathKey, String content) throws Exception;
+
+    protected abstract String doGetConfig(String pathKey) throws Exception;
+
+    protected abstract boolean doRemoveConfig(String pathKey) throws Exception;
+
+    protected abstract Collection<String> doGetConfigKeys(String groupPath);
+
+    protected abstract void doAddListener(String pathKey, ConfigurationListener listener);
+
+    protected abstract void doRemoveListener(String pathKey, ConfigurationListener listener);
+
+    protected String buildGroupPath(String group) {
+        return buildPath(rootPath, group);
+    }
+
+    protected String buildPathKey(String group, String key) {
+        return buildPath(buildGroupPath(group), key);
+    }
+
+    /**
+     * Get the root path from the specified {@link URL connection URl}
+     *
+     * @param url the specified {@link URL connection URl}
+     * @return non-null
+     */
+    protected String getRootPath(URL url) {
+        String rootPath = PATH_SEPARATOR + getConfigNamespace(url) + getConfigBasePath(url);
+        rootPath = PathUtils.normalize(rootPath);
+        if (rootPath.endsWith(PATH_SEPARATOR)) {
+            rootPath = rootPath.substring(0, rootPath.length() - 1);
+        }
+        return rootPath;
+    }
+
+    /**
+     * Get the namespace from the specified {@link URL connection URl}
+     *
+     * @param url the specified {@link URL connection URl}
+     * @return non-null
+     */
+    protected String getConfigNamespace(URL url) {
+        return url.getParameter(CONFIG_NAMESPACE_KEY, DEFAULT_GROUP);
+    }
+
+    /**
+     * Get the config base path from the specified {@link URL connection URl}
+     *
+     * @param url the specified {@link URL connection URl}
+     * @return non-null
+     */
+    protected String getConfigBasePath(URL url) {
+        String configBasePath = url.getParameter(CONFIG_BASE_PATH_PARAM_NAME, DEFAULT_CONFIG_BASE_PATH);
+        if (StringUtils.isNotEmpty(configBasePath) && !configBasePath.startsWith(PATH_SEPARATOR)) {
+            configBasePath = PATH_SEPARATOR + configBasePath;
+        }
+        return configBasePath;
+    }
+}
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 36fb61f..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
@@ -17,13 +17,14 @@
 package org.apache.dubbo.common.config.configcenter.file;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration;
 import org.apache.dubbo.common.config.configcenter.ConfigChangeType;
 import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
 import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
 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;
 
@@ -39,6 +40,7 @@ import java.nio.file.Path;
 import java.nio.file.WatchEvent;
 import java.nio.file.WatchKey;
 import java.nio.file.WatchService;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
@@ -48,7 +50,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
-import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.concurrent.Callable;
 import java.util.concurrent.SynchronousQueue;
@@ -67,14 +68,13 @@ import static java.util.Collections.unmodifiableMap;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.apache.commons.io.FileUtils.readFileToString;
-import static org.apache.dubbo.common.utils.StringUtils.isBlank;
 
 /**
  * File-System based {@link DynamicConfiguration} implementation
  *
  * @since 2.7.5
  */
-public class FileSystemDynamicConfiguration extends AbstractDynamicConfiguration {
+public class FileSystemDynamicConfiguration extends TreePathDynamicConfiguration {
 
     public static final String CONFIG_CENTER_DIR_PARAM_NAME = PARAM_NAME_PREFIX + "dir";
 
@@ -147,6 +147,7 @@ public class FileSystemDynamicConfiguration extends AbstractDynamicConfiguration
         MODIFIERS = initWatchEventModifiers();
         DELAY = initDelay(MODIFIERS);
         WATCH_EVENTS_LOOP_THREAD_POOL = newWatchEventsLoopThreadPool();
+        registerDubboShutdownHook();
     }
 
     /**
@@ -193,7 +194,7 @@ public class FileSystemDynamicConfiguration extends AbstractDynamicConfiguration
                                           String threadPoolPrefixName,
                                           int threadPoolSize,
                                           long keepAliveTime) {
-        super(threadPoolPrefixName, threadPoolSize, keepAliveTime);
+        super(rootDirectory.getAbsolutePath(), threadPoolPrefixName, threadPoolSize, keepAliveTime, DEFAULT_GROUP, -1L);
         this.rootDirectory = rootDirectory;
         this.encoding = encoding;
         this.processingDirectories = initProcessingDirectories();
@@ -209,47 +210,13 @@ public class FileSystemDynamicConfiguration extends AbstractDynamicConfiguration
         return isBasedPoolingWatchService() ? new LinkedHashSet<>() : emptySet();
     }
 
-    @Override
-    public void addListener(String key, String group, ConfigurationListener listener) {
-        doInListener(key, group, (configFilePath, listeners) -> {
-
-            if (listeners.isEmpty()) { // If no element, it indicates watchService was registered before
-                ThrowableConsumer.execute(configFilePath, configFile -> {
-                    FileUtils.forceMkdirParent(configFile);
-                    // A rootDirectory to be watched
-                    File configDirectory = configFile.getParentFile();
-                    if (configDirectory != null) {
-                        // Register the configDirectory
-                        configDirectory.toPath().register(watchService.get(), INTEREST_PATH_KINDS, MODIFIERS);
-                    }
-                });
-            }
-
-            // Add into cache
-            listeners.add(listener);
-        });
-    }
-
-    @Override
-    public void removeListener(String key, String group, ConfigurationListener listener) {
-        doInListener(key, group, (file, listeners) -> {
-            // Remove into cache
-            listeners.remove(listener);
-        });
-    }
-
-    public File groupDirectory(String group) {
-        String actualGroup = isBlank(group) ? DEFAULT_GROUP : group;
-        return new File(rootDirectory, actualGroup);
-    }
-
     public File configFile(String key, String group) {
-        return new File(groupDirectory(group), key);
+        return new File(buildPathKey(group, key));
     }
 
-    private void doInListener(String key, String group, BiConsumer<File, List<ConfigurationListener>> consumer) {
+    private void doInListener(String configFilePath, BiConsumer<File, List<ConfigurationListener>> consumer) {
         watchService.ifPresent(watchService -> {
-            File configFile = configFile(key, group);
+            File configFile = new File(configFilePath);
             executeMutually(configFile.getParentFile(), () -> {
                 // process the WatchEvents if not start
                 if (!isProcessingWatchEvents()) {
@@ -265,6 +232,24 @@ public class FileSystemDynamicConfiguration extends AbstractDynamicConfiguration
         });
     }
 
+    /**
+     * 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;
     }
@@ -359,47 +344,78 @@ public class FileSystemDynamicConfiguration extends AbstractDynamicConfiguration
     }
 
     @Override
-    public boolean publishConfig(String key, String group, String content) {
-        return delay(key, group, configFile -> {
+    protected boolean doPublishConfig(String pathKey, String content) throws Exception {
+        return delay(pathKey, configFile -> {
             FileUtils.write(configFile, content, getEncoding());
             return true;
         });
     }
 
     @Override
-    public SortedSet<String> getConfigKeys(String group) {
-        File[] files = groupDirectory(group).listFiles(File::isFile);
+    protected String doGetConfig(String pathKey) throws Exception {
+        File configFile = new File(pathKey);
+        return getConfig(configFile);
+    }
+
+    @Override
+    protected boolean doRemoveConfig(String pathKey) throws Exception {
+        delay(pathKey, configFile -> {
+            String content = getConfig(configFile);
+            FileUtils.deleteQuietly(configFile);
+            return content;
+        });
+        return true;
+    }
+
+    @Override
+    protected Collection<String> doGetConfigKeys(String groupPath) {
+        File[] files = new File(groupPath).listFiles(File::isFile);
         if (files == null) {
             return new TreeSet<>();
         } else {
             return Stream.of(files)
                     .map(File::getName)
-                    .collect(TreeSet::new, Set::add, Set::addAll);
+                    .collect(Collectors.toList());
         }
     }
 
-    public String removeConfig(String key, String group) {
-        return delay(key, group, configFile -> {
-
-            String content = getConfig(configFile);
-
-            FileUtils.deleteQuietly(configFile);
+    @Override
+    protected void doAddListener(String pathKey, ConfigurationListener listener) {
+        doInListener(pathKey, (configFilePath, listeners) -> {
+            if (listeners.isEmpty()) { // If no element, it indicates watchService was registered before
+                ThrowableConsumer.execute(configFilePath, configFile -> {
+                    FileUtils.forceMkdirParent(configFile);
+                    // A rootDirectory to be watched
+                    File configDirectory = configFile.getParentFile();
+                    if (configDirectory != null) {
+                        // Register the configDirectory
+                        configDirectory.toPath().register(watchService.get(), INTEREST_PATH_KINDS, MODIFIERS);
+                    }
+                });
+            }
+            // Add into cache
+            listeners.add(listener);
+        });
+    }
 
-            return content;
+    @Override
+    protected void doRemoveListener(String pathKey, ConfigurationListener listener) {
+        doInListener(pathKey, (file, listeners) -> {
+            // Remove into cache
+            listeners.remove(listener);
         });
     }
 
     /**
      * Delay action for {@link #configFile(String, String) config file}
      *
-     * @param key      the key to represent a configuration
-     * @param group    the group where the key belongs to
-     * @param function the customized {@link Function function} with {@link File}
-     * @param <V>      the computed value
+     * @param configFilePath the key to represent a configuration
+     * @param function       the customized {@link Function function} with {@link File}
+     * @param <V>            the computed value
      * @return
      */
-    protected <V> V delay(String key, String group, ThrowableFunction<File, V> function) {
-        File configFile = configFile(key, group);
+    protected <V> V delay(String configFilePath, ThrowableFunction<File, V> function) {
+        File configFile = new File(configFilePath);
         // Must be based on PoolingWatchService and has listeners under config file
         if (isBasedPoolingWatchService()) {
             File configDirectory = configFile.getParentFile();
@@ -410,8 +426,8 @@ public class FileSystemDynamicConfiguration extends AbstractDynamicConfiguration
                         // wait for delay in seconds
                         long timeout = SECONDS.toMillis(delay);
                         if (logger.isDebugEnabled()) {
-                            logger.debug(format("The config[key : %s, group : %s] is about to delay in %d ms.",
-                                    key, group, timeout));
+                            logger.debug(format("The config[path : %s] is about to delay in %d ms.",
+                                    configFilePath, timeout));
                         }
                         configDirectory.wait(timeout);
                     }
@@ -439,9 +455,9 @@ public class FileSystemDynamicConfiguration extends AbstractDynamicConfiguration
     }
 
     /**
-     * Is processing on {@link #groupDirectory(String) config rootDirectory}
+     * Is processing on {@link #buildGroupPath(String) config rootDirectory}
      *
-     * @param configDirectory {@link #groupDirectory(String) config rootDirectory}
+     * @param configDirectory {@link #buildGroupPath(String) config rootDirectory}
      * @return if processing , return <code>true</code>, or <code>false</code>
      */
     private boolean isProcessing(File configDirectory) {
@@ -459,12 +475,6 @@ public class FileSystemDynamicConfiguration extends AbstractDynamicConfiguration
                 .collect(Collectors.toSet());
     }
 
-    @Override
-    protected String doGetConfig(String key, String group) throws Exception {
-        File configFile = configFile(key, group);
-        return getConfig(configFile);
-    }
-
     protected String getConfig(File configFile) {
         return ThrowableFunction.execute(configFile,
                 file -> canRead(configFile) ? readFileToString(configFile, getEncoding()) : null);
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java
index eca3495..657c1ab 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java
@@ -20,6 +20,7 @@ package org.apache.dubbo.common.constants;
 import org.apache.dubbo.common.URL;
 
 import java.net.NetworkInterface;
+import java.util.Properties;
 import java.util.concurrent.ExecutorService;
 import java.util.regex.Pattern;
 
@@ -44,6 +45,11 @@ public interface CommonConstants {
 
     String ANY_VALUE = "*";
 
+    /**
+     * @since 2.7.8
+     */
+    char COMMA_SEPARATOR_CHAR = ',';
+
     String COMMA_SEPARATOR = ",";
 
     String DOT_SEPARATOR = ".";
@@ -188,6 +194,14 @@ public interface CommonConstants {
     String REMOTE_METADATA_STORAGE_TYPE = "remote";
 
     /**
+     * The composite metadata storage type includes {@link #DEFAULT_METADATA_STORAGE_TYPE "local"} and
+     * {@link #REMOTE_METADATA_STORAGE_TYPE "remote"}.
+     *
+     * @since 2.7.8
+     */
+    String COMPOSITE_METADATA_STORAGE_TYPE = "composite";
+
+    /**
      * Consumer side 's proxy class
      */
     String PROXY_CLASS_REF = "refClass";
@@ -315,4 +329,19 @@ public interface CommonConstants {
 
     String SSL_ENABLED_KEY = "ssl-enabled";
 
+
+    /**
+     * The parameter key for the class path of the ServiceNameMapping {@link Properties} file
+     *
+     * @since 2.7.8
+     */
+    String SERVICE_NAME_MAPPING_PROPERTIES_FILE_KEY = "service-name-mapping.properties-path";
+
+    /**
+     * The default class path of the ServiceNameMapping {@link Properties} file
+     *
+     * @since 2.7.8
+     */
+    String DEFAULT_SERVICE_NAME_MAPPING_PROPERTIES_PATH = "META-INF/dubbo/service-name-mapping.properties";
+
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java
index 465c7e3..ce83136 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java
@@ -76,6 +76,8 @@ public interface RegistryConstants {
 
     /**
      * The parameter key of the subscribed service names for Service-Oriented Registry
+     * <p>
+     * If there is a multiple-values, the  "comma" is the separator.
      *
      * @since 2.7.5
      */
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/convert/Converter.java b/dubbo-common/src/main/java/org/apache/dubbo/common/convert/Converter.java
index 5bc2d4d..e36fdf2 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/convert/Converter.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/convert/Converter.java
@@ -88,4 +88,21 @@ public interface Converter<S, T> extends Prioritized {
                 .findFirst()
                 .orElse(null);
     }
+
+    /**
+     * Convert the value of source to target-type value if possible
+     *
+     * @param source     the value of source
+     * @param targetType the target type
+     * @param <T>        the target type
+     * @return <code>null</code> if can't be converted
+     * @since 2.7.8
+     */
+    static <T> T convertIfPossible(Object source, Class<T> targetType) {
+        Converter converter = getConverter(source.getClass(), targetType);
+        if (converter != null) {
+            return (T) converter.convert(source);
+        }
+        return null;
+    }
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/convert/multiple/MultiValueConverter.java b/dubbo-common/src/main/java/org/apache/dubbo/common/convert/multiple/MultiValueConverter.java
index 298b459..637d1a8 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/convert/multiple/MultiValueConverter.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/convert/multiple/MultiValueConverter.java
@@ -16,11 +16,13 @@
  */
 package org.apache.dubbo.common.convert.multiple;
 
+import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.extension.SPI;
 import org.apache.dubbo.common.lang.Prioritized;
 
 import java.util.Collection;
 
+import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader;
 import static org.apache.dubbo.common.utils.TypeUtils.findActualTypeArgument;
 
 /**
@@ -61,4 +63,30 @@ public interface MultiValueConverter<S> extends Prioritized {
         return findActualTypeArgument(getClass(), MultiValueConverter.class, 0);
     }
 
+    /**
+     * Find the {@link MultiValueConverter} instance from {@link ExtensionLoader} with the specified source and target type
+     *
+     * @param sourceType the source type
+     * @param targetType the target type
+     * @return <code>null</code> if not found
+     * @see ExtensionLoader#getSupportedExtensionInstances()
+     * @since 2.7.8
+     */
+    static MultiValueConverter<?> find(Class<?> sourceType, Class<?> targetType) {
+        return getExtensionLoader(MultiValueConverter.class)
+                .getSupportedExtensionInstances()
+                .stream()
+                .filter(converter -> converter.accept(sourceType, targetType))
+                .findFirst()
+                .orElse(null);
+    }
+
+    static <T> T convertIfPossible(Object source, Class<?> multiValueType, Class<?> elementType) {
+        Class<?> sourceType = source.getClass();
+        MultiValueConverter converter = find(sourceType, multiValueType);
+        if (converter != null) {
+            return (T) converter.convert(source, multiValueType, elementType);
+        }
+        return null;
+    }
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PathUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PathUtils.java
index 3e38ce1..c291d79 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PathUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PathUtils.java
@@ -63,7 +63,12 @@ public interface PathUtils {
         if (index > -1) {
             normalizedPath = normalizedPath.substring(0, index);
         }
-        return replace(normalizedPath, "//", "/");
+
+        while (normalizedPath.contains("//")) {
+            normalizedPath = replace(normalizedPath, "//", "/");
+        }
+
+        return normalizedPath;
     }
 
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringConstantFieldValuePredicate.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringConstantFieldValuePredicate.java
new file mode 100644
index 0000000..534a612
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringConstantFieldValuePredicate.java
@@ -0,0 +1,67 @@
+/*
+ * 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.utils;
+
+import java.lang.reflect.Field;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.lang.reflect.Modifier.isFinal;
+import static java.lang.reflect.Modifier.isPublic;
+import static java.lang.reflect.Modifier.isStatic;
+import static org.apache.dubbo.common.utils.FieldUtils.getFieldValue;
+
+/**
+ * The constant field value {@link Predicate} for the specified {@link Class}
+ *
+ * @see Predicate
+ * @since 2.7.8
+ */
+public class StringConstantFieldValuePredicate implements Predicate<String> {
+
+    private final Set<String> constantFieldValues;
+
+    public StringConstantFieldValuePredicate(Class<?> targetClass) {
+        this.constantFieldValues = getConstantFieldValues(targetClass);
+    }
+
+    public static Predicate<String> of(Class<?> targetClass) {
+        return new StringConstantFieldValuePredicate(targetClass);
+    }
+
+    private Set<String> getConstantFieldValues(Class<?> targetClass) {
+        return Stream.of(targetClass.getFields())
+                .filter(f -> isStatic(f.getModifiers()))         // static
+                .filter(f -> isPublic(f.getModifiers()))         // public
+                .filter(f -> isFinal(f.getModifiers()))          // final
+                .map(this::getConstantValue)
+                .filter(v -> v instanceof String)                // filters String type
+                .map(String.class::cast)                         // Casts String type
+                .collect(Collectors.toSet());
+    }
+
+    @Override
+    public boolean test(String s) {
+        return constantFieldValues.contains(s);
+    }
+
+    private Object getConstantValue(Field field) {
+        return getFieldValue(null, field);
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java
index 42ee05b..fc44475 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java
@@ -28,13 +28,17 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import static java.lang.String.valueOf;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.unmodifiableSet;
 import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR;
 import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN;
 import static org.apache.dubbo.common.constants.CommonConstants.DOT_REGEX;
@@ -700,6 +704,45 @@ public final class StringUtils {
     }
 
     /**
+     * Split the specified value to be a {@link Set}
+     *
+     * @param value         the content to be split
+     * @param separatorChar a char to separate
+     * @return non-null read-only {@link Set}
+     * @since 2.7.8
+     */
+    public static Set<String> splitToSet(String value, char separatorChar) {
+        return splitToSet(value, separatorChar, false);
+    }
+
+    /**
+     * Split the specified value to be a {@link Set}
+     *
+     * @param value         the content to be split
+     * @param separatorChar a char to separate
+     * @param trimElements  require to trim the elements or not
+     * @return non-null read-only {@link Set}
+     * @since 2.7.8
+     */
+    public static Set<String> splitToSet(String value, char separatorChar, boolean trimElements) {
+        List<String> values = splitToList(value, separatorChar);
+        int size = values.size();
+
+        if (size < 1) { // empty condition
+            return emptySet();
+        }
+
+        if (!trimElements) { // Do not require to trim the elements
+            return new LinkedHashSet(values);
+        }
+
+        return unmodifiableSet(values
+                .stream()
+                .map(String::trim)
+                .collect(LinkedHashSet::new, Set::add, Set::addAll));
+    }
+
+    /**
      * join string.
      *
      * @param array String array.
@@ -797,7 +840,7 @@ public final class StringUtils {
     }
 
     public static String getQueryStringValue(String qs, String key) {
-        Map<String, String> map = StringUtils.parseQueryString(qs);
+        Map<String, String> map = parseQueryString(qs);
         return map.get(key);
     }
 
@@ -1051,4 +1094,16 @@ public final class StringUtils {
         return (byte) ((hi << 4) + lo);
     }
 
+    /**
+     * Create the common-delimited {@link String} by one or more {@link String} members
+     *
+     * @param one    one {@link String}
+     * @param others others {@link String}
+     * @return <code>null</code> if <code>one</code> or <code>others</code> is <code>null</code>
+     * @since 2.7.8
+     */
+    public static String toCommaDelimitedString(String one, String... others) {
+        String another = arrayToDelimitedString(others, COMMA_SEPARATOR);
+        return isEmpty(another) ? one : one + COMMA_SEPARATOR + another;
+    }
 }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/DubboReference.java b/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/DubboReference.java
index eb1a2e6..5492ac0 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/DubboReference.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/annotation/DubboReference.java
@@ -16,6 +16,8 @@
  */
 package org.apache.dubbo.config.annotation;
 
+import org.apache.dubbo.common.constants.RegistryConstants;
+
 import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -279,4 +281,11 @@ public @interface DubboReference {
      * @since 2.7.3
      */
     String id() default "";
+
+    /**
+     * @return The service names that the Dubbo interface subscribed
+     * @see RegistryConstants#SUBSCRIBED_SERVICE_NAMES_KEY
+     * @since 2.7.8
+     */
+    String[] services() default {};
 }
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 ed0188a..868fadc 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() {
     }
 
@@ -372,6 +372,15 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
     }
 
     /**
+     * @throws IllegalStateException
+     * @since 2.7.8
+     */
+    @Override
+    public void destroy() throws IllegalStateException {
+        clear();
+    }
+
+    /**
      * Add the dubbo {@link AbstractConfig config}
      *
      * @param config the dubbo {@link AbstractConfig config}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/event/EventListener.java b/dubbo-common/src/main/java/org/apache/dubbo/event/EventListener.java
index 06c6f6a..bb36c77 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/event/EventListener.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/event/EventListener.java
@@ -56,7 +56,7 @@ public interface EventListener<E extends Event> extends java.util.EventListener,
      * The comparison rule , refer to {@link #compareTo}.
      */
     default int getPriority() {
-        return MIN_PRIORITY;
+        return NORMAL_PRIORITY;
     }
 
     /**
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..5ba606d 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
@@ -1,877 +1,900 @@
-/*
- * 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;
-
-import org.apache.dubbo.common.utils.CollectionUtils;
-
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.hamcrest.CoreMatchers.anyOf;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-
-public class URLTest {
-
-    @Test
-    public void test_valueOf_noProtocolAndHost() throws Exception {
-        URL url = URL.valueOf("/context/path?version=1.0.0&application=morgan");
-        assertURLStrDecoder(url);
-        assertNull(url.getProtocol());
-        assertNull(url.getUsername());
-        assertNull(url.getPassword());
-        assertNull(url.getHost());
-        assertNull(url.getAddress());
-        assertEquals(0, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
-        assertEquals("morgan", url.getParameter("application"));
-
-        url = URL.valueOf("context/path?version=1.0.0&application=morgan");
-        //                 ^^^^^^^ Caution , parse as host
-        assertURLStrDecoder(url);
-        assertNull(url.getProtocol());
-        assertNull(url.getUsername());
-        assertNull(url.getPassword());
-        assertEquals("context", url.getHost());
-        assertEquals(0, url.getPort());
-        assertEquals("path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
-        assertEquals("morgan", url.getParameter("application"));
-    }
-
-    private void assertURLStrDecoder(URL url) {
-        String fullURLStr = url.toFullString();
-        URL newUrl =  URLStrParser.parseEncodedStr(URL.encode(fullURLStr));
-        assertEquals(URL.valueOf(fullURLStr), newUrl);
-
-        URL newUrl2 =  URLStrParser.parseDecodedStr(fullURLStr);
-        assertEquals(URL.valueOf(fullURLStr), newUrl2);
-    }
-
-    @Test
-    public void test_valueOf_noProtocol() throws Exception {
-        URL url = URL.valueOf("10.20.130.230");
-        assertURLStrDecoder(url);
-        assertNull(url.getProtocol());
-        assertNull(url.getUsername());
-        assertNull(url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230", url.getAddress());
-        assertEquals(0, url.getPort());
-        assertNull(url.getPath());
-        assertEquals(0, url.getParameters().size());
-
-        url = URL.valueOf("10.20.130.230:20880");
-        assertURLStrDecoder(url);
-        assertNull(url.getProtocol());
-        assertNull(url.getUsername());
-        assertNull(url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertNull(url.getPath());
-        assertEquals(0, url.getParameters().size());
-
-        url = URL.valueOf("10.20.130.230/context/path");
-        assertURLStrDecoder(url);
-        assertNull(url.getProtocol());
-        assertNull(url.getUsername());
-        assertNull(url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230", url.getAddress());
-        assertEquals(0, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(0, url.getParameters().size());
-
-        url = URL.valueOf("10.20.130.230:20880/context/path");
-        assertURLStrDecoder(url);
-        assertNull(url.getProtocol());
-        assertNull(url.getUsername());
-        assertNull(url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(0, url.getParameters().size());
-
-        url = URL.valueOf("admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
-        assertURLStrDecoder(url);
-        assertNull(url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
-        assertEquals("morgan", url.getParameter("application"));
-    }
-
-    @Test
-    public void test_valueOf_noHost() throws Exception {
-        URL url = URL.valueOf("file:///home/user1/router.js");
-        assertURLStrDecoder(url);
-        assertEquals("file", url.getProtocol());
-        assertNull(url.getUsername());
-        assertNull(url.getPassword());
-        assertNull(url.getHost());
-        assertNull(url.getAddress());
-        assertEquals(0, url.getPort());
-        assertEquals("home/user1/router.js", url.getPath());
-        assertEquals(0, url.getParameters().size());
-
-        // Caution!!
-        url = URL.valueOf("file://home/user1/router.js");
-        //                      ^^ only tow slash!
-        assertURLStrDecoder(url);
-        assertEquals("file", url.getProtocol());
-        assertNull(url.getUsername());
-        assertNull(url.getPassword());
-        assertEquals("home", url.getHost());
-        assertEquals(0, url.getPort());
-        assertEquals("user1/router.js", url.getPath());
-        assertEquals(0, url.getParameters().size());
-
-
-        url = URL.valueOf("file:/home/user1/router.js");
-        assertURLStrDecoder(url);
-        assertEquals("file", url.getProtocol());
-        assertNull(url.getUsername());
-        assertNull(url.getPassword());
-        assertNull(url.getHost());
-        assertNull(url.getAddress());
-        assertEquals(0, url.getPort());
-        assertEquals("home/user1/router.js", url.getPath());
-        assertEquals(0, url.getParameters().size());
-
-        url = URL.valueOf("file:///d:/home/user1/router.js");
-        assertURLStrDecoder(url);
-        assertEquals("file", url.getProtocol());
-        assertNull(url.getUsername());
-        assertNull(url.getPassword());
-        assertNull(url.getHost());
-        assertNull(url.getAddress());
-        assertEquals(0, url.getPort());
-        assertEquals("d:/home/user1/router.js", url.getPath());
-        assertEquals(0, url.getParameters().size());
-
-        url = URL.valueOf("file:///home/user1/router.js?p1=v1&p2=v2");
-        assertURLStrDecoder(url);
-        assertEquals("file", url.getProtocol());
-        assertNull(url.getUsername());
-        assertNull(url.getPassword());
-        assertNull(url.getHost());
-        assertNull(url.getAddress());
-        assertEquals(0, url.getPort());
-        assertEquals("home/user1/router.js", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        Map<String, String> params = new HashMap<String, String>();
-        params.put("p1", "v1");
-        params.put("p2", "v2");
-        assertEquals(params, url.getParameters());
-
-        url = URL.valueOf("file:/home/user1/router.js?p1=v1&p2=v2");
-        assertURLStrDecoder(url);
-        assertEquals("file", url.getProtocol());
-        assertNull(url.getUsername());
-        assertNull(url.getPassword());
-        assertNull(url.getHost());
-        assertNull(url.getAddress());
-        assertEquals(0, url.getPort());
-        assertEquals("home/user1/router.js", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        params = new HashMap<String, String>();
-        params.put("p1", "v1");
-        params.put("p2", "v2");
-        assertEquals(params, url.getParameters());
-    }
-
-    @Test
-    public void test_valueOf_WithProtocolHost() throws Exception {
-        URL url = URL.valueOf("dubbo://10.20.130.230");
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertNull(url.getUsername());
-        assertNull(url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230", url.getAddress());
-        assertEquals(0, url.getPort());
-        assertNull(url.getPath());
-        assertEquals(0, url.getParameters().size());
-
-        url = URL.valueOf("dubbo://10.20.130.230:20880/context/path");
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertNull(url.getUsername());
-        assertNull(url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(0, url.getParameters().size());
-
-        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880");
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertNull(url.getPath());
-        assertEquals(0, url.getParameters().size());
-
-        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880?version=1.0.0");
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertNull(url.getPath());
-        assertEquals(1, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
-
-        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
-        assertEquals("morgan", url.getParameter("application"));
-
-        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&noValue");
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(3, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
-        assertEquals("morgan", url.getParameter("application"));
-        assertEquals("noValue", url.getParameter("noValue"));
-    }
-
-    // TODO Do not want to use spaces? See: DUBBO-502, URL class handles special conventions for special characters.
-    @Test
-    public void test_valueOf_spaceSafe() throws Exception {
-        URL url = URL.valueOf("http://1.2.3.4:8080/path?key=value1 value2");
-        assertURLStrDecoder(url);
-        assertEquals("http://1.2.3.4:8080/path?key=value1 value2", url.toString());
-        assertEquals("value1 value2", url.getParameter("key"));
-    }
-
-    @Test
-    public void test_noValueKey() throws Exception {
-        URL url = URL.valueOf("http://1.2.3.4:8080/path?k0&k1=v1");
-
-        assertURLStrDecoder(url);
-        assertTrue(url.hasParameter("k0"));
-
-        // If a Key has no corresponding Value, then the Key also used as the Value.
-        assertEquals("k0", url.getParameter("k0"));
-    }
-
-    @Test
-    public void test_valueOf_Exception_noProtocol() throws Exception {
-        try {
-            URL.valueOf("://1.2.3.4:8080/path");
-            fail();
-        } catch (IllegalStateException expected) {
-            assertEquals("url missing protocol: \"://1.2.3.4:8080/path\"", expected.getMessage());
-        }
-
-        try {
-            String encodedURLStr = URL.encode("://1.2.3.4:8080/path");
-            URLStrParser.parseEncodedStr(encodedURLStr);
-            fail();
-        } catch (IllegalStateException expected) {
-            assertEquals("url missing protocol: \"://1.2.3.4:8080/path\"", URL.decode(expected.getMessage()));
-        }
-
-        try {
-            URLStrParser.parseDecodedStr("://1.2.3.4:8080/path");
-            fail();
-        } catch (IllegalStateException expected) {
-            assertEquals("url missing protocol: \"://1.2.3.4:8080/path\"", expected.getMessage());
-        }
-    }
-
-    @Test
-    public void test_getAddress() throws Exception {
-        URL url1 = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
-        assertURLStrDecoder(url1);
-        assertEquals("10.20.130.230:20880", url1.getAddress());
-    }
-
-    @Test
-    public void test_getAbsolutePath() throws Exception {
-        URL url = new URL("p1", "1.2.2.2", 33);
-        assertURLStrDecoder(url);
-        assertNull(url.getAbsolutePath());
-
-        url = new URL("file", null, 90, "/home/user1/route.js");
-        assertURLStrDecoder(url);
-        assertEquals("/home/user1/route.js", url.getAbsolutePath());
-    }
-
-    @Test
-    public void test_equals() throws Exception {
-        URL url1 = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
-        assertURLStrDecoder(url1);
-
-        Map<String, String> params = new HashMap<String, String>();
-        params.put("version", "1.0.0");
-        params.put("application", "morgan");
-        URL url2 = new URL("dubbo", "admin", "hello1234", "10.20.130.230", 20880, "context/path", params);
-
-        assertURLStrDecoder(url2);
-        assertEquals(url1, url2);
-    }
-
-    @Test
-    public void test_toString() throws Exception {
-        URL url1 = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
-        assertURLStrDecoder(url1);
-        assertThat(url1.toString(), anyOf(
-                equalTo("dubbo://10.20.130.230:20880/context/path?version=1.0.0&application=morgan"),
-                equalTo("dubbo://10.20.130.230:20880/context/path?application=morgan&version=1.0.0"))
-        );
-    }
-
-    @Test
-    public void test_toFullString() throws Exception {
-        URL url1 = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
-        assertURLStrDecoder(url1);
-        assertThat(url1.toFullString(), anyOf(
-                equalTo("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan"),
-                equalTo("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan&version=1.0.0"))
-        );
-    }
-
-    @Test
-    public void test_set_methods() throws Exception {
-        URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
-        assertURLStrDecoder(url);
-
-        url = url.setHost("host");
-
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("host", url.getHost());
-        assertEquals("host:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
-        assertEquals("morgan", url.getParameter("application"));
-
-        url = url.setPort(1);
-
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("host", url.getHost());
-        assertEquals("host:1", url.getAddress());
-        assertEquals(1, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
-        assertEquals("morgan", url.getParameter("application"));
-
-        url = url.setPath("path");
-
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("host", url.getHost());
-        assertEquals("host:1", url.getAddress());
-        assertEquals(1, url.getPort());
-        assertEquals("path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
-        assertEquals("morgan", url.getParameter("application"));
-
-        url = url.setProtocol("protocol");
-
-        assertURLStrDecoder(url);
-        assertEquals("protocol", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("host", url.getHost());
-        assertEquals("host:1", url.getAddress());
-        assertEquals(1, url.getPort());
-        assertEquals("path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
-        assertEquals("morgan", url.getParameter("application"));
-
-        url = url.setUsername("username");
-
-        assertURLStrDecoder(url);
-        assertEquals("protocol", url.getProtocol());
-        assertEquals("username", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("host", url.getHost());
-        assertEquals("host:1", url.getAddress());
-        assertEquals(1, url.getPort());
-        assertEquals("path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
-        assertEquals("morgan", url.getParameter("application"));
-
-        url = url.setPassword("password");
-
-        assertURLStrDecoder(url);
-        assertEquals("protocol", url.getProtocol());
-        assertEquals("username", url.getUsername());
-        assertEquals("password", url.getPassword());
-        assertEquals("host", url.getHost());
-        assertEquals("host:1", url.getAddress());
-        assertEquals(1, url.getPort());
-        assertEquals("path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
-        assertEquals("morgan", url.getParameter("application"));
-    }
-
-    @Test
-    public void test_removeParameters() throws Exception {
-        URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&k1=v1&k2=v2");
-        assertURLStrDecoder(url);
-
-        url = url.removeParameter("version");
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(3, url.getParameters().size());
-        assertEquals("morgan", url.getParameter("application"));
-        assertEquals("v1", url.getParameter("k1"));
-        assertEquals("v2", url.getParameter("k2"));
-        assertNull(url.getParameter("version"));
-
-        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&k1=v1&k2=v2");
-        url = url.removeParameters("version", "application", "NotExistedKey");
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("v1", url.getParameter("k1"));
-        assertEquals("v2", url.getParameter("k2"));
-        assertNull(url.getParameter("version"));
-        assertNull(url.getParameter("application"));
-
-        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&k1=v1&k2=v2");
-        url = url.removeParameters(Arrays.asList("version", "application"));
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("v1", url.getParameter("k1"));
-        assertEquals("v2", url.getParameter("k2"));
-        assertNull(url.getParameter("version"));
-        assertNull(url.getParameter("application"));
-    }
-
-    @Test
-    public void test_addParameter() throws Exception {
-        URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
-        url = url.addParameter("k1", "v1");
-
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("morgan", url.getParameter("application"));
-        assertEquals("v1", url.getParameter("k1"));
-    }
-
-    @Test
-    public void test_addParameter_sameKv() throws Exception {
-        URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan&k1=v1");
-        URL newUrl = url.addParameter("k1", "v1");
-
-        assertURLStrDecoder(url);
-        assertSame(newUrl, url);
-    }
-
-
-    @Test
-    public void test_addParameters() throws Exception {
-        URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
-        url = url.addParameters(CollectionUtils.toStringMap("k1", "v1", "k2", "v2"));
-
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(3, url.getParameters().size());
-        assertEquals("morgan", url.getParameter("application"));
-        assertEquals("v1", url.getParameter("k1"));
-        assertEquals("v2", url.getParameter("k2"));
-
-        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
-        url = url.addParameters("k1", "v1", "k2", "v2", "application", "xxx");
-
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(3, url.getParameters().size());
-        assertEquals("xxx", url.getParameter("application"));
-        assertEquals("v1", url.getParameter("k1"));
-        assertEquals("v2", url.getParameter("k2"));
-
-        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
-        url = url.addParametersIfAbsent(CollectionUtils.toStringMap("k1", "v1", "k2", "v2", "application", "xxx"));
-
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(3, url.getParameters().size());
-        assertEquals("morgan", url.getParameter("application"));
-        assertEquals("v1", url.getParameter("k1"));
-        assertEquals("v2", url.getParameter("k2"));
-
-        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
-        url = url.addParameter("k1", "v1");
-
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("morgan", url.getParameter("application"));
-        assertEquals("v1", url.getParameter("k1"));
-
-        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
-        url = url.addParameter("application", "xxx");
-
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(1, url.getParameters().size());
-        assertEquals("xxx", url.getParameter("application"));
-    }
-
-    @Test
-    public void test_addParameters_SameKv() throws Exception {
-        {
-            URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan&k1=v1");
-            URL newUrl = url.addParameters(CollectionUtils.toStringMap("k1", "v1"));
-
-            assertURLStrDecoder(url);
-            assertSame(url, newUrl);
-        }
-        {
-            URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan&k1=v1&k2=v2");
-            URL newUrl = url.addParameters(CollectionUtils.toStringMap("k1", "v1", "k2", "v2"));
-
-            assertURLStrDecoder(url);
-            assertSame(newUrl, url);
-        }
-    }
-
-    @Test
-    public void test_addParameterIfAbsent() throws Exception {
-        URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
-        url = url.addParameterIfAbsent("application", "xxx");
-
-        assertURLStrDecoder(url);
-        assertEquals("dubbo", url.getProtocol());
-        assertEquals("admin", url.getUsername());
-        assertEquals("hello1234", url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(1, url.getParameters().size());
-        assertEquals("morgan", url.getParameter("application"));
-    }
-
-    @Test
-    public void test_windowAbsolutePathBeginWithSlashIsValid() throws Exception {
-        final String osProperty = System.getProperties().getProperty("os.name");
-        if (!osProperty.toLowerCase().contains("windows")) return;
-
-        System.out.println("Test Windows valid path string.");
-
-        File f0 = new File("C:/Windows");
-        File f1 = new File("/C:/Windows");
-
-        File f2 = new File("C:\\Windows");
-        File f3 = new File("/C:\\Windows");
-        File f4 = new File("\\C:\\Windows");
-
-        assertEquals(f0, f1);
-        assertEquals(f0, f2);
-        assertEquals(f0, f3);
-        assertEquals(f0, f4);
-    }
-
-    @Test
-    public void test_javaNetUrl() throws Exception {
-        java.net.URL url = new java.net.URL("http://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan#anchor1");
-
-        assertEquals("http", url.getProtocol());
-        assertEquals("admin:hello1234", url.getUserInfo());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals(20880, url.getPort());
-        assertEquals("/context/path", url.getPath());
-        assertEquals("version=1.0.0&application=morgan", url.getQuery());
-        assertEquals("anchor1", url.getRef());
-
-        assertEquals("admin:hello1234@10.20.130.230:20880", url.getAuthority());
-        assertEquals("/context/path?version=1.0.0&application=morgan", url.getFile());
-    }
-
-    @Test
-    public void test_Anyhost() throws Exception {
-        URL url = URL.valueOf("dubbo://0.0.0.0:20880");
-        assertURLStrDecoder(url);
-        assertEquals("0.0.0.0", url.getHost());
-        assertTrue(url.isAnyHost());
-    }
-
-    @Test
-    public void test_Localhost() throws Exception {
-        URL url = URL.valueOf("dubbo://127.0.0.1:20880");
-        assertURLStrDecoder(url);
-        assertEquals("127.0.0.1", url.getHost());
-        assertEquals("127.0.0.1:20880", url.getAddress());
-        assertTrue(url.isLocalHost());
-
-        url = URL.valueOf("dubbo://127.0.1.1:20880");
-        assertURLStrDecoder(url);
-        assertEquals("127.0.1.1", url.getHost());
-        assertEquals("127.0.1.1:20880", url.getAddress());
-        assertTrue(url.isLocalHost());
-
-        url = URL.valueOf("dubbo://localhost:20880");
-        assertURLStrDecoder(url);
-        assertEquals("localhost", url.getHost());
-        assertEquals("localhost:20880", url.getAddress());
-        assertTrue(url.isLocalHost());
-    }
-
-    @Test
-    public void test_Path() throws Exception {
-        URL url = new URL("dubbo", "localhost", 20880, "////path");
-        assertURLStrDecoder(url);
-        assertEquals("path", url.getPath());
-    }
-
-    @Test
-    public void testAddParameters() throws Exception {
-        URL url = URL.valueOf("dubbo://127.0.0.1:20880");
-        assertURLStrDecoder(url);
-
-        Map<String, String> parameters = new HashMap<String, String>();
-        parameters.put("version", null);
-        url.addParameters(parameters);
-        assertURLStrDecoder(url);
-    }
-
-    @Test
-    public void testUserNamePasswordContainsAt() {
-        // Test username or password contains "@"
-        URL url = URL.valueOf("ad@min:hello@1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
-        assertURLStrDecoder(url);
-        assertNull(url.getProtocol());
-        assertEquals("ad@min", url.getUsername());
-        assertEquals("hello@1234", url.getPassword());
-        assertEquals("10.20.130.230", url.getHost());
-        assertEquals("10.20.130.230:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
-        assertEquals("morgan", url.getParameter("application"));
-    }
-
-
-    @Test
-    public void testIpV6Address() {
-        // Test username or password contains "@"
-        URL url = URL.valueOf("ad@min111:haha@1234@2001:0db8:85a3:08d3:1319:8a2e:0370:7344:20880/context/path?version=1.0.0&application=morgan");
-        assertURLStrDecoder(url);
-        assertNull(url.getProtocol());
-        assertEquals("ad@min111", url.getUsername());
-        assertEquals("haha@1234", url.getPassword());
-        assertEquals("2001:0db8:85a3:08d3:1319:8a2e:0370:7344", url.getHost());
-        assertEquals("2001:0db8:85a3:08d3:1319:8a2e:0370:7344:20880", url.getAddress());
-        assertEquals(20880, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
-        assertEquals("morgan", url.getParameter("application"));
-    }
-
-    @Test
-    public void testIpV6AddressWithScopeId() {
-        URL url = URL.valueOf("2001:0db8:85a3:08d3:1319:8a2e:0370:7344%5/context/path?version=1.0.0&application=morgan");
-        assertURLStrDecoder(url);
-        assertNull(url.getProtocol());
-        assertEquals("2001:0db8:85a3:08d3:1319:8a2e:0370:7344%5", url.getHost());
-        assertEquals("2001:0db8:85a3:08d3:1319:8a2e:0370:7344%5", url.getAddress());
-        assertEquals(0, url.getPort());
-        assertEquals("context/path", url.getPath());
-        assertEquals(2, url.getParameters().size());
-        assertEquals("1.0.0", url.getParameter("version"));
-        assertEquals("morgan", url.getParameter("application"));
-    }
-
-    @Test
-    public void testDefaultPort() {
-        Assertions.assertEquals("10.20.153.10:2181", URL.appendDefaultPort("10.20.153.10:0", 2181));
-        Assertions.assertEquals("10.20.153.10:2181", URL.appendDefaultPort("10.20.153.10", 2181));
-    }
-
-    @Test
-    public void testGetServiceKey() {
-        URL url1 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName");
-        assertURLStrDecoder(url1);
-        Assertions.assertEquals("org.apache.dubbo.test.interfaceName", url1.getServiceKey());
-
-        URL url2 = URL.valueOf("10.20.130.230:20880/org.apache.dubbo.test.interfaceName?interface=org.apache.dubbo.test.interfaceName");
-        assertURLStrDecoder(url2);
-        Assertions.assertEquals("org.apache.dubbo.test.interfaceName", url2.getServiceKey());
-
-        URL url3 = URL.valueOf("10.20.130.230:20880/org.apache.dubbo.test.interfaceName?interface=org.apache.dubbo.test.interfaceName&group=group1&version=1.0.0");
-        assertURLStrDecoder(url3);
-        Assertions.assertEquals("group1/org.apache.dubbo.test.interfaceName:1.0.0", url3.getServiceKey());
-
-        URL url4 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName");
-        assertURLStrDecoder(url4);
-        Assertions.assertEquals("context/path", url4.getPathKey());
-
-        URL url5 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&group=group1&version=1.0.0");
-        assertURLStrDecoder(url5);
-        Assertions.assertEquals("group1/context/path:1.0.0", url5.getPathKey());
-    }
-
-    @Test
-    public void testGetColonSeparatedKey() {
-        URL url1 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&group=group&version=1.0.0");
-        assertURLStrDecoder(url1);
-        Assertions.assertEquals("org.apache.dubbo.test.interfaceName:1.0.0:group", url1.getColonSeparatedKey());
-
-        URL url2 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&version=1.0.0");
-        assertURLStrDecoder(url2);
-        Assertions.assertEquals("org.apache.dubbo.test.interfaceName:1.0.0:", url2.getColonSeparatedKey());
-
-        URL url3 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&group=group");
-        assertURLStrDecoder(url3);
-        Assertions.assertEquals("org.apache.dubbo.test.interfaceName::group", url3.getColonSeparatedKey());
-
-        URL url4 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName");
-        assertURLStrDecoder(url4);
-        Assertions.assertEquals("org.apache.dubbo.test.interfaceName::", url4.getColonSeparatedKey());
-
-        URL url5 = URL.valueOf("10.20.130.230:20880/org.apache.dubbo.test.interfaceName");
-        assertURLStrDecoder(url5);
-        Assertions.assertEquals("org.apache.dubbo.test.interfaceName::", url5.getColonSeparatedKey());
-
-        URL url6 = URL.valueOf("10.20.130.230:20880/org.apache.dubbo.test.interfaceName?interface=org.apache.dubbo.test.interfaceName1");
-        assertURLStrDecoder(url6);
-        Assertions.assertEquals("org.apache.dubbo.test.interfaceName1::", url6.getColonSeparatedKey());
-    }
-
-    @Test
-    public void testValueOf() {
-        URL url = URL.valueOf("10.20.130.230");
-        assertURLStrDecoder(url);
-
-        url = URL.valueOf("10.20.130.230:20880");
-        assertURLStrDecoder(url);
-
-        url = URL.valueOf("dubbo://10.20.130.230:20880");
-        assertURLStrDecoder(url);
-
-        url = URL.valueOf("dubbo://10.20.130.230:20880/path");
-        assertURLStrDecoder(url);
-    }
-}
+/*
+ * 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;
+
+import org.apache.dubbo.common.utils.CollectionUtils;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+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;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class URLTest {
+
+    @Test
+    public void test_valueOf_noProtocolAndHost() throws Exception {
+        URL url = URL.valueOf("/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url);
+        assertNull(url.getProtocol());
+        assertNull(url.getUsername());
+        assertNull(url.getPassword());
+        assertNull(url.getHost());
+        assertNull(url.getAddress());
+        assertEquals(0, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("morgan", url.getParameter("application"));
+
+        url = URL.valueOf("context/path?version=1.0.0&application=morgan");
+        //                 ^^^^^^^ Caution , parse as host
+        assertURLStrDecoder(url);
+        assertNull(url.getProtocol());
+        assertNull(url.getUsername());
+        assertNull(url.getPassword());
+        assertEquals("context", url.getHost());
+        assertEquals(0, url.getPort());
+        assertEquals("path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("morgan", url.getParameter("application"));
+    }
+
+    private void assertURLStrDecoder(URL url) {
+        String fullURLStr = url.toFullString();
+        URL newUrl = URLStrParser.parseEncodedStr(URL.encode(fullURLStr));
+        assertEquals(URL.valueOf(fullURLStr), newUrl);
+
+        URL newUrl2 = URLStrParser.parseDecodedStr(fullURLStr);
+        assertEquals(URL.valueOf(fullURLStr), newUrl2);
+    }
+
+    @Test
+    public void test_valueOf_noProtocol() throws Exception {
+        URL url = URL.valueOf("10.20.130.230");
+        assertURLStrDecoder(url);
+        assertNull(url.getProtocol());
+        assertNull(url.getUsername());
+        assertNull(url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230", url.getAddress());
+        assertEquals(0, url.getPort());
+        assertNull(url.getPath());
+        assertEquals(0, url.getParameters().size());
+
+        url = URL.valueOf("10.20.130.230:20880");
+        assertURLStrDecoder(url);
+        assertNull(url.getProtocol());
+        assertNull(url.getUsername());
+        assertNull(url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertNull(url.getPath());
+        assertEquals(0, url.getParameters().size());
+
+        url = URL.valueOf("10.20.130.230/context/path");
+        assertURLStrDecoder(url);
+        assertNull(url.getProtocol());
+        assertNull(url.getUsername());
+        assertNull(url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230", url.getAddress());
+        assertEquals(0, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(0, url.getParameters().size());
+
+        url = URL.valueOf("10.20.130.230:20880/context/path");
+        assertURLStrDecoder(url);
+        assertNull(url.getProtocol());
+        assertNull(url.getUsername());
+        assertNull(url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(0, url.getParameters().size());
+
+        url = URL.valueOf("admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url);
+        assertNull(url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("morgan", url.getParameter("application"));
+    }
+
+    @Test
+    public void test_valueOf_noHost() throws Exception {
+        URL url = URL.valueOf("file:///home/user1/router.js");
+        assertURLStrDecoder(url);
+        assertEquals("file", url.getProtocol());
+        assertNull(url.getUsername());
+        assertNull(url.getPassword());
+        assertNull(url.getHost());
+        assertNull(url.getAddress());
+        assertEquals(0, url.getPort());
+        assertEquals("home/user1/router.js", url.getPath());
+        assertEquals(0, url.getParameters().size());
+
+        // Caution!!
+        url = URL.valueOf("file://home/user1/router.js");
+        //                      ^^ only tow slash!
+        assertURLStrDecoder(url);
+        assertEquals("file", url.getProtocol());
+        assertNull(url.getUsername());
+        assertNull(url.getPassword());
+        assertEquals("home", url.getHost());
+        assertEquals(0, url.getPort());
+        assertEquals("user1/router.js", url.getPath());
+        assertEquals(0, url.getParameters().size());
+
+
+        url = URL.valueOf("file:/home/user1/router.js");
+        assertURLStrDecoder(url);
+        assertEquals("file", url.getProtocol());
+        assertNull(url.getUsername());
+        assertNull(url.getPassword());
+        assertNull(url.getHost());
+        assertNull(url.getAddress());
+        assertEquals(0, url.getPort());
+        assertEquals("home/user1/router.js", url.getPath());
+        assertEquals(0, url.getParameters().size());
+
+        url = URL.valueOf("file:///d:/home/user1/router.js");
+        assertURLStrDecoder(url);
+        assertEquals("file", url.getProtocol());
+        assertNull(url.getUsername());
+        assertNull(url.getPassword());
+        assertNull(url.getHost());
+        assertNull(url.getAddress());
+        assertEquals(0, url.getPort());
+        assertEquals("d:/home/user1/router.js", url.getPath());
+        assertEquals(0, url.getParameters().size());
+
+        url = URL.valueOf("file:///home/user1/router.js?p1=v1&p2=v2");
+        assertURLStrDecoder(url);
+        assertEquals("file", url.getProtocol());
+        assertNull(url.getUsername());
+        assertNull(url.getPassword());
+        assertNull(url.getHost());
+        assertNull(url.getAddress());
+        assertEquals(0, url.getPort());
+        assertEquals("home/user1/router.js", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        Map<String, String> params = new HashMap<String, String>();
+        params.put("p1", "v1");
+        params.put("p2", "v2");
+        assertEquals(params, url.getParameters());
+
+        url = URL.valueOf("file:/home/user1/router.js?p1=v1&p2=v2");
+        assertURLStrDecoder(url);
+        assertEquals("file", url.getProtocol());
+        assertNull(url.getUsername());
+        assertNull(url.getPassword());
+        assertNull(url.getHost());
+        assertNull(url.getAddress());
+        assertEquals(0, url.getPort());
+        assertEquals("home/user1/router.js", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        params = new HashMap<String, String>();
+        params.put("p1", "v1");
+        params.put("p2", "v2");
+        assertEquals(params, url.getParameters());
+    }
+
+    @Test
+    public void test_valueOf_WithProtocolHost() throws Exception {
+        URL url = URL.valueOf("dubbo://10.20.130.230");
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertNull(url.getUsername());
+        assertNull(url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230", url.getAddress());
+        assertEquals(0, url.getPort());
+        assertNull(url.getPath());
+        assertEquals(0, url.getParameters().size());
+
+        url = URL.valueOf("dubbo://10.20.130.230:20880/context/path");
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertNull(url.getUsername());
+        assertNull(url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(0, url.getParameters().size());
+
+        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880");
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertNull(url.getPath());
+        assertEquals(0, url.getParameters().size());
+
+        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880?version=1.0.0");
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertNull(url.getPath());
+        assertEquals(1, url.getParameters().size());
+        assertEquals("1.0.0", url.getParameter("version"));
+
+        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("morgan", url.getParameter("application"));
+
+        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&noValue");
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(3, url.getParameters().size());
+        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("morgan", url.getParameter("application"));
+        assertEquals("noValue", url.getParameter("noValue"));
+    }
+
+    // TODO Do not want to use spaces? See: DUBBO-502, URL class handles special conventions for special characters.
+    @Test
+    public void test_valueOf_spaceSafe() throws Exception {
+        URL url = URL.valueOf("http://1.2.3.4:8080/path?key=value1 value2");
+        assertURLStrDecoder(url);
+        assertEquals("http://1.2.3.4:8080/path?key=value1 value2", url.toString());
+        assertEquals("value1 value2", url.getParameter("key"));
+    }
+
+    @Test
+    public void test_noValueKey() throws Exception {
+        URL url = URL.valueOf("http://1.2.3.4:8080/path?k0&k1=v1");
+
+        assertURLStrDecoder(url);
+        assertTrue(url.hasParameter("k0"));
+
+        // If a Key has no corresponding Value, then the Key also used as the Value.
+        assertEquals("k0", url.getParameter("k0"));
+    }
+
+    @Test
+    public void test_valueOf_Exception_noProtocol() throws Exception {
+        try {
+            URL.valueOf("://1.2.3.4:8080/path");
+            fail();
+        } catch (IllegalStateException expected) {
+            assertEquals("url missing protocol: \"://1.2.3.4:8080/path\"", expected.getMessage());
+        }
+
+        try {
+            String encodedURLStr = URL.encode("://1.2.3.4:8080/path");
+            URLStrParser.parseEncodedStr(encodedURLStr);
+            fail();
+        } catch (IllegalStateException expected) {
+            assertEquals("url missing protocol: \"://1.2.3.4:8080/path\"", URL.decode(expected.getMessage()));
+        }
+
+        try {
+            URLStrParser.parseDecodedStr("://1.2.3.4:8080/path");
+            fail();
+        } catch (IllegalStateException expected) {
+            assertEquals("url missing protocol: \"://1.2.3.4:8080/path\"", expected.getMessage());
+        }
+    }
+
+    @Test
+    public void test_getAddress() throws Exception {
+        URL url1 = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url1);
+        assertEquals("10.20.130.230:20880", url1.getAddress());
+    }
+
+    @Test
+    public void test_getAbsolutePath() throws Exception {
+        URL url = new URL("p1", "1.2.2.2", 33);
+        assertURLStrDecoder(url);
+        assertNull(url.getAbsolutePath());
+
+        url = new URL("file", null, 90, "/home/user1/route.js");
+        assertURLStrDecoder(url);
+        assertEquals("/home/user1/route.js", url.getAbsolutePath());
+    }
+
+    @Test
+    public void test_equals() throws Exception {
+        URL url1 = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url1);
+
+        Map<String, String> params = new HashMap<String, String>();
+        params.put("version", "1.0.0");
+        params.put("application", "morgan");
+        URL url2 = new URL("dubbo", "admin", "hello1234", "10.20.130.230", 20880, "context/path", params);
+
+        assertURLStrDecoder(url2);
+        assertEquals(url1, url2);
+    }
+
+    @Test
+    public void test_toString() throws Exception {
+        URL url1 = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url1);
+        assertThat(url1.toString(), anyOf(
+                equalTo("dubbo://10.20.130.230:20880/context/path?version=1.0.0&application=morgan"),
+                equalTo("dubbo://10.20.130.230:20880/context/path?application=morgan&version=1.0.0"))
+        );
+    }
+
+    @Test
+    public void test_toFullString() throws Exception {
+        URL url1 = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url1);
+        assertThat(url1.toFullString(), anyOf(
+                equalTo("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan"),
+                equalTo("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan&version=1.0.0"))
+        );
+    }
+
+    @Test
+    public void test_set_methods() throws Exception {
+        URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url);
+
+        url = url.setHost("host");
+
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("host", url.getHost());
+        assertEquals("host:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("morgan", url.getParameter("application"));
+
+        url = url.setPort(1);
+
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("host", url.getHost());
+        assertEquals("host:1", url.getAddress());
+        assertEquals(1, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("morgan", url.getParameter("application"));
+
+        url = url.setPath("path");
+
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("host", url.getHost());
+        assertEquals("host:1", url.getAddress());
+        assertEquals(1, url.getPort());
+        assertEquals("path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("morgan", url.getParameter("application"));
+
+        url = url.setProtocol("protocol");
+
+        assertURLStrDecoder(url);
+        assertEquals("protocol", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("host", url.getHost());
+        assertEquals("host:1", url.getAddress());
+        assertEquals(1, url.getPort());
+        assertEquals("path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("morgan", url.getParameter("application"));
+
+        url = url.setUsername("username");
+
+        assertURLStrDecoder(url);
+        assertEquals("protocol", url.getProtocol());
+        assertEquals("username", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("host", url.getHost());
+        assertEquals("host:1", url.getAddress());
+        assertEquals(1, url.getPort());
+        assertEquals("path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("morgan", url.getParameter("application"));
+
+        url = url.setPassword("password");
+
+        assertURLStrDecoder(url);
+        assertEquals("protocol", url.getProtocol());
+        assertEquals("username", url.getUsername());
+        assertEquals("password", url.getPassword());
+        assertEquals("host", url.getHost());
+        assertEquals("host:1", url.getAddress());
+        assertEquals(1, url.getPort());
+        assertEquals("path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("morgan", url.getParameter("application"));
+    }
+
+    @Test
+    public void test_removeParameters() throws Exception {
+        URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&k1=v1&k2=v2");
+        assertURLStrDecoder(url);
+
+        url = url.removeParameter("version");
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(3, url.getParameters().size());
+        assertEquals("morgan", url.getParameter("application"));
+        assertEquals("v1", url.getParameter("k1"));
+        assertEquals("v2", url.getParameter("k2"));
+        assertNull(url.getParameter("version"));
+
+        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&k1=v1&k2=v2");
+        url = url.removeParameters("version", "application", "NotExistedKey");
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("v1", url.getParameter("k1"));
+        assertEquals("v2", url.getParameter("k2"));
+        assertNull(url.getParameter("version"));
+        assertNull(url.getParameter("application"));
+
+        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&k1=v1&k2=v2");
+        url = url.removeParameters(Arrays.asList("version", "application"));
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("v1", url.getParameter("k1"));
+        assertEquals("v2", url.getParameter("k2"));
+        assertNull(url.getParameter("version"));
+        assertNull(url.getParameter("application"));
+    }
+
+    @Test
+    public void test_addParameter() throws Exception {
+        URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
+        url = url.addParameter("k1", "v1");
+
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("morgan", url.getParameter("application"));
+        assertEquals("v1", url.getParameter("k1"));
+    }
+
+    @Test
+    public void test_addParameter_sameKv() throws Exception {
+        URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan&k1=v1");
+        URL newUrl = url.addParameter("k1", "v1");
+
+        assertURLStrDecoder(url);
+        assertSame(newUrl, url);
+    }
+
+
+    @Test
+    public void test_addParameters() throws Exception {
+        URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
+        url = url.addParameters(CollectionUtils.toStringMap("k1", "v1", "k2", "v2"));
+
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(3, url.getParameters().size());
+        assertEquals("morgan", url.getParameter("application"));
+        assertEquals("v1", url.getParameter("k1"));
+        assertEquals("v2", url.getParameter("k2"));
+
+        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
+        url = url.addParameters("k1", "v1", "k2", "v2", "application", "xxx");
+
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(3, url.getParameters().size());
+        assertEquals("xxx", url.getParameter("application"));
+        assertEquals("v1", url.getParameter("k1"));
+        assertEquals("v2", url.getParameter("k2"));
+
+        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
+        url = url.addParametersIfAbsent(CollectionUtils.toStringMap("k1", "v1", "k2", "v2", "application", "xxx"));
+
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(3, url.getParameters().size());
+        assertEquals("morgan", url.getParameter("application"));
+        assertEquals("v1", url.getParameter("k1"));
+        assertEquals("v2", url.getParameter("k2"));
+
+        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
+        url = url.addParameter("k1", "v1");
+
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("morgan", url.getParameter("application"));
+        assertEquals("v1", url.getParameter("k1"));
+
+        url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
+        url = url.addParameter("application", "xxx");
+
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(1, url.getParameters().size());
+        assertEquals("xxx", url.getParameter("application"));
+    }
+
+    @Test
+    public void test_addParameters_SameKv() throws Exception {
+        {
+            URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan&k1=v1");
+            URL newUrl = url.addParameters(CollectionUtils.toStringMap("k1", "v1"));
+
+            assertURLStrDecoder(url);
+            assertSame(url, newUrl);
+        }
+        {
+            URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan&k1=v1&k2=v2");
+            URL newUrl = url.addParameters(CollectionUtils.toStringMap("k1", "v1", "k2", "v2"));
+
+            assertURLStrDecoder(url);
+            assertSame(newUrl, url);
+        }
+    }
+
+    @Test
+    public void test_addParameterIfAbsent() throws Exception {
+        URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan");
+        url = url.addParameterIfAbsent("application", "xxx");
+
+        assertURLStrDecoder(url);
+        assertEquals("dubbo", url.getProtocol());
+        assertEquals("admin", url.getUsername());
+        assertEquals("hello1234", url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(1, url.getParameters().size());
+        assertEquals("morgan", url.getParameter("application"));
+    }
+
+    @Test
+    public void test_windowAbsolutePathBeginWithSlashIsValid() throws Exception {
+        final String osProperty = System.getProperties().getProperty("os.name");
+        if (!osProperty.toLowerCase().contains("windows")) return;
+
+        System.out.println("Test Windows valid path string.");
+
+        File f0 = new File("C:/Windows");
+        File f1 = new File("/C:/Windows");
+
+        File f2 = new File("C:\\Windows");
+        File f3 = new File("/C:\\Windows");
+        File f4 = new File("\\C:\\Windows");
+
+        assertEquals(f0, f1);
+        assertEquals(f0, f2);
+        assertEquals(f0, f3);
+        assertEquals(f0, f4);
+    }
+
+    @Test
+    public void test_javaNetUrl() throws Exception {
+        java.net.URL url = new java.net.URL("http://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan#anchor1");
+
+        assertEquals("http", url.getProtocol());
+        assertEquals("admin:hello1234", url.getUserInfo());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals(20880, url.getPort());
+        assertEquals("/context/path", url.getPath());
+        assertEquals("version=1.0.0&application=morgan", url.getQuery());
+        assertEquals("anchor1", url.getRef());
+
+        assertEquals("admin:hello1234@10.20.130.230:20880", url.getAuthority());
+        assertEquals("/context/path?version=1.0.0&application=morgan", url.getFile());
+    }
+
+    @Test
+    public void test_Anyhost() throws Exception {
+        URL url = URL.valueOf("dubbo://0.0.0.0:20880");
+        assertURLStrDecoder(url);
+        assertEquals("0.0.0.0", url.getHost());
+        assertTrue(url.isAnyHost());
+    }
+
+    @Test
+    public void test_Localhost() throws Exception {
+        URL url = URL.valueOf("dubbo://127.0.0.1:20880");
+        assertURLStrDecoder(url);
+        assertEquals("127.0.0.1", url.getHost());
+        assertEquals("127.0.0.1:20880", url.getAddress());
+        assertTrue(url.isLocalHost());
+
+        url = URL.valueOf("dubbo://127.0.1.1:20880");
+        assertURLStrDecoder(url);
+        assertEquals("127.0.1.1", url.getHost());
+        assertEquals("127.0.1.1:20880", url.getAddress());
+        assertTrue(url.isLocalHost());
+
+        url = URL.valueOf("dubbo://localhost:20880");
+        assertURLStrDecoder(url);
+        assertEquals("localhost", url.getHost());
+        assertEquals("localhost:20880", url.getAddress());
+        assertTrue(url.isLocalHost());
+    }
+
+    @Test
+    public void test_Path() throws Exception {
+        URL url = new URL("dubbo", "localhost", 20880, "////path");
+        assertURLStrDecoder(url);
+        assertEquals("path", url.getPath());
+    }
+
+    @Test
+    public void testAddParameters() throws Exception {
+        URL url = URL.valueOf("dubbo://127.0.0.1:20880");
+        assertURLStrDecoder(url);
+
+        Map<String, String> parameters = new HashMap<String, String>();
+        parameters.put("version", null);
+        url.addParameters(parameters);
+        assertURLStrDecoder(url);
+    }
+
+    @Test
+    public void testUserNamePasswordContainsAt() {
+        // Test username or password contains "@"
+        URL url = URL.valueOf("ad@min:hello@1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url);
+        assertNull(url.getProtocol());
+        assertEquals("ad@min", url.getUsername());
+        assertEquals("hello@1234", url.getPassword());
+        assertEquals("10.20.130.230", url.getHost());
+        assertEquals("10.20.130.230:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("morgan", url.getParameter("application"));
+    }
+
+
+    @Test
+    public void testIpV6Address() {
+        // Test username or password contains "@"
+        URL url = URL.valueOf("ad@min111:haha@1234@2001:0db8:85a3:08d3:1319:8a2e:0370:7344:20880/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url);
+        assertNull(url.getProtocol());
+        assertEquals("ad@min111", url.getUsername());
+        assertEquals("haha@1234", url.getPassword());
+        assertEquals("2001:0db8:85a3:08d3:1319:8a2e:0370:7344", url.getHost());
+        assertEquals("2001:0db8:85a3:08d3:1319:8a2e:0370:7344:20880", url.getAddress());
+        assertEquals(20880, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("morgan", url.getParameter("application"));
+    }
+
+    @Test
+    public void testIpV6AddressWithScopeId() {
+        URL url = URL.valueOf("2001:0db8:85a3:08d3:1319:8a2e:0370:7344%5/context/path?version=1.0.0&application=morgan");
+        assertURLStrDecoder(url);
+        assertNull(url.getProtocol());
+        assertEquals("2001:0db8:85a3:08d3:1319:8a2e:0370:7344%5", url.getHost());
+        assertEquals("2001:0db8:85a3:08d3:1319:8a2e:0370:7344%5", url.getAddress());
+        assertEquals(0, url.getPort());
+        assertEquals("context/path", url.getPath());
+        assertEquals(2, url.getParameters().size());
+        assertEquals("1.0.0", url.getParameter("version"));
+        assertEquals("morgan", url.getParameter("application"));
+    }
+
+    @Test
+    public void testDefaultPort() {
+        Assertions.assertEquals("10.20.153.10:2181", URL.appendDefaultPort("10.20.153.10:0", 2181));
+        Assertions.assertEquals("10.20.153.10:2181", URL.appendDefaultPort("10.20.153.10", 2181));
+    }
+
+    @Test
+    public void testGetServiceKey() {
+        URL url1 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName");
+        assertURLStrDecoder(url1);
+        Assertions.assertEquals("org.apache.dubbo.test.interfaceName", url1.getServiceKey());
+
+        URL url2 = URL.valueOf("10.20.130.230:20880/org.apache.dubbo.test.interfaceName?interface=org.apache.dubbo.test.interfaceName");
+        assertURLStrDecoder(url2);
+        Assertions.assertEquals("org.apache.dubbo.test.interfaceName", url2.getServiceKey());
+
+        URL url3 = URL.valueOf("10.20.130.230:20880/org.apache.dubbo.test.interfaceName?interface=org.apache.dubbo.test.interfaceName&group=group1&version=1.0.0");
+        assertURLStrDecoder(url3);
+        Assertions.assertEquals("group1/org.apache.dubbo.test.interfaceName:1.0.0", url3.getServiceKey());
+
+        URL url4 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName");
+        assertURLStrDecoder(url4);
+        Assertions.assertEquals("context/path", url4.getPathKey());
+
+        URL url5 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&group=group1&version=1.0.0");
+        assertURLStrDecoder(url5);
+        Assertions.assertEquals("group1/context/path:1.0.0", url5.getPathKey());
+    }
+
+    @Test
+    public void testGetColonSeparatedKey() {
+        URL url1 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&group=group&version=1.0.0");
+        assertURLStrDecoder(url1);
+        Assertions.assertEquals("org.apache.dubbo.test.interfaceName:1.0.0:group", url1.getColonSeparatedKey());
+
+        URL url2 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&version=1.0.0");
+        assertURLStrDecoder(url2);
+        Assertions.assertEquals("org.apache.dubbo.test.interfaceName:1.0.0:", url2.getColonSeparatedKey());
+
+        URL url3 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&group=group");
+        assertURLStrDecoder(url3);
+        Assertions.assertEquals("org.apache.dubbo.test.interfaceName::group", url3.getColonSeparatedKey());
+
+        URL url4 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName");
+        assertURLStrDecoder(url4);
+        Assertions.assertEquals("org.apache.dubbo.test.interfaceName::", url4.getColonSeparatedKey());
+
+        URL url5 = URL.valueOf("10.20.130.230:20880/org.apache.dubbo.test.interfaceName");
+        assertURLStrDecoder(url5);
+        Assertions.assertEquals("org.apache.dubbo.test.interfaceName::", url5.getColonSeparatedKey());
+
+        URL url6 = URL.valueOf("10.20.130.230:20880/org.apache.dubbo.test.interfaceName?interface=org.apache.dubbo.test.interfaceName1");
+        assertURLStrDecoder(url6);
+        Assertions.assertEquals("org.apache.dubbo.test.interfaceName1::", url6.getColonSeparatedKey());
+    }
+
+    @Test
+    public void testValueOf() {
+        URL url = URL.valueOf("10.20.130.230");
+        assertURLStrDecoder(url);
+
+        url = URL.valueOf("10.20.130.230:20880");
+        assertURLStrDecoder(url);
+
+        url = URL.valueOf("dubbo://10.20.130.230:20880");
+        assertURLStrDecoder(url);
+
+        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);
+    }
+
+    @Test
+    public void testGetParameter() {
+        URL url = URL.valueOf("http://127.0.0.1:8080/path?i=1&b=false");
+        assertEquals(Integer.valueOf(1), url.getParameter("i", Integer.class));
+        assertEquals(Boolean.FALSE, url.getParameter("b", Boolean.class));
+    }
+}
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 4f2f700..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,15 +28,17 @@ 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.assertTrue;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
  * {@link AbstractDynamicConfiguration} Test
@@ -49,7 +51,7 @@ public class AbstractDynamicConfigurationTest {
 
     @BeforeEach
     public void init() {
-        configuration = new AbstractDynamicConfiguration() {
+        configuration = new AbstractDynamicConfiguration(null) {
             @Override
             protected String doGetConfig(String key, String group) throws Exception {
                 return null;
@@ -59,6 +61,11 @@ public class AbstractDynamicConfigurationTest {
             protected void doClose() throws Exception {
 
             }
+
+            @Override
+            protected boolean doRemoveConfig(String key, String group) throws Exception {
+                return false;
+            }
         };
     }
 
@@ -71,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
@@ -91,6 +102,11 @@ public class AbstractDynamicConfigurationTest {
             protected void doClose() throws Exception {
 
             }
+
+            @Override
+            protected boolean doRemoveConfig(String key, String group) throws Exception {
+                return false;
+            }
         };
 
         ThreadPoolExecutor threadPoolExecutor = configuration.getWorkersThreadPool();
@@ -149,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 1422ba6..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");
@@ -141,10 +144,36 @@ public class FileSystemDynamicConfigurationTest {
 
 
         processedEvent.set(false);
-        File keyFile = configuration.configFile(KEY, DEFAULT_GROUP);
+        configuration.getRootDirectory();
+        File keyFile = new File(KEY, DEFAULT_GROUP);
         FileUtils.deleteQuietly(keyFile);
         while (!processedEvent.get()) {
             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-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/URLRevisionResolverTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/constants/CommonConstantsTest.java
similarity index 50%
copy from dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/URLRevisionResolverTest.java
copy to dubbo-common/src/test/java/org/apache/dubbo/common/constants/CommonConstantsTest.java
index 3386bba..bc85dfb 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/URLRevisionResolverTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/constants/CommonConstantsTest.java
@@ -14,34 +14,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.registry.client.metadata;
+package org.apache.dubbo.common.constants;
 
 import org.junit.jupiter.api.Test;
 
-import static java.util.Arrays.asList;
-import static org.apache.dubbo.registry.client.metadata.URLRevisionResolver.NO_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.registry.client.metadata.URLRevisionResolverTest";
-
-    private final URLRevisionResolver resolver = new URLRevisionResolver();
+public class CommonConstantsTest {
 
     @Test
-    public void testResolve() {
-        String revision = resolver.resolve(asList());
-        assertEquals(NO_REVISION, revision);
-
-        revision = resolver.resolve(null);
-        assertEquals(NO_REVISION, revision);
-
-        revision = resolver.resolve(asList(URL));
-        assertEquals("7960327984321481979", 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/convert/StringToOptionalConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/ConverterTest.java
similarity index 53%
copy from dubbo-common/src/test/java/org/apache/dubbo/convert/StringToOptionalConverterTest.java
copy to dubbo-common/src/test/java/org/apache/dubbo/common/convert/ConverterTest.java
index 242ae60..1f5a36c 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToOptionalConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/ConverterTest.java
@@ -14,42 +14,36 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert;
+package org.apache.dubbo.common.convert;
 
-import org.apache.dubbo.common.convert.Converter;
-import org.apache.dubbo.common.convert.StringToOptionalConverter;
-
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import java.util.Optional;
-
+import static org.apache.dubbo.common.convert.Converter.convertIfPossible;
+import static org.apache.dubbo.common.convert.Converter.getConverter;
 import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader;
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertSame;
 
 /**
- * {@link StringToOptionalConverter} Test
+ * {@link Converter} Test-Cases
  *
- * @since 2.7.6
+ * @since 2.7.8
  */
-public class StringToOptionalConverterTest {
-
-    private StringToOptionalConverter converter;
-
-    @BeforeEach
-    public void init() {
-        converter = (StringToOptionalConverter) getExtensionLoader(Converter.class).getExtension("string-to-optional");
-    }
+public class ConverterTest {
 
     @Test
-    public void testAccept() {
-        assertTrue(converter.accept(String.class, Optional.class));
+    public void testGetConverter() {
+        getExtensionLoader(Converter.class)
+                .getSupportedExtensionInstances()
+                .forEach(converter -> {
+                    assertSame(converter, getConverter(converter.getSourceType(), converter.getTargetType()));
+                });
     }
 
     @Test
-    public void testConvert() {
-        assertEquals(Optional.of("1"), converter.convert("1"));
-        assertEquals(Optional.empty(), converter.convert(null));
+    public void testConvertIfPossible() {
+        assertEquals(Integer.valueOf(2), convertIfPossible("2", Integer.class));
+        assertEquals(Boolean.FALSE, convertIfPossible("false", Boolean.class));
+        assertEquals(Double.valueOf(1), convertIfPossible("1", Double.class));
     }
 }
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToBooleanConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToBooleanConverterTest.java
similarity index 92%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/StringToBooleanConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToBooleanConverterTest.java
index 3b1d75b..e955fa9 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToBooleanConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToBooleanConverterTest.java
@@ -14,10 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert;
-
-import org.apache.dubbo.common.convert.Converter;
-import org.apache.dubbo.common.convert.StringToBooleanConverter;
+package org.apache.dubbo.common.convert;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToCharArrayConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToCharArrayConverterTest.java
similarity index 92%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/StringToCharArrayConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToCharArrayConverterTest.java
index 492a129..bc3a606 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToCharArrayConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToCharArrayConverterTest.java
@@ -14,10 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert;
-
-import org.apache.dubbo.common.convert.Converter;
-import org.apache.dubbo.common.convert.StringToCharArrayConverter;
+package org.apache.dubbo.common.convert;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToCharacterConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToCharacterConverterTest.java
similarity index 92%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/StringToCharacterConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToCharacterConverterTest.java
index c9e88c2..87f3367 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToCharacterConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToCharacterConverterTest.java
@@ -14,10 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert;
-
-import org.apache.dubbo.common.convert.Converter;
-import org.apache.dubbo.common.convert.StringToCharacterConverter;
+package org.apache.dubbo.common.convert;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToDoubleConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToDoubleConverterTest.java
similarity index 92%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/StringToDoubleConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToDoubleConverterTest.java
index 668f3e6..2c74736 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToDoubleConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToDoubleConverterTest.java
@@ -14,10 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert;
-
-import org.apache.dubbo.common.convert.Converter;
-import org.apache.dubbo.common.convert.StringToDoubleConverter;
+package org.apache.dubbo.common.convert;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToFloatConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToFloatConverterTest.java
similarity index 92%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/StringToFloatConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToFloatConverterTest.java
index aa17499..b4b36f3 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToFloatConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToFloatConverterTest.java
@@ -14,10 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert;
-
-import org.apache.dubbo.common.convert.Converter;
-import org.apache.dubbo.common.convert.StringToFloatConverter;
+package org.apache.dubbo.common.convert;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToIntegerConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToIntegerConverterTest.java
similarity index 92%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/StringToIntegerConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToIntegerConverterTest.java
index 9c7d24b..1ccebfd 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToIntegerConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToIntegerConverterTest.java
@@ -14,10 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert;
-
-import org.apache.dubbo.common.convert.Converter;
-import org.apache.dubbo.common.convert.StringToIntegerConverter;
+package org.apache.dubbo.common.convert;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToLongConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToLongConverterTest.java
similarity index 92%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/StringToLongConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToLongConverterTest.java
index e14424a..c7cd926 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToLongConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToLongConverterTest.java
@@ -14,10 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert;
-
-import org.apache.dubbo.common.convert.Converter;
-import org.apache.dubbo.common.convert.StringToLongConverter;
+package org.apache.dubbo.common.convert;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToOptionalConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToOptionalConverterTest.java
similarity index 92%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/StringToOptionalConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToOptionalConverterTest.java
index 242ae60..9cb79e2 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToOptionalConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToOptionalConverterTest.java
@@ -14,10 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert;
-
-import org.apache.dubbo.common.convert.Converter;
-import org.apache.dubbo.common.convert.StringToOptionalConverter;
+package org.apache.dubbo.common.convert;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToShortConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToShortConverterTest.java
similarity index 92%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/StringToShortConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToShortConverterTest.java
index 3f1d493..9ecdc20 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToShortConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToShortConverterTest.java
@@ -14,10 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert;
-
-import org.apache.dubbo.common.convert.Converter;
-import org.apache.dubbo.common.convert.StringToShortConverter;
+package org.apache.dubbo.common.convert;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToStringConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToStringConverterTest.java
similarity index 92%
copy from dubbo-common/src/test/java/org/apache/dubbo/convert/StringToStringConverterTest.java
copy to dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToStringConverterTest.java
index 57806c3..517585d 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToStringConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/StringToStringConverterTest.java
@@ -14,10 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert;
-
-import org.apache.dubbo.common.convert.Converter;
-import org.apache.dubbo.common.convert.StringToStringConverter;
+package org.apache.dubbo.common.convert;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/MultiValueConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/MultiValueConverterTest.java
new file mode 100644
index 0000000..ea64628
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/MultiValueConverterTest.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.common.convert.multiple;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Collection;
+import java.util.Deque;
+import java.util.List;
+import java.util.NavigableSet;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.BlockingDeque;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TransferQueue;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link MultiValueConverter} Test
+ *
+ * @since 2.7.8
+ */
+public class MultiValueConverterTest {
+
+    @Test
+    public void testFind() {
+        MultiValueConverter converter = MultiValueConverter.find(String.class, String[].class);
+        assertEquals(StringToArrayConverter.class, converter.getClass());
+
+        converter = MultiValueConverter.find(String.class, BlockingDeque.class);
+        assertEquals(StringToBlockingDequeConverter.class, converter.getClass());
+
+        converter = MultiValueConverter.find(String.class, BlockingQueue.class);
+        assertEquals(StringToBlockingQueueConverter.class, converter.getClass());
+
+        converter = MultiValueConverter.find(String.class, Collection.class);
+        assertEquals(StringToCollectionConverter.class, converter.getClass());
+
+        converter = MultiValueConverter.find(String.class, Deque.class);
+        assertEquals(StringToDequeConverter.class, converter.getClass());
+
+        converter = MultiValueConverter.find(String.class, List.class);
+        assertEquals(StringToListConverter.class, converter.getClass());
+
+        converter = MultiValueConverter.find(String.class, NavigableSet.class);
+        assertEquals(StringToNavigableSetConverter.class, converter.getClass());
+
+        converter = MultiValueConverter.find(String.class, Queue.class);
+        assertEquals(StringToQueueConverter.class, converter.getClass());
+
+        converter = MultiValueConverter.find(String.class, Set.class);
+        assertEquals(StringToSetConverter.class, converter.getClass());
+
+        converter = MultiValueConverter.find(String.class, TransferQueue.class);
+        assertEquals(StringToTransferQueueConverter.class, converter.getClass());
+    }
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToArrayConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToArrayConverterTest.java
similarity index 95%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToArrayConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToArrayConverterTest.java
index 1781356..8279e07 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToArrayConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToArrayConverterTest.java
@@ -14,9 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert.multiple;
-
-import org.apache.dubbo.common.convert.multiple.StringToArrayConverter;
+package org.apache.dubbo.common.convert.multiple;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToBlockingDequeConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToBlockingDequeConverterTest.java
similarity index 95%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToBlockingDequeConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToBlockingDequeConverterTest.java
index 6f9597d..f0975ad 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToBlockingDequeConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToBlockingDequeConverterTest.java
@@ -14,10 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert.multiple;
+package org.apache.dubbo.common.convert.multiple;
 
-import org.apache.dubbo.common.convert.multiple.MultiValueConverter;
-import org.apache.dubbo.common.convert.multiple.StringToBlockingDequeConverter;
 import org.apache.dubbo.common.utils.CollectionUtils;
 
 import org.junit.jupiter.api.BeforeEach;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToBlockingQueueConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToBlockingQueueConverterTest.java
similarity index 95%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToBlockingQueueConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToBlockingQueueConverterTest.java
index 4fa7532..f1d8cb8 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToBlockingQueueConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToBlockingQueueConverterTest.java
@@ -14,10 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert.multiple;
+package org.apache.dubbo.common.convert.multiple;
 
-import org.apache.dubbo.common.convert.multiple.MultiValueConverter;
-import org.apache.dubbo.common.convert.multiple.StringToBlockingQueueConverter;
 import org.apache.dubbo.common.utils.CollectionUtils;
 
 import org.junit.jupiter.api.BeforeEach;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToCollectionConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToCollectionConverterTest.java
similarity index 95%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToCollectionConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToCollectionConverterTest.java
index f0b06ec..564ece3 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToCollectionConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToCollectionConverterTest.java
@@ -14,10 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert.multiple;
-
-import org.apache.dubbo.common.convert.multiple.MultiValueConverter;
-import org.apache.dubbo.common.convert.multiple.StringToCollectionConverter;
+package org.apache.dubbo.common.convert.multiple;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToDequeConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToDequeConverterTest.java
similarity index 95%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToDequeConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToDequeConverterTest.java
index e810092..3d4b785 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToDequeConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToDequeConverterTest.java
@@ -14,10 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert.multiple;
+package org.apache.dubbo.common.convert.multiple;
 
-import org.apache.dubbo.common.convert.multiple.MultiValueConverter;
-import org.apache.dubbo.common.convert.multiple.StringToDequeConverter;
 import org.apache.dubbo.common.utils.CollectionUtils;
 
 import org.junit.jupiter.api.BeforeEach;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToListConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToListConverterTest.java
similarity index 95%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToListConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToListConverterTest.java
index af9ee91..4258199 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToListConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToListConverterTest.java
@@ -14,10 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert.multiple;
+package org.apache.dubbo.common.convert.multiple;
 
-import org.apache.dubbo.common.convert.multiple.MultiValueConverter;
-import org.apache.dubbo.common.convert.multiple.StringToListConverter;
 import org.apache.dubbo.common.utils.CollectionUtils;
 
 import org.junit.jupiter.api.BeforeEach;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToNavigableSetConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToNavigableSetConverterTest.java
similarity index 95%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToNavigableSetConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToNavigableSetConverterTest.java
index face60d..e7e1660 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToNavigableSetConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToNavigableSetConverterTest.java
@@ -14,10 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert.multiple;
+package org.apache.dubbo.common.convert.multiple;
 
-import org.apache.dubbo.common.convert.multiple.MultiValueConverter;
-import org.apache.dubbo.common.convert.multiple.StringToListConverter;
 import org.apache.dubbo.common.utils.CollectionUtils;
 
 import org.junit.jupiter.api.BeforeEach;
@@ -47,7 +45,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
- * {@link StringToListConverter} Test
+ * {@link StringToNavigableSetConverter} Test
  *
  * @since 2.7.6
  */
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToQueueConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToQueueConverterTest.java
similarity index 97%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToQueueConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToQueueConverterTest.java
index 539693a..2933d04 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToQueueConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToQueueConverterTest.java
@@ -14,9 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert.multiple;
+package org.apache.dubbo.common.convert.multiple;
 
-import org.apache.dubbo.common.convert.multiple.StringToQueueConverter;
 import org.apache.dubbo.common.utils.CollectionUtils;
 
 import org.junit.jupiter.api.BeforeEach;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToSetConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToSetConverterTest.java
similarity index 97%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToSetConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToSetConverterTest.java
index 269d709..5925cec 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToSetConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToSetConverterTest.java
@@ -14,9 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert.multiple;
+package org.apache.dubbo.common.convert.multiple;
 
-import org.apache.dubbo.common.convert.multiple.StringToSetConverter;
 import org.apache.dubbo.common.utils.CollectionUtils;
 
 import org.junit.jupiter.api.BeforeEach;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToSortedSetConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToSortedSetConverterTest.java
similarity index 95%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToSortedSetConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToSortedSetConverterTest.java
index 6af8f9d..2ed1252 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToSortedSetConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToSortedSetConverterTest.java
@@ -14,10 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert.multiple;
+package org.apache.dubbo.common.convert.multiple;
 
-import org.apache.dubbo.common.convert.multiple.MultiValueConverter;
-import org.apache.dubbo.common.convert.multiple.StringToListConverter;
 import org.apache.dubbo.common.utils.CollectionUtils;
 
 import org.junit.jupiter.api.BeforeEach;
@@ -47,7 +45,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
- * {@link StringToListConverter} Test
+ * {@link StringToSortedSetConverter} Test
  *
  * @since 2.7.6
  */
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToTransferQueueConverterTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToTransferQueueConverterTest.java
similarity index 95%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToTransferQueueConverterTest.java
rename to dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToTransferQueueConverterTest.java
index 4d8d66b..e4cc101 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/multiple/StringToTransferQueueConverterTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/convert/multiple/StringToTransferQueueConverterTest.java
@@ -14,10 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert.multiple;
+package org.apache.dubbo.common.convert.multiple;
 
-import org.apache.dubbo.common.convert.multiple.MultiValueConverter;
-import org.apache.dubbo.common.convert.multiple.StringToListConverter;
 import org.apache.dubbo.common.utils.CollectionUtils;
 
 import org.junit.jupiter.api.BeforeEach;
@@ -48,7 +46,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
- * {@link StringToListConverter} Test
+ * {@link StringToTransferQueueConverter} Test
  *
  * @since 2.7.6
  */
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java
index 0144059..24cb292 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.dubbo.common.utils;
 
-import com.alibaba.fastjson.JSONObject;
 import org.apache.dubbo.common.model.Person;
 import org.apache.dubbo.common.model.SerializablePerson;
 import org.apache.dubbo.common.model.User;
@@ -26,6 +25,7 @@ import org.apache.dubbo.common.model.person.PersonInfo;
 import org.apache.dubbo.common.model.person.PersonStatus;
 import org.apache.dubbo.common.model.person.Phone;
 
+import com.alibaba.fastjson.JSONObject;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
diff --git a/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportFactory.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringConstantFieldValuePredicateTest.java
similarity index 51%
copy from dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportFactory.java
copy to dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringConstantFieldValuePredicateTest.java
index 0ffed8d..0618542 100644
--- a/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportFactory.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringConstantFieldValuePredicateTest.java
@@ -14,27 +14,34 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.metadata.store.zookeeper;
+package org.apache.dubbo.common.utils;
 
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.metadata.report.MetadataReport;
-import org.apache.dubbo.metadata.report.support.AbstractMetadataReportFactory;
-import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter;
+import org.junit.jupiter.api.Test;
+
+import java.util.function.Predicate;
+
+import static org.apache.dubbo.common.utils.StringConstantFieldValuePredicate.of;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
- * ZookeeperRegistryFactory.
+ * {@link StringConstantFieldValuePredicate} Test
+ *
+ * @since 2.7.8
  */
-public class ZookeeperMetadataReportFactory extends AbstractMetadataReportFactory {
+public class StringConstantFieldValuePredicateTest {
 
-    private ZookeeperTransporter zookeeperTransporter;
+    public static final String S1 = "1";
 
-    public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
-        this.zookeeperTransporter = zookeeperTransporter;
-    }
+    public static final Object O1 = "2";
 
-    @Override
-    public MetadataReport createMetadataReport(URL url) {
-        return new ZookeeperMetadataReport(url, zookeeperTransporter);
-    }
+    public static final Object O2 = 3;
 
+    @Test
+    public void test() {
+        Predicate<String> predicate = of(getClass());
+        assertTrue(predicate.test("1"));
+        assertTrue(predicate.test("2"));
+        assertFalse(predicate.test("3"));
+    }
 }
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 736e9d8..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
@@ -18,19 +18,33 @@ package org.apache.dubbo.common.utils;
 
 import org.junit.jupiter.api.Test;
 
-import java.util.*;
-
+import java.util.ArrayList;
+import java.util.Arrays;
+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;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.isEmptyOrNullString;
 import static org.hamcrest.Matchers.nullValue;
-import static org.junit.jupiter.api.Assertions.*;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class StringUtilsTest {
     @Test
@@ -234,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";
@@ -363,4 +393,29 @@ 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);
+        assertNull(value);
+
+        value = toCommaDelimitedString(null, null);
+        assertNull(value);
+
+        value = toCommaDelimitedString("");
+        assertEquals("", value);
+
+        value = toCommaDelimitedString("one");
+        assertEquals("one", value);
+
+        value = toCommaDelimitedString("one", "two");
+        assertEquals("one,two", value);
+
+        value = toCommaDelimitedString("one", "two", "three");
+        assertEquals("one,two,three", value);
+    }
+
 }
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 58fc3cc..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
@@ -49,7 +49,12 @@ public class ConfigManagerTest {
 
     @BeforeEach
     public void init() {
-        configManager.clear();
+        configManager.destroy();
+    }
+
+    @Test
+    public void testDestroy() {
+        assertTrue(configManager.configsCache.isEmpty());
     }
 
     @Test
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/event/EchoEventListener2.java b/dubbo-common/src/test/java/org/apache/dubbo/event/EchoEventListener2.java
index 18e7cf9..0d27802 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/event/EchoEventListener2.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/event/EchoEventListener2.java
@@ -41,7 +41,7 @@ public class EchoEventListener2 extends Vector<EventListener<Event>> implements
 
     @Override
     public int getPriority() {
-        return 0;
+        return -1;
     }
 
     public int getEventOccurs() {
diff --git a/dubbo-compatible/src/test/java/org/apache/dubbo/config/ConfigTest.java b/dubbo-compatible/src/test/java/org/apache/dubbo/config/ConfigTest.java
index 95608d7..9ce2191 100644
--- a/dubbo-compatible/src/test/java/org/apache/dubbo/config/ConfigTest.java
+++ b/dubbo-compatible/src/test/java/org/apache/dubbo/config/ConfigTest.java
@@ -35,14 +35,14 @@ public class ConfigTest {
 
     @AfterEach
     public void tearDown() {
-        ApplicationModel.getConfigManager().clear();
+        ApplicationModel.reset();
     }
 
     @BeforeEach
     public void setup() {
         // In IDE env, make sure adding the following argument to VM options
         System.setProperty("java.net.preferIPv4Stack", "true");
-        ApplicationModel.getConfigManager().clear();
+        ApplicationModel.reset();
     }
 
     @Test
diff --git a/dubbo-compatible/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java b/dubbo-compatible/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java
index d30f632..883dc12 100644
--- a/dubbo-compatible/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java
+++ b/dubbo-compatible/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java
@@ -38,12 +38,12 @@ public class ReferenceConfigTest {
 
     @BeforeEach
     public void setUp() {
-        ApplicationModel.getConfigManager().clear();
+        ApplicationModel.reset();
     }
 
     @AfterEach
     public void tearDown() {
-        ApplicationModel.getConfigManager().clear();
+        ApplicationModel.reset();
     }
 
     @Test
diff --git a/dubbo-config/dubbo-config-api/pom.xml b/dubbo-config/dubbo-config-api/pom.xml
index 89580bd..4ccfd67 100644
--- a/dubbo-config/dubbo-config-api/pom.xml
+++ b/dubbo-config/dubbo-config-api/pom.xml
@@ -135,6 +135,12 @@
             <artifactId>dubbo-registry-eureka</artifactId>
             <version>${project.parent.version}</version>
             <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.google.guava</groupId>
+                    <artifactId>guava</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
         <dependency>
@@ -156,6 +162,12 @@
             <artifactId>dubbo-metadata-report-zookeeper</artifactId>
             <version>${project.parent.version}</version>
             <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.google.guava</groupId>
+                    <artifactId>guava</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
         <dependency>
@@ -170,6 +182,19 @@
             <artifactId>dubbo-metadata-report-nacos</artifactId>
             <version>${project.parent.version}</version>
             <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.google.guava</groupId>
+                    <artifactId>guava</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-metadata-report-consul</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
         </dependency>
 
         <dependency>
@@ -191,6 +216,12 @@
             <artifactId>dubbo-configcenter-nacos</artifactId>
             <version>${project.parent.version}</version>
             <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.google.guava</groupId>
+                    <artifactId>guava</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
         <dependency>
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 df9eed2..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
@@ -19,6 +19,7 @@ package org.apache.dubbo.config;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.Version;
 import org.apache.dubbo.common.bytecode.Wrapper;
+import org.apache.dubbo.common.constants.RegistryConstants;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
@@ -31,10 +32,10 @@ import org.apache.dubbo.config.annotation.Reference;
 import org.apache.dubbo.config.bootstrap.DubboBootstrap;
 import org.apache.dubbo.config.event.ReferenceConfigDestroyedEvent;
 import org.apache.dubbo.config.event.ReferenceConfigInitializedEvent;
+import org.apache.dubbo.config.support.Parameter;
 import org.apache.dubbo.config.utils.ConfigValidationUtils;
 import org.apache.dubbo.event.Event;
 import org.apache.dubbo.event.EventDispatcher;
-import org.apache.dubbo.metadata.WritableMetadataService;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Protocol;
 import org.apache.dubbo.rpc.ProxyFactory;
@@ -57,25 +58,25 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 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;
+import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR_CHAR;
 import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;
-import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
 import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_VALUE;
-import static org.apache.dubbo.common.constants.CommonConstants.METADATA_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROXY_CLASS_REF;
-import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
 import static org.apache.dubbo.common.constants.CommonConstants.REVISION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.SEMICOLON_SPLIT_PATTERN;
 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.splitToSet;
 import static org.apache.dubbo.config.Constants.DUBBO_IP_TO_REGISTRY;
-import static org.apache.dubbo.registry.Constants.CONSUMER_PROTOCOL;
 import static org.apache.dubbo.registry.Constants.REGISTER_IP_KEY;
 import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL;
 import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY;
@@ -140,6 +141,13 @@ public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
 
     private DubboBootstrap bootstrap;
 
+    /**
+     * The service names that the Dubbo interface subscribed.
+     *
+     * @since 2.7.8
+     */
+    private String services;
+
     public ReferenceConfig() {
         super();
         this.repository = ApplicationModel.getServiceRepository();
@@ -150,6 +158,40 @@ public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
         this.repository = ApplicationModel.getServiceRepository();
     }
 
+    /**
+     * Get a string presenting the service names that the Dubbo interface subscribed.
+     * If it is a multiple-values, the content will be a comma-delimited String.
+     *
+     * @return non-null
+     * @see RegistryConstants#SUBSCRIBED_SERVICE_NAMES_KEY
+     * @since 2.7.8
+     */
+    @Parameter(key = SUBSCRIBED_SERVICE_NAMES_KEY)
+    public String getServices() {
+        return services;
+    }
+
+    /**
+     * It's an alias method for {@link #getServices()}, but the more convenient.
+     *
+     * @return the String {@link List} presenting the Dubbo interface subscribed
+     * @since 2.7.8
+     */
+    @Parameter(excluded = true)
+    public Set<String> getSubscribedServices() {
+        return splitToSet(getServices(), COMMA_SEPARATOR_CHAR);
+    }
+
+    /**
+     * Set the service names that the Dubbo interface subscribed.
+     *
+     * @param services If it is a multiple-values, the content will be a comma-delimited String.
+     * @since 2.7.8
+     */
+    public void setServices(String services) {
+        this.services = services;
+    }
+
     public synchronized T get() {
         if (destroyed) {
             throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
@@ -221,10 +263,6 @@ public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
         // appendParameters(map, consumer, Constants.DEFAULT_KEY);
         AbstractConfig.appendParameters(map, consumer);
         AbstractConfig.appendParameters(map, this);
-        MetadataReportConfig metadataReportConfig = getMetadataReportConfig();
-        if (metadataReportConfig != null && metadataReportConfig.isValid()) {
-            map.putIfAbsent(METADATA_KEY, REMOTE_METADATA_STORAGE_TYPE);
-        }
         Map<String, AsyncMethodInfo> attributes = null;
         if (CollectionUtils.isNotEmpty(getMethods())) {
             attributes = new HashMap<>();
@@ -355,16 +393,6 @@ public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
         if (logger.isInfoEnabled()) {
             logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
         }
-        /**
-         * @since 2.7.0
-         * ServiceData Store
-         */
-        String metadata = map.get(METADATA_KEY);
-        WritableMetadataService metadataService = WritableMetadataService.getExtension(metadata == null ? DEFAULT_METADATA_STORAGE_TYPE : metadata);
-        if (metadataService != null) {
-            URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
-            metadataService.publishServiceDefinition(consumerURL);
-        }
         // create service proxy
         return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
     }
@@ -469,7 +497,7 @@ public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
     }
 
     private void postProcessConfig() {
-        List<ConfigPostProcessor> configPostProcessors =ExtensionLoader.getExtensionLoader(ConfigPostProcessor.class)
+        List<ConfigPostProcessor> configPostProcessors = ExtensionLoader.getExtensionLoader(ConfigPostProcessor.class)
                 .getActivateExtension(URL.valueOf("configPostProcessor://"), (String[]) null);
         configPostProcessors.forEach(component -> component.postProcessReferConfig(this));
     }
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java
index 3ad4545..5173bf7 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java
@@ -37,7 +37,6 @@ import org.apache.dubbo.config.support.Parameter;
 import org.apache.dubbo.config.utils.ConfigValidationUtils;
 import org.apache.dubbo.event.Event;
 import org.apache.dubbo.event.EventDispatcher;
-import org.apache.dubbo.metadata.WritableMetadataService;
 import org.apache.dubbo.rpc.Exporter;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Protocol;
@@ -68,16 +67,13 @@ import java.util.concurrent.TimeUnit;
 
 import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
-import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
 import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
 import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_IP_TO_BIND;
 import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_VALUE;
-import static org.apache.dubbo.common.constants.CommonConstants.METADATA_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
 import static org.apache.dubbo.common.constants.CommonConstants.REGISTER_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
 import static org.apache.dubbo.common.constants.CommonConstants.REVISION_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_KEY;
@@ -187,7 +183,7 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
 
         if (bootstrap == null) {
             bootstrap = DubboBootstrap.getInstance();
-            bootstrap.init();
+            bootstrap.initialize();
         }
 
         checkAndUpdateSubConfigs();
@@ -344,10 +340,6 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
         AbstractConfig.appendParameters(map, provider);
         AbstractConfig.appendParameters(map, protocolConfig);
         AbstractConfig.appendParameters(map, this);
-        MetadataReportConfig metadataReportConfig = getMetadataReportConfig();
-        if (metadataReportConfig != null && metadataReportConfig.isValid()) {
-            map.putIfAbsent(METADATA_KEY, REMOTE_METADATA_STORAGE_TYPE);
-        }
         if (CollectionUtils.isNotEmpty(getMethods())) {
             for (MethodConfig method : getMethods()) {
                 AbstractConfig.appendParameters(map, method, method.getName());
@@ -502,14 +494,6 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
                     Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                     exporters.add(exporter);
                 }
-                /**
-                 * @since 2.7.0
-                 * ServiceData Store
-                 */
-                WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));
-                if (metadataService != null) {
-                    metadataService.publishServiceDefinition(url);
-                }
             }
         }
         this.urls.add(url);
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java
index cd92fe5..10dd4f2 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java
@@ -52,7 +52,6 @@ import org.apache.dubbo.config.bootstrap.builders.ReferenceBuilder;
 import org.apache.dubbo.config.bootstrap.builders.RegistryBuilder;
 import org.apache.dubbo.config.bootstrap.builders.ServiceBuilder;
 import org.apache.dubbo.config.context.ConfigManager;
-import org.apache.dubbo.config.metadata.ConfigurableMetadataServiceExporter;
 import org.apache.dubbo.config.utils.ConfigValidationUtils;
 import org.apache.dubbo.config.utils.ReferenceConfigCache;
 import org.apache.dubbo.event.EventDispatcher;
@@ -66,6 +65,7 @@ import org.apache.dubbo.registry.client.DefaultServiceInstance;
 import org.apache.dubbo.registry.client.ServiceDiscovery;
 import org.apache.dubbo.registry.client.ServiceDiscoveryRegistry;
 import org.apache.dubbo.registry.client.ServiceInstance;
+import org.apache.dubbo.registry.client.ServiceInstanceCustomizer;
 import org.apache.dubbo.registry.support.AbstractRegistryFactory;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 
@@ -93,17 +93,17 @@ import static org.apache.dubbo.common.config.configcenter.DynamicConfiguration.g
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
 import static org.apache.dubbo.common.constants.CommonConstants.REGISTRY_SPLIT_PATTERN;
 import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader;
 import static org.apache.dubbo.common.function.ThrowableAction.execute;
 import static org.apache.dubbo.common.utils.StringUtils.isNotEmpty;
-import static org.apache.dubbo.metadata.WritableMetadataService.getExtension;
 import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.setMetadataStorageType;
 import static org.apache.dubbo.remoting.Constants.CLIENT_KEY;
 
 /**
  * See {@link ApplicationModel} and {@link ExtensionLoader} for why this class is designed to be singleton.
- *
+ * <p>
  * The bootstrap class of Dubbo
- *
+ * <p>
  * Get singleton instance by calling static method {@link #getInstance()}.
  * Designed as singleton because some classes inside Dubbo, such as ExtensionLoader, are designed only for one instance per process.
  *
@@ -141,7 +141,7 @@ public class DubboBootstrap extends GenericEventListener {
 
     private final EventDispatcher eventDispatcher = EventDispatcher.getDefaultExtension();
 
-    private final ExecutorRepository executorRepository = ExtensionLoader.getExtensionLoader(ExecutorRepository.class).getDefaultExtension();
+    private final ExecutorRepository executorRepository = getExtensionLoader(ExecutorRepository.class).getDefaultExtension();
 
     private final ConfigManager configManager;
 
@@ -165,7 +165,7 @@ public class DubboBootstrap extends GenericEventListener {
 
     private volatile MetadataService metadataService;
 
-    private volatile MetadataServiceExporter metadataServiceExporter;
+    private volatile Set<MetadataServiceExporter> metadataServiceExporters;
 
     private List<ServiceConfigBase<?>> exportedServices = new ArrayList<>();
 
@@ -500,7 +500,7 @@ public class DubboBootstrap extends GenericEventListener {
     /**
      * Initialize
      */
-    private void initialize() {
+    public void initialize() {
         if (!initialized.compareAndSet(false, true)) {
             return;
         }
@@ -509,14 +509,17 @@ public class DubboBootstrap extends GenericEventListener {
 
         startConfigCenter();
 
-        useRegistryAsConfigCenterIfNecessary();
-
         loadRemoteConfigs();
 
         checkGlobalConfigs();
 
+        // @since 2.7.8
+        startMetadataCenter();
+
         initMetadataService();
 
+        initMetadataServiceExports();
+
         initEventListener();
 
         if (logger.isInfoEnabled()) {
@@ -583,6 +586,9 @@ public class DubboBootstrap extends GenericEventListener {
     }
 
     private void startConfigCenter() {
+
+        useRegistryAsConfigCenterIfNecessary();
+
         Collection<ConfigCenterConfig> configCenters = configManager.getConfigCenters();
 
         // check Config Center
@@ -610,7 +616,10 @@ public class DubboBootstrap extends GenericEventListener {
         configManager.refreshAll();
     }
 
-    private void startMetadataReport() {
+    private void startMetadataCenter() {
+
+        useRegistryAsMetadataCenterIfNecessary();
+
         ApplicationConfig applicationConfig = getApplication();
 
         String metadataType = applicationConfig.getMetadataType();
@@ -646,33 +655,87 @@ public class DubboBootstrap extends GenericEventListener {
             return;
         }
 
-        configManager.getDefaultRegistries().stream()
-                .filter(registryConfig -> registryConfig.getUseAsConfigCenter() == null || registryConfig.getUseAsConfigCenter())
-                .forEach(registryConfig -> {
-                    String protocol = registryConfig.getProtocol();
-                    String id = "config-center-" + protocol + "-" + registryConfig.getPort();
-                    ConfigCenterConfig cc = new ConfigCenterConfig();
-                    cc.setId(id);
-                    if (cc.getParameters() == null) {
-                        cc.setParameters(new HashMap<>());
-                    }
-                    if (registryConfig.getParameters() != null) {
-                        cc.getParameters().putAll(registryConfig.getParameters());
-                    }
-                    cc.getParameters().put(CLIENT_KEY, registryConfig.getClient());
-                    cc.setProtocol(registryConfig.getProtocol());
-                    cc.setPort(registryConfig.getPort());
-                    cc.setAddress(getRegistryCompatibleAddress(registryConfig.getAddress()));
-                    cc.setNamespace(registryConfig.getGroup());
-                    cc.setUsername(registryConfig.getUsername());
-                    cc.setPassword(registryConfig.getPassword());
-                    if (registryConfig.getTimeout() != null) {
-                        cc.setTimeout(registryConfig.getTimeout().longValue());
-                    }
-                    cc.setHighestPriority(false);
-                    configManager.addConfigCenter(cc);
-                });
-        startConfigCenter();
+        configManager
+                .getDefaultRegistries()
+                .stream()
+                .filter(this::isUsedRegistryAsConfigCenter)
+                .map(this::registryAsConfigCenter)
+                .forEach(configManager::addConfigCenter);
+    }
+
+    private boolean isUsedRegistryAsConfigCenter(RegistryConfig registryConfig) {
+        // TODO: confirm ? registryConfig.getUseAsConfigCenter() == null || registryConfig.getUseAsConfigCenter()
+        return Boolean.TRUE.equals(registryConfig.getUseAsConfigCenter());
+    }
+
+    private ConfigCenterConfig registryAsConfigCenter(RegistryConfig registryConfig) {
+        String protocol = registryConfig.getProtocol();
+        Integer port = registryConfig.getPort();
+        String id = "config-center-" + protocol + "-" + port;
+        ConfigCenterConfig cc = new ConfigCenterConfig();
+        cc.setId(id);
+        if (cc.getParameters() == null) {
+            cc.setParameters(new HashMap<>());
+        }
+        if (registryConfig.getParameters() != null) {
+            cc.getParameters().putAll(registryConfig.getParameters()); // copy the parameters
+        }
+        cc.getParameters().put(CLIENT_KEY, registryConfig.getClient());
+        cc.setProtocol(protocol);
+        cc.setPort(port);
+        cc.setGroup(registryConfig.getGroup());
+        cc.setAddress(getRegistryCompatibleAddress(registryConfig.getAddress()));
+        cc.setNamespace(registryConfig.getGroup());
+        cc.setUsername(registryConfig.getUsername());
+        cc.setPassword(registryConfig.getPassword());
+        if (registryConfig.getTimeout() != null) {
+            cc.setTimeout(registryConfig.getTimeout().longValue());
+        }
+        cc.setHighestPriority(false);
+        return cc;
+    }
+
+    private void useRegistryAsMetadataCenterIfNecessary() {
+
+        Collection<MetadataReportConfig> metadataConfigs = configManager.getMetadataConfigs();
+
+        if (CollectionUtils.isNotEmpty(metadataConfigs)) {
+            return;
+        }
+
+        configManager
+                .getDefaultRegistries()
+                .stream()
+                .filter(this::isUsedRegistryAsMetadataCenter)
+                .map(this::registryAsMetadataCenter)
+                .forEach(configManager::addMetadataReport);
+
+    }
+
+    private boolean isUsedRegistryAsMetadataCenter(RegistryConfig registryConfig) {
+        // TODO: confirm ? registryConfig.getUseAsMetadataCenter() == null || registryConfig.getUseAsMetadataCenter()
+        return Boolean.TRUE.equals(registryConfig.getUseAsMetadataCenter());
+    }
+
+    private MetadataReportConfig registryAsMetadataCenter(RegistryConfig registryConfig) {
+        String protocol = registryConfig.getProtocol();
+        Integer port = registryConfig.getPort();
+        String id = "metadata-center-" + protocol + "-" + port;
+        MetadataReportConfig metadataReportConfig = new MetadataReportConfig();
+        metadataReportConfig.setId(id);
+        if (metadataReportConfig.getParameters() == null) {
+            metadataReportConfig.setParameters(new HashMap<>());
+        }
+        if (registryConfig.getParameters() != null) {
+            metadataReportConfig.getParameters().putAll(registryConfig.getParameters()); // copy the parameters
+        }
+        metadataReportConfig.getParameters().put(CLIENT_KEY, registryConfig.getClient());
+        metadataReportConfig.setGroup(registryConfig.getGroup());
+        metadataReportConfig.setAddress(getRegistryCompatibleAddress(registryConfig.getAddress()));
+        metadataReportConfig.setUsername(registryConfig.getUsername());
+        metadataReportConfig.setPassword(registryConfig.getPassword());
+        metadataReportConfig.setTimeout(registryConfig.getTimeout());
+        return metadataReportConfig;
     }
 
     private String getRegistryCompatibleAddress(String registryAddress) {
@@ -719,12 +782,17 @@ public class DubboBootstrap extends GenericEventListener {
 
 
     /**
-     * Initialize {@link MetadataService} from {@link WritableMetadataService}'s extension
+     * Initialize {@link #metadataService WritableMetadataService} from {@link WritableMetadataService}'s extension
      */
     private void initMetadataService() {
-        startMetadataReport();
-        this.metadataService = getExtension(getMetadataType());
-        this.metadataServiceExporter = new ConfigurableMetadataServiceExporter(metadataService);
+        this.metadataService = WritableMetadataService.getExtension(getMetadataType());
+    }
+
+    /**
+     * Initialize {@link #metadataServiceExporters MetadataServiceExporter}
+     */
+    private void initMetadataServiceExports() {
+        this.metadataServiceExporters = getExtensionLoader(MetadataServiceExporter.class).getSupportedExtensionInstances();
     }
 
     /**
@@ -926,13 +994,21 @@ public class DubboBootstrap extends GenericEventListener {
      * export {@link MetadataService}
      */
     private void exportMetadataService() {
-        metadataServiceExporter.export();
+        metadataServiceExporters
+                .stream()
+                .filter(this::supports)
+                .forEach(MetadataServiceExporter::export);
     }
 
     private void unexportMetadataService() {
-        if (metadataServiceExporter != null && metadataServiceExporter.isExported()) {
-            metadataServiceExporter.unexport();
-        }
+        metadataServiceExporters
+                .stream()
+                .filter(this::supports)
+                .forEach(MetadataServiceExporter::unexport);
+    }
+
+    private boolean supports(MetadataServiceExporter exporter) {
+        return exporter.supports(getMetadataType());
     }
 
     private void exportServices() {
@@ -1025,9 +1101,37 @@ public class DubboBootstrap extends GenericEventListener {
 
         ServiceInstance serviceInstance = createServiceInstance(serviceName, host, port);
 
+        preRegisterServiceInstance(serviceInstance);
+
         getServiceDiscoveries().forEach(serviceDiscovery -> serviceDiscovery.register(serviceInstance));
     }
 
+    /**
+     * Pre-register {@link ServiceInstance the service instance}
+     *
+     * @param serviceInstance {@link ServiceInstance the service instance}
+     * @since 2.7.8
+     */
+    private void preRegisterServiceInstance(ServiceInstance serviceInstance) {
+        customizeServiceInstance(serviceInstance);
+    }
+
+    /**
+     * Customize {@link ServiceInstance the service instance}
+     *
+     * @param serviceInstance {@link ServiceInstance the service instance}
+     * @since 2.7.8
+     */
+    private void customizeServiceInstance(ServiceInstance serviceInstance) {
+        ExtensionLoader<ServiceInstanceCustomizer> loader =
+                getExtensionLoader(ServiceInstanceCustomizer.class);
+        // FIXME, sort customizer before apply
+        loader.getSupportedExtensionInstances().forEach(customizer -> {
+            // customizes
+            customizer.customize(serviceInstance);
+        });
+    }
+
     private URL selectMetadataServiceExportedURL() {
 
         URL selectedURL = null;
@@ -1118,7 +1222,7 @@ public class DubboBootstrap extends GenericEventListener {
     }
 
     private void clearConfigs() {
-        configManager.clear();
+        configManager.destroy();
         if (logger.isDebugEnabled()) {
             logger.debug(NAME + "'s configs have been clear.");
         }
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ReferenceBuilder.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ReferenceBuilder.java
index 69e7984..99af153 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ReferenceBuilder.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ReferenceBuilder.java
@@ -24,6 +24,8 @@ import org.apache.dubbo.config.ReferenceConfigBase;
 import java.util.ArrayList;
 import java.util.List;
 
+import static org.apache.dubbo.common.utils.StringUtils.toCommaDelimitedString;
+
 /**
  * This is a builder for build {@link ReferenceConfigBase}.
  *
@@ -65,6 +67,13 @@ public class ReferenceBuilder<T> extends AbstractReferenceBuilder<ReferenceConfi
      */
     private String protocol;
 
+    /**
+     * The string presenting the service names that the Dubbo interface subscribed
+     *
+     * @since 2.7.8
+     */
+    private String services;
+
     public static ReferenceBuilder newBuilder() {
         return new ReferenceBuilder();
     }
@@ -119,6 +128,17 @@ public class ReferenceBuilder<T> extends AbstractReferenceBuilder<ReferenceConfi
         return getThis();
     }
 
+    /**
+     * @param service       one service name
+     * @param otherServices other service names
+     * @return {@link ReferenceBuilder}
+     * @since 2.7.8
+     */
+    public ReferenceBuilder<T> services(String service, String... otherServices) {
+        this.services = toCommaDelimitedString(service, otherServices);
+        return getThis();
+    }
+
     public ReferenceConfig<T> build() {
         ReferenceConfig<T> reference = new ReferenceConfig<>();
         super.build(reference);
@@ -132,6 +152,8 @@ public class ReferenceBuilder<T> extends AbstractReferenceBuilder<ReferenceConfi
         reference.setMethods(methods);
         reference.setConsumer(consumer);
         reference.setProtocol(protocol);
+        // @since 2.7.8
+        reference.setServices(services);
 
         return reference;
     }
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/RegistryBuilder.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/RegistryBuilder.java
index f2ffab8..d717aaa 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/RegistryBuilder.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/RegistryBuilder.java
@@ -285,6 +285,16 @@ public class RegistryBuilder extends AbstractBuilder<RegistryConfig, RegistryBui
         return getThis();
     }
 
+    /**
+     * @param name   the parameter name
+     * @param value the parameter value
+     * @return {@link RegistryBuilder}
+     * @since 2.7.8
+     */
+    public RegistryBuilder parameter(String name, String value) {
+        return appendParameter(name, value);
+    }
+
     public RegistryBuilder appendParameters(Map<String, String> appendParameters) {
         this.parameters = appendParameters(parameters, appendParameters);
         return getThis();
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListener.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListener.java
new file mode 100644
index 0000000..9731034
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListener.java
@@ -0,0 +1,74 @@
+/*
+ * 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.AbstractInterfaceConfig;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.MetadataReportConfig;
+import org.apache.dubbo.config.event.ReferenceConfigInitializedEvent;
+import org.apache.dubbo.config.event.ServiceConfigExportedEvent;
+import org.apache.dubbo.event.EventListener;
+import org.apache.dubbo.event.GenericEventListener;
+import org.apache.dubbo.metadata.WritableMetadataService;
+
+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.metadata.WritableMetadataService.getExtension;
+
+/**
+ * An {@link EventListener} {@link WritableMetadataService#publishServiceDefinition(URL) publishs the service definition}
+ * when {@link ServiceConfigExportedEvent the event of the exported Dubbo service} and
+ * {@link ReferenceConfigInitializedEvent the event of the referenced Dubbo service} is raised.
+ *
+ * @see GenericEventListener
+ * @see ServiceConfigExportedEvent
+ * @see ReferenceConfigInitializedEvent
+ * @since 2.7.8
+ */
+public class PublishingServiceDefinitionListener extends GenericEventListener {
+
+    public void onEvent(ReferenceConfigInitializedEvent event) {
+        handleEvent(event.getReferenceConfig());
+    }
+
+    public void onEvent(ServiceConfigExportedEvent event) {
+        handleEvent(event.getServiceConfig());
+    }
+
+    private void handleEvent(AbstractInterfaceConfig config) {
+        String metadataType = getMetadataType(config);
+        for (URL exportedURL : config.getExportedUrls()) {
+            WritableMetadataService metadataService = getExtension(metadataType);
+            if (metadataService != null) {
+                metadataService.publishServiceDefinition(exportedURL);
+            }
+        }
+    }
+
+    private String getMetadataType(AbstractInterfaceConfig config) {
+        ApplicationConfig applicationConfig = config.getApplication();
+        String metadataType = applicationConfig.getMetadataType();
+        if (REMOTE_METADATA_STORAGE_TYPE.equals(metadataType)) {
+            MetadataReportConfig metadataReportConfig = config.getMetadataReportConfig();
+            if (metadataReportConfig == null || !metadataReportConfig.isValid()) {
+                metadataType = DEFAULT_METADATA_STORAGE_TYPE;
+            }
+        }
+        return metadataType;
+    }
+}
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/listener/ServiceNameMappingListener.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/listener/ServiceNameMappingListener.java
index 4dcfde3..8607b51 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/listener/ServiceNameMappingListener.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/listener/ServiceNameMappingListener.java
@@ -24,8 +24,6 @@ import org.apache.dubbo.metadata.ServiceNameMapping;
 
 import java.util.List;
 
-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.metadata.ServiceNameMapping.getDefaultExtension;
 
 /**
@@ -45,11 +43,7 @@ public class ServiceNameMappingListener implements EventListener<ServiceConfigEx
         ServiceConfig serviceConfig = event.getServiceConfig();
         List<URL> exportedURLs = serviceConfig.getExportedUrls();
         exportedURLs.forEach(url -> {
-            String serviceInterface = url.getServiceInterface();
-            String group = url.getParameter(GROUP_KEY);
-            String version = url.getParameter(VERSION_KEY);
-            String protocol = url.getProtocol();
-            serviceNameMapping.map(serviceInterface, group, version, protocol);
+            serviceNameMapping.map(url);
         });
     }
 }
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/AbstractMetadataServiceExporter.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/AbstractMetadataServiceExporter.java
new file mode 100644
index 0000000..ce2b389
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/AbstractMetadataServiceExporter.java
@@ -0,0 +1,150 @@
+/*
+ * 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.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.metadata.MetadataService;
+import org.apache.dubbo.metadata.MetadataServiceExporter;
+import org.apache.dubbo.metadata.MetadataServiceType;
+import org.apache.dubbo.metadata.WritableMetadataService;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static java.util.EnumSet.of;
+import static org.apache.dubbo.metadata.MetadataServiceType.getOrDefault;
+
+/**
+ * The abstract implementation of {@link MetadataServiceExporter} to provider the commons features for sub-types
+ *
+ * @see MetadataServiceExporter
+ * @see MetadataService
+ * @since 2.7.8
+ */
+public abstract class AbstractMetadataServiceExporter implements MetadataServiceExporter {
+
+    protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+    protected final WritableMetadataService metadataService;
+
+    private final int priority;
+
+    private final Set<MetadataServiceType> supportedMetadataServiceTypes;
+
+    private volatile boolean exported = false;
+
+    public AbstractMetadataServiceExporter(String metadataType,
+                                           int priority,
+                                           MetadataServiceType supportMetadataServiceType,
+                                           MetadataServiceType... otherSupportMetadataServiceTypes) {
+        this(metadataType, priority, of(supportMetadataServiceType, otherSupportMetadataServiceTypes));
+    }
+
+    public AbstractMetadataServiceExporter(String metadataType,
+                                           int priority,
+                                           Set<MetadataServiceType> supportedMetadataServiceTypes) {
+        this.metadataService = WritableMetadataService.getExtension(metadataType);
+        this.priority = priority;
+        this.supportedMetadataServiceTypes = supportedMetadataServiceTypes;
+    }
+
+    @Override
+    public final MetadataServiceExporter export() {
+        if (!isExported()) {
+            try {
+                doExport();
+                exported = true;
+            } catch (Exception e) {
+                if (logger.isErrorEnabled()) {
+                    logger.error("Exporting the MetadataService fails", e);
+                }
+                exported = false;
+            }
+        } else {
+            if (logger.isWarnEnabled()) {
+                logger.warn("The MetadataService has been exported : " + getExportedURLs());
+            }
+        }
+        return this;
+    }
+
+    @Override
+    public final MetadataServiceExporter unexport() {
+        if (isExported()) {
+            try {
+                doUnexport();
+                exported = false;
+            } catch (Exception e) {
+                if (logger.isErrorEnabled()) {
+                    logger.error("UnExporting the MetadataService fails", e);
+                }
+            }
+        }
+        return this;
+    }
+
+    @Override
+    public List<URL> getExportedURLs() {
+        return metadataService
+                .getExportedURLs()
+                .stream()
+                .map(URL::valueOf)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public boolean isExported() {
+        return exported;
+    }
+
+    @Override
+    public final boolean supports(String metadataType) {
+        MetadataServiceType metadataServiceType = getOrDefault(metadataType);
+        return supportedMetadataServiceTypes.contains(metadataServiceType);
+    }
+
+    @Override
+    public int getPriority() {
+        return priority;
+    }
+
+    /**
+     * Exports the {@link MetadataService}
+     *
+     * @throws Exception If some exception occurs
+     */
+    protected abstract void doExport() throws Exception;
+
+    /**
+     * Unexports the {@link MetadataService}
+     *
+     * @throws Exception If some exception occurs
+     */
+    protected abstract void doUnexport() throws Exception;
+
+    /**
+     * Get the underlying of {@link MetadataService}
+     *
+     * @return non-null
+     */
+    public WritableMetadataService getMetadataService() {
+        return metadataService;
+    }
+}
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java
index fdb011f..d89fb77 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java
@@ -17,8 +17,6 @@
 package org.apache.dubbo.config.metadata;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.config.ApplicationConfig;
 import org.apache.dubbo.config.ProtocolConfig;
 import org.apache.dubbo.config.RegistryConfig;
@@ -26,12 +24,15 @@ import org.apache.dubbo.config.ServiceConfig;
 import org.apache.dubbo.config.context.ConfigManager;
 import org.apache.dubbo.metadata.MetadataService;
 import org.apache.dubbo.metadata.MetadataServiceExporter;
+import org.apache.dubbo.metadata.MetadataServiceType;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import java.util.ArrayList;
 import java.util.List;
 
 import static java.util.Collections.emptyList;
+import static java.util.EnumSet.allOf;
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
 import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
 
 /**
@@ -49,56 +50,41 @@ import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
  * @see ConfigManager
  * @since 2.7.5
  */
-public class ConfigurableMetadataServiceExporter implements MetadataServiceExporter {
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    private final MetadataService metadataService;
+public class ConfigurableMetadataServiceExporter extends AbstractMetadataServiceExporter {
 
     private volatile ServiceConfig<MetadataService> serviceConfig;
 
-    public ConfigurableMetadataServiceExporter(MetadataService metadataService) {
-        this.metadataService = metadataService;
+    public ConfigurableMetadataServiceExporter() {
+        super(DEFAULT_METADATA_STORAGE_TYPE, MAX_PRIORITY, allOf(MetadataServiceType.class));
     }
 
     @Override
-    public ConfigurableMetadataServiceExporter export() {
-
-        if (!isExported()) {
-
-            ServiceConfig<MetadataService> serviceConfig = new ServiceConfig<>();
-            serviceConfig.setApplication(getApplicationConfig());
-            serviceConfig.setRegistries(getRegistries());
-            serviceConfig.setProtocol(generateMetadataProtocol());
-            serviceConfig.setInterface(MetadataService.class);
-            serviceConfig.setRef(metadataService);
-            serviceConfig.setGroup(getApplicationConfig().getName());
-            serviceConfig.setVersion(metadataService.version());
-
-            // export
-            serviceConfig.export();
-
-            if (logger.isInfoEnabled()) {
-                logger.info("The MetadataService exports urls : " + serviceConfig.getExportedUrls());
-            }
-
-            this.serviceConfig = serviceConfig;
-
-        } else {
-            if (logger.isWarnEnabled()) {
-                logger.warn("The MetadataService has been exported : " + serviceConfig.getExportedUrls());
-            }
+    protected void doExport() throws Exception {
+
+        ServiceConfig<MetadataService> serviceConfig = new ServiceConfig<>();
+        serviceConfig.setApplication(getApplicationConfig());
+        serviceConfig.setRegistries(getRegistries());
+        serviceConfig.setProtocol(generateMetadataProtocol());
+        serviceConfig.setInterface(MetadataService.class);
+        serviceConfig.setRef(metadataService);
+        serviceConfig.setGroup(getApplicationConfig().getName());
+        serviceConfig.setVersion(metadataService.version());
+
+        // export
+        serviceConfig.export();
+
+        if (logger.isInfoEnabled()) {
+            logger.info("The MetadataService exports urls : " + serviceConfig.getExportedUrls());
         }
 
-        return this;
+        this.serviceConfig = serviceConfig;
     }
 
     @Override
-    public ConfigurableMetadataServiceExporter unexport() {
-        if (isExported()) {
+    protected void doUnexport() throws Exception {
+        if (serviceConfig != null) {
             serviceConfig.unexport();
         }
-        return this;
     }
 
     @Override
@@ -110,6 +96,11 @@ public class ConfigurableMetadataServiceExporter implements MetadataServiceExpor
         return serviceConfig != null && serviceConfig.isExported();
     }
 
+    @Override
+    public int getPriority() {
+        return MAX_PRIORITY;
+    }
+
     private ApplicationConfig getApplicationConfig() {
         return ApplicationModel.getConfigManager().getApplication().get();
     }
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/RemoteMetadataServiceExporter.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/RemoteMetadataServiceExporter.java
new file mode 100644
index 0000000..6d48921
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/RemoteMetadataServiceExporter.java
@@ -0,0 +1,79 @@
+/*
+ * 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.constants.CommonConstants;
+import org.apache.dubbo.metadata.MetadataServiceExporter;
+import org.apache.dubbo.metadata.MetadataServiceType;
+import org.apache.dubbo.metadata.URLRevisionResolver;
+import org.apache.dubbo.metadata.WritableMetadataService;
+import org.apache.dubbo.metadata.report.MetadataReport;
+import org.apache.dubbo.metadata.report.MetadataReportInstance;
+import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
+
+import java.util.SortedSet;
+
+import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
+
+/**
+ * The implementation of {@link MetadataServiceExporter} for
+ * {@link CommonConstants#REMOTE_METADATA_STORAGE_TYPE "remote" metadata storage type}
+ *
+ * @see MetadataServiceExporter
+ * @since 2.7.8
+ */
+public class RemoteMetadataServiceExporter extends AbstractMetadataServiceExporter {
+
+    private final URLRevisionResolver urlRevisionResolver;
+
+    public RemoteMetadataServiceExporter() {
+        super(REMOTE_METADATA_STORAGE_TYPE, MIN_PRIORITY, MetadataServiceType.REMOTE, MetadataServiceType.COMPOSITE);
+        this.urlRevisionResolver = URLRevisionResolver.INSTANCE;
+    }
+
+    @Override
+    protected void doExport() throws Exception {
+        WritableMetadataService metadataServiceDelegate = WritableMetadataService.getDefaultExtension();
+        if (publishServiceMetadata(metadataServiceDelegate)) {
+            publicConsumerMetadata(metadataServiceDelegate);
+        }
+    }
+
+    private boolean publishServiceMetadata(WritableMetadataService metadataServiceDelegate) {
+        String serviceName = metadataServiceDelegate.serviceName();
+        SortedSet<String> exportedURLs = metadataServiceDelegate.getExportedURLs();
+        String revision = urlRevisionResolver.resolve(exportedURLs);
+        return getMetadataReport().saveExportedURLs(serviceName, revision, exportedURLs);
+    }
+
+    private boolean publicConsumerMetadata(WritableMetadataService metadataServiceDelegate) {
+        String serviceName = metadataServiceDelegate.serviceName();
+        SortedSet<String> subscribedURLs = metadataServiceDelegate.getSubscribedURLs();
+        String revision = urlRevisionResolver.resolve(subscribedURLs);
+        getMetadataReport().saveSubscribedData(new SubscriberMetadataIdentifier(serviceName, revision), subscribedURLs);
+        return true;
+    }
+
+    private MetadataReport getMetadataReport() {
+        return MetadataReportInstance.getMetadataReport(true);
+    }
+
+    @Override
+    protected void doUnexport() throws Exception {
+        // DOES NOTHING
+    }
+}
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 2e46fa2..8a91d1c 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
@@ -223,10 +223,12 @@ public class ConfigValidationUtils {
         ApplicationConfig application = interfaceConfig.getApplication();
         AbstractConfig.appendParameters(map, monitor);
         AbstractConfig.appendParameters(map, application);
-        String address = monitor.getAddress();
+        String address = null;
         String sysaddress = System.getProperty("dubbo.monitor.address");
         if (sysaddress != null && sysaddress.length() > 0) {
             address = sysaddress;
+        } else if (monitor != null) {
+            address = monitor.getAddress();
         }
         if (ConfigUtils.isNotEmpty(address)) {
             if (!map.containsKey(PROTOCOL_KEY)) {
@@ -237,7 +239,8 @@ public class ConfigValidationUtils {
                 }
             }
             return UrlUtils.parseURL(address, map);
-        } else if ((REGISTRY_PROTOCOL.equals(monitor.getProtocol()) || SERVICE_REGISTRY_PROTOCOL.equals(monitor.getProtocol()))
+        } else if (monitor != null &&
+                (REGISTRY_PROTOCOL.equals(monitor.getProtocol()) || SERVICE_REGISTRY_PROTOCOL.equals(monitor.getProtocol()))
                 && registryURL != null) {
             return URLBuilder.from(registryURL)
                     .setProtocol(DUBBO_PROTOCOL)
diff --git a/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.event.EventListener b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.event.EventListener
index db73041..b1946c0 100644
--- a/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.event.EventListener
+++ b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.event.EventListener
@@ -1,2 +1,4 @@
 service-mapping=org.apache.dubbo.config.event.listener.ServiceNameMappingListener
-config-logging=org.apache.dubbo.config.event.listener.LoggingEventListener
\ No newline at end of file
+config-logging=org.apache.dubbo.config.event.listener.LoggingEventListener
+# since 2.7.8
+publishing-service-definition=org.apache.dubbo.config.event.listener.PublishingServiceDefinitionListener
\ No newline at end of file
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
new file mode 100644
index 0000000..1b843b6
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter
@@ -0,0 +1,3 @@
+# since 2.7.8
+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/ReferenceConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java
index c992e48..775596c 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java
@@ -21,6 +21,7 @@ import org.apache.dubbo.config.annotation.Method;
 import org.apache.dubbo.config.annotation.Reference;
 import org.apache.dubbo.config.api.DemoService;
 import org.apache.dubbo.config.provider.impl.DemoServiceImpl;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
@@ -33,12 +34,12 @@ public class ReferenceConfigTest {
 
     @BeforeEach
     public void setUp() {
-//        ApplicationModel.getConfigManager().clear();
+        ApplicationModel.reset();
     }
 
     @AfterEach
     public void tearDown() {
-//        ApplicationModel.getConfigManager().clear();
+        ApplicationModel.reset();
     }
 
     @Test
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ZookeeperDubboServiceConsumerBootstrap.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ConsulDubboServiceConsumerBootstrap.java
similarity index 80%
copy from dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ZookeeperDubboServiceConsumerBootstrap.java
copy to dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ConsulDubboServiceConsumerBootstrap.java
index 9b0d866..824de82 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ZookeeperDubboServiceConsumerBootstrap.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ConsulDubboServiceConsumerBootstrap.java
@@ -18,18 +18,22 @@ package org.apache.dubbo.config.bootstrap;
 
 import org.apache.dubbo.config.bootstrap.rest.UserService;
 
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
+
 /**
  * Dubbo Provider Bootstrap
  *
  * @since 2.7.5
  */
-public class ZookeeperDubboServiceConsumerBootstrap {
+public class ConsulDubboServiceConsumerBootstrap {
 
     public static void main(String[] args) throws Exception {
 
         DubboBootstrap bootstrap = DubboBootstrap.getInstance()
-                .application("zookeeper-dubbo-consumer")
-                .registry("zookeeper", builder -> builder.address("zookeeper://127.0.0.1:2181?registry-type=service&subscribed-services=zookeeper-dubbo-provider"))
+                .application("consul-dubbo-consumer", app -> app.metadata(DEFAULT_METADATA_STORAGE_TYPE))
+                .registry("zookeeper", builder -> builder.address("consul://127.0.0.1:8500?registry-type=service&subscribed-services=consul-dubbo-provider")
+                        .useAsConfigCenter(true)
+                        .useAsMetadataCenter(true))
                 .reference("echo", builder -> builder.interfaceClass(EchoService.class).protocol("dubbo"))
                 .reference("user", builder -> builder.interfaceClass(UserService.class).protocol("rest"))
                 .start();
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ZookeeperDubboServiceProviderBootstrap.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ConsulDubboServiceProviderBootstrap.java
similarity index 77%
copy from dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ZookeeperDubboServiceProviderBootstrap.java
copy to dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ConsulDubboServiceProviderBootstrap.java
index c653fe7..ef38944 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ZookeeperDubboServiceProviderBootstrap.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ConsulDubboServiceProviderBootstrap.java
@@ -19,15 +19,19 @@ package org.apache.dubbo.config.bootstrap;
 import org.apache.dubbo.config.bootstrap.rest.UserService;
 import org.apache.dubbo.config.bootstrap.rest.UserServiceImpl;
 
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
+
 /**
  * TODO
  */
-public class ZookeeperDubboServiceProviderBootstrap {
+public class ConsulDubboServiceProviderBootstrap {
 
     public static void main(String[] args) {
         DubboBootstrap.getInstance()
-                .application("zookeeper-dubbo-provider")
-                .registry(builder -> builder.address("zookeeper://127.0.0.1:2181?registry-type=service"))
+                .application("consul-dubbo-provider", app -> app.metadata(DEFAULT_METADATA_STORAGE_TYPE))
+                .registry(builder -> builder.address("consul://127.0.0.1:8500?registry-type=service")
+                        .useAsConfigCenter(true)
+                        .useAsMetadataCenter(true))
                 .protocol("dubbo", builder -> builder.port(-1).name("dubbo"))
                 .protocol("rest", builder -> builder.port(8081).name("rest"))
                 .service("echo", builder -> builder.interfaceClass(EchoService.class).ref(new EchoServiceImpl()).protocolIds("dubbo"))
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/NacosDubboServiceConsumerBootstrap.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/NacosDubboServiceConsumerBootstrap.java
index b91659d..37262e2 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/NacosDubboServiceConsumerBootstrap.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/NacosDubboServiceConsumerBootstrap.java
@@ -17,9 +17,10 @@
 package org.apache.dubbo.config.bootstrap;
 
 import org.apache.dubbo.config.ApplicationConfig;
-import org.apache.dubbo.config.MetadataReportConfig;
 import org.apache.dubbo.config.bootstrap.rest.UserService;
 
+import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
+
 /**
  * Dubbo Provider Bootstrap
  *
@@ -30,21 +31,25 @@ public class NacosDubboServiceConsumerBootstrap {
     public static void main(String[] args) throws Exception {
 
         ApplicationConfig applicationConfig = new ApplicationConfig("dubbo-nacos-consumer-demo");
-//        applicationConfig.setMetadataType("remote");
+        applicationConfig.setMetadataType(REMOTE_METADATA_STORAGE_TYPE);
         DubboBootstrap bootstrap = DubboBootstrap.getInstance()
                 .application(applicationConfig)
-                // Zookeeper
-//                .registry("nacos", builder -> builder.address("nacos://127.0.0.1:8848?registry.type=service&subscribed.services=dubbo-nacos-provider-demo"))
-//                .registry("nacos", builder -> builder.address("nacos://127.0.0.1:8848?registry-type=service&subscribed-services=dubbo-nacos-provider-demo"))
-                .registry("nacos", builder -> builder.address("nacos://127.0.0.1:8848?registry-type=service&subscribed-services=dubbo-nacos-provider-demo"))
-                .metadataReport(new MetadataReportConfig("nacos://127.0.0.1:8848"))
+                // Nacos in service registry type
+                .registry("nacos", builder -> builder.address("nacos://127.0.0.1:8848?registry-type=service")
+                        .useAsConfigCenter(true)
+                        .useAsMetadataCenter(true))
+                // Nacos in traditional registry type
+//                .registry("nacos-traditional", builder -> builder.address("nacos://127.0.0.1:8848"))
+                .reference("echo", builder -> builder.interfaceClass(EchoService.class).protocol("dubbo"))
                 .reference("user", builder -> builder.interfaceClass(UserService.class).protocol("rest"))
                 .start();
 
+        EchoService echoService = bootstrap.getCache().get(EchoService.class);
         UserService userService = bootstrap.getCache().get(UserService.class);
 
-        for (int i = 0; i < 500; i++) {
+        for (int i = 0; i < 5; i++) {
             Thread.sleep(2000L);
+            System.out.println(echoService.echo("Hello,World"));
             System.out.println(userService.getUser(i * 1L));
         }
     }
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/NacosDubboServiceProviderBootstrap.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/NacosDubboServiceProviderBootstrap.java
index 35ed723..f55790a 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/NacosDubboServiceProviderBootstrap.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/NacosDubboServiceProviderBootstrap.java
@@ -17,10 +17,13 @@
 package org.apache.dubbo.config.bootstrap;
 
 import org.apache.dubbo.config.ApplicationConfig;
-import org.apache.dubbo.config.MetadataReportConfig;
 import org.apache.dubbo.config.bootstrap.rest.UserService;
 import org.apache.dubbo.config.bootstrap.rest.UserServiceImpl;
 
+import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_TYPE_KEY;
+import static org.apache.dubbo.common.constants.RegistryConstants.SERVICE_REGISTRY_TYPE;
+
 /**
  * Dubbo Provider Bootstrap
  *
@@ -30,15 +33,16 @@ public class NacosDubboServiceProviderBootstrap {
 
     public static void main(String[] args) {
         ApplicationConfig applicationConfig = new ApplicationConfig("dubbo-nacos-provider-demo");
-//        applicationConfig.setMetadataType("remote");
+        applicationConfig.setMetadataType(REMOTE_METADATA_STORAGE_TYPE);
         DubboBootstrap.getInstance()
                 .application(applicationConfig)
-                // Zookeeper in service registry type
-                .registry("nacos", builder -> builder.address("nacos://127.0.0.1:8848?registry-type=service").useAsConfigCenter(true))
-                // Nacos
-//                .registry("nacos", builder -> builder.address("nacos://127.0.0.1:8848?registry.type=service"))
-//                .registry(RegistryBuilder.newBuilder().address("etcd3://127.0.0.1:2379?registry.type=service").build())
-                .metadataReport(new MetadataReportConfig("nacos://127.0.0.1:8848"))
+                // Nacos in service registry type
+                .registry("nacos", builder -> builder.address("nacos://127.0.0.1:8848")
+                        .parameter(REGISTRY_TYPE_KEY, SERVICE_REGISTRY_TYPE)
+                        .useAsConfigCenter(true)
+                        .useAsMetadataCenter(true))
+                // Nacos in traditional registry type
+//                .registry("nacos-traditional", builder -> builder.address("nacos://127.0.0.1:8848"))
                 .protocol("dubbo", builder -> builder.port(20885).name("dubbo"))
                 .protocol("rest", builder -> builder.port(9090).name("rest"))
                 .service(builder -> builder.id("echo").interfaceClass(EchoService.class).ref(new EchoServiceImpl()).protocolIds("dubbo"))
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ZookeeperDubboServiceConsumerBootstrap.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ZookeeperDubboServiceConsumerBootstrap.java
index 9b0d866..27f3eb9 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ZookeeperDubboServiceConsumerBootstrap.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ZookeeperDubboServiceConsumerBootstrap.java
@@ -18,6 +18,10 @@ package org.apache.dubbo.config.bootstrap;
 
 import org.apache.dubbo.config.bootstrap.rest.UserService;
 
+import static org.apache.dubbo.common.constants.CommonConstants.COMPOSITE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_TYPE_KEY;
+import static org.apache.dubbo.common.constants.RegistryConstants.SERVICE_REGISTRY_TYPE;
+
 /**
  * Dubbo Provider Bootstrap
  *
@@ -28,9 +32,12 @@ public class ZookeeperDubboServiceConsumerBootstrap {
     public static void main(String[] args) throws Exception {
 
         DubboBootstrap bootstrap = DubboBootstrap.getInstance()
-                .application("zookeeper-dubbo-consumer")
-                .registry("zookeeper", builder -> builder.address("zookeeper://127.0.0.1:2181?registry-type=service&subscribed-services=zookeeper-dubbo-provider"))
-                .reference("echo", builder -> builder.interfaceClass(EchoService.class).protocol("dubbo"))
+                .application("zookeeper-dubbo-consumer", app -> app.metadata(COMPOSITE_METADATA_STORAGE_TYPE))
+                .registry("zookeeper", builder -> builder.address("zookeeper://127.0.0.1:2181")
+                        .parameter(REGISTRY_TYPE_KEY, SERVICE_REGISTRY_TYPE)
+                        .useAsConfigCenter(true)
+                        .useAsMetadataCenter(true))
+                .reference("echo", builder -> builder.interfaceClass(EchoService.class).protocol("dubbo").services("zookeeper-dubbo-provider"))
                 .reference("user", builder -> builder.interfaceClass(UserService.class).protocol("rest"))
                 .start();
 
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ZookeeperDubboServiceProviderBootstrap.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ZookeeperDubboServiceProviderBootstrap.java
index c653fe7..e9b946f 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ZookeeperDubboServiceProviderBootstrap.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/ZookeeperDubboServiceProviderBootstrap.java
@@ -19,6 +19,10 @@ package org.apache.dubbo.config.bootstrap;
 import org.apache.dubbo.config.bootstrap.rest.UserService;
 import org.apache.dubbo.config.bootstrap.rest.UserServiceImpl;
 
+import static org.apache.dubbo.common.constants.CommonConstants.COMPOSITE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_TYPE_KEY;
+import static org.apache.dubbo.common.constants.RegistryConstants.SERVICE_REGISTRY_TYPE;
+
 /**
  * TODO
  */
@@ -26,8 +30,11 @@ public class ZookeeperDubboServiceProviderBootstrap {
 
     public static void main(String[] args) {
         DubboBootstrap.getInstance()
-                .application("zookeeper-dubbo-provider")
-                .registry(builder -> builder.address("zookeeper://127.0.0.1:2181?registry-type=service"))
+                .application("zookeeper-dubbo-provider", app -> app.metadata(COMPOSITE_METADATA_STORAGE_TYPE))
+                .registry(builder -> builder.address("zookeeper://127.0.0.1:2181")
+                        .parameter(REGISTRY_TYPE_KEY, SERVICE_REGISTRY_TYPE)
+                        .useAsConfigCenter(true)
+                        .useAsMetadataCenter(true))
                 .protocol("dubbo", builder -> builder.port(-1).name("dubbo"))
                 .protocol("rest", builder -> builder.port(8081).name("rest"))
                 .service("echo", builder -> builder.interfaceClass(EchoService.class).ref(new EchoServiceImpl()).protocolIds("dubbo"))
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/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-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/url/ExporterSideConfigUrlTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/url/ExporterSideConfigUrlTest.java
index e1fbfb4..5c19d04 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/url/ExporterSideConfigUrlTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/url/ExporterSideConfigUrlTest.java
@@ -19,6 +19,7 @@ package org.apache.dubbo.config.url;
 
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeAll;
@@ -42,13 +43,13 @@ public class ExporterSideConfigUrlTest extends UrlTestBase {
 
     @BeforeEach
     public void setUp() {
+        ApplicationModel.reset();
         initServConf();
-//        ApplicationModel.getConfigManager().clear();
     }
 
     @AfterEach()
     public void teardown() {
-//        ApplicationModel.getConfigManager().clear();
+        ApplicationModel.reset();
     }
 
     @Test
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToStringConverterTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java
similarity index 52%
rename from dubbo-common/src/test/java/org/apache/dubbo/convert/StringToStringConverterTest.java
rename to dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java
index 57806c3..8096276 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/convert/StringToStringConverterTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java
@@ -14,41 +14,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.convert;
+package org.apache.dubbo.metadata;
 
-import org.apache.dubbo.common.convert.Converter;
-import org.apache.dubbo.common.convert.StringToStringConverter;
-
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader;
+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.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
- * {@link StringToStringConverter} Test
+ * {@link MetadataServiceExporter} Test-Cases
  *
- * @since 2.7.6
+ * @since 2.7.8
  */
-public class StringToStringConverterTest {
-
-    private StringToStringConverter converter;
-
-    @BeforeEach
-    public void init() {
-        converter = (StringToStringConverter) getExtensionLoader(Converter.class).getExtension("string-to-string");
-    }
-
-    @Test
-    public void testAccept() {
-        assertTrue(converter.accept(String.class, String.class));
-    }
+public class MetadataServiceExporterTest {
 
     @Test
-    public void testConvert() {
-        assertEquals("1", converter.convert("1"));
-        assertNull(converter.convert(null));
+    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/pom.xml b/dubbo-config/dubbo-config-spring/pom.xml
index 74cc929..31bfcf4 100644
--- a/dubbo-config/dubbo-config-spring/pom.xml
+++ b/dubbo-config/dubbo-config-spring/pom.xml
@@ -142,6 +142,27 @@
             <scope>test</scope>
         </dependency>
 
+        <!-- Zookeeper dependencies for testing -->
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-registry-zookeeper</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-metadata-report-zookeeper</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.google.guava</groupId>
+                    <artifactId>guava</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
         <!-- Nacos dependencies for testing -->
         <dependency>
             <groupId>org.apache.dubbo</groupId>
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java
index 05c8139..07de2ee 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessor.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.DubboService;
 import org.apache.dubbo.config.annotation.Reference;
 import org.apache.dubbo.config.annotation.Service;
 import org.apache.dubbo.config.spring.ReferenceBean;
 import org.apache.dubbo.config.spring.ServiceBean;
-import org.apache.dubbo.config.spring.context.event.ServiceBeanExportedEvent;
 
 import com.alibaba.spring.beans.factory.annotation.AbstractAnnotationBeanPostProcessor;
 import org.springframework.beans.BeansException;
@@ -31,12 +31,9 @@ import org.springframework.beans.factory.config.RuntimeBeanReference;
 import org.springframework.beans.factory.support.AbstractBeanDefinition;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContextAware;
-import org.springframework.context.ApplicationListener;
 import org.springframework.core.annotation.AnnotationAttributes;
 
 import java.lang.reflect.Field;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.Collection;
 import java.util.Collections;
@@ -46,7 +43,6 @@ import java.util.concurrent.ConcurrentMap;
 
 import static com.alibaba.spring.util.AnnotationUtils.getAttribute;
 import static com.alibaba.spring.util.AnnotationUtils.getAttributes;
-import static java.lang.reflect.Proxy.newProxyInstance;
 import static org.apache.dubbo.config.spring.beans.factory.annotation.ServiceBeanNameBuilder.create;
 import static org.springframework.util.StringUtils.hasText;
 
@@ -60,7 +56,7 @@ import static org.springframework.util.StringUtils.hasText;
  * @since 2.5.7
  */
 public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBeanPostProcessor implements
-        ApplicationContextAware, ApplicationListener<ServiceBeanExportedEvent> {
+        ApplicationContextAware {
 
     /**
      * The bean name of {@link ReferenceAnnotationBeanPostProcessor}
@@ -81,9 +77,6 @@ public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBean
     private final ConcurrentMap<InjectionMetadata.InjectedElement, ReferenceBean<?>> injectedMethodReferenceBeanCache =
             new ConcurrentHashMap<>(CACHE_SIZE);
 
-    private final ConcurrentMap<String, ReferencedBeanInvocationHandler> referencedBeanInvocationHandlersCache =
-            new ConcurrentHashMap<>();
-
     private ApplicationContext applicationContext;
 
     /**
@@ -142,11 +135,13 @@ public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBean
 
         boolean localServiceBean = isLocalServiceBean(referencedBeanName, referenceBean, attributes);
 
+        prepareReferenceBean(referencedBeanName, referenceBean, localServiceBean);
+
         registerReferenceBean(referencedBeanName, referenceBean, attributes, localServiceBean, injectedType);
 
         cacheInjectedReferenceBean(referenceBean, injectedElement);
 
-        return getOrCreateProxy(referencedBeanName, referenceBean, localServiceBean, injectedType);
+        return referenceBean.get();
     }
 
     /**
@@ -261,23 +256,19 @@ public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBean
     }
 
     /**
-     * Get or Create a proxy of {@link ReferenceBean} for the specified the type of Dubbo service interface
+     * Prepare {@link ReferenceBean}
      *
-     * @param referencedBeanName   The name of bean that annotated Dubbo's {@link Service @Service} in the Spring {@link ApplicationContext}
-     * @param referenceBean        the instance of {@link ReferenceBean}
-     * @param localServiceBean     Is Local Service bean or not
-     * @param serviceInterfaceType the type of Dubbo service interface
-     * @return non-null
-     * @since 2.7.4
+     * @param referencedBeanName The name of bean that annotated Dubbo's {@link DubboService @DubboService}
+     *                           in the Spring {@link ApplicationContext}
+     * @param referenceBean      the instance of {@link ReferenceBean}
+     * @param localServiceBean   Is Local Service bean or not
+     * @since 2.7.8
      */
-    private Object getOrCreateProxy(String referencedBeanName, ReferenceBean referenceBean, boolean localServiceBean,
-                                    Class<?> serviceInterfaceType) {
-        if (localServiceBean) { // If the local @Service Bean exists, build a proxy of Service
-            return newProxyInstance(getClassLoader(), new Class[]{serviceInterfaceType},
-                    newReferencedBeanInvocationHandler(referencedBeanName));
-        } else {
+    private void prepareReferenceBean(String referencedBeanName, ReferenceBean referenceBean, boolean localServiceBean) {
+        //  Issue : https://github.com/apache/dubbo/issues/6224
+        if (localServiceBean) { // If the local @Service Bean exists
+            referenceBean.setInjvm(Boolean.TRUE);
             exportServiceBeanIfNecessary(referencedBeanName); // If the referenced ServiceBean exits, export it immediately
-            return referenceBean.get();
         }
     }
 
@@ -295,58 +286,6 @@ public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBean
         return applicationContext.getBean(referencedBeanName, ServiceBean.class);
     }
 
-    private InvocationHandler newReferencedBeanInvocationHandler(String referencedBeanName) {
-        return referencedBeanInvocationHandlersCache.computeIfAbsent(referencedBeanName,
-                ReferencedBeanInvocationHandler::new);
-    }
-
-    /**
-     * The {@link InvocationHandler} class for the referenced Bean
-     */
-    @Override
-    public void onApplicationEvent(ServiceBeanExportedEvent event) {
-        initReferencedBeanInvocationHandler(event.getServiceBean());
-    }
-
-    private void initReferencedBeanInvocationHandler(ServiceBean serviceBean) {
-        String serviceBeanName = serviceBean.getBeanName();
-        referencedBeanInvocationHandlersCache.computeIfPresent(serviceBeanName, (name, handler) -> {
-            handler.init();
-            return null;
-        });
-    }
-
-    private class ReferencedBeanInvocationHandler implements InvocationHandler {
-
-        private final String referencedBeanName;
-
-        private Object bean;
-
-        private ReferencedBeanInvocationHandler(String referencedBeanName) {
-            this.referencedBeanName = referencedBeanName;
-        }
-
-        @Override
-        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-            Object result = null;
-            try {
-                if (bean == null) {
-                    init();
-                }
-                result = method.invoke(bean, args);
-            } catch (InvocationTargetException e) {
-                // re-throws the actual Exception.
-                throw e.getTargetException();
-            }
-            return result;
-        }
-
-        private void init() {
-            ServiceBean serviceBean = applicationContext.getBean(referencedBeanName, ServiceBean.class);
-            this.bean = serviceBean.getRef();
-        }
-    }
-
     @Override
     protected String buildInjectedObjectCacheKey(AnnotationAttributes attributes, Object bean, String beanName,
                                                  Class<?> injectedType, InjectionMetadata.InjectedElement injectedElement) {
@@ -402,7 +341,6 @@ public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBean
     public void destroy() throws Exception {
         super.destroy();
         this.referenceBeanCache.clear();
-        this.referencedBeanInvocationHandlersCache.clear();
         this.injectedFieldReferenceBeanCache.clear();
         this.injectedMethodReferenceBeanCache.clear();
     }
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/schema/AnnotationBeanDefinitionParser.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/schema/AnnotationBeanDefinitionParser.java
index bcc16bf..05d9027 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/schema/AnnotationBeanDefinitionParser.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/schema/AnnotationBeanDefinitionParser.java
@@ -25,7 +25,6 @@ import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
 import org.springframework.beans.factory.xml.ParserContext;
 import org.w3c.dom.Element;
 
-import static org.apache.dubbo.config.spring.util.DubboBeanUtils.registerCommonBeans;
 import static org.springframework.util.StringUtils.commaDelimitedListToStringArray;
 import static org.springframework.util.StringUtils.trimArrayElements;
 
@@ -58,8 +57,13 @@ public class AnnotationBeanDefinitionParser extends AbstractSingleBeanDefinition
 
         builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
 
-        // @since 2.7.6 Register the common beans
-        registerCommonBeans(parserContext.getRegistry());
+        /**
+         * @since 2.7.6 Register the common beans
+         * @since 2.7.8 comment this code line, and migrated to
+         * @see DubboNamespaceHandler#parse(Element, ParserContext)
+         * @see https://github.com/apache/dubbo/issues/6174
+         */
+        // registerCommonBeans(parserContext.getRegistry());
     }
 
     @Override
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/schema/DubboBeanDefinitionParser.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/schema/DubboBeanDefinitionParser.java
index 31b299a..084479a 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/schema/DubboBeanDefinitionParser.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/schema/DubboBeanDefinitionParser.java
@@ -29,14 +29,13 @@ import org.apache.dubbo.config.ProviderConfig;
 import org.apache.dubbo.config.RegistryConfig;
 import org.apache.dubbo.config.spring.ReferenceBean;
 import org.apache.dubbo.config.spring.ServiceBean;
-import org.apache.dubbo.config.spring.beans.factory.annotation.DubboConfigAliasPostProcessor;
 
 import org.springframework.beans.PropertyValue;
 import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.config.BeanDefinitionHolder;
 import org.springframework.beans.factory.config.RuntimeBeanReference;
 import org.springframework.beans.factory.config.TypedStringValue;
-import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
 import org.springframework.beans.factory.support.ManagedList;
 import org.springframework.beans.factory.support.ManagedMap;
 import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -55,7 +54,6 @@ import java.util.HashSet;
 import java.util.Set;
 import java.util.regex.Pattern;
 
-import static com.alibaba.spring.util.BeanRegistrar.registerInfrastructureBean;
 import static org.apache.dubbo.common.constants.CommonConstants.HIDE_KEY_PREFIX;
 
 /**
@@ -80,7 +78,7 @@ public class DubboBeanDefinitionParser implements BeanDefinitionParser {
     }
 
     @SuppressWarnings("unchecked")
-    private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
+    private static RootBeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
         RootBeanDefinition beanDefinition = new RootBeanDefinition();
         beanDefinition.setBeanClass(beanClass);
         beanDefinition.setLazyInit(false);
@@ -130,7 +128,7 @@ public class DubboBeanDefinitionParser implements BeanDefinitionParser {
                 parseProperties(element.getChildNodes(), classDefinition, parserContext);
                 beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
             }
-        }  else if (ProviderConfig.class.equals(beanClass)) {
+        } else if (ProviderConfig.class.equals(beanClass)) {
             parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
         } else if (ConsumerConfig.class.equals(beanClass)) {
             parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
@@ -350,11 +348,18 @@ public class DubboBeanDefinitionParser implements BeanDefinitionParser {
                 if (methods == null) {
                     methods = new ManagedList();
                 }
-                BeanDefinition methodBeanDefinition = parse(element,
+                RootBeanDefinition methodBeanDefinition = parse(element,
                         parserContext, MethodConfig.class, false);
-                String name = id + "." + methodName;
+                String beanName = id + "." + methodName;
+
+                // If the PropertyValue named "id" can't be found,
+                // bean name will be taken as the "id" PropertyValue for MethodConfig
+                if (!hasPropertyValue(methodBeanDefinition, "id")) {
+                    addPropertyValue(methodBeanDefinition, "id", beanName);
+                }
+
                 BeanDefinitionHolder methodBeanDefinitionHolder = new BeanDefinitionHolder(
-                        methodBeanDefinition, name);
+                        methodBeanDefinition, beanName);
                 methods.add(methodBeanDefinitionHolder);
             }
         }
@@ -363,6 +368,17 @@ public class DubboBeanDefinitionParser implements BeanDefinitionParser {
         }
     }
 
+    private static boolean hasPropertyValue(AbstractBeanDefinition beanDefinition, String propertyName) {
+        return beanDefinition.getPropertyValues().contains(propertyName);
+    }
+
+    private static void addPropertyValue(AbstractBeanDefinition beanDefinition, String propertyName, String propertyValue) {
+        if (StringUtils.isBlank(propertyName) || StringUtils.isBlank(propertyValue)) {
+            return;
+        }
+        beanDefinition.getPropertyValues().addPropertyValue(propertyName, propertyValue);
+    }
+
     @SuppressWarnings("unchecked")
     private static void parseArguments(String id, NodeList nodeList, RootBeanDefinition beanDefinition,
                                        ParserContext parserContext) {
@@ -395,22 +411,9 @@ public class DubboBeanDefinitionParser implements BeanDefinitionParser {
 
     @Override
     public BeanDefinition parse(Element element, ParserContext parserContext) {
-        // Register DubboConfigAliasPostProcessor
-        registerDubboConfigAliasPostProcessor(parserContext.getRegistry());
-
         return parse(element, parserContext, beanClass, required);
     }
 
-    /**
-     * Register {@link DubboConfigAliasPostProcessor}
-     *
-     * @param registry {@link BeanDefinitionRegistry}
-     * @since 2.7.5 [Feature] https://github.com/apache/dubbo/issues/5093
-     */
-    private void registerDubboConfigAliasPostProcessor(BeanDefinitionRegistry registry) {
-        registerInfrastructureBean(registry, DubboConfigAliasPostProcessor.BEAN_NAME, DubboConfigAliasPostProcessor.class);
-    }
-
     private static String resolveAttribute(Element element, String attributeName, ParserContext parserContext) {
         String attributeValue = element.getAttribute(attributeName);
         Environment environment = parserContext.getReaderContext().getEnvironment();
diff --git a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd
index bfaaf3f..b12d815 100644
--- a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd
+++ b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd
@@ -6,7 +6,8 @@
             targetNamespace="http://dubbo.apache.org/schema/dubbo">
 
     <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
-    <xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans.xsd"/>
+    <xsd:import namespace="http://www.springframework.org/schema/beans"
+                schemaLocation="http://www.springframework.org/schema/beans/spring-beans.xsd"/>
     <xsd:import namespace="http://www.springframework.org/schema/tool"/>
 
     <xsd:annotation>
@@ -702,7 +703,8 @@
         </xsd:attribute>
         <xsd:attribute name="cluster" type="xsd:string" use="optional">
             <xsd:annotation>
-                <xsd:documentation><![CDATA[ The config center cluster, it's real meaning may very on different Config Center products. ]]></xsd:documentation>
+                <xsd:documentation>
+                    <![CDATA[ The config center cluster, it's real meaning may very on different Config Center products. ]]></xsd:documentation>
             </xsd:annotation>
         </xsd:attribute>
         <xsd:attribute name="namespace" type="xsd:string" use="optional">
@@ -945,7 +947,8 @@
                 </xsd:attribute>
                 <xsd:attribute name="threadpool" type="xsd:string">
                     <xsd:annotation>
-                        <xsd:documentation><![CDATA[ Consumer threadpool: cached, fixed, limited, eager]]></xsd:documentation>
+                        <xsd:documentation>
+                            <![CDATA[ Consumer threadpool: cached, fixed, limited, eager]]></xsd:documentation>
                     </xsd:annotation>
                 </xsd:attribute>
                 <xsd:attribute name="corethreads" type="xsd:string">
@@ -1011,6 +1014,14 @@
                         <xsd:documentation><![CDATA[ The service protocol. ]]></xsd:documentation>
                     </xsd:annotation>
                 </xsd:attribute>
+                <xsd:attribute name="services" type="xsd:string">
+                    <xsd:annotation>
+                        <xsd:documentation>
+                            <![CDATA[ The service names that the Dubbo interface subscribed.
+                            If it is a multiple-values, the content will be a comma-delimited String. ]]>
+                        </xsd:documentation>
+                    </xsd:annotation>
+                </xsd:attribute>
                 <xsd:anyAttribute namespace="##other" processContents="lax"/>
             </xsd:extension>
         </xsd:complexContent>
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessorTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessorTest.java
index 679d46d..598933d 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessorTest.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceAnnotationBeanPostProcessorTest.java
@@ -22,11 +22,14 @@ import org.apache.dubbo.config.spring.ReferenceBean;
 import org.apache.dubbo.config.spring.api.DemoService;
 import org.apache.dubbo.config.spring.api.HelloService;
 import org.apache.dubbo.config.utils.ReferenceConfigCache;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.annotation.Around;
 import org.aspectj.lang.annotation.Aspect;
+import org.junit.After;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -67,6 +70,16 @@ import static org.junit.Assert.assertTrue;
 @EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
 public class ReferenceAnnotationBeanPostProcessorTest {
 
+    @Before
+    public void setUp() {
+        ApplicationModel.reset();
+    }
+
+    @After
+    public void tearDown() {
+        ApplicationModel.reset();
+    }
+
     private static final String AOP_SUFFIX = "(based on AOP)";
 
     @Aspect
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 4395c39..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,15 +17,20 @@
 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.Assert;
+import org.junit.Before;
 import org.junit.Test;
 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;
 
@@ -33,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;
 
@@ -40,6 +46,7 @@ import static org.springframework.util.ReflectionUtils.findField;
  * {@link ReferenceBeanBuilder} Test
  *
  * @see ReferenceBeanBuilder
+ * @see DubboReference
  * @see Reference
  * @since 2.6.4
  */
@@ -47,7 +54,7 @@ import static org.springframework.util.ReflectionUtils.findField;
 @ContextConfiguration(classes = ReferenceBeanBuilderTest.class)
 public class ReferenceBeanBuilderTest {
 
-    @Reference(
+    @DubboReference(
             interfaceClass = CharSequence.class,
             interfaceName = "java.lang.CharSequence",
             version = "1.0.0", group = "TEST_GROUP", url = "dubbo://localhost:12345",
@@ -62,17 +69,27 @@ 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();
 
     @Autowired
     private ApplicationContext context;
 
+    @Before
+    public void init() {
+        ApplicationModel.reset();
+    }
+
     @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());
@@ -81,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());
@@ -108,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-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationBeanPostProcessorTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationBeanPostProcessorTest.java
index a509b43..171cda3 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationBeanPostProcessorTest.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceAnnotationBeanPostProcessorTest.java
@@ -18,8 +18,11 @@ package org.apache.dubbo.config.spring.beans.factory.annotation;
 
 import org.apache.dubbo.config.spring.ServiceBean;
 import org.apache.dubbo.config.spring.api.HelloService;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
+import org.junit.After;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -49,6 +52,16 @@ import java.util.Map;
 })
 public class ServiceAnnotationBeanPostProcessorTest {
 
+    @Before
+    public void setUp() {
+        ApplicationModel.reset();
+    }
+
+    @After
+    public void tearDown() {
+        ApplicationModel.reset();
+    }
+
     @Autowired
     private ConfigurableListableBeanFactory beanFactory;
 
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceClassPostProcessorTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceClassPostProcessorTest.java
index 4422e75..e6c1d77 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceClassPostProcessorTest.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ServiceClassPostProcessorTest.java
@@ -18,8 +18,11 @@ package org.apache.dubbo.config.spring.beans.factory.annotation;
 
 import org.apache.dubbo.config.spring.ServiceBean;
 import org.apache.dubbo.config.spring.api.HelloService;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
+import org.junit.After;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -49,6 +52,16 @@ import java.util.Map;
 })
 public class ServiceClassPostProcessorTest {
 
+    @Before
+    public void setUp() {
+        ApplicationModel.reset();
+    }
+
+    @After
+    public void tearDown() {
+        ApplicationModel.reset();
+    }
+
     @Autowired
     private ConfigurableListableBeanFactory beanFactory;
 
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/schema/GenericServiceTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/config/MultipleServicesWithMethodConfigsTest.java
similarity index 62%
copy from dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/schema/GenericServiceTest.java
copy to dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/config/MultipleServicesWithMethodConfigsTest.java
index 410e3e9..d66c8c3 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/schema/GenericServiceTest.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/config/MultipleServicesWithMethodConfigsTest.java
@@ -14,37 +14,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.config.spring.schema;
-
-import org.apache.dubbo.config.spring.ReferenceBean;
-import org.apache.dubbo.config.spring.ServiceBean;
+package org.apache.dubbo.config.spring.beans.factory.config;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.ImportResource;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringRunner;
 
-import static org.junit.Assert.assertNotNull;
-
 @RunWith(SpringRunner.class)
-@ContextConfiguration(classes = GenericServiceTest.class)
-@ImportResource(locations = "classpath:/META-INF/spring/dubbo-generic-consumer.xml")
-public class GenericServiceTest {
-
-    @Autowired
-    @Qualifier("demoServiceRef")
-    private ReferenceBean referenceBean;
+@ContextConfiguration(classes = MultipleServicesWithMethodConfigsTest.class)
+@ImportResource(locations = "classpath:/META-INF/spring/multiple-services-with-methods.xml")
+public class MultipleServicesWithMethodConfigsTest {
 
     @Autowired
-    @Qualifier("demoService")
-    private ServiceBean serviceBean;
+    private ApplicationContext applicationContext;
 
     @Test
-    public void testBeanDefinitionParser() {
-        assertNotNull(referenceBean);
-        assertNotNull(serviceBean);
+    public void test() {
+//        Map<String, MethodConfig> methodConfigs = applicationContext.getBeansOfType(MethodConfig.class);
+//        assertEquals(2, methodConfigs.size());
     }
 }
+
+
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/annotation/DubboComponentScanRegistrarTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/annotation/DubboComponentScanRegistrarTest.java
index 2116302..80a8d40 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/annotation/DubboComponentScanRegistrarTest.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/annotation/DubboComponentScanRegistrarTest.java
@@ -41,12 +41,12 @@ public class DubboComponentScanRegistrarTest {
 
     @BeforeEach
     public void setUp() {
-        ApplicationModel.getConfigManager().clear();
+        ApplicationModel.reset();
     }
 
     @AfterEach
     public void tearDown() {
-        ApplicationModel.getConfigManager().clear();
+        ApplicationModel.reset();
     }
 
     @Test
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/annotation/EnableDubboTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/annotation/EnableDubboTest.java
index 42fadfc..d9364d0 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/annotation/EnableDubboTest.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/annotation/EnableDubboTest.java
@@ -52,13 +52,13 @@ public class EnableDubboTest {
 
     @BeforeEach
     public void setUp() {
-        ApplicationModel.getConfigManager().clear();
+        ApplicationModel.reset();
         context = new AnnotationConfigApplicationContext();
     }
 
     @AfterEach
     public void tearDown() {
-        ApplicationModel.getConfigManager().clear();
+        ApplicationModel.reset();
         context.close();
     }
 
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/annotation/consumer/test/TestConsumerConfiguration.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/annotation/consumer/test/TestConsumerConfiguration.java
index b5b0aa5..47e0d30 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/annotation/consumer/test/TestConsumerConfiguration.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/annotation/consumer/test/TestConsumerConfiguration.java
@@ -38,7 +38,10 @@ public class TestConsumerConfiguration {
 
     private static final String remoteURL = "dubbo://127.0.0.1:12345?version=2.5.7";
 
-    @Reference(version = "2.5.7", url = remoteURL, application = "dubbo-demo-application")
+    @Reference(version = "2.5.7",
+            url = remoteURL,
+            application = "dubbo-demo-application",
+            filter = "mymock")
     private DemoService demoService;
 
     @Autowired
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/properties/DefaultDubboConfigBinderTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/properties/DefaultDubboConfigBinderTest.java
index aea9fb9..2123b54 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/properties/DefaultDubboConfigBinderTest.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/context/properties/DefaultDubboConfigBinderTest.java
@@ -20,7 +20,10 @@ package org.apache.dubbo.config.spring.context.properties;
 import org.apache.dubbo.config.ApplicationConfig;
 import org.apache.dubbo.config.ProtocolConfig;
 import org.apache.dubbo.config.RegistryConfig;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.jupiter.api.Assertions;
 import org.junit.runner.RunWith;
@@ -34,6 +37,16 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 @ContextConfiguration(classes = DefaultDubboConfigBinder.class)
 public class DefaultDubboConfigBinderTest {
 
+    @Before
+    public void setUp() {
+        ApplicationModel.reset();
+    }
+
+    @After
+    public void tearDown() {
+        ApplicationModel.reset();
+    }
+
     @Autowired
     private DubboConfigBinder dubboConfigBinder;
 
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/issues/Issue6252Test.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/issues/Issue6252Test.java
new file mode 100644
index 0000000..8c34e7e
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/issues/Issue6252Test.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.config.spring.issues;
+
+import org.apache.dubbo.config.spring.ReferenceBean;
+import org.apache.dubbo.config.spring.context.annotation.EnableDubboConfig;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+
+/**
+ * The test-case for https://github.com/apache/dubbo/issues/6252
+ *
+ * @since 2.7.8
+ */
+@Configuration
+@EnableDubboConfig
+@PropertySource("classpath:/META-INF/issue-6252-test.properties")
+public class Issue6252Test {
+
+    @Bean
+    public static ReferenceBean referenceBean() {
+        return new ReferenceBean();
+    }
+
+    @Test
+    public void test() throws Exception {
+        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Issue6252Test.class);
+        context.getBean(ReferenceBean.class);
+        context.close();
+    }
+
+}
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/samples/ZookeeperDubboSpringConsumerBootstrap.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/samples/ZookeeperDubboSpringConsumerBootstrap.java
new file mode 100644
index 0000000..4c567e5
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/samples/ZookeeperDubboSpringConsumerBootstrap.java
@@ -0,0 +1,53 @@
+/*
+ * 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.spring.samples;
+
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.apache.dubbo.config.spring.api.DemoService;
+import org.apache.dubbo.config.spring.context.annotation.EnableDubboConfig;
+
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.PropertySource;
+
+/**
+ * Zookeeper Dubbo Spring Provider Bootstrap
+ *
+ * @since 2.7.8
+ */
+@EnableDubboConfig
+@PropertySource("classpath:/META-INF/service-introspection/zookeeper-dubbb-consumer.properties")
+public class ZookeeperDubboSpringConsumerBootstrap {
+
+    @DubboReference(services = "${dubbo.provider.name},${dubbo.provider.name1},${dubbo.provider.name2}")
+    private DemoService demoService;
+
+    public static void main(String[] args) throws Exception {
+        Class<?> beanType = ZookeeperDubboSpringConsumerBootstrap.class;
+        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(beanType);
+
+        ZookeeperDubboSpringConsumerBootstrap bootstrap = context.getBean(ZookeeperDubboSpringConsumerBootstrap.class);
+
+        for (int i = 0; i < 100; i++) {
+            System.out.println(bootstrap.demoService.sayName("Hello"));
+            Thread.sleep(1000L);
+        }
+
+        System.in.read();
+
+        context.close();
+    }
+}
diff --git a/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportFactory.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/samples/ZookeeperDubboSpringConsumerXmlBootstrap.java
similarity index 51%
copy from dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportFactory.java
copy to dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/samples/ZookeeperDubboSpringConsumerXmlBootstrap.java
index 0ffed8d..08c1450 100644
--- a/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportFactory.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/samples/ZookeeperDubboSpringConsumerXmlBootstrap.java
@@ -14,27 +14,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.metadata.store.zookeeper;
+package org.apache.dubbo.config.spring.samples;
 
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.metadata.report.MetadataReport;
-import org.apache.dubbo.metadata.report.support.AbstractMetadataReportFactory;
-import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter;
+import org.apache.dubbo.config.spring.api.DemoService;
+
+import org.springframework.context.support.ClassPathXmlApplicationContext;
 
 /**
- * ZookeeperRegistryFactory.
+ * Zookeeper Dubbo Spring Provider XML Bootstrap
+ *
+ * @since 2.7.8
  */
-public class ZookeeperMetadataReportFactory extends AbstractMetadataReportFactory {
+public class ZookeeperDubboSpringConsumerXmlBootstrap {
 
-    private ZookeeperTransporter zookeeperTransporter;
+    public static void main(String[] args) throws Exception {
+        String location = "classpath:/META-INF/service-introspection/zookeeper-dubbo-consumer.xml";
+        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(location);
 
-    public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
-        this.zookeeperTransporter = zookeeperTransporter;
-    }
+        DemoService demoService = context.getBean("demoService", DemoService.class);
 
-    @Override
-    public MetadataReport createMetadataReport(URL url) {
-        return new ZookeeperMetadataReport(url, zookeeperTransporter);
-    }
+        for (int i = 0; i < 100; i++) {
+            System.out.println(demoService.sayName("Hello"));
+        }
 
+        context.close();
+    }
 }
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/samples/ZookeeperDubboSpringProviderBootstrap.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/samples/ZookeeperDubboSpringProviderBootstrap.java
new file mode 100644
index 0000000..ddd4010
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/samples/ZookeeperDubboSpringProviderBootstrap.java
@@ -0,0 +1,60 @@
+/*
+ * 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.spring.samples;
+
+import org.apache.dubbo.config.annotation.DubboService;
+import org.apache.dubbo.config.spring.api.Box;
+import org.apache.dubbo.config.spring.api.DemoService;
+import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
+import org.apache.dubbo.rpc.RpcContext;
+
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.PropertySource;
+
+import static java.lang.String.format;
+
+/**
+ * Zookeeper Dubbo Spring Provider Bootstrap
+ *
+ * @since 2.7.8
+ */
+@EnableDubbo
+@PropertySource("classpath:/META-INF/service-introspection/zookeeper-dubbb-provider.properties")
+public class ZookeeperDubboSpringProviderBootstrap {
+
+    public static void main(String[] args) throws Exception {
+        AnnotationConfigApplicationContext context =
+                new AnnotationConfigApplicationContext(ZookeeperDubboSpringProviderBootstrap.class);
+        System.in.read();
+        context.close();
+    }
+}
+
+@DubboService
+class DefaultDemoService implements DemoService {
+
+    @Override
+    public String sayName(String name) {
+        RpcContext rpcContext = RpcContext.getContext();
+        return format("[%s:%s] Say - %s", rpcContext.getLocalHost(), rpcContext.getLocalPort(), name);
+    }
+
+    @Override
+    public Box getBox() {
+        return null;
+    }
+}
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/schema/DubboNamespaceHandlerTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/schema/DubboNamespaceHandlerTest.java
index a7c733c..705b890 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/schema/DubboNamespaceHandlerTest.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/schema/DubboNamespaceHandlerTest.java
@@ -49,12 +49,12 @@ import static org.hamcrest.MatcherAssert.assertThat;
 public class DubboNamespaceHandlerTest {
     @BeforeEach
     public void setUp() {
-        ApplicationModel.getConfigManager().clear();
+        ApplicationModel.reset();
     }
 
     @AfterEach
     public void tearDown() {
-        ApplicationModel.getConfigManager().clear();
+        ApplicationModel.reset();
     }
 
     @Configuration
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/schema/GenericServiceTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/schema/GenericServiceTest.java
index 410e3e9..6deb2ab 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/schema/GenericServiceTest.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/schema/GenericServiceTest.java
@@ -18,7 +18,10 @@ package org.apache.dubbo.config.spring.schema;
 
 import org.apache.dubbo.config.spring.ReferenceBean;
 import org.apache.dubbo.config.spring.ServiceBean;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -34,6 +37,16 @@ import static org.junit.Assert.assertNotNull;
 @ImportResource(locations = "classpath:/META-INF/spring/dubbo-generic-consumer.xml")
 public class GenericServiceTest {
 
+    @Before
+    public void setUp() {
+        ApplicationModel.reset();
+    }
+
+    @After
+    public void tearDown() {
+        ApplicationModel.reset();
+    }
+
     @Autowired
     @Qualifier("demoServiceRef")
     private ReferenceBean referenceBean;
diff --git a/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/issue-6252-test.properties b/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/issue-6252-test.properties
new file mode 100644
index 0000000..413806d
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/issue-6252-test.properties
@@ -0,0 +1,11 @@
+dubbo.application.name=demo-zk
+dubbo.application.qos-enable=false
+dubbo.protocol.name=dubbo
+dubbo.protocol.port=-1
+dubbo.scan.basePackages=com.example.demo
+dubbo.consumer.check=false
+dubbo.registries.z214.address=zookeeper://192.168.99.214:2181
+dubbo.registries.z214.timeout=60000
+dubbo.registries.z214.subscribe=false
+dubbo.registries.z205.address=zookeeper://192.168.99.205:2181
+dubbo.registries.z205.timeout=60000
\ No newline at end of file
diff --git a/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/service-introspection/zookeeper-dubbb-consumer.properties b/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/service-introspection/zookeeper-dubbb-consumer.properties
new file mode 100644
index 0000000..1795afa
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/service-introspection/zookeeper-dubbb-consumer.properties
@@ -0,0 +1,14 @@
+# Dubbo Consumer for Zookeeper
+
+dubbo.application.name = zookeeper-dubbo-spring-consumer
+
+dubbo.registry.address = zookeeper://127.0.0.1:2181?registry-type=service
+dubbo.registry.useAsConfigCenter = true
+dubbo.registry.useAsMetadataCenter = true
+
+dubbo.protocol.name = dubbo
+dubbo.protocol.port = -1
+
+dubbo.provider.name = zookeeper-dubbo-spring-provider
+dubbo.provider.name1 = zookeeper-dubbo-spring-provider-1
+dubbo.provider.name2 = zookeeper-dubbo-spring-provider-2
\ No newline at end of file
diff --git a/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/service-introspection/zookeeper-dubbb-provider.properties b/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/service-introspection/zookeeper-dubbb-provider.properties
new file mode 100644
index 0000000..1b977a5
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/service-introspection/zookeeper-dubbb-provider.properties
@@ -0,0 +1,10 @@
+# Dubbo Provider for Zookeeper
+
+dubbo.application.name = zookeeper-dubbo-spring-provider-1
+
+dubbo.registry.address = zookeeper://127.0.0.1:2181?registry-type=service
+dubbo.registry.useAsConfigCenter = true
+dubbo.registry.useAsMetadataCenter = true
+
+dubbo.protocol.name = dubbo
+dubbo.protocol.port = -1
\ No newline at end of file
diff --git a/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/service-introspection/zookeeper-dubbo-consumer.xml b/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/service-introspection/zookeeper-dubbo-consumer.xml
new file mode 100644
index 0000000..92305f5
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/service-introspection/zookeeper-dubbo-consumer.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
+       xmlns="http://www.springframework.org/schema/beans"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
+       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
+
+    <!-- 当前应用信息配置 -->
+    <dubbo:application name="zookeeper-dubbo-spring-consumer"/>
+
+    <!-- 连接注册中心配置 -->
+    <dubbo:registry address="zookeeper://127.0.0.1:2181?registry-type=service" use-as-config-center="true"
+                    use-as-metadata-center="true"/>
+
+    <dubbo:reference id="demoService" interface="org.apache.dubbo.config.spring.api.DemoService"
+                     services="zookeeper-dubbo-spring-provider,zookeeper-dubbo-spring-provider"/>
+
+</beans>
\ No newline at end of file
diff --git a/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/spring/multiple-services-with-methods.xml b/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/spring/multiple-services-with-methods.xml
new file mode 100644
index 0000000..848990d
--- /dev/null
+++ b/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/spring/multiple-services-with-methods.xml
@@ -0,0 +1,45 @@
+<!--
+  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.
+  -->
+<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
+       xmlns="http://www.springframework.org/schema/beans"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
+    http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
+    ">
+
+    <!-- current application configuration -->
+    <dubbo:application id="application" name="demo-provider"/>
+
+    <!-- registry center configuration -->
+    <dubbo:registry id="registry" address="N/A"/>
+
+    <!-- protocol configuration -->
+    <dubbo:protocol name="dubbo" port="-1"/>
+
+    <!-- service configuration -->
+    <dubbo:service interface="org.apache.dubbo.config.spring.api.DemoService"
+                   class="org.apache.dubbo.config.spring.impl.DemoServiceImpl">
+        <dubbo:method name="sayName" timeout="500" />
+    </dubbo:service>
+
+    <dubbo:service interface="org.apache.dubbo.config.spring.api.DemoService"
+                   class="org.apache.dubbo.config.spring.impl.DemoServiceImpl_LongWaiting">
+        <dubbo:method name="sayName" timeout="1000" />
+    </dubbo:service>
+
+
+</beans>
\ No newline at end of file
diff --git a/dubbo-configcenter/dubbo-configcenter-consul/src/main/java/org/apache/dubbo/configcenter/consul/ConsulDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-consul/src/main/java/org/apache/dubbo/configcenter/consul/ConsulDynamicConfiguration.java
index e0e9a86..5bf8abe 100644
--- a/dubbo-configcenter/dubbo-configcenter-consul/src/main/java/org/apache/dubbo/configcenter/consul/ConsulDynamicConfiguration.java
+++ b/dubbo-configcenter/dubbo-configcenter-consul/src/main/java/org/apache/dubbo/configcenter/consul/ConsulDynamicConfiguration.java
@@ -21,11 +21,10 @@ import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.configcenter.ConfigChangeType;
 import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
 import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
-import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
+import org.apache.dubbo.common.config.configcenter.TreePathDynamicConfiguration;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
-import org.apache.dubbo.common.utils.StringUtils;
 
 import com.google.common.base.Charsets;
 import com.google.common.net.HostAndPort;
@@ -34,39 +33,36 @@ import com.orbitz.consul.KeyValueClient;
 import com.orbitz.consul.cache.KVCache;
 import com.orbitz.consul.model.kv.Value;
 
+import java.util.Collection;
 import java.util.LinkedHashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
-import static org.apache.dubbo.common.config.configcenter.Constants.CONFIG_NAMESPACE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PATH_SEPARATOR;
-import static org.apache.dubbo.common.utils.StringUtils.EMPTY_STRING;
 
 /**
  * config center implementation for consul
  */
-public class ConsulDynamicConfiguration implements DynamicConfiguration {
+public class ConsulDynamicConfiguration extends TreePathDynamicConfiguration {
     private static final Logger logger = LoggerFactory.getLogger(ConsulDynamicConfiguration.class);
 
     private static final int DEFAULT_PORT = 8500;
     private static final int DEFAULT_WATCH_TIMEOUT = 60 * 1000;
     private static final String WATCH_TIMEOUT = "consul-watch-timeout";
 
-    private URL url;
-    private String rootPath;
     private Consul client;
+
     private KeyValueClient kvClient;
+
     private ConcurrentMap<String, ConsulListener> watchers = new ConcurrentHashMap<>();
 
     public ConsulDynamicConfiguration(URL url) {
-        this.url = url;
-        this.rootPath = PATH_SEPARATOR + url.getParameter(CONFIG_NAMESPACE_KEY, DEFAULT_GROUP) + PATH_SEPARATOR + "config";
+        super(url);
         String host = url.getHost();
         int port = url.getPort() != 0 ? url.getPort() : DEFAULT_PORT;
         client = Consul.builder().withHostAndPort(HostAndPort.fromParts(host, port)).build();
@@ -74,112 +70,69 @@ public class ConsulDynamicConfiguration implements DynamicConfiguration {
     }
 
     @Override
-    public void addListener(String key, String group, ConfigurationListener listener) {
-        logger.info("register listener " + listener.getClass() + " for config with key: " + key + ", group: " + group);
-        String normalizedKey = convertKey(group, key);
-        ConsulListener watcher = watchers.computeIfAbsent(normalizedKey, k -> new ConsulListener(key, group));
-        watcher.addListener(listener);
+    public String getInternalProperty(String key) {
+        logger.info("getting config from: " + key);
+        return kvClient.getValueAsString(key, Charsets.UTF_8).orElse(null);
     }
 
     @Override
-    public void removeListener(String key, String group, ConfigurationListener listener) {
-        logger.info("unregister listener " + listener.getClass() + " for config with key: " + key + ", group: " + group);
-        ConsulListener watcher = watchers.get(convertKey(group, key));
-        if (watcher != null) {
-            watcher.removeListener(listener);
-        }
+    protected boolean doPublishConfig(String pathKey, String content) throws Exception {
+        return kvClient.putValue(pathKey, content);
+    }
+
+    @Override
+    protected String doGetConfig(String pathKey) throws Exception {
+        return getInternalProperty(pathKey);
     }
 
     @Override
-    public String getConfig(String key, String group, long timeout) throws IllegalStateException {
-        return (String) getInternalProperty(convertKey(group, key));
+    protected boolean doRemoveConfig(String pathKey) throws Exception {
+        kvClient.deleteKey(pathKey);
+        return true;
     }
 
     @Override
-    public SortedSet<String> getConfigKeys(String group) throws UnsupportedOperationException {
-        SortedSet<String> configKeys = new TreeSet<>();
-        String normalizedKey = convertKey(group, EMPTY_STRING);
-        List<String> keys = kvClient.getKeys(normalizedKey);
+    protected Collection<String> doGetConfigKeys(String groupPath) {
+        List<String> keys = kvClient.getKeys(groupPath);
+        List<String> configKeys = new LinkedList<>();
         if (CollectionUtils.isNotEmpty(keys)) {
             keys.stream()
-                    .filter(k -> !k.equals(normalizedKey))
+                    .filter(k -> !k.equals(groupPath))
                     .map(k -> k.substring(k.lastIndexOf(PATH_SEPARATOR) + 1))
                     .forEach(configKeys::add);
         }
         return configKeys;
-//        SortedSet<String> configKeys = new TreeSet<>();
-//        String normalizedKey = convertKey(group, key);
-//        kvClient.getValueAsString(normalizedKey).ifPresent(v -> {
-//            Collections.addAll(configKeys, v.split(","));
-//        });
-//        return configKeys;
     }
 
-    /**
-     * @param key     the key to represent a configuration
-     * @param group   the group where the key belongs to
-     * @param content the content of configuration
-     * @return
-     * @throws UnsupportedOperationException
-     */
     @Override
-    public boolean publishConfig(String key, String group, String content) throws UnsupportedOperationException {
-//        String normalizedKey = convertKey(group, key);
-//        Value value = kvClient.getValue(normalizedKey).orElseThrow(() -> new IllegalArgumentException(normalizedKey + " does not exit."));
-//        Optional<String> old = value.getValueAsString();
-//        if (old.isPresent()) {
-//            content = old.get() + "," + content;
-//        }
-//
-//        while (!kvClient.putValue(key, content, value.getModifyIndex())) {
-//            value = kvClient.getValue(normalizedKey).orElseThrow(() -> new IllegalArgumentException(normalizedKey + " does not exit."));
-//            old = value.getValueAsString();
-//            if (old.isPresent()) {
-//                content = old.get() + "," + content;
-//            }
-//            try {
-//                Thread.sleep(10);
-//            } catch (InterruptedException e) {
-//                e.printStackTrace();
-//            }
-//        }
-//        return true;
-        String normalizedKey = convertKey(group, key);
-        return kvClient.putValue(normalizedKey, content);
+    protected void doAddListener(String pathKey, ConfigurationListener listener) {
+        logger.info("register listener " + listener.getClass() + " for config with key: " + pathKey);
+        ConsulListener watcher = watchers.computeIfAbsent(pathKey, k -> new ConsulListener(pathKey));
+        watcher.addListener(listener);
     }
 
     @Override
-    public Object getInternalProperty(String key) {
-        logger.info("getting config from: " + key);
-        return kvClient.getValueAsString(key, Charsets.UTF_8).orElse(null);
+    protected void doRemoveListener(String pathKey, ConfigurationListener listener) {
+        logger.info("unregister listener " + listener.getClass() + " for config with key: " + pathKey);
+        ConsulListener watcher = watchers.get(pathKey);
+        if (watcher != null) {
+            watcher.removeListener(listener);
+        }
     }
 
     @Override
-    public void close() throws Exception {
+    protected void doClose() throws Exception {
         client.destroy();
     }
 
-    private String buildPath(String group) {
-        String actualGroup = StringUtils.isEmpty(group) ? DEFAULT_GROUP : group;
-        return rootPath + PATH_SEPARATOR + actualGroup;
-    }
-
-    private String convertKey(String group, String key) {
-        return buildPath(group) + PATH_SEPARATOR + key;
-    }
-
     private class ConsulListener implements KVCache.Listener<String, Value> {
 
         private KVCache kvCache;
         private Set<ConfigurationListener> listeners = new LinkedHashSet<>();
-        private String key;
-        private String group;
         private String normalizedKey;
 
-        public ConsulListener(String key, String group) {
-            this.key = key;
-            this.group = group;
-            this.normalizedKey = convertKey(group, key);
+        public ConsulListener(String normalizedKey) {
+            this.normalizedKey = normalizedKey;
             initKVCache();
         }
 
@@ -201,7 +154,7 @@ public class ConsulDynamicConfiguration implements DynamicConfiguration {
                 // Values are encoded in key/value store, decode it if needed
                 Optional<String> decodedValue = newValue.get().getValueAsString();
                 decodedValue.ifPresent(v -> listeners.forEach(l -> {
-                    ConfigChangedEvent event = new ConfigChangedEvent(key, group, v, ConfigChangeType.MODIFIED);
+                    ConfigChangedEvent event = new ConfigChangedEvent(normalizedKey, getGroup(), v, ConfigChangeType.MODIFIED);
                     l.process(event);
                 }));
             });
diff --git a/dubbo-configcenter/dubbo-configcenter-consul/src/test/java/org/apache/dubbo/configcenter/consul/ConsulDynamicConfigurationTest.java b/dubbo-configcenter/dubbo-configcenter-consul/src/test/java/org/apache/dubbo/configcenter/consul/ConsulDynamicConfigurationTest.java
index d924c83..c54d103 100644
--- a/dubbo-configcenter/dubbo-configcenter-consul/src/test/java/org/apache/dubbo/configcenter/consul/ConsulDynamicConfigurationTest.java
+++ b/dubbo-configcenter/dubbo-configcenter-consul/src/test/java/org/apache/dubbo/configcenter/consul/ConsulDynamicConfigurationTest.java
@@ -30,7 +30,9 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
+import java.util.Arrays;
 import java.util.Optional;
+import java.util.TreeSet;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
@@ -51,10 +53,10 @@ public class ConsulDynamicConfigurationTest {
         consul = ConsulStarterBuilder.consulStarter()
                 .build()
                 .start();
-        configCenterUrl = URL.valueOf("consul://localhost:" + consul.getHttpPort());
+        configCenterUrl = URL.valueOf("consul://127.0.0.1:" + consul.getHttpPort());
 
         configuration = new ConsulDynamicConfiguration(configCenterUrl);
-        client = Consul.builder().withHostAndPort(HostAndPort.fromParts("localhost", consul.getHttpPort())).build();
+        client = Consul.builder().withHostAndPort(HostAndPort.fromParts("127.0.0.1", consul.getHttpPort())).build();
         kvClient = client.keyValueClient();
     }
 
@@ -75,6 +77,14 @@ public class ConsulDynamicConfigurationTest {
     }
 
     @Test
+    public void testPublishConfig() {
+        configuration.publishConfig("value", "metadata", "1");
+        // test equals
+        assertEquals("1", configuration.getConfig("value", "/metadata"));
+        assertEquals("1", kvClient.getValueAsString("/dubbo/config/metadata/value").get());
+    }
+
+    @Test
     public void testAddListener() {
         KVCache cache = KVCache.newCache(kvClient, "/dubbo/config/dubbo/foo");
         cache.addListener(newValues -> {
@@ -103,13 +113,11 @@ public class ConsulDynamicConfigurationTest {
     }
 
     @Test
-    public void testPublishConfig() {
-        configuration.publishConfig("foo", "value1");
-        Assertions.assertEquals("value1", configuration.getString("/dubbo/config/dubbo/foo"));
-    }
-
-    @Test
     public void testGetConfigKeys() {
-
+        configuration.publishConfig("v1", "metadata", "1");
+        configuration.publishConfig("v2", "metadata", "2");
+        configuration.publishConfig("v3", "metadata", "3");
+        // test equals
+        assertEquals(new TreeSet(Arrays.asList("v1", "v2", "v3")), configuration.getConfigKeys("metadata"));
     }
 }
diff --git a/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java
index 2227015..cca467f 100644
--- a/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java
+++ b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java
@@ -30,6 +30,7 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.nacos.api.NacosFactory;
+import com.alibaba.nacos.api.PropertyKeyConst;
 import com.alibaba.nacos.api.config.ConfigService;
 import com.alibaba.nacos.api.config.listener.AbstractSharedListener;
 import com.alibaba.nacos.api.exception.NacosException;
@@ -50,31 +51,17 @@ import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.concurrent.Executor;
 import java.util.stream.Stream;
 
-import static com.alibaba.nacos.api.PropertyKeyConst.ACCESS_KEY;
-import static com.alibaba.nacos.api.PropertyKeyConst.CLUSTER_NAME;
-import static com.alibaba.nacos.api.PropertyKeyConst.CONFIG_LONG_POLL_TIMEOUT;
-import static com.alibaba.nacos.api.PropertyKeyConst.CONFIG_RETRY_TIME;
-import static com.alibaba.nacos.api.PropertyKeyConst.CONTEXT_PATH;
-import static com.alibaba.nacos.api.PropertyKeyConst.ENABLE_REMOTE_SYNC_CONFIG;
 import static com.alibaba.nacos.api.PropertyKeyConst.ENCODE;
-import static com.alibaba.nacos.api.PropertyKeyConst.ENDPOINT;
-import static com.alibaba.nacos.api.PropertyKeyConst.ENDPOINT_PORT;
-import static com.alibaba.nacos.api.PropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING;
-import static com.alibaba.nacos.api.PropertyKeyConst.IS_USE_ENDPOINT_PARSING_RULE;
-import static com.alibaba.nacos.api.PropertyKeyConst.MAX_RETRY;
-import static com.alibaba.nacos.api.PropertyKeyConst.NAMESPACE;
-import static com.alibaba.nacos.api.PropertyKeyConst.NAMING_CLIENT_BEAT_THREAD_COUNT;
 import static com.alibaba.nacos.api.PropertyKeyConst.NAMING_LOAD_CACHE_AT_START;
-import static com.alibaba.nacos.api.PropertyKeyConst.NAMING_POLLING_THREAD_COUNT;
-import static com.alibaba.nacos.api.PropertyKeyConst.RAM_ROLE_NAME;
-import static com.alibaba.nacos.api.PropertyKeyConst.SECRET_KEY;
 import static com.alibaba.nacos.api.PropertyKeyConst.SERVER_ADDR;
 import static com.alibaba.nacos.client.naming.utils.UtilAndComs.NACOS_NAMING_LOG_NAME;
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptyList;
 import static org.apache.dubbo.common.constants.RemotingConstants.BACKUP_KEY;
+import static org.apache.dubbo.common.utils.StringConstantFieldValuePredicate.of;
 import static org.apache.dubbo.common.utils.StringUtils.HYPHEN_CHAR;
 import static org.apache.dubbo.common.utils.StringUtils.SLASH_CHAR;
+import static org.apache.dubbo.common.utils.StringUtils.isBlank;
 
 /**
  * The nacos implementation of {@link DynamicConfiguration}
@@ -159,24 +146,13 @@ public class NacosDynamicConfiguration implements DynamicConfiguration {
 
     private static void setProperties(URL url, Properties properties) {
         putPropertyIfAbsent(url, properties, NACOS_NAMING_LOG_NAME);
-        putPropertyIfAbsent(url, properties, IS_USE_CLOUD_NAMESPACE_PARSING);
-        putPropertyIfAbsent(url, properties, IS_USE_ENDPOINT_PARSING_RULE);
-        putPropertyIfAbsent(url, properties, ENDPOINT);
-        putPropertyIfAbsent(url, properties, ENDPOINT_PORT);
-        putPropertyIfAbsent(url, properties, NAMESPACE);
-        putPropertyIfAbsent(url, properties, ACCESS_KEY);
-        putPropertyIfAbsent(url, properties, SECRET_KEY);
-        putPropertyIfAbsent(url, properties, RAM_ROLE_NAME);
-        putPropertyIfAbsent(url, properties, CONTEXT_PATH);
-        putPropertyIfAbsent(url, properties, CLUSTER_NAME);
-        putPropertyIfAbsent(url, properties, ENCODE);
-        putPropertyIfAbsent(url, properties, CONFIG_LONG_POLL_TIMEOUT);
-        putPropertyIfAbsent(url, properties, CONFIG_RETRY_TIME);
-        putPropertyIfAbsent(url, properties, MAX_RETRY);
-        putPropertyIfAbsent(url, properties, ENABLE_REMOTE_SYNC_CONFIG);
+
+        // Get the parameters from constants
+        Map<String, String> parameters = url.getParameters(of(PropertyKeyConst.class));
+        // Put all parameters
+        properties.putAll(parameters);
+
         putPropertyIfAbsent(url, properties, NAMING_LOAD_CACHE_AT_START, "true");
-        putPropertyIfAbsent(url, properties, NAMING_CLIENT_BEAT_THREAD_COUNT);
-        putPropertyIfAbsent(url, properties, NAMING_POLLING_THREAD_COUNT);
     }
 
     private static void putPropertyIfAbsent(URL url, Properties properties, String propertyName) {
@@ -260,13 +236,9 @@ public class NacosDynamicConfiguration implements DynamicConfiguration {
         boolean published = false;
         String resolvedGroup = resolveGroup(group);
         try {
-            String value = configService.getConfig(key, resolvedGroup, getDefaultTimeout());
-            if (StringUtils.isNotEmpty(value)) {
-                content = value + "," + content;
-            }
             published = configService.publishConfig(key, resolvedGroup, content);
         } catch (NacosException e) {
-            logger.error(e.getErrMsg());
+            logger.error(e.getErrMsg(), e);
         }
         return published;
     }
@@ -279,7 +251,6 @@ public class NacosDynamicConfiguration implements DynamicConfiguration {
     /**
      * TODO Nacos does not support atomic update of the value mapped to a key.
      *
-     * @param key
      * @param group the specified group
      * @return
      */
@@ -307,6 +278,19 @@ public class NacosDynamicConfiguration implements DynamicConfiguration {
         return keys;
     }
 
+    @Override
+    public boolean removeConfig(String key, String group) {
+        boolean removed = false;
+        try {
+            removed = configService.removeConfig(key, group);
+        } catch (NacosException e) {
+            if (logger.isErrorEnabled()) {
+                logger.error(e.getMessage(), e);
+            }
+        }
+        return removed;
+    }
+
     private Stream<String> toKeysStream(String content) {
         JSONObject jsonObject = JSON.parseObject(content);
         JSONArray pageItems = jsonObject.getJSONArray("pageItems");
@@ -376,6 +360,6 @@ public class NacosDynamicConfiguration implements DynamicConfiguration {
     }
 
     protected String resolveGroup(String group) {
-        return group.replace(SLASH_CHAR, HYPHEN_CHAR);
+        return isBlank(group) ? group : group.replace(SLASH_CHAR, HYPHEN_CHAR);
     }
 }
diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java
index 4bd65a9..a96f843 100644
--- a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java
+++ b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java
@@ -18,36 +18,21 @@ package org.apache.dubbo.configcenter.support.zookeeper;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
-import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
+import org.apache.dubbo.common.config.configcenter.TreePathDynamicConfiguration;
 import org.apache.dubbo.common.utils.NamedThreadFactory;
-import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.remoting.zookeeper.ZookeeperClient;
 import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.List;
-import java.util.SortedSet;
-import java.util.TreeSet;
+import java.util.Collection;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
-import static java.util.Collections.emptySortedSet;
-import static java.util.Collections.unmodifiableSortedSet;
-import static org.apache.dubbo.common.config.configcenter.Constants.CONFIG_NAMESPACE_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.PATH_SEPARATOR;
-import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty;
-import static org.apache.dubbo.common.utils.StringUtils.EMPTY_STRING;
-
 /**
  *
  */
-public class ZookeeperDynamicConfiguration implements DynamicConfiguration {
-
-    private static final Logger logger = LoggerFactory.getLogger(ZookeeperDynamicConfiguration.class);
+public class ZookeeperDynamicConfiguration extends TreePathDynamicConfiguration {
 
     private Executor executor;
     // The final root path would be: /configRootPath/"config"
@@ -60,8 +45,9 @@ public class ZookeeperDynamicConfiguration implements DynamicConfiguration {
 
 
     ZookeeperDynamicConfiguration(URL url, ZookeeperTransporter zookeeperTransporter) {
+        super(url);
         this.url = url;
-        rootPath = PATH_SEPARATOR + url.getParameter(CONFIG_NAMESPACE_KEY, DEFAULT_GROUP) + "/config";
+        rootPath = getRootPath(url);
 
         initializedLatch = new CountDownLatch(1);
         this.cacheListener = new CacheListener(rootPath, initializedLatch);
@@ -87,52 +73,44 @@ public class ZookeeperDynamicConfiguration implements DynamicConfiguration {
      * @return
      */
     @Override
-    public Object getInternalProperty(String key) {
+    public String getInternalProperty(String key) {
         return zkClient.getContent(key);
     }
 
-    /**
-     * For service governance, multi group is not supported by this implementation. So group is not used at present.
-     */
     @Override
-    public void addListener(String key, String group, ConfigurationListener listener) {
-        cacheListener.addListener(getPathKey(group, key), listener);
+    protected void doClose() throws Exception {
+        zkClient.close();
     }
 
     @Override
-    public void removeListener(String key, String group, ConfigurationListener listener) {
-        cacheListener.removeListener(getPathKey(group, key), listener);
+    protected boolean doPublishConfig(String pathKey, String content) throws Exception {
+        zkClient.create(pathKey, content, false);
+        return true;
     }
 
     @Override
-    public String getConfig(String key, String group, long timeout) throws IllegalStateException {
-        return (String) getInternalProperty(getPathKey(group, key));
+    protected String doGetConfig(String pathKey) throws Exception {
+        return zkClient.getContent(pathKey);
     }
 
     @Override
-    public boolean publishConfig(String key, String group, String content) {
-        String path = getPathKey(group, key);
-        zkClient.create(path, content, false);
+    protected boolean doRemoveConfig(String pathKey) throws Exception {
+        zkClient.delete(pathKey);
         return true;
     }
 
     @Override
-    public SortedSet<String> getConfigKeys(String group) {
-        String path = getPathKey(group, EMPTY_STRING);
-        List<String> nodes = zkClient.getChildren(path);
-        return isEmpty(nodes) ? emptySortedSet() : unmodifiableSortedSet(new TreeSet<>(nodes));
+    protected Collection<String> doGetConfigKeys(String groupPath) {
+        return zkClient.getChildren(groupPath);
     }
 
-    private String buildPath(String group) {
-        String actualGroup = StringUtils.isEmpty(group) ? DEFAULT_GROUP : group;
-        return rootPath + PATH_SEPARATOR + actualGroup;
+    @Override
+    protected void doAddListener(String pathKey, ConfigurationListener listener) {
+        cacheListener.addListener(pathKey, listener);
     }
 
-    private String getPathKey(String group, String key) {
-        if (StringUtils.isEmpty(key)) {
-            return buildPath(group);
-        }
-        return buildPath(group) + PATH_SEPARATOR + key;
+    @Override
+    protected void doRemoveListener(String pathKey, ConfigurationListener listener) {
+        cacheListener.removeListener(pathKey, listener);
     }
-
 }
diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java
index 3c06fbe..9d4a0c3 100644
--- a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java
+++ b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java
@@ -58,7 +58,7 @@ public class ZookeeperDynamicConfigurationTest {
     public static void setUp() throws Exception {
         zkServer = new TestingServer(zkServerPort, true);
 
-        client = CuratorFrameworkFactory.newClient("localhost:" + zkServerPort, 60 * 1000, 60 * 1000,
+        client = CuratorFrameworkFactory.newClient("127.0.0.1:" + zkServerPort, 60 * 1000, 60 * 1000,
                 new ExponentialBackoffRetry(1000, 3));
         client.start();
 
@@ -73,7 +73,7 @@ public class ZookeeperDynamicConfigurationTest {
         }
 
 
-        configUrl = URL.valueOf("zookeeper://localhost:" + zkServerPort);
+        configUrl = URL.valueOf("zookeeper://127.0.0.1:" + zkServerPort);
 
         configuration = ExtensionLoader.getExtensionLoader(DynamicConfigurationFactory.class).getExtension(configUrl.getProtocol()).getDynamicConfiguration(configUrl);
     }
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
new file mode 100644
index 0000000..9ad130b
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/CompositeServiceNameMapping.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.metadata;
+
+
+import org.apache.dubbo.common.URL;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import static java.util.Collections.emptySet;
+import static java.util.Collections.unmodifiableSet;
+import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader;
+import static org.apache.dubbo.common.utils.CollectionUtils.isNotEmpty;
+
+/**
+ * The composite implementation of {@link ServiceNameMapping}
+ *
+ * @see ParameterizedServiceNameMapping
+ * @see PropertiesFileServiceNameMapping
+ * @see DynamicConfigurationServiceNameMapping
+ * @since 2.7.8
+ */
+public class CompositeServiceNameMapping implements ServiceNameMapping {
+
+    private volatile List<ServiceNameMapping> serviceNameMappings;
+
+    private List<ServiceNameMapping> getServiceNameMappings() {
+        if (this.serviceNameMappings == null) {
+            synchronized (this) {
+                if (this.serviceNameMappings == null) {
+                    Set<ServiceNameMapping> serviceNameMappings = loadAllServiceNameMappings();
+
+                    removeSelf(serviceNameMappings);
+
+                    this.serviceNameMappings = new LinkedList<>(serviceNameMappings);
+                }
+            }
+        }
+        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();
+        serviceNameMappings.forEach(serviceNameMapping -> serviceNameMapping.map(exportedURL));
+    }
+
+    @Override
+    public Set<String> get(URL subscribedURL) {
+        List<ServiceNameMapping> serviceNameMappings = getServiceNameMappings();
+        Set<String> serviceNames = null;
+        for (ServiceNameMapping serviceNameMapping : serviceNameMappings) {
+            serviceNames = serviceNameMapping.get(subscribedURL);
+            if (isNotEmpty(serviceNames)) {
+                break;
+            }
+        }
+        return serviceNames == null ? emptySet() : unmodifiableSet(serviceNames);
+    }
+
+    @Override
+    public int getPriority() {
+        return MIN_PRIORITY;
+    }
+}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMapping.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMapping.java
index d84eba0..2a04cb7 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMapping.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMapping.java
@@ -16,6 +16,7 @@
  */
 package org.apache.dubbo.metadata;
 
+import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
@@ -27,11 +28,17 @@ import java.util.Set;
 
 import static java.lang.String.valueOf;
 import static java.util.Arrays.asList;
+import static org.apache.dubbo.common.config.configcenter.DynamicConfiguration.getDynamicConfiguration;
+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.utils.CollectionUtils.isNotEmpty;
 import static org.apache.dubbo.common.utils.StringUtils.SLASH;
 import static org.apache.dubbo.rpc.model.ApplicationModel.getName;
 
 /**
  * The {@link ServiceNameMapping} implementation based on {@link DynamicConfiguration}
+ *
+ * @since 2.7.5
  */
 public class DynamicConfigurationServiceNameMapping implements ServiceNameMapping {
 
@@ -41,14 +48,24 @@ public class DynamicConfigurationServiceNameMapping implements ServiceNameMappin
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
+    /**
+     * The priority of {@link DynamicConfigurationServiceNameMapping} is
+     * lower than {@link ParameterizedServiceNameMapping}
+     */
+    static final int PRIORITY = PropertiesFileServiceNameMapping.PRIORITY + 1;
+
     @Override
-    public void map(String serviceInterface, String group, String version, String protocol) {
+    public void map(URL exportedURL) {
+
+        String serviceInterface = exportedURL.getServiceInterface();
 
         if (IGNORED_SERVICE_INTERFACES.contains(serviceInterface)) {
             return;
         }
 
-        DynamicConfiguration dynamicConfiguration = DynamicConfiguration.getDynamicConfiguration();
+        String group = exportedURL.getParameter(GROUP_KEY);
+        String version = exportedURL.getParameter(VERSION_KEY);
+        String protocol = exportedURL.getProtocol();
 
         // the Dubbo Service Key as group
         // the service(application) name as key
@@ -56,7 +73,7 @@ public class DynamicConfigurationServiceNameMapping implements ServiceNameMappin
         String key = getName();
         String content = valueOf(System.currentTimeMillis());
         execute(() -> {
-            dynamicConfiguration.publishConfig(key, buildGroup(serviceInterface, group, version, protocol), content);
+            getDynamicConfiguration().publishConfig(key, buildGroup(serviceInterface, group, version, protocol), content);
             if (logger.isInfoEnabled()) {
                 logger.info(String.format("Dubbo service[%s] mapped to interface name[%s].",
                         group, serviceInterface, group));
@@ -65,14 +82,19 @@ public class DynamicConfigurationServiceNameMapping implements ServiceNameMappin
     }
 
     @Override
-    public Set<String> get(String serviceInterface, String group, String version, String protocol) {
+    public Set<String> get(URL subscribedURL) {
 
-        DynamicConfiguration dynamicConfiguration = DynamicConfiguration.getDynamicConfiguration();
+        String serviceInterface = subscribedURL.getServiceInterface();
+        String group = subscribedURL.getParameter(GROUP_KEY);
+        String version = subscribedURL.getParameter(VERSION_KEY);
+        String protocol = subscribedURL.getProtocol();
 
         Set<String> serviceNames = new LinkedHashSet<>();
         execute(() -> {
-            Set<String> keys = dynamicConfiguration.getConfigKeys(buildGroup(serviceInterface, group, version, protocol));
-            serviceNames.addAll(keys);
+            Set<String> keys = getDynamicConfiguration().getConfigKeys(buildGroup(serviceInterface, group, version, protocol));
+            if (isNotEmpty(keys)) {
+                serviceNames.addAll(keys);
+            }
         });
         return Collections.unmodifiableSet(serviceNames);
     }
@@ -96,4 +118,9 @@ public class DynamicConfigurationServiceNameMapping implements ServiceNameMappin
             }
         }
     }
+
+    @Override
+    public int getPriority() {
+        return PRIORITY;
+    }
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataConstants.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataConstants.java
index e03ddd6..d670089 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataConstants.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataConstants.java
@@ -16,11 +16,22 @@
  */
 package org.apache.dubbo.metadata;
 
-public class MetadataConstants {
-    public static final String KEY_SEPARATOR = ":";
-    public static final String DEFAULT_PATH_TAG = "metadata";
-    public static final String KEY_REVISON_PREFIX = "revision";
-    public static final String META_DATA_STORE_TAG = ".metaData";
-    public static final String SERVICE_META_DATA_STORE_TAG = ".smd";
-    public static final String CONSUMER_META_DATA_STORE_TAG = ".cmd";
+public interface MetadataConstants {
+    String KEY_SEPARATOR = ":";
+    String DEFAULT_PATH_TAG = "metadata";
+    String KEY_REVISON_PREFIX = "revision";
+    String META_DATA_STORE_TAG = ".metaData";
+    String SERVICE_META_DATA_STORE_TAG = ".smd";
+    String CONSUMER_META_DATA_STORE_TAG = ".cmd";
+
+    /**
+     * @since 2.7.8
+     */
+    String EXPORTED_URLS_TAG = "exported-urls";
+
+    /**
+     * @since 2.7.8
+     */
+    String SUBSCRIBED_URLS_TAG = "subscribed-urls";
+
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataService.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataService.java
index 0780149..660ac42 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataService.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataService.java
@@ -28,6 +28,7 @@ import java.util.stream.StreamSupport;
 
 import static java.util.Collections.unmodifiableSortedSet;
 import static java.util.stream.StreamSupport.stream;
+import static org.apache.dubbo.common.URL.buildKey;
 
 /**
  * A framework interface of Dubbo Metadata Service defines the contract of Dubbo Services registartion and subscription
@@ -90,7 +91,7 @@ public interface MetadataService {
      * @see #toSortedStrings(Stream)
      * @see URL#toFullString()
      */
-    default SortedSet<String> getSubscribedURLs(){
+    default SortedSet<String> getSubscribedURLs() {
         throw new UnsupportedOperationException("This operation is not supported for consumer.");
     }
 
@@ -165,7 +166,9 @@ public interface MetadataService {
      *
      * @return
      */
-    String getServiceDefinition(String interfaceName, String version, String group);
+    default String getServiceDefinition(String interfaceName, String version, String group) {
+        return getServiceDefinition(buildKey(interfaceName, group, version));
+    }
 
     /**
      * Interface definition.
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataServiceExporter.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataServiceExporter.java
index 16d1e3b..34d3b54 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataServiceExporter.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataServiceExporter.java
@@ -17,9 +17,14 @@
 package org.apache.dubbo.metadata;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.SPI;
+import org.apache.dubbo.common.lang.Prioritized;
 
 import java.util.List;
 
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader;
+
 /**
  * The exporter of {@link MetadataService}
  *
@@ -28,7 +33,8 @@ import java.util.List;
  * @see #unexport()
  * @since 2.7.5
  */
-public interface MetadataServiceExporter {
+@SPI(DEFAULT_METADATA_STORAGE_TYPE)
+public interface MetadataServiceExporter extends Prioritized {
 
     /**
      * Exports the {@link MetadataService} as a Dubbo service
@@ -57,5 +63,38 @@ public interface MetadataServiceExporter {
      * @return if {@link #export()} was executed, return <code>true</code>, or <code>false</code>
      */
     boolean isExported();
+
+    /**
+     * Does current implementation support the specified metadata type?
+     *
+     * @param metadataType the specified metadata type
+     * @return If supports, return <code>true</code>, or <code>false</code>
+     * @since 2.7.8
+     */
+    default boolean supports(String metadataType) {
+        return true;
+    }
+
+    /**
+     * Get the extension of {@link MetadataServiceExporter} by the type.
+     * If not found, return the default implementation
+     *
+     * @param metadataType the metadata type
+     * @return non-null
+     * @since 2.7.8
+     */
+    static MetadataServiceExporter getExtension(String metadataType) {
+        return getExtensionLoader(MetadataServiceExporter.class).getOrDefaultExtension(metadataType);
+    }
+
+    /**
+     * Get the default extension of {@link MetadataServiceExporter}
+     *
+     * @return non-null
+     * @since 2.7.8
+     */
+    static MetadataServiceExporter getDefaultExtension() {
+        return getExtension(DEFAULT_METADATA_STORAGE_TYPE);
+    }
 }
 
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataServiceType.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataServiceType.java
new file mode 100644
index 0000000..3af6b38
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataServiceType.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.metadata;
+
+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;
+
+/**
+ * The type enumerations of {@link MetadataService}
+ *
+ * @see MetadataService
+ * @since 2.7.8
+ */
+public enum MetadataServiceType {
+
+    /**
+     * The default type of {@link MetadataService}
+     */
+    DEFAULT(DEFAULT_METADATA_STORAGE_TYPE),
+
+    /**
+     * The remote type of {@link MetadataService}
+     */
+    REMOTE(REMOTE_METADATA_STORAGE_TYPE),
+
+    /**
+     * The composite type of {@link MetadataService}
+     */
+    COMPOSITE(COMPOSITE_METADATA_STORAGE_TYPE);
+
+    /**
+     * The {@link String} value of type
+     */
+    private final String value;
+
+    MetadataServiceType(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public static MetadataServiceType getOrDefault(String value) {
+        MetadataServiceType targetType = null;
+        for (MetadataServiceType type : values()) {
+            if (type.getValue().equals(value)) {
+                targetType = type;
+                break;
+            }
+        }
+        if (targetType == null) {
+            targetType = DEFAULT;
+        }
+        return targetType;
+    }
+}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataUtil.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataUtil.java
deleted file mode 100644
index 85ce30e..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataUtil.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata;
-
-/**
- * FIXME
- * 2019-07-31
- */
-public class MetadataUtil {
-
-//    public static String getMetadataKey(URL url, KeyTypeEnum keyType){
-//
-//    }
-//
-//    private String getIdentifierKey() {
-//        return serviceInterface
-//                + KEY_SEPARATOR + (version == null ? "" : version)
-//                + KEY_SEPARATOR + (group == null ? "" : group)
-//                + KEY_SEPARATOR + (side == null ? "" : side)
-//                + KEY_SEPARATOR + application;
-//    }
-//
-//    private String getFilePathKey() {
-//        return getFilePathKey(DEFAULT_PATH_TAG);
-//    }
-//
-//    private String getFilePathKey(String pathTag) {
-//        return pathTag
-//                + (StringUtils.isEmpty(toServicePath()) ? "" : (PATH_SEPARATOR + toServicePath()))
-//                + (version == null ? "" : (PATH_SEPARATOR + version))
-//                + (group == null ? "" : (PATH_SEPARATOR + group))
-//                + (side == null ? "" : (PATH_SEPARATOR + side))
-//                + (getApplication() == null ? "" : (PATH_SEPARATOR + getApplication()));
-//    }
-}
diff --git a/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportFactory.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ParameterizedServiceNameMapping.java
similarity index 56%
copy from dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportFactory.java
copy to dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ParameterizedServiceNameMapping.java
index 0ffed8d..893a6f4 100644
--- a/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportFactory.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ParameterizedServiceNameMapping.java
@@ -14,27 +14,34 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.metadata.store.zookeeper;
+package org.apache.dubbo.metadata;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.metadata.report.MetadataReport;
-import org.apache.dubbo.metadata.report.support.AbstractMetadataReportFactory;
-import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter;
+
+import java.util.Set;
+
+import static org.apache.dubbo.common.constants.RegistryConstants.SUBSCRIBED_SERVICE_NAMES_KEY;
 
 /**
- * ZookeeperRegistryFactory.
+ * The parameterized implementation of {@link ServiceNameMapping}
+ *
+ * @see ReadOnlyServiceNameMapping
+ * @since 2.7.8
  */
-public class ZookeeperMetadataReportFactory extends AbstractMetadataReportFactory {
+public class ParameterizedServiceNameMapping extends ReadOnlyServiceNameMapping {
 
-    private ZookeeperTransporter zookeeperTransporter;
+    /**
+     * The priority of {@link PropertiesFileServiceNameMapping}
+     */
+    static final int PRIORITY = MAX_PRIORITY + 99;
 
-    public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
-        this.zookeeperTransporter = zookeeperTransporter;
+    @Override
+    public Set<String> get(URL subscribedURL) {
+        return getValue(subscribedURL.getParameter(SUBSCRIBED_SERVICE_NAMES_KEY));
     }
 
     @Override
-    public MetadataReport createMetadataReport(URL url) {
-        return new ZookeeperMetadataReport(url, zookeeperTransporter);
+    public int getPriority() {
+        return PRIORITY;
     }
-
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/PropertiesFileServiceNameMapping.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/PropertiesFileServiceNameMapping.java
new file mode 100644
index 0000000..7870895
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/PropertiesFileServiceNameMapping.java
@@ -0,0 +1,148 @@
+/*
+ * 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.Configuration;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.utils.ClassUtils;
+import org.apache.dubbo.common.utils.PathUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+import static java.lang.String.format;
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_SERVICE_NAME_MAPPING_PROPERTIES_PATH;
+import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.SERVICE_NAME_MAPPING_PROPERTIES_FILE_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
+import static org.apache.dubbo.common.utils.StringUtils.SLASH;
+import static org.apache.dubbo.metadata.MetadataConstants.KEY_SEPARATOR;
+
+/**
+ * The externalized {@link Properties} file implementation of {@link ServiceNameMapping},
+ * the default properties class path is
+ * {@link CommonConstants#DEFAULT_SERVICE_NAME_MAPPING_PROPERTIES_PATH "/META-INF/dubbo/service-name-mapping.properties"},
+ * whose format as following:
+ * <pre>
+ * dubbo\:com.acme.Interface1\:default = Service1
+ * thirft\:com.acme.InterfaceX = Service1,Service2
+ * rest\:com.acme.interfaceN = Service3
+ * </pre>
+ * <p>
+ * THe search path could be configured by the externalized property {@link CommonConstants#SERVICE_NAME_MAPPING_PROPERTIES_FILE_KEY}
+ *
+ * @see ReadOnlyServiceNameMapping
+ * @see ParameterizedServiceNameMapping
+ * @since 2.7.8
+ */
+public class PropertiesFileServiceNameMapping extends ReadOnlyServiceNameMapping {
+
+    /**
+     * The priority of {@link PropertiesFileServiceNameMapping} is
+     * lower than {@link ParameterizedServiceNameMapping}
+     */
+    static final int PRIORITY = ParameterizedServiceNameMapping.PRIORITY + 1;
+
+
+    private final List<Properties> propertiesList;
+
+    public PropertiesFileServiceNameMapping() {
+        this.propertiesList = loadPropertiesList();
+    }
+
+    @Override
+    public Set<String> get(URL subscribedURL) {
+        String propertyKey = getPropertyKey(subscribedURL);
+        String propertyValue = null;
+
+        for (Properties properties : propertiesList) {
+            propertyValue = properties.getProperty(propertyKey);
+            if (propertyValue != null) {
+                break;
+            }
+        }
+
+        return getValue(propertyValue);
+    }
+
+    private String getPropertyKey(URL url) {
+        String protocol = url.getProtocol();
+        String serviceInterface = url.getServiceInterface();
+        // Optional
+        String group = url.getParameter(GROUP_KEY);
+        String version = url.getParameter(VERSION_KEY);
+
+        StringBuilder propertyKeyBuilder = new StringBuilder(protocol)
+                .append(KEY_SEPARATOR)
+                .append(serviceInterface);
+
+        appendIfPresent(propertyKeyBuilder, group);
+        appendIfPresent(propertyKeyBuilder, version);
+
+        return propertyKeyBuilder.toString();
+    }
+
+    private void appendIfPresent(StringBuilder builder, String value) {
+        if (!StringUtils.isBlank(value)) {
+            builder.append(KEY_SEPARATOR).append(value);
+        }
+    }
+
+    private List<Properties> loadPropertiesList() {
+        List<Properties> propertiesList = new LinkedList<>();
+        String propertiesPath = getPropertiesPath();
+        try {
+            Enumeration<java.net.URL> resources = ClassUtils.getClassLoader().getResources(propertiesPath);
+            while (resources.hasMoreElements()) {
+                java.net.URL resource = resources.nextElement();
+                InputStream inputStream = resource.openStream();
+                Properties properties = new Properties();
+                properties.load(new InputStreamReader(inputStream, "UTF-8"));
+                propertiesList.add(properties);
+            }
+        } catch (IOException e) {
+            if (logger.isErrorEnabled()) {
+                logger.error(format("The path of ServiceNameMapping's Properties file[path : %s] can't be loaded", propertiesPath), e);
+            }
+        }
+        return propertiesList;
+    }
+
+    private String getPropertiesPath() {
+        Configuration configuration = ApplicationModel.getEnvironment().getConfiguration();
+        String propertyPath = configuration.getString(SERVICE_NAME_MAPPING_PROPERTIES_FILE_KEY, DEFAULT_SERVICE_NAME_MAPPING_PROPERTIES_PATH);
+        propertyPath = PathUtils.normalize(propertyPath);
+        if (propertyPath.startsWith(SLASH)) {
+            propertyPath = propertyPath.substring(SLASH.length());
+        }
+        return propertyPath;
+    }
+
+    @Override
+    public int getPriority() {
+        return PRIORITY;
+    }
+}
diff --git a/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportFactory.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ReadOnlyServiceNameMapping.java
similarity index 54%
copy from dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportFactory.java
copy to dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ReadOnlyServiceNameMapping.java
index 0ffed8d..58035d3 100644
--- a/dubbo-metadata/dubbo-metadata-report-zookeeper/src/main/java/org/apache/dubbo/metadata/store/zookeeper/ZookeeperMetadataReportFactory.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ReadOnlyServiceNameMapping.java
@@ -14,27 +14,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.metadata.store.zookeeper;
+package org.apache.dubbo.metadata;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.metadata.report.MetadataReport;
-import org.apache.dubbo.metadata.report.support.AbstractMetadataReportFactory;
-import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+
+import java.util.Set;
+
+import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR_CHAR;
+import static org.apache.dubbo.common.utils.StringUtils.splitToSet;
 
 /**
- * ZookeeperRegistryFactory.
+ * Read-Only implementation of {@link ServiceNameMapping}
+ *
+ * @since 2.7.8
  */
-public class ZookeeperMetadataReportFactory extends AbstractMetadataReportFactory {
+public abstract class ReadOnlyServiceNameMapping implements ServiceNameMapping {
 
-    private ZookeeperTransporter zookeeperTransporter;
-
-    public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
-        this.zookeeperTransporter = zookeeperTransporter;
-    }
+    protected final Logger logger = LoggerFactory.getLogger(getClass());
 
     @Override
-    public MetadataReport createMetadataReport(URL url) {
-        return new ZookeeperMetadataReport(url, zookeeperTransporter);
+    public void map(URL exportedURL) {
+        // DO NOTING for mapping
     }
 
+    protected Set<String> getValue(String rawValue) {
+        return splitToSet(rawValue, COMMA_SEPARATOR_CHAR, true);
+    }
 }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMapping.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMapping.java
index 74113f2..46f657d 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMapping.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMapping.java
@@ -16,7 +16,9 @@
  */
 package org.apache.dubbo.metadata;
 
+import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.SPI;
+import org.apache.dubbo.common.lang.Prioritized;
 
 import java.util.Set;
 
@@ -28,7 +30,7 @@ import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoad
  * @since 2.7.5
  */
 @SPI("default")
-public interface ServiceNameMapping {
+public interface ServiceNameMapping extends Prioritized {
 
     /**
      * Map the specified Dubbo service interface, group, version and protocol to current Dubbo service name
@@ -37,8 +39,20 @@ public interface ServiceNameMapping {
      * @param group            the group of Dubbo service interface (optional)
      * @param version          the version of Dubbo service interface version (optional)
      * @param protocol         the protocol of Dubbo service interface exported (optional)
+     * @deprecated 2.7.8 This method will be removed since 3.0
      */
-    void map(String serviceInterface, String group, String version, String protocol);
+    @Deprecated
+    default void map(String serviceInterface, String group, String version, String protocol) {
+        throw new UnsupportedOperationException("This method has been deprecated and should not be invoked!");
+    }
+
+    /**
+     * Map the specified Dubbo service {@link URL} to current Dubbo service name
+     *
+     * @param exportedURL the {@link URL} that the Dubbo Provider exported
+     * @since 2.7.8
+     */
+    void map(URL exportedURL);
 
     /**
      * Get the service names from the specified Dubbo service interface, group, version and protocol
@@ -47,10 +61,22 @@ public interface ServiceNameMapping {
      * @param group            the group of Dubbo service interface (optional)
      * @param version          the version of Dubbo service interface version (optional)
      * @param protocol         the protocol of Dubbo service interface exported (optional)
-     * @return
+     * @return non-null {@link Set}
+     * @deprecated 2.7.8 This method will be removed since 3.0
      */
-    Set<String> get(String serviceInterface, String group, String version, String protocol);
+    @Deprecated
+    default Set<String> get(String serviceInterface, String group, String version, String protocol) {
+        throw new UnsupportedOperationException("This method has been deprecated and should not be invoked!");
+    }
 
+    /**
+     * Get the service names from the subscribed Dubbo service {@link URL}
+     *
+     * @param subscribedURL the {@link URL} that the Dubbo consumer subscribed
+     * @return non-null {@link Set}
+     * @since 2.7.8
+     */
+    Set<String> get(URL subscribedURL);
 
     /**
      * Get the default extension of {@link ServiceNameMapping}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/URLRevisionResolver.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/URLRevisionResolver.java
similarity index 67%
rename from dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/URLRevisionResolver.java
rename to dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/URLRevisionResolver.java
index 66f4dbf..f3fe79c 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/URLRevisionResolver.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/URLRevisionResolver.java
@@ -14,12 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.registry.client.metadata;
+package org.apache.dubbo.metadata;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.compiler.support.ClassUtils;
-import org.apache.dubbo.metadata.MetadataService;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
@@ -27,7 +27,9 @@ import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 
+import static java.util.Collections.emptyList;
 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.common.utils.CollectionUtils.isEmpty;
@@ -35,29 +37,64 @@ import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty;
 /**
  * A class to resolve the version from {@link URL URLs}
  *
+ * @revised 2.7.8 repackage and refactor
  * @since 2.7.5
  */
 public class URLRevisionResolver {
 
-    public static final String NO_REVISION = "N/A";
+    /**
+     * @since 2.7.8
+     */
+    public static final String UNKNOWN_REVISION = "X";
+
+    /**
+     * @since 2.7.8
+     */
+    public static final URLRevisionResolver INSTANCE = new URLRevisionResolver();
+
+    /**
+     * Resolve revision as {@link String} from the specified the {@link URL#toFullString() strings} presenting the {@link URL URLs}.
+     *
+     * @param url    one {@link URL}
+     * @param others the others {@link URL}
+     * @return non-null
+     * @since 2.7.8
+     */
+    public String resolve(String url, String... others) {
+        List<String> urls = new ArrayList<>(others.length + 1);
+        urls.add(url);
+        urls.addAll(Arrays.asList(others));
+        return resolve(urls);
+    }
 
     /**
      * Resolve revision as {@link String}
      *
      * @param urls {@link URL#toFullString() strings} presenting the {@link URL URLs}
      * @return non-null
+     * @revised 2.7.8 refactor the parameter as the super interface (from Collection to Iterable)
      */
-    public String resolve(Collection<String> urls) {
+    public String resolve(Iterable<String> urls) {
+        List<URL> urlsList = toURLsList(urls);
+        return resolve(urlsList);
+    }
+
+    /**
+     * Resolve revision as {@link String} from the specified the {@link URL URLs}.
+     *
+     * @param urls the {@link URL URLs}
+     * @return non-null
+     * @since 2.7.8
+     */
+    public String resolve(Collection<URL> urls) {
 
         if (isEmpty(urls)) {
-            return NO_REVISION;
+            return UNKNOWN_REVISION;
         }
 
-        List<URL> urlsList = toURLsList(urls);
-
-        SortedSet<String> methodSignatures = resolveMethodSignatures(urlsList);
+        SortedSet<String> methodSignatures = resolveMethodSignatures(urls);
 
-        SortedSet<String> urlParameters = resolveURLParameters(urlsList);
+        SortedSet<String> urlParameters = resolveURLParameters(urls);
 
         SortedSet<String> values = new TreeSet<>(methodSignatures);
 
@@ -66,18 +103,22 @@ public class URLRevisionResolver {
         return values.stream()
                 .map(this::hashCode)                     // generate Long hashCode
                 .reduce(Long::sum)                       // sum hashCode
-                .map(String::valueOf)                    // Long to String
-                .orElse(NO_REVISION);                    // NO_REVISION as default
+                .map(Long::toHexString)                  // Using Hex for the shorten content
+                .orElse(UNKNOWN_REVISION);               // NO_REVISION as default
     }
 
-    private List<URL> toURLsList(Collection<String> urls) {
-        return urls.stream()
+    private List<URL> toURLsList(Iterable<String> urls) {
+        if (urls == null) {
+            return emptyList();
+        }
+        return StreamSupport.
+                stream(urls.spliterator(), false)
                 .map(URL::valueOf)                             // String to URL
                 .filter(url -> isNotMetadataService(url.getServiceInterface())) // filter not MetadataService interface
                 .collect(Collectors.toList());
     }
 
-    private SortedSet<String> resolveMethodSignatures(List<URL> urls) {
+    private SortedSet<String> resolveMethodSignatures(Collection<URL> urls) {
         return urls.stream()
                 .map(URL::getServiceInterface)                 // get the service interface
                 .map(ClassUtils::forName)                      // load business interface class
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/WritableMetadataService.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/WritableMetadataService.java
index 3a17395..6cde356 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/WritableMetadataService.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/WritableMetadataService.java
@@ -20,10 +20,10 @@ import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.extension.SPI;
 import org.apache.dubbo.metadata.store.InMemoryWritableMetadataService;
-import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
 import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader;
+import static org.apache.dubbo.rpc.model.ApplicationModel.getName;
 
 /**
  * Local {@link MetadataService} that extends {@link MetadataService} and provides the modification, which is used for
@@ -40,7 +40,7 @@ public interface WritableMetadataService extends MetadataService {
      */
     @Override
     default String serviceName() {
-        return ApplicationModel.getApplication();
+        return getName();
     }
 
     /**
@@ -63,7 +63,9 @@ public interface WritableMetadataService extends MetadataService {
      * fresh Exports
      *
      * @return If success , return <code>true</code>
+     * @deprecated Recommend to use {@link MetadataServiceExporter} since 2.7.8
      */
+    @Deprecated
     default boolean refreshMetadata(String exportedRevision, String subscribedRevision) {
         return true;
     }
@@ -84,7 +86,7 @@ public interface WritableMetadataService extends MetadataService {
      */
     boolean unsubscribeURL(URL url);
 
-    void publishServiceDefinition(URL providerUrl);
+    void publishServiceDefinition(URL url);
 
     /**
      * Get {@link ExtensionLoader#getDefaultExtension() the defautl extension} of {@link WritableMetadataService}
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 e5fc587..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
@@ -17,34 +17,144 @@
 package org.apache.dubbo.metadata.report;
 
 
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.metadata.URLRevisionResolver;
 import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
 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.google.gson.Gson;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import static java.util.Collections.emptyList;
+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);
 
     void storeConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, Map<String, String> serviceParameterMap);
 
-    void saveServiceMetadata(ServiceMetadataIdentifier metadataIdentifier, URL url);
+    /**
+     * @deprecated 2.7.8
+     */
+    @Deprecated
+    default void saveServiceMetadata(ServiceMetadataIdentifier metadataIdentifier, URL url) {
+    }
 
-    void removeServiceMetadata(ServiceMetadataIdentifier metadataIdentifier);
+    /**
+     * @deprecated 2.7.8
+     */
+    @Deprecated
+    default void removeServiceMetadata(ServiceMetadataIdentifier metadataIdentifier) {
 
-    List<String> getExportedURLs(ServiceMetadataIdentifier metadataIdentifier);
+    }
 
-    void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Set<String> urls);
+    /**
+     * @deprecated 2.7.8
+     */
+    @Deprecated
+    default List<String> getExportedURLs(ServiceMetadataIdentifier metadataIdentifier) {
+        return emptyList();
+    }
 
-    List<String> getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier);
+    void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Collection<String> urls);
+
+    Collection<String> getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier);
 
     String getServiceDefinition(MetadataIdentifier metadataIdentifier);
-}
+
+    /**
+     * Save the exported {@link URL#toFullString() strings} presenting the {@link URL URLs} in bulk.
+     *
+     * @param exportedURLs the exported {@link URL urls}
+     * @return If successful, return <code>true</code>, or <code>false</code>
+     * @since 2.7.8
+     */
+    default boolean saveExportedURLs(SortedSet<String> exportedURLs) {
+        return saveExportedURLs(getName(), exportedURLs);
+    }
+
+    /**
+     * Save the exported {@link URL#toFullString() strings} presenting the {@link URL URLs} in bulk.
+     *
+     * @param serviceName  the specified Dubbo service name
+     * @param exportedURLs the exported {@link URL urls}
+     * @return If successful, return <code>true</code>, or <code>false</code>
+     * @since 2.7.8
+     */
+    default boolean saveExportedURLs(String serviceName, SortedSet<String> exportedURLs) {
+        return saveExportedURLs(serviceName, new URLRevisionResolver().resolve(exportedURLs), exportedURLs);
+    }
+
+    /**
+     * Save the exported {@link URL#toFullString() strings} presenting the {@link URL URLs} in bulk.
+     *
+     * @param serviceName              the specified Dubbo service name
+     * @param exportedServicesRevision the revision of the exported Services
+     * @param exportedURLs             the exported {@link URL urls}
+     * @return If successful, return <code>true</code>, or <code>false</code>
+     * @since 2.7.8
+     */
+    default boolean saveExportedURLs(String serviceName, String exportedServicesRevision, SortedSet<String> exportedURLs) {
+        Gson gson = new Gson();
+        String content = gson.toJson(exportedURLs);
+        return saveExportedURLs(serviceName, exportedServicesRevision, content);
+    }
+
+    /**
+     * Save the exported {@link URL#toFullString() strings} presenting the {@link URL URLs} in bulk.
+     *
+     * @param serviceName              the specified Dubbo service name
+     * @param exportedServicesRevision the revision of the exported Services
+     * @param exportedURLsContent      the content of the exported {@link URL urls}
+     * @return If successful, return <code>true</code>, or <code>false</code>
+     * @since 2.7.8
+     */
+    default boolean saveExportedURLs(String serviceName, String exportedServicesRevision, String exportedURLsContent) {
+        return true;
+    }
+
+    /**
+     * Get the {@link URL#toFullString() strings} presenting the {@link URL URLs} that were exported by the provider
+     *
+     * @param serviceName              the specified Dubbo service name
+     * @param exportedServicesRevision the revision of the exported Services
+     * @return non-null
+     * @since 2.7.8
+     */
+    default SortedSet<String> getExportedURLs(String serviceName, String exportedServicesRevision) {
+        String exportedURLsContent = getExportedURLsContent(serviceName, exportedServicesRevision);
+        if (StringUtils.isBlank(exportedURLsContent)) {
+            return emptySortedSet();
+        }
+        Gson gson = new Gson();
+        return gson.fromJson(exportedURLsContent, TreeSet.class);
+    }
+
+    /**
+     * Get the {@link URL#toFullString() strings} presenting the {@link URL URLs} that were exported by the provider
+     *
+     * @param serviceName              the specified Dubbo service name
+     * @param exportedServicesRevision the revision of the exported Services
+     * @return the content of the exported {@link URL urls} if found, or <code>null</code>
+     * @since 2.7.8
+     */
+    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/identifier/KeyTypeEnum.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/KeyTypeEnum.java
index 2c0da57..3253f85 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/KeyTypeEnum.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/KeyTypeEnum.java
@@ -16,9 +16,47 @@
  */
 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.common.utils.StringUtils.EMPTY_STRING;
+import static org.apache.dubbo.common.utils.StringUtils.isBlank;
+import static org.apache.dubbo.metadata.MetadataConstants.KEY_SEPARATOR;
+
 /**
  * 2019-08-15
  */
 public enum KeyTypeEnum {
-    PATH, UNIQUE_KEY
+
+    PATH(PATH_SEPARATOR) {
+        public String build(String one, String... others) {
+            return buildPath(one, others);
+        }
+    },
+
+    UNIQUE_KEY(KEY_SEPARATOR) {
+        public String build(String one, String... others) {
+            StringBuilder keyBuilder = new StringBuilder(one);
+            for (String other : others) {
+                keyBuilder.append(separator).append(isBlank(other) ? EMPTY_STRING : other);
+            }
+            return keyBuilder.toString();
+        }
+    };
+
+    final String separator;
+
+    KeyTypeEnum(String separator) {
+        this.separator = separator;
+    }
+
+    /**
+     * Build Key
+     *
+     * @param one    one
+     * @param others the others
+     * @return
+     * @since 2.7.8
+     */
+    public abstract String build(String one, String... others);
+
 }
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
new file mode 100644
index 0000000..d962ca5
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReport.java
@@ -0,0 +1,162 @@
+/*
+ * 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.common.config.configcenter.DynamicConfiguration;
+import org.apache.dubbo.common.config.configcenter.DynamicConfigurationFactory;
+import org.apache.dubbo.metadata.report.MetadataReport;
+import org.apache.dubbo.metadata.report.identifier.BaseMetadataIdentifier;
+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 java.util.List;
+
+import static org.apache.dubbo.common.config.configcenter.DynamicConfigurationFactory.getDynamicConfigurationFactory;
+import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
+import static org.apache.dubbo.metadata.MetadataConstants.EXPORTED_URLS_TAG;
+
+/**
+ * The generic implementation of {@link MetadataReport} based on {@link DynamicConfiguration
+ * the config-center infrastructure}
+ *
+ * @see AbstractMetadataReport
+ * @since 2.7.8
+ */
+public class ConfigCenterBasedMetadataReport extends AbstractMetadataReport {
+
+    private final KeyTypeEnum keyType;
+
+    private final String group;
+
+    private final DynamicConfiguration dynamicConfiguration;
+
+    public ConfigCenterBasedMetadataReport(URL reportServerURL, KeyTypeEnum keyTypeEnum) {
+        super(reportServerURL);
+        this.keyType = keyTypeEnum;
+        this.group = reportServerURL.getParameter(GROUP_KEY, DEFAULT_ROOT);
+        String extensionName = reportServerURL.getProtocol();
+        DynamicConfigurationFactory dynamicConfigurationFactory = getDynamicConfigurationFactory(extensionName);
+        dynamicConfiguration = dynamicConfigurationFactory.getDynamicConfiguration(reportServerURL);
+    }
+
+
+    @Override
+    protected void doStoreProviderMetadata(MetadataIdentifier providerMetadataIdentifier, String serviceDefinitions) {
+        saveMetadata(providerMetadataIdentifier, serviceDefinitions);
+    }
+
+    @Override
+    protected void doStoreConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, String serviceParameterString) {
+        saveMetadata(consumerMetadataIdentifier, serviceParameterString);
+    }
+
+    @Override
+    protected void doSaveMetadata(ServiceMetadataIdentifier metadataIdentifier, URL url) {
+        saveMetadata(metadataIdentifier, URL.encode(url.toFullString()));
+    }
+
+    @Override
+    protected void doRemoveMetadata(ServiceMetadataIdentifier metadataIdentifier) {
+        removeMetadata(metadataIdentifier);
+    }
+
+    @Override
+    protected List<String> doGetExportedURLs(ServiceMetadataIdentifier metadataIdentifier) {
+        throw new UnsupportedOperationException("doGetExportedURLs method will not be supported!");
+    }
+
+    @Override
+    protected void doSaveSubscriberData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, String urlListStr) {
+        saveMetadata(subscriberMetadataIdentifier, urlListStr);
+    }
+
+    @Override
+    protected String doGetSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier) {
+        return getMetadata(subscriberMetadataIdentifier);
+    }
+
+    @Override
+    public String getServiceDefinition(MetadataIdentifier metadataIdentifier) {
+        return getMetadata(metadataIdentifier);
+    }
+
+    @Override
+    public boolean saveExportedURLs(String serviceName, String exportedServicesRevision, String exportedURLsContent) {
+        String key = buildExportedURLsMetadataKey(serviceName, exportedServicesRevision);
+        return dynamicConfiguration.publishConfig(key, group, exportedURLsContent);
+    }
+
+    @Override
+    public String getExportedURLsContent(String serviceName, String exportedServicesRevision) {
+        String key = buildExportedURLsMetadataKey(serviceName, exportedServicesRevision);
+        return dynamicConfiguration.getConfig(key, group);
+    }
+
+    private String buildExportedURLsMetadataKey(String serviceName, String exportedServicesRevision) {
+        return keyType.build(EXPORTED_URLS_TAG, serviceName, exportedServicesRevision);
+    }
+
+    protected void saveMetadata(BaseMetadataIdentifier metadataIdentifier, String value) {
+        String key = getKey(metadataIdentifier);
+        dynamicConfiguration.publishConfig(key, group, value);
+    }
+
+    protected void saveMetadata(MetadataIdentifier metadataIdentifier, String value) {
+        String key = getKey(metadataIdentifier);
+        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);
+    }
+
+    protected String getMetadata(SubscriberMetadataIdentifier metadataIdentifier) {
+        String key = getKey(metadataIdentifier);
+        return dynamicConfiguration.getConfig(key, group);
+    }
+
+    protected void removeMetadata(MetadataIdentifier metadataIdentifier) {
+        String key = getKey(metadataIdentifier);
+        dynamicConfiguration.removeConfig(key, group);
+    }
+
+    protected void removeMetadata(ServiceMetadataIdentifier metadataIdentifier) {
+        String key = getKey(metadataIdentifier);
+        dynamicConfiguration.removeConfig(key, group);
+    }
+
+    protected String getKey(BaseMetadataIdentifier metadataIdentifier) {
+        return metadataIdentifier.getUniqueKey(keyType);
+    }
+
+    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
new file mode 100644
index 0000000..5b0f780
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReportFactory.java
@@ -0,0 +1,86 @@
+/*
+ * 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.common.config.configcenter.DynamicConfiguration;
+import org.apache.dubbo.metadata.report.MetadataReport;
+import org.apache.dubbo.metadata.report.MetadataReportFactory;
+import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.apache.dubbo.common.config.configcenter.TreePathDynamicConfiguration.CONFIG_BASE_PATH_PARAM_NAME;
+import static org.apache.dubbo.metadata.MetadataConstants.DEFAULT_PATH_TAG;
+import static org.apache.dubbo.rpc.cluster.Constants.EXPORT_KEY;
+import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY;
+
+/**
+ * The abstract implementation of {@link MetadataReportFactory} based on
+ * {@link DynamicConfiguration the config-center infrastructure}
+ *
+ * @see MetadataReportFactory
+ * @see MetadataReport
+ * @see DynamicConfiguration
+ * @since 2.7.8
+ */
+public abstract class ConfigCenterBasedMetadataReportFactory implements MetadataReportFactory {
+
+    /**
+     * org.apache.dubbo.metadata.report.MetadataReport
+     */
+    private static final String URL_PATH = MetadataReport.class.getName();
... 5020 lines suppressed ...