You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by li...@apache.org on 2020/08/04 07:09:40 UTC

[dubbo] 03/27: metadata read & write

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

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

commit b33aef310dc2f5d3293cf2c3b358f5af6e382692
Author: ken.lj <ke...@gmail.com>
AuthorDate: Mon Jun 15 10:21:28 2020 +0800

    metadata read & write
---
 .../dubbo/config/AbstractInterfaceConfig.java      |   2 +
 .../apache/dubbo/config/context/ConfigManager.java |   8 +
 .../org/apache/dubbo/config/ReferenceConfig.java   |  17 +-
 .../org/apache/dubbo/config/ServiceConfig.java     |  14 +-
 .../dubbo/config/bootstrap/DubboBootstrap.java     | 313 +++++----------------
 .../metadata/DefaultMetadataParamsFilter.java      |  47 ++++
 .../org/apache/dubbo/metadata/MetadataInfo.java    | 100 +++++++
 .../dubbo/metadata/MetadataParamsFilter.java}      |  20 +-
 .../org/apache/dubbo/metadata/MetadataService.java |   2 +
 .../apache/dubbo/metadata/RevisionResolver.java    |  57 ++++
 .../apache/dubbo/metadata/URLRevisionResolver.java | 152 ----------
 .../dubbo/metadata/WritableMetadataService.java    |  22 +-
 .../dubbo/metadata/report/MetadataReport.java      | 130 +--------
 .../metadata/report/MetadataReportInstance.java    |  24 +-
 .../store/RemoteWritableMetadataService.java       | 122 --------
 ...g.apache.dubbo.metadata.WritableMetadataService |   2 -
 .../InMemoryWritableMetadataServiceTest.java       | 157 -----------
 .../RemoteWritableMetadataServiceDelegateTest.java | 216 ++++++++++++++
 .../registry/client/ServiceDiscoveryRegistry.java  |   7 +-
 .../registry/client/ServiceInstanceCustomizer.java |   3 -
 .../CustomizableServiceInstanceListener.java       |  44 ---
 .../listener/ServiceInstancesChangedListener.java  |  26 +-
 ...ExportedServicesRevisionMetadataCustomizer.java |  57 ----
 .../registry/client/metadata/MetadataUtils.java    | 112 ++++++++
 .../metadata/ServiceInstanceMetadataUtils.java     |   7 +
 ...bscribedServicesRevisionMetadataCustomizer.java |  58 ----
 .../proxy/BaseMetadataServiceProxyFactory.java     |  58 ----
 .../proxy/DefaultMetadataServiceProxyFactory.java  |  85 ------
 .../proxy/MetadataServiceProxyFactory.java         |  56 ----
 .../metadata/proxy/RemoteMetadataServiceProxy.java | 111 --------
 .../proxy/RemoteMetadataServiceProxyFactory.java   |  34 ---
 .../store/InMemoryWritableMetadataService.java     |  76 +++--
 .../metadata/store/RemoteMetadataServiceImpl.java  | 126 +++++++++
 .../registry/integration/RegistryDirectory.java    |   1 +
 .../internal/org.apache.dubbo.event.EventListener  |   5 +-
 .../event/listener/LoggingEventListenerTest.java   |   5 +-
 .../ServiceInstancesChangedListenerTest.java       |  27 --
 37 files changed, 873 insertions(+), 1430 deletions(-)

diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java
index 82b5636..970a6ae 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java
@@ -280,6 +280,8 @@ public abstract class AbstractInterfaceConfig extends AbstractMethodConfig {
                     RegistryConfig registryConfig = new RegistryConfig();
                     registryConfig.refresh();
                     registryConfigs.add(registryConfig);
+                } else {
+                    registryConfigs = new ArrayList<>(registryConfigs);
                 }
                 setRegistries(registryConfigs);
             }
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 868fadc..218fef0 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
@@ -164,6 +164,14 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
         return getConfigs(getTagName(MetadataReportConfig.class));
     }
 
+    public Collection<MetadataReportConfig> getDefaultMetadataConfigs() {
+        Collection<MetadataReportConfig> defaults = getDefaultConfigs(getConfigsMap(getTagName(MetadataReportConfig.class)));
+        if (CollectionUtils.isEmpty(defaults)) {
+            return getMetadataConfigs();
+        }
+        return defaults;
+    }
+
     // MetadataReportConfig correlative methods
 
     public void addProvider(ProviderConfig providerConfig) {
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 da90597..23f7bf6 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
@@ -36,7 +36,7 @@ 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.registry.client.metadata.MetadataUtils;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Protocol;
 import org.apache.dubbo.rpc.ProxyFactory;
@@ -66,7 +66,6 @@ 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;
@@ -391,16 +390,10 @@ 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);
-        }
+
+        URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
+        MetadataUtils.publishServiceDefinition(consumerURL);
+
         // create service proxy
         return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
     }
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 cf5af44..3a7702b 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,7 @@ 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.registry.client.metadata.MetadataUtils;
 import org.apache.dubbo.rpc.Exporter;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Protocol;
@@ -68,7 +68,6 @@ 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;
@@ -210,6 +209,7 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
     }
 
     public void exported() {
+
         // dispatch a ServiceConfigExportedEvent since 2.7.4
         dispatch(new ServiceConfigExportedEvent(this));
     }
@@ -502,14 +502,8 @@ 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);
-                }
+
+                MetadataUtils.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 1ef096b..3bdffa9 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
@@ -19,7 +19,6 @@ package org.apache.dubbo.config.bootstrap;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.config.Environment;
 import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
-import org.apache.dubbo.common.config.configcenter.DynamicConfigurationFactory;
 import org.apache.dubbo.common.config.configcenter.wrapper.CompositeDynamicConfiguration;
 import org.apache.dubbo.common.extension.ExtensionLoader;
 import org.apache.dubbo.common.lang.ShutdownHookCallback;
@@ -28,7 +27,6 @@ import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.concurrent.ScheduledCompletableFuture;
 import org.apache.dubbo.common.threadpool.manager.ExecutorRepository;
-import org.apache.dubbo.common.utils.ArrayUtils;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.config.ApplicationConfig;
@@ -54,6 +52,7 @@ 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;
@@ -62,13 +61,14 @@ import org.apache.dubbo.event.GenericEventListener;
 import org.apache.dubbo.metadata.MetadataService;
 import org.apache.dubbo.metadata.MetadataServiceExporter;
 import org.apache.dubbo.metadata.WritableMetadataService;
-import org.apache.dubbo.metadata.report.MetadataReportFactory;
 import org.apache.dubbo.metadata.report.MetadataReportInstance;
 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.client.metadata.MetadataUtils;
+import org.apache.dubbo.registry.client.metadata.store.RemoteMetadataServiceImpl;
 import org.apache.dubbo.registry.support.AbstractRegistryFactory;
 import org.apache.dubbo.rpc.model.ApplicationModel;
 
@@ -77,7 +77,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Optional;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.concurrent.CompletableFuture;
@@ -88,29 +87,25 @@ import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Consumer;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
-import static java.lang.String.format;
 import static java.util.Arrays.asList;
 import static java.util.concurrent.Executors.newSingleThreadExecutor;
 import static org.apache.dubbo.common.config.ConfigurationUtils.parseProperties;
 import static org.apache.dubbo.common.config.configcenter.DynamicConfiguration.getDynamicConfiguration;
 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.isEmpty;
 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.
  *
@@ -134,7 +129,7 @@ public class DubboBootstrap extends GenericEventListener {
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
-    private static volatile DubboBootstrap instance;
+    private static DubboBootstrap instance;
 
     private final AtomicBoolean awaited = new AtomicBoolean(false);
 
@@ -148,7 +143,7 @@ public class DubboBootstrap extends GenericEventListener {
 
     private final EventDispatcher eventDispatcher = EventDispatcher.getDefaultExtension();
 
-    private final ExecutorRepository executorRepository = getExtensionLoader(ExecutorRepository.class).getDefaultExtension();
+    private final ExecutorRepository executorRepository = ExtensionLoader.getExtensionLoader(ExecutorRepository.class).getDefaultExtension();
 
     private final ConfigManager configManager;
 
@@ -172,7 +167,7 @@ public class DubboBootstrap extends GenericEventListener {
 
     private volatile MetadataService metadataService;
 
-    private volatile Set<MetadataServiceExporter> metadataServiceExporters;
+    private volatile MetadataServiceExporter metadataServiceExporter;
 
     private List<ServiceConfigBase<?>> exportedServices = new ArrayList<>();
 
@@ -183,13 +178,9 @@ public class DubboBootstrap extends GenericEventListener {
     /**
      * See {@link ApplicationModel} and {@link ExtensionLoader} for why DubboBootstrap is designed to be singleton.
      */
-    public static DubboBootstrap getInstance() {
+    public static synchronized DubboBootstrap getInstance() {
         if (instance == null) {
-            synchronized (DubboBootstrap.class) {
-                if (instance == null) {
-                    instance = new DubboBootstrap();
-                }
-            }
+            instance = new DubboBootstrap();
         }
         return instance;
     }
@@ -511,7 +502,7 @@ public class DubboBootstrap extends GenericEventListener {
     /**
      * Initialize
      */
-    public void initialize() {
+    private void initialize() {
         if (!initialized.compareAndSet(false, true)) {
             return;
         }
@@ -520,17 +511,14 @@ public class DubboBootstrap extends GenericEventListener {
 
         startConfigCenter();
 
+        useRegistryAsConfigCenterIfNecessary();
+
         loadRemoteConfigs();
 
         checkGlobalConfigs();
 
-        // @since 2.7.8
-        startMetadataCenter();
-
         initMetadataService();
 
-        initMetadataServiceExports();
-
         initEventListener();
 
         if (logger.isInfoEnabled()) {
@@ -597,9 +585,6 @@ public class DubboBootstrap extends GenericEventListener {
     }
 
     private void startConfigCenter() {
-
-        useRegistryAsConfigCenterIfNecessary();
-
         Collection<ConfigCenterConfig> configCenters = configManager.getConfigCenters();
 
         // check Config Center
@@ -627,10 +612,7 @@ public class DubboBootstrap extends GenericEventListener {
         configManager.refreshAll();
     }
 
-    private void startMetadataCenter() {
-
-        useRegistryAsMetadataCenterIfNecessary();
-
+    private void startMetadataReport() {
         ApplicationConfig applicationConfig = getApplication();
 
         String metadataType = applicationConfig.getMetadataType();
@@ -638,17 +620,18 @@ public class DubboBootstrap extends GenericEventListener {
         Collection<MetadataReportConfig> metadataReportConfigs = configManager.getMetadataConfigs();
         if (CollectionUtils.isEmpty(metadataReportConfigs)) {
             if (REMOTE_METADATA_STORAGE_TYPE.equals(metadataType)) {
-                throw new IllegalStateException("No MetadataConfig found, you must specify the remote Metadata Center address when 'metadata=remote' is enabled.");
+                throw new IllegalStateException("No MetadataConfig found, Metadata Center address is required when 'metadata=remote' is enabled.");
             }
             return;
         }
-        MetadataReportConfig metadataReportConfig = metadataReportConfigs.iterator().next();
-        ConfigValidationUtils.validateMetadataConfig(metadataReportConfig);
-        if (!metadataReportConfig.isValid()) {
-            return;
-        }
 
-        MetadataReportInstance.init(metadataReportConfig.toUrl());
+        for (MetadataReportConfig metadataReportConfig : metadataReportConfigs) {
+            ConfigValidationUtils.validateMetadataConfig(metadataReportConfig);
+            if (!metadataReportConfig.isValid()) {
+                return;
+            }
+            MetadataReportInstance.init(metadataReportConfig);
+        }
     }
 
     /**
@@ -666,159 +649,33 @@ public class DubboBootstrap extends GenericEventListener {
             return;
         }
 
-        configManager
-                .getDefaultRegistries()
-                .stream()
-                .filter(this::isUsedRegistryAsConfigCenter)
-                .map(this::registryAsConfigCenter)
-                .forEach(configManager::addConfigCenter);
-    }
-
-    private boolean isUsedRegistryAsConfigCenter(RegistryConfig registryConfig) {
-        return isUsedRegistryAsCenter(registryConfig, registryConfig::getUseAsConfigCenter, "config",
-                DynamicConfigurationFactory.class);
-    }
-
-    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));
-        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) {
-        return isUsedRegistryAsCenter(registryConfig, registryConfig::getUseAsMetadataCenter, "metadata",
-                MetadataReportFactory.class);
-    }
-
-    /**
-     * Is used the specified registry as a center infrastructure
-     *
-     * @param registryConfig       the {@link RegistryConfig}
-     * @param usedRegistryAsCenter the configured value on
-     * @param centerType           the type name of center
-     * @param extensionClass       an extension class of a center infrastructure
-     * @return
-     * @since 2.7.8
-     */
-    private boolean isUsedRegistryAsCenter(RegistryConfig registryConfig, Supplier<Boolean> usedRegistryAsCenter,
-                                           String centerType,
-                                           Class<?> extensionClass) {
-        final boolean supported;
-
-        Boolean configuredValue = usedRegistryAsCenter.get();
-        if (configuredValue != null) { // If configured, take its value.
-            supported = configuredValue.booleanValue();
-        } else {                       // Or check the extension existence
-            String protocol = registryConfig.getProtocol();
-            supported = supportsExtension(extensionClass, protocol);
-            if (logger.isInfoEnabled()) {
-                logger.info(format("No value is configured in the registry, the %s extension[name : %s] %s as the %s center"
-                        , extensionClass.getSimpleName(), protocol, supported ? "supports" : "does not support", centerType));
-            }
-        }
-
-        if (logger.isInfoEnabled()) {
-            logger.info(format("The registry[%s] will be %s as the %s center", registryConfig,
-                    supported ? "used" : "not used", centerType));
-        }
-        return supported;
-    }
-
-    /**
-     * Supports the extension with the specified class and name
-     *
-     * @param extensionClass the {@link Class} of extension
-     * @param name           the name of extension
-     * @return if supports, return <code>true</code>, or <code>false</code>
-     * @since 2.7.8
-     */
-    private boolean supportsExtension(Class<?> extensionClass, String name) {
-        if (isNotEmpty(name)) {
-            ExtensionLoader extensionLoader = getExtensionLoader(extensionClass);
-            return extensionLoader.hasExtension(name);
-        }
-        return false;
-    }
-
-    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));
-        metadataReportConfig.setUsername(registryConfig.getUsername());
-        metadataReportConfig.setPassword(registryConfig.getPassword());
-        metadataReportConfig.setTimeout(registryConfig.getTimeout());
-        return metadataReportConfig;
-    }
-
-    private String getRegistryCompatibleAddress(RegistryConfig registryConfig) {
-        String registryAddress = registryConfig.getAddress();
-        String[] addresses = REGISTRY_SPLIT_PATTERN.split(registryAddress);
-        if (ArrayUtils.isEmpty(addresses)) {
-            throw new IllegalStateException("Invalid registry address found.");
-        }
-        String address = addresses[0];
-        // since 2.7.8
-        // Issue : https://github.com/apache/dubbo/issues/6476
-        StringBuilder metadataAddressBuilder = new StringBuilder();
-        URL url = URL.valueOf(address);
-        String protocolFromAddress = url.getProtocol();
-        if (isEmpty(protocolFromAddress)) {
-            // If the protocol from address is missing, is like :
-            // "dubbo.registry.address = 127.0.0.1:2181"
-            String protocolFromConfig = registryConfig.getProtocol();
-            metadataAddressBuilder.append(protocolFromConfig).append("://");
-        }
-        metadataAddressBuilder.append(address);
-        return metadataAddressBuilder.toString();
+        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(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();
     }
 
     private void loadRemoteConfigs() {
@@ -857,17 +714,12 @@ public class DubboBootstrap extends GenericEventListener {
 
 
     /**
-     * Initialize {@link #metadataService WritableMetadataService} from {@link WritableMetadataService}'s extension
+     * Initialize {@link MetadataService} from {@link WritableMetadataService}'s extension
      */
     private void initMetadataService() {
-        this.metadataService = WritableMetadataService.getExtension(getMetadataType());
-    }
-
-    /**
-     * Initialize {@link #metadataServiceExporters MetadataServiceExporter}
-     */
-    private void initMetadataServiceExports() {
-        this.metadataServiceExporters = getExtensionLoader(MetadataServiceExporter.class).getSupportedExtensionInstances();
+        startMetadataReport();
+        this.metadataService = getExtension(getMetadataType());
+        this.metadataServiceExporter = new ConfigurableMetadataServiceExporter(metadataService);
     }
 
     /**
@@ -1069,21 +921,13 @@ public class DubboBootstrap extends GenericEventListener {
      * export {@link MetadataService}
      */
     private void exportMetadataService() {
-        metadataServiceExporters
-                .stream()
-                .filter(this::supports)
-                .forEach(MetadataServiceExporter::export);
+        metadataServiceExporter.export();
     }
 
     private void unexportMetadataService() {
-        Optional.ofNullable(metadataServiceExporters)
-                .ifPresent(set -> set.stream()
-                        .filter(this::supports)
-                        .forEach(MetadataServiceExporter::unexport));
-    }
-
-    private boolean supports(MetadataServiceExporter exporter) {
-        return exporter.supports(getMetadataType());
+        if (metadataServiceExporter != null && metadataServiceExporter.isExported()) {
+            metadataServiceExporter.unexport();
+        }
     }
 
     private void exportServices() {
@@ -1176,35 +1020,15 @@ public class DubboBootstrap extends GenericEventListener {
 
         ServiceInstance serviceInstance = createServiceInstance(serviceName, host, port);
 
-        preRegisterServiceInstance(serviceInstance);
+        // register metadata
+        publishMetadataToRemote(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 void publishMetadataToRemote(ServiceInstance serviceInstance) {
+        RemoteMetadataServiceImpl remoteMetadataService = MetadataUtils.getRemoteMetadataService();
+        remoteMetadataService.publishMetadata(serviceInstance);
     }
 
     private URL selectMetadataServiceExportedURL() {
@@ -1244,6 +1068,15 @@ public class DubboBootstrap extends GenericEventListener {
     private ServiceInstance createServiceInstance(String serviceName, String host, int port) {
         this.serviceInstance = new DefaultServiceInstance(serviceName, host, port);
         setMetadataStorageType(serviceInstance, getMetadataType());
+
+        ExtensionLoader<ServiceInstanceCustomizer> loader =
+                ExtensionLoader.getExtensionLoader(ServiceInstanceCustomizer.class);
+        // FIXME, sort customizer before apply
+        loader.getSupportedExtensionInstances().forEach(customizer -> {
+            // customizes
+            customizer.customize(this.serviceInstance);
+        });
+
         return this.serviceInstance;
     }
 
@@ -1297,7 +1130,7 @@ public class DubboBootstrap extends GenericEventListener {
     }
 
     private void clearConfigs() {
-        configManager.destroy();
+        configManager.clear();
         if (logger.isDebugEnabled()) {
             logger.debug(NAME + "'s configs have been clear.");
         }
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DefaultMetadataParamsFilter.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DefaultMetadataParamsFilter.java
new file mode 100644
index 0000000..7f4b85c
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DefaultMetadataParamsFilter.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.metadata;
+
+import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_VERSION_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.LOADBALANCE_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.RELEASE_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
+import static org.apache.dubbo.remoting.Constants.CODEC_KEY;
+import static org.apache.dubbo.remoting.Constants.CONNECTIONS_KEY;
+import static org.apache.dubbo.remoting.Constants.EXCHANGER_KEY;
+import static org.apache.dubbo.remoting.Constants.SERIALIZATION_KEY;
+import static org.apache.dubbo.rpc.Constants.DEPRECATED_KEY;
+import static org.apache.dubbo.rpc.Constants.MOCK_KEY;
+import static org.apache.dubbo.rpc.Constants.TOKEN_KEY;
+import static org.apache.dubbo.rpc.cluster.Constants.WARMUP_KEY;
+import static org.apache.dubbo.rpc.cluster.Constants.WEIGHT_KEY;
+
+public class DefaultMetadataParamsFilter implements MetadataParamsFilter {
+    @Override
+    public String[] include() {
+        return new String[]{
+                CODEC_KEY, EXCHANGER_KEY, SERIALIZATION_KEY, CLUSTER_KEY, CONNECTIONS_KEY, DEPRECATED_KEY,
+                GROUP_KEY, LOADBALANCE_KEY, MOCK_KEY, PATH_KEY, TIMEOUT_KEY, TOKEN_KEY, VERSION_KEY, WARMUP_KEY,
+                WEIGHT_KEY, TIMESTAMP_KEY, DUBBO_VERSION_KEY, RELEASE_KEY
+        };
+    }
+}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java
index 20933fb..bf9aa68 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java
@@ -17,12 +17,22 @@
 package org.apache.dubbo.metadata;
 
 import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.common.utils.ArrayUtils;
+import org.apache.dubbo.common.utils.StringUtils;
 
 import java.io.Serializable;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
+import static org.apache.dubbo.common.constants.CommonConstants.DOT_SEPARATOR;
+import static org.apache.dubbo.common.constants.CommonConstants.GROUP_CHAR_SEPERATOR;
+import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
+
 public class MetadataInfo implements Serializable {
     private String app;
     private String revision;
@@ -37,6 +47,36 @@ public class MetadataInfo implements Serializable {
         this.services = services == null ? new HashMap<>() : services;
     }
 
+    public void addService(ServiceInfo serviceInfo) {
+        if (serviceInfo == null) {
+            return;
+        }
+        this.services.put(serviceInfo.getMatchKey(), serviceInfo);
+    }
+
+    public void removeService(ServiceInfo serviceInfo) {
+        if (serviceInfo == null) {
+            return;
+        }
+        this.services.remove(serviceInfo.getMatchKey());
+    }
+
+    public void removeService(String key) {
+        if (key == null) {
+            return;
+        }
+        this.services.remove(key);
+    }
+
+    public String revision() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(app);
+        for (Map.Entry<String, ServiceInfo> entry : services.entrySet()) {
+            sb.append(entry.getValue().toString());
+        }
+        return RevisionResolver.calRevision(sb.toString());
+    }
+
     public String getApp() {
         return app;
     }
@@ -82,6 +122,7 @@ public class MetadataInfo implements Serializable {
     }
 
     public static class ServiceInfo implements Serializable {
+        private static ExtensionLoader<MetadataParamsFilter> loader = ExtensionLoader.getExtensionLoader(MetadataParamsFilter.class);
         private String name;
         private String group;
         private String version;
@@ -91,10 +132,45 @@ public class MetadataInfo implements Serializable {
 
         private transient Map<String, Map<String, String>> methodParams;
         private transient String serviceKey;
+        private transient String matchKey;
 
         public ServiceInfo() {
         }
 
+        public ServiceInfo(URL url) {
+            // FIXME, how to match registry.
+            this(
+                    url.getServiceInterface(),
+                    url.getParameter(GROUP_KEY),
+                    url.getParameter(VERSION_KEY),
+                    url.getProtocol(),
+                    "",
+                    null
+            );
+
+            Map<String, String> params = new HashMap<>();
+            List<MetadataParamsFilter> filters = loader.getActivateExtension(url, "params-filter");
+            for (MetadataParamsFilter filter : filters) {
+                String[] paramsIncluded = filter.include();
+                if (ArrayUtils.isNotEmpty(paramsIncluded)) {
+                    for (String p : paramsIncluded) {
+                        String value = url.getParameter(p);
+                        if (StringUtils.isNotEmpty(value) && params.get(p) == null) {
+                            params.put(p, value);
+                        }
+                        String[] methods = url.getParameter(METHODS_KEY, (String[]) null);
+                        for (String method : methods) {
+                            String mValue = url.getMethodParameter(method, p);
+                            if (StringUtils.isNotEmpty(mValue)) {
+                                params.put(method + DOT_SEPARATOR + p, mValue);
+                            }
+                        }
+                    }
+                }
+            }
+            this.params = params;
+        }
+
         public ServiceInfo(String name, String group, String version, String protocol, String registry, Map<String, String> params) {
             this.name = name;
             this.group = group;
@@ -104,9 +180,33 @@ public class MetadataInfo implements Serializable {
             this.params = params == null ? new HashMap<>() : params;
 
             this.serviceKey = URL.buildKey(name, group, version);
+            this.matchKey = buildMatchKey();
+        }
+
+        public String getMatchKey() {
+            if (matchKey != null) {
+                return matchKey;
+            }
+            buildMatchKey();
+            return matchKey;
+        }
+
+        private String buildMatchKey() {
+            matchKey = getServiceKey();
+            if (StringUtils.isNotEmpty(protocol)) {
+                matchKey = getServiceKey() + GROUP_CHAR_SEPERATOR + protocol;
+            }
+            if (StringUtils.isNotEmpty(registry)) {
+                matchKey = getServiceKey() + GROUP_CHAR_SEPERATOR + registry;
+            }
+            return matchKey;
         }
 
         public String getServiceKey() {
+            if (serviceKey != null) {
+                return serviceKey;
+            }
+            this.serviceKey = URL.buildKey(name, group, version);
             return serviceKey;
         }
 
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/LocalMetadataServiceTest.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataParamsFilter.java
similarity index 64%
rename from dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/LocalMetadataServiceTest.java
rename to dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataParamsFilter.java
index 45fc8e0..5ccb9aa 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/LocalMetadataServiceTest.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataParamsFilter.java
@@ -16,21 +16,9 @@
  */
 package org.apache.dubbo.metadata;
 
-import org.apache.dubbo.metadata.store.InMemoryWritableMetadataService;
+import org.apache.dubbo.common.extension.SPI;
 
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-/**
- * {@link WritableMetadataService} Test
- *
- * @since 2.7.5
- */
-public class LocalMetadataServiceTest {
-
-    @Test
-    public void testDefaultExtension() {
-        assertEquals(InMemoryWritableMetadataService.class, WritableMetadataService.getDefaultExtension().getClass());
-    }
+@SPI
+public interface MetadataParamsFilter {
+   String[] include();
 }
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 660ac42..ba9e50b 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
@@ -177,6 +177,8 @@ public interface MetadataService {
      */
     String getServiceDefinition(String serviceKey);
 
+    MetadataInfo getMetadataInfo();
+
     /**
      * Is the {@link URL} for the {@link MetadataService} or not?
      *
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/RevisionResolver.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/RevisionResolver.java
new file mode 100644
index 0000000..76d8cfb
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/RevisionResolver.java
@@ -0,0 +1,57 @@
+/*
+ * 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.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class RevisionResolver {
+    private static final Logger logger = LoggerFactory.getLogger(RevisionResolver.class);
+    private static char hexDigits[] = {
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+    };
+
+    private static MessageDigest mdInst;
+
+    static {
+        try {
+            mdInst = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException e) {
+            logger.error("Failed to calculate metadata revision", e);
+        }
+    }
+
+    public static String calRevision(String metadata) {
+        mdInst.update(metadata.getBytes(UTF_8));
+        byte[] md5 = mdInst.digest();
+
+        int j = md5.length;
+        char str[] = new char[j * 2];
+        int k = 0;
+        for (int i = 0; i < j; i++) {
+            byte byte0 = md5[i];
+            str[k++] = hexDigits[byte0 >>> 4 & 0xf];
+            str[k++] = hexDigits[byte0 & 0xf];
+        }
+        return new String(str);
+    }
+}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/URLRevisionResolver.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/URLRevisionResolver.java
deleted file mode 100644
index f3fe79c..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/URLRevisionResolver.java
+++ /dev/null
@@ -1,152 +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;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.compiler.support.ClassUtils;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-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;
-
-/**
- * A class to resolve the version from {@link URL URLs}
- *
- * @revised 2.7.8 repackage and refactor
- * @since 2.7.5
- */
-public class URLRevisionResolver {
-
-    /**
-     * @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(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 UNKNOWN_REVISION;
-        }
-
-        SortedSet<String> methodSignatures = resolveMethodSignatures(urls);
-
-        SortedSet<String> urlParameters = resolveURLParameters(urls);
-
-        SortedSet<String> values = new TreeSet<>(methodSignatures);
-
-        values.addAll(urlParameters);
-
-        return values.stream()
-                .map(this::hashCode)                     // generate Long hashCode
-                .reduce(Long::sum)                       // sum hashCode
-                .map(Long::toHexString)                  // Using Hex for the shorten content
-                .orElse(UNKNOWN_REVISION);               // NO_REVISION as default
-    }
-
-    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(Collection<URL> urls) {
-        return urls.stream()
-                .map(URL::getServiceInterface)                 // get the service interface
-                .map(ClassUtils::forName)                      // load business interface class
-                .map(Class::getMethods)                        // get all public methods from business interface
-                .map(Arrays::asList)                           // Array to List
-                .flatMap(Collection::stream)                   // flat Stream<Stream> to be Stream
-                .map(Object::toString)                         // Method to String
-                .collect(TreeSet::new, Set::add, Set::addAll); // sort and remove the duplicate
-    }
-
-    private SortedSet<String> resolveURLParameters(Collection<URL> urls) {
-        return urls.stream()
-                .map(url -> url.removeParameter(PID_KEY))
-                .map(url -> url.removeParameter(TIMESTAMP_KEY))
-                .map(URL::toParameterString)
-                .collect(TreeSet::new, Set::add, Set::addAll); // sort and remove the duplicate
-    }
-
-    private long hashCode(String value) {
-        long h = 0;
-        char[] chars = value.toCharArray();
-        for (int i = 0; i < chars.length; i++) {
-            h = 31L * h + chars[i];
-        }
-        return h;
-    }
-
-    private boolean isNotMetadataService(String serviceInterface) {
-        return !MetadataService.class.getName().equals(serviceInterface);
-    }
-}
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 6cde356..b453842 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
@@ -18,12 +18,9 @@ package org.apache.dubbo.metadata;
 
 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
@@ -31,7 +28,6 @@ import static org.apache.dubbo.rpc.model.ApplicationModel.getName;
  *
  * @since 2.7.5
  */
-@SPI(DEFAULT_METADATA_STORAGE_TYPE)
 public interface WritableMetadataService extends MetadataService {
     /**
      * Gets the current Dubbo Service name
@@ -40,7 +36,7 @@ public interface WritableMetadataService extends MetadataService {
      */
     @Override
     default String serviceName() {
-        return getName();
+        return ApplicationModel.getApplication();
     }
 
     /**
@@ -60,17 +56,6 @@ public interface WritableMetadataService extends MetadataService {
     boolean unexportURL(URL url);
 
     /**
-     * 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;
-    }
-
-    /**
      * Subscribes a {@link URL}
      *
      * @param url a {@link URL}
@@ -86,13 +71,12 @@ public interface WritableMetadataService extends MetadataService {
      */
     boolean unsubscribeURL(URL url);
 
-    void publishServiceDefinition(URL url);
+    void publishServiceDefinition(URL providerUrl);
 
     /**
      * Get {@link ExtensionLoader#getDefaultExtension() the defautl extension} of {@link WritableMetadataService}
      *
      * @return non-null
-     * @see InMemoryWritableMetadataService
      */
     static WritableMetadataService getDefaultExtension() {
         return getExtensionLoader(WritableMetadataService.class).getDefaultExtension();
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 068d3a0..4bc133d 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
@@ -18,143 +18,43 @@ package org.apache.dubbo.metadata.report;
 
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.utils.StringUtils;
-import org.apache.dubbo.metadata.URLRevisionResolver;
+import org.apache.dubbo.metadata.MetadataInfo;
 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;
+import java.util.Set;
 
-/**
- * The interface to report the metadata
- *
- * @see AutoCloseable since 2.7.8
- */
-public interface MetadataReport extends AutoCloseable {
+public interface MetadataReport {
 
     void storeProviderMetadata(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition);
 
     void storeConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, Map<String, String> serviceParameterMap);
 
-    /**
-     * @deprecated 2.7.8
-     */
-    @Deprecated
-    default void saveServiceMetadata(ServiceMetadataIdentifier metadataIdentifier, URL url) {
-    }
-
-    /**
-     * @deprecated 2.7.8
-     */
-    @Deprecated
-    default void removeServiceMetadata(ServiceMetadataIdentifier metadataIdentifier) {
-
-    }
-
-    /**
-     * @deprecated 2.7.8
-     */
-    @Deprecated
-    default List<String> getExportedURLs(ServiceMetadataIdentifier metadataIdentifier) {
-        return emptyList();
-    }
-
-    void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Collection<String> urls);
-
-    Collection<String> getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier);
+    List<String> getExportedURLs(ServiceMetadataIdentifier metadataIdentifier);
 
     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);
+    default void publishAppMetadata(SubscriberMetadataIdentifier identifier, MetadataInfo metadataInfo) {
     }
 
-    /**
-     * 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);
+    default MetadataInfo getAppMetadata(SubscriberMetadataIdentifier identifier, Map<String, String> instanceMetadata) {
+        return null;
     }
 
     /**
-     * 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);
-    }
+     * deprecated
+     **/
 
-    /**
-     * 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;
-    }
+    void saveServiceMetadata(ServiceMetadataIdentifier metadataIdentifier, URL url);
 
-    /**
-     * 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);
-    }
+    void removeServiceMetadata(ServiceMetadataIdentifier metadataIdentifier);
 
-    /**
-     * 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;
-    }
+    void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Set<String> urls);
+
+    List<String> getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier);
 
-}
\ No newline at end of file
+}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReportInstance.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReportInstance.java
index 4f84e0c..4b66fe4 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReportInstance.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReportInstance.java
@@ -19,7 +19,10 @@ package org.apache.dubbo.metadata.report;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.URLBuilder;
 import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.config.MetadataReportConfig;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_DIRECTORY;
@@ -32,33 +35,34 @@ public class MetadataReportInstance {
 
     private static AtomicBoolean init = new AtomicBoolean(false);
 
-    private static MetadataReport metadataReport;
+    private static final List<MetadataReport> metadataReports = new ArrayList<>();
 
-    public static void init(URL metadataReportURL) {
+    public static void init(MetadataReportConfig config) {
         if (init.get()) {
             return;
         }
         MetadataReportFactory metadataReportFactory = ExtensionLoader.getExtensionLoader(MetadataReportFactory.class).getAdaptiveExtension();
-        if (METADATA_REPORT_KEY.equals(metadataReportURL.getProtocol())) {
-            String protocol = metadataReportURL.getParameter(METADATA_REPORT_KEY, DEFAULT_DIRECTORY);
-            metadataReportURL = URLBuilder.from(metadataReportURL)
+        URL url = config.toUrl();
+        if (METADATA_REPORT_KEY.equals(url.getProtocol())) {
+            String protocol = url.getParameter(METADATA_REPORT_KEY, DEFAULT_DIRECTORY);
+            url = URLBuilder.from(url)
                     .setProtocol(protocol)
                     .removeParameter(METADATA_REPORT_KEY)
                     .build();
         }
-        metadataReport = metadataReportFactory.getMetadataReport(metadataReportURL);
+        metadataReports.add(metadataReportFactory.getMetadataReport(url));
         init.set(true);
     }
 
-    public static MetadataReport getMetadataReport() {
-        return getMetadataReport(false);
+    public static List<MetadataReport> getMetadataReports() {
+        return getMetadataReports(false);
     }
 
-    public static MetadataReport getMetadataReport(boolean checked) {
+    public static List<MetadataReport> getMetadataReports(boolean checked) {
         if (checked) {
             checkInit();
         }
-        return metadataReport;
+        return metadataReports;
     }
 
     private static void checkInit() {
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/store/RemoteWritableMetadataService.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/store/RemoteWritableMetadataService.java
deleted file mode 100644
index c2ae3e5..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/store/RemoteWritableMetadataService.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.metadata.store;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.utils.StringUtils;
-import org.apache.dubbo.metadata.URLRevisionResolver;
-import org.apache.dubbo.metadata.WritableMetadataService;
-import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
-import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
-import org.apache.dubbo.metadata.report.MetadataReport;
-import org.apache.dubbo.metadata.report.MetadataReportInstance;
-import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
-
-import java.util.SortedSet;
-
-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.GROUP_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
-import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
-
-/**
- * The {@link WritableMetadataService} implementation stores the metadata of Dubbo services in metadata center when they
- * exported.
- * It is used by server (provider).
- *
- * @since 2.7.5
- */
-public class RemoteWritableMetadataService extends AbstractAbstractWritableMetadataService {
-
-    private final InMemoryWritableMetadataService writableMetadataServiceDelegate;
-
-    private final URLRevisionResolver urlRevisionResolver;
-
-    public RemoteWritableMetadataService() {
-        this.writableMetadataServiceDelegate = (InMemoryWritableMetadataService) WritableMetadataService.getDefaultExtension();
-        urlRevisionResolver = URLRevisionResolver.INSTANCE;
-    }
-
-    public MetadataReport getMetadataReport() {
-        return MetadataReportInstance.getMetadataReport(true);
-    }
-
-    @Override
-    protected void publishConsumerParameters(URL consumerURL) {
-        getMetadataReport().storeConsumerMetadata(new MetadataIdentifier(consumerURL.getServiceInterface(),
-                consumerURL.getParameter(VERSION_KEY), consumerURL.getParameter(GROUP_KEY), CONSUMER_SIDE,
-                consumerURL.getParameter(APPLICATION_KEY)), consumerURL.getParameters());
-    }
-
-    @Override
-    protected void publishProviderServiceDefinition(URL providerURL) {
-        try {
-            String interfaceName = providerURL.getParameter(INTERFACE_KEY);
-            if (StringUtils.isNotEmpty(interfaceName)) {
-                Class interfaceClass = Class.forName(interfaceName);
-                FullServiceDefinition fullServiceDefinition = ServiceDefinitionBuilder.buildFullDefinition(interfaceClass,
-                        providerURL.getParameters());
-                getMetadataReport().storeProviderMetadata(new MetadataIdentifier(providerURL.getServiceInterface(),
-                        providerURL.getParameter(VERSION_KEY), providerURL.getParameter(GROUP_KEY),
-                        PROVIDER_SIDE, providerURL.getParameter(APPLICATION_KEY)), fullServiceDefinition);
-                return;
-            }
-            logger.error("publishProvider interfaceName is empty . url: " + providerURL.toFullString());
-        } catch (ClassNotFoundException e) {
-            //ignore error
-            logger.error("publishProvider getServiceDescriptor error. url: " + providerURL.toFullString(), e);
-        }
-    }
-
-    @Override
-    public boolean exportURL(URL url) {
-        return writableMetadataServiceDelegate.exportURL(url);
-    }
-
-    @Override
-    public boolean unexportURL(URL url) {
-        return writableMetadataServiceDelegate.unexportURL(url);
-    }
-
-    @Override
-    public boolean subscribeURL(URL url) {
-        return writableMetadataServiceDelegate.subscribeURL(url);
-    }
-
-    @Override
-    public boolean unsubscribeURL(URL url) {
-        return writableMetadataServiceDelegate.unsubscribeURL(url);
-    }
-
-    @Override
-    public SortedSet<String> getExportedURLs(String serviceInterface, String group, String version, String protocol) {
-        return writableMetadataServiceDelegate.getExportedURLs(serviceInterface, group, version, protocol);
-    }
-
-    @Override
-    public String getServiceDefinition(String serviceKey) {
-        return writableMetadataServiceDelegate.getServiceDefinition(serviceKey);
-    }
-
-    @Override
-    public SortedSet<String> getSubscribedURLs() {
-        return writableMetadataServiceDelegate.getSubscribedURLs();
-    }
-
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.WritableMetadataService b/dubbo-metadata/dubbo-metadata-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.WritableMetadataService
deleted file mode 100644
index 26bc942..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.WritableMetadataService
+++ /dev/null
@@ -1,2 +0,0 @@
-local=org.apache.dubbo.metadata.store.InMemoryWritableMetadataService
-remote=org.apache.dubbo.metadata.store.RemoteWritableMetadataService
\ No newline at end of file
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/InMemoryWritableMetadataServiceTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/InMemoryWritableMetadataServiceTest.java
deleted file mode 100644
index 187f126..0000000
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/InMemoryWritableMetadataServiceTest.java
+++ /dev/null
@@ -1,157 +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;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.config.ApplicationConfig;
-import org.apache.dubbo.metadata.store.InMemoryWritableMetadataService;
-import org.apache.dubbo.rpc.model.ApplicationModel;
-
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.unmodifiableSortedSet;
-import static org.apache.dubbo.common.URL.valueOf;
-import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.VERSION_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 InMemoryWritableMetadataService} Test
- *
- * @since 2.7.5
- */
-public class InMemoryWritableMetadataServiceTest {
-
-    private WritableMetadataService metadataService = new InMemoryWritableMetadataService();
-
-    private static final String TEST_SERVICE = "org.apache.dubbo.test.TestService";
-
-    private static final URL BASE_URL = valueOf("dubbo://127.0.0.1:20880/" + TEST_SERVICE);
-    private static final URL REST_BASE_URL = valueOf("rest://127.0.0.1:20880/" + TEST_SERVICE);
-    private static final URL BASE_URL_GROUP = BASE_URL.addParameter(GROUP_KEY, "test");
-    private static final URL BASE_URL_GROUP_AND_VERSION = BASE_URL_GROUP.addParameter(VERSION_KEY, "1.0.0");
-    private static final URL BASE_URL_GROUP_AND_VERSION_AND_PROTOCOL = BASE_URL_GROUP_AND_VERSION.addParameter(PROTOCOL_KEY, "rest");
-
-    @BeforeEach
-    public void init() {
-        ApplicationConfig applicationConfig = new ApplicationConfig("test");
-        ApplicationModel.getConfigManager().setApplication(applicationConfig);
-    }
-
-    @AfterEach
-    public void reset() {
-        ApplicationModel.reset();
-    }
-
-    @Test
-    public void testServiceName() {
-        assertEquals("test", metadataService.serviceName());
-    }
-
-    @Test
-    public void testVersion() {
-        assertEquals("1.0.0", MetadataService.VERSION);
-        assertEquals("1.0.0", metadataService.version());
-    }
-
-    @Test
-    public void testGetExportedURLs() {
-
-        assertTrue(metadataService.exportURL(BASE_URL));
-        Set<String> exportedURLs = metadataService.getExportedURLs(TEST_SERVICE);
-        assertEquals(1, exportedURLs.size());
-        assertEquals(asSortedSet(BASE_URL.toFullString()), exportedURLs);
-        assertTrue(metadataService.unexportURL(BASE_URL));
-
-        assertTrue(metadataService.exportURL(BASE_URL));
-        assertFalse(metadataService.exportURL(BASE_URL));
-
-        assertTrue(metadataService.exportURL(BASE_URL_GROUP));
-        assertTrue(metadataService.exportURL(BASE_URL_GROUP_AND_VERSION));
-
-        exportedURLs = metadataService.getExportedURLs(TEST_SERVICE);
-        assertEquals(asSortedSet(BASE_URL.toFullString()), exportedURLs);
-        assertEquals(asSortedSet(
-                BASE_URL.toFullString(),
-                BASE_URL_GROUP.toFullString(),
-                BASE_URL_GROUP_AND_VERSION.toFullString()), metadataService.getExportedURLs());
-
-        assertTrue(metadataService.exportURL(REST_BASE_URL));
-        exportedURLs = metadataService.getExportedURLs(TEST_SERVICE);
-        assertEquals(asSortedSet(BASE_URL.toFullString(), REST_BASE_URL.toFullString()), exportedURLs);
-
-        metadataService.exportURL(BASE_URL_GROUP_AND_VERSION_AND_PROTOCOL);
-
-        exportedURLs = metadataService.getExportedURLs(TEST_SERVICE, "test", "1.0.0", "rest");
-
-        assertEquals(asSortedSet(BASE_URL_GROUP_AND_VERSION_AND_PROTOCOL.toFullString()), exportedURLs);
-    }
-
-    @Test
-    public void testGetSubscribedURLs() {
-        assertTrue(metadataService.subscribeURL(BASE_URL));
-        assertFalse(metadataService.subscribeURL(BASE_URL));
-
-        assertTrue(metadataService.subscribeURL(BASE_URL_GROUP));
-        assertTrue(metadataService.subscribeURL(BASE_URL_GROUP_AND_VERSION));
-        assertTrue(metadataService.subscribeURL(REST_BASE_URL));
-
-        Set<String> subscribedURLs = metadataService.getSubscribedURLs();
-        assertEquals(4, subscribedURLs.size());
-        assertEquals(asSortedSet(
-                BASE_URL.toFullString(),
-                REST_BASE_URL.toFullString(),
-                BASE_URL_GROUP.toFullString(),
-                BASE_URL_GROUP_AND_VERSION.toFullString()), subscribedURLs);
-
-        assertTrue(metadataService.unsubscribeURL(REST_BASE_URL));
-        subscribedURLs = metadataService.getSubscribedURLs();
-        assertEquals(3, subscribedURLs.size());
-        assertEquals(asSortedSet(
-                BASE_URL.toFullString(),
-                BASE_URL_GROUP.toFullString(),
-                BASE_URL_GROUP_AND_VERSION.toFullString()), subscribedURLs);
-
-        assertTrue(metadataService.unsubscribeURL(BASE_URL_GROUP));
-        subscribedURLs = metadataService.getSubscribedURLs();
-        assertEquals(2, subscribedURLs.size());
-        assertEquals(asSortedSet(
-                BASE_URL.toFullString(),
-                BASE_URL_GROUP_AND_VERSION.toFullString()), subscribedURLs);
-
-        assertTrue(metadataService.unsubscribeURL(BASE_URL_GROUP_AND_VERSION));
-        subscribedURLs = metadataService.getSubscribedURLs();
-        assertEquals(1, subscribedURLs.size());
-        assertEquals(asSortedSet(
-                BASE_URL.toFullString()), subscribedURLs);
-    }
-
-    private static <T extends Comparable<T>> SortedSet<T> asSortedSet(T... values) {
-        return unmodifiableSortedSet(new TreeSet<>(asList(values)));
-    }
-
-}
\ No newline at end of file
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/store/RemoteWritableMetadataServiceDelegateTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/store/RemoteWritableMetadataServiceDelegateTest.java
new file mode 100644
index 0000000..7c95e31
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/store/RemoteWritableMetadataServiceDelegateTest.java
@@ -0,0 +1,216 @@
+/*
+ * 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.
+ */
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.metadata.store;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.metadata.WritableMetadataService;
+import org.apache.dubbo.metadata.report.MetadataReportInstance;
+import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum;
+import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier;
+import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
+import org.apache.dubbo.metadata.test.JTestMetadataReport4Test;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.SortedSet;
+
+
+/**
+ * 2019-08-27
+ */
+public class RemoteWritableMetadataServiceDelegateTest {
+    static URL metadataURL = URL.valueOf("JTest://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.Tes33tService?version=1.0.0&application=vic");
+
+    RemoteWritableMetadataServiceDelegate metadataReportService;
+
+    String interfaceName = "org.apache.dubbo.metadata.store.InterfaceNameTestService80", version = "0.6.9", group = null;
+    URL url = URL.valueOf("dubbo://" + NetUtils.getLocalAddress().getHostName() + ":4444/?interface=" + interfaceName + "&version="
+            + version + "&application=vicpubprovder&side=provider");
+
+    @BeforeAll
+    public static void beforeAll() {
+        MetadataReportInstance.init(metadataURL);
+    }
+
+    @BeforeEach
+    public void before() {
+        metadataReportService = new RemoteWritableMetadataServiceDelegate();
+    }
+
+
+    @Test
+    public void testInstance() {
+        WritableMetadataService metadataReportService1 = WritableMetadataService.getExtension("remote");
+        WritableMetadataService metadataReportService2 = WritableMetadataService.getExtension("remote");
+        Assertions.assertSame(metadataReportService1, metadataReportService2);
+        Assertions.assertTrue(metadataReportService1 instanceof RemoteWritableMetadataServiceDelegate);
+    }
+
+    @Test
+    public void testPublishServiceDefinition() throws InterruptedException {
+        String interfaceName = "org.apache.dubbo.metadata.store.InterfaceNameTestService2", version = "0.9.9", group = null;
+        URL tmpUrl = URL.valueOf("dubbo://" + NetUtils.getLocalAddress().getHostName() + ":4444/?interface=" + interfaceName + "&version="
+                + version + "&application=vicpubprovder&side=provider");
+        metadataReportService.publishServiceDefinition(tmpUrl);
+        Thread.sleep(150);
+        String v = metadataReportService.getServiceDefinition(interfaceName, version, group);
+        Assertions.assertNotNull(v);
+    }
+
+    @Test
+    public void testExportURL() throws InterruptedException {
+        URL url = URL.valueOf("dubbo://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.Test567Service?version=1.0.44&application=vicpubprovder&side=provider");
+        metadataReportService.exportURL(url);
+        Thread.sleep(100);
+        Assertions.assertTrue(getInMemoryWriableMetadataService().exportedServiceURLs.size() == 1);
+        Assertions.assertEquals(getInMemoryWriableMetadataService().exportedServiceURLs.get(url.getServiceKey()).first(), url);
+    }
+
+    @Test
+    public void testSubscribeURL() throws InterruptedException {
+        URL url = URL.valueOf("subscriber://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.Test0678Service?version=1.3.144&application=vicpubprovder&side=provider");
+        int origSize = getInMemoryWriableMetadataService().subscribedServiceURLs.size();
+        metadataReportService.subscribeURL(url);
+        Thread.sleep(100);
+        int size = getInMemoryWriableMetadataService().subscribedServiceURLs.size();
+        Assertions.assertTrue(size - origSize == 1);
+        Assertions.assertEquals(getInMemoryWriableMetadataService().subscribedServiceURLs.get(url.getServiceKey()).first(), url);
+    }
+
+    @Test
+    public void testUnExportURL() throws InterruptedException {
+        URL url = URL.valueOf("dubbo://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.Test0567Service?version=1.2.44&application=vicpubprovder&side=provider");
+        int origSize = getInMemoryWriableMetadataService().exportedServiceURLs.size();
+        metadataReportService.exportURL(url);
+        Thread.sleep(100);
+        int size = getInMemoryWriableMetadataService().exportedServiceURLs.size();
+        Assertions.assertTrue(size - origSize == 1);
+        Assertions.assertEquals(getInMemoryWriableMetadataService().exportedServiceURLs.get(url.getServiceKey()).first(), url);
+
+        metadataReportService.unexportURL(url);
+        int unexportSize = getInMemoryWriableMetadataService().exportedServiceURLs.size();
+        Assertions.assertTrue(size - unexportSize == 1);
+    }
+
+    @Test
+    public void testUnSubscribeURL() throws InterruptedException {
+        URL url = URL.valueOf("subscriber://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.Test0678Service?version=1.5.477&application=vicpubprovder&side=provider");
+        int origSize = getInMemoryWriableMetadataService().subscribedServiceURLs.size();
+        metadataReportService.subscribeURL(url);
+        Thread.sleep(100);
+        int size = getInMemoryWriableMetadataService().subscribedServiceURLs.size();
+        Assertions.assertTrue(size - origSize == 1);
+        Assertions.assertEquals(getInMemoryWriableMetadataService().subscribedServiceURLs.get(url.getServiceKey()).first(), url);
+
+        metadataReportService.unsubscribeURL(url);
+        Thread.sleep(100);
+        Assertions.assertTrue(getInMemoryWriableMetadataService().subscribedServiceURLs.size() == 0);
+    }
+
+    @Test
+    public void testRefreshMetadataService() throws InterruptedException {
+        URL publishUrl = URL.valueOf("dubbo://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.TestRefreshMetadataService?version=1.6.8&application=vicpubprovder&side=provider");
+        URL publishUrl2 = URL.valueOf("dubbo://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.TestRefreshMetadata2Service?version=1.6.5&application=vicpubprovder&side=provider");
+        metadataReportService.exportURL(publishUrl);
+        metadataReportService.exportURL(publishUrl2);
+        String exportedRevision = "9999";
+        JTestMetadataReport4Test jTestMetadataReport4Test = (JTestMetadataReport4Test) MetadataReportInstance.getMetadataReports(true);
+        int origSize = jTestMetadataReport4Test.store.size();
+        int num = countNum();
+        Assertions.assertTrue(metadataReportService.refreshMetadata(exportedRevision, "1109"));
+        Thread.sleep(200);
+        int size = jTestMetadataReport4Test.store.size();
+        Assertions.assertTrue(size - origSize == num);
+        Assertions.assertEquals(jTestMetadataReport4Test.store.get(getServiceMetadataIdentifier(publishUrl, exportedRevision).getUniqueKey(KeyTypeEnum.UNIQUE_KEY)), publishUrl.toFullString());
+        Assertions.assertEquals(jTestMetadataReport4Test.store.get(getServiceMetadataIdentifier(publishUrl2, exportedRevision).getUniqueKey(KeyTypeEnum.UNIQUE_KEY)), publishUrl2.toFullString());
+    }
+
+
+    // unstable test
+//    @Test
+//    public void testRefreshMetadataSubscription() throws InterruptedException {
+//        URL subscriberUrl1 = URL.valueOf("subscriber://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.TestRefreshMetadata00Service?version=2.0.8&application=vicpubprovder&side=provider");
+//        URL subscriberUrl2 = URL.valueOf("subscriber://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.TestRefreshMetadata09Service?version=2.0.5&application=vicpubprovder&side=provider");
+//        metadataReportService.subscribeURL(subscriberUrl1);
+//        metadataReportService.subscribeURL(subscriberUrl2);
+//        String exportedRevision = "9999";
+//        String subscriberRevision = "2099";
+//        String applicationName = "wriableMetadataService";
+//        JTestMetadataReport4Test jTestMetadataReport4Test = (JTestMetadataReport4Test) MetadataReportInstance.getMetadataReport(true);
+//        int origSize = jTestMetadataReport4Test.store.size();
+//        ApplicationModel.setApplication(applicationName);
+//        Assertions.assertTrue(metadataReportService.refreshMetadata(exportedRevision, subscriberRevision));
+//        Thread.sleep(200);
+//        int size = jTestMetadataReport4Test.store.size();
+//        Assertions.assertTrue(size - origSize == 1);
+//        String r = jTestMetadataReport4Test.store.get(getSubscriberMetadataIdentifier(
+//                subscriberRevision).getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
+//        Assertions.assertNotNull(r);
+//    }
+
+
+    private ServiceMetadataIdentifier getServiceMetadataIdentifier(URL publishUrl, String exportedRevision) {
+        ServiceMetadataIdentifier serviceMetadataIdentifier = new ServiceMetadataIdentifier(publishUrl);
+        serviceMetadataIdentifier.setRevision(exportedRevision);
+        serviceMetadataIdentifier.setProtocol(publishUrl.getProtocol());
+        return serviceMetadataIdentifier;
+    }
+
+    private SubscriberMetadataIdentifier getSubscriberMetadataIdentifier(String subscriberRevision) {
+        SubscriberMetadataIdentifier subscriberMetadataIdentifier = new SubscriberMetadataIdentifier();
+        subscriberMetadataIdentifier.setRevision(subscriberRevision);
+        subscriberMetadataIdentifier.setApplication(ApplicationModel.getApplication());
+        return subscriberMetadataIdentifier;
+    }
+
+    private InMemoryWritableMetadataService getInMemoryWriableMetadataService() {
+        return (InMemoryWritableMetadataService) metadataReportService.defaultWritableMetadataService;
+    }
+
+    private int countNum() {
+        int num = 0;
+        for (SortedSet<URL> tmp : getInMemoryWriableMetadataService().exportedServiceURLs.values()) {
+            num += tmp.size();
+        }
+        if (!getInMemoryWriableMetadataService().subscribedServiceURLs.values().isEmpty()) {
+            num++;
+        }
+        return num;
+    }
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java
index 6232850..2607054 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java
@@ -29,6 +29,7 @@ import org.apache.dubbo.metadata.WritableMetadataService;
 import org.apache.dubbo.registry.NotifyListener;
 import org.apache.dubbo.registry.Registry;
 import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
+import org.apache.dubbo.registry.client.metadata.MetadataUtils;
 import org.apache.dubbo.registry.client.metadata.SubscribedURLsSynthesizer;
 import org.apache.dubbo.registry.support.FailbackRegistry;
 
@@ -64,7 +65,6 @@ import static org.apache.dubbo.common.function.ThrowableAction.execute;
 import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty;
 import static org.apache.dubbo.common.utils.StringUtils.isBlank;
 import static org.apache.dubbo.registry.client.ServiceDiscoveryFactory.getExtension;
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getMetadataStorageType;
 
 /**
  * Being different to the traditional registry, {@link ServiceDiscoveryRegistry} that is a new service-oriented
@@ -132,8 +132,7 @@ public class ServiceDiscoveryRegistry extends FailbackRegistry {
         this.serviceDiscovery = createServiceDiscovery(registryURL);
         this.subscribedServices = parseServices(registryURL.getParameter(SUBSCRIBED_SERVICE_NAMES_KEY));
         this.serviceNameMapping = ServiceNameMapping.getDefaultExtension();
-        String metadataStorageType = getMetadataStorageType(registryURL);
-        this.writableMetadataService = WritableMetadataService.getExtension(metadataStorageType);
+        this.writableMetadataService = MetadataUtils.getLocalMetadataService();
     }
 
     public ServiceDiscovery getServiceDiscovery() {
@@ -299,7 +298,7 @@ public class ServiceDiscoveryRegistry extends FailbackRegistry {
     protected void subscribeURLs(URL url, NotifyListener listener, String serviceName) {
         // register ServiceInstancesChangedListener
         ServiceInstancesChangedListener serviceListener = serviceListeners.computeIfAbsent(serviceName,
-                k -> new ServiceInstancesChangedListener(serviceName) {
+                k -> new ServiceInstancesChangedListener(serviceName, serviceDiscovery, url) {
                     @Override
                     protected void notifyAddresses() {
                         listener.notifyServiceInstances();
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstanceCustomizer.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstanceCustomizer.java
index 10674fc..11f2c28 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstanceCustomizer.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstanceCustomizer.java
@@ -19,13 +19,10 @@ package org.apache.dubbo.registry.client;
 import org.apache.dubbo.common.extension.SPI;
 import org.apache.dubbo.common.lang.Prioritized;
 import org.apache.dubbo.registry.client.event.ServiceInstancePreRegisteredEvent;
-import org.apache.dubbo.registry.client.event.listener.CustomizableServiceInstanceListener;
 
 /**
  * The interface to customize {@link ServiceInstance the service instance} on {@link ServiceInstancePreRegisteredEvent}
  *
- * @see CustomizableServiceInstanceListener
- * @see ServiceInstancePreRegisteredEvent
  * @see ServiceInstance#getMetadata()
  * @since 2.7.5
  */
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/CustomizableServiceInstanceListener.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/CustomizableServiceInstanceListener.java
deleted file mode 100644
index f739ca8..0000000
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/CustomizableServiceInstanceListener.java
+++ /dev/null
@@ -1,44 +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.registry.client.event.listener;
-
-import org.apache.dubbo.common.extension.ExtensionLoader;
-import org.apache.dubbo.event.EventListener;
-import org.apache.dubbo.registry.client.ServiceInstance;
-import org.apache.dubbo.registry.client.ServiceInstanceCustomizer;
-import org.apache.dubbo.registry.client.event.ServiceInstancePreRegisteredEvent;
-
-/**
- * Customize the {@link ServiceInstance} before registering to Registry.
- *
- * @since 2.7.5
- * @deprecated 2.7.8 Current class will be removed since 3.0.0
- */
-@Deprecated
-public class CustomizableServiceInstanceListener implements EventListener<ServiceInstancePreRegisteredEvent> {
-
-    @Override
-    public void onEvent(ServiceInstancePreRegisteredEvent event) {
-        ExtensionLoader<ServiceInstanceCustomizer> loader =
-                ExtensionLoader.getExtensionLoader(ServiceInstanceCustomizer.class);
-        // FIXME, sort customizer before apply
-        loader.getSupportedExtensionInstances().forEach(customizer -> {
-            // customizes
-            customizer.customize(event.getServiceInstance());
-        });
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListener.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListener.java
index 33f0bcc..5f40b2a 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListener.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListener.java
@@ -16,17 +16,19 @@
  */
 package org.apache.dubbo.registry.client.event.listener;
 
+import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.event.ConditionalEventListener;
 import org.apache.dubbo.event.EventListener;
 import org.apache.dubbo.metadata.MetadataInfo;
 import org.apache.dubbo.metadata.MetadataInfo.ServiceInfo;
 import org.apache.dubbo.metadata.MetadataService;
-import org.apache.dubbo.metadata.MetadataUtils;
-import org.apache.dubbo.metadata.store.RemoteMetadataServiceImpl;
+import org.apache.dubbo.registry.client.ServiceDiscovery;
 import org.apache.dubbo.registry.client.ServiceInstance;
 import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
+import org.apache.dubbo.registry.client.metadata.MetadataUtils;
 import org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils;
+import org.apache.dubbo.registry.client.metadata.store.RemoteMetadataServiceImpl;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -50,6 +52,8 @@ import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataU
 public abstract class ServiceInstancesChangedListener implements ConditionalEventListener<ServiceInstancesChangedEvent> {
 
     private final String serviceName;
+    private final ServiceDiscovery serviceDiscovery;
+    private final URL url;
 
     private List<ServiceInstance> instances;
 
@@ -61,8 +65,10 @@ public abstract class ServiceInstancesChangedListener implements ConditionalEven
 
     private Map<String, List<ServiceInstance>> serviceToInstances;
 
-    protected ServiceInstancesChangedListener(String serviceName) {
+    protected ServiceInstancesChangedListener(String serviceName, ServiceDiscovery serviceDiscovery, URL url) {
         this.serviceName = serviceName;
+        this.serviceDiscovery = serviceDiscovery;
+        this.url = url;
     }
 
     /**
@@ -131,16 +137,15 @@ public abstract class ServiceInstancesChangedListener implements ConditionalEven
 
     private MetadataInfo getMetadataInfo(ServiceInstance instance) {
         String metadataType = ServiceInstanceMetadataUtils.getMetadataStorageType(instance);
-        String cluster = ServiceInstanceMetadataUtils.getRemoteCluster(instance);
 
         MetadataInfo metadataInfo;
         try {
             if (REMOTE_METADATA_STORAGE_TYPE.equals(metadataType)) {
-                RemoteMetadataServiceImpl remoteMetadataService = MetadataUtils.getConsumerRemoteMetadataService(consumerUrl.getServiceKey());
-                metadataInfo = remoteMetadataService.getMetadata(serviceName, revision);
+                RemoteMetadataServiceImpl remoteMetadataService = MetadataUtils.getRemoteMetadataService();
+                metadataInfo = remoteMetadataService.getMetadata(instance);
             } else {
-                MetadataService localMetadataService = MetadataUtils.getLocalMetadataService();
-                metadataInfo = localMetadataService.getMetadataInfo();
+                MetadataService metadataServiceProxy = MetadataUtils.getMetadataServiceProxy(instance);
+                metadataInfo = metadataServiceProxy.getMetadataInfo();
             }
         } catch (Exception e) {
             // TODO, load metadata backup
@@ -149,7 +154,6 @@ public abstract class ServiceInstancesChangedListener implements ConditionalEven
         return metadataInfo;
     }
 
-
     protected abstract void notifyAddresses();
 
     /**
@@ -161,6 +165,10 @@ public abstract class ServiceInstancesChangedListener implements ConditionalEven
         return serviceName;
     }
 
+    public URL getUrl() {
+        return url;
+    }
+
     /**
      * @param event {@link ServiceInstancesChangedEvent event}
      * @return If service name matches, return <code>true</code>, or <code>false</code>
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ExportedServicesRevisionMetadataCustomizer.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ExportedServicesRevisionMetadataCustomizer.java
deleted file mode 100644
index 951f957..0000000
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ExportedServicesRevisionMetadataCustomizer.java
+++ /dev/null
@@ -1,57 +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.registry.client.metadata;
-
-import org.apache.dubbo.metadata.URLRevisionResolver;
-import org.apache.dubbo.metadata.WritableMetadataService;
-import org.apache.dubbo.registry.client.ServiceInstance;
-import org.apache.dubbo.registry.client.ServiceInstanceMetadataCustomizer;
-
-import java.util.SortedSet;
-
-import static org.apache.dubbo.metadata.WritableMetadataService.getExtension;
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.EXPORTED_SERVICES_REVISION_PROPERTY_NAME;
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getMetadataStorageType;
-
-/**
- * The customizer to a add the metadata that the reversion of Dubbo exported services calculates.
- * <p>
- * The reversion is calculated on the methods that all Dubbo exported interfaces declare
- *
- * @since 2.7.5
- */
-public class ExportedServicesRevisionMetadataCustomizer extends ServiceInstanceMetadataCustomizer {
-
-    @Override
-    protected String resolveMetadataPropertyName(ServiceInstance serviceInstance) {
-        return EXPORTED_SERVICES_REVISION_PROPERTY_NAME;
-    }
-
-    @Override
-    protected String resolveMetadataPropertyValue(ServiceInstance serviceInstance) {
-
-        String metadataStorageType = getMetadataStorageType(serviceInstance);
-
-        WritableMetadataService writableMetadataService = getExtension(metadataStorageType);
-
-        SortedSet<String> exportedURLs = writableMetadataService.getExportedURLs();
-
-        URLRevisionResolver resolver = new URLRevisionResolver();
-
-        return resolver.resolve(exportedURLs);
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataUtils.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataUtils.java
new file mode 100644
index 0000000..0926f7d
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataUtils.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.client.metadata;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.metadata.MetadataService;
+import org.apache.dubbo.metadata.WritableMetadataService;
+import org.apache.dubbo.registry.client.ServiceInstance;
+import org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService;
+import org.apache.dubbo.registry.client.metadata.store.RemoteMetadataServiceImpl;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.Protocol;
+import org.apache.dubbo.rpc.ProxyFactory;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URLS_PROPERTY_NAME;
+
+public class MetadataUtils {
+
+    private static final Object LOCK = new Object();
+    private static final Object REMOTE_LOCK = new Object();
+
+    public static ConcurrentMap<String, MetadataService> metadataServiceProxies = new ConcurrentHashMap<>();
+
+    private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
+
+    private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
+
+    public static RemoteMetadataServiceImpl remoteMetadataService;
+
+    public static WritableMetadataService localMetadataService;
+
+    public static RemoteMetadataServiceImpl getRemoteMetadataService() {
+        if (remoteMetadataService == null) {
+            synchronized (REMOTE_LOCK) {
+                if (remoteMetadataService == null) {
+                    remoteMetadataService = new RemoteMetadataServiceImpl(getLocalMetadataService());
+                }
+            }
+        }
+        return remoteMetadataService;
+    }
+
+    public static WritableMetadataService getLocalMetadataService() {
+        if (localMetadataService == null) {
+            synchronized (LOCK) {
+                if (localMetadataService == null) {
+                    localMetadataService = new InMemoryWritableMetadataService();
+                }
+            }
+        }
+        return localMetadataService;
+    }
+
+    public static void publishServiceDefinition(URL url) {
+        // store in local
+        getLocalMetadataService().publishServiceDefinition(url);
+        // send to remote
+        getRemoteMetadataService().publishServiceDefinition(url);
+    }
+
+    public static MetadataService getMetadataServiceProxy(ServiceInstance instance) {
+        String key = instance.getServiceName() + "##" +
+                ServiceInstanceMetadataUtils.getExportedServicesRevision(instance);
+        return metadataServiceProxies.computeIfAbsent(key, k -> {
+            MetadataServiceURLBuilder builder = null;
+            ExtensionLoader<MetadataServiceURLBuilder> loader
+                    = ExtensionLoader.getExtensionLoader(MetadataServiceURLBuilder.class);
+
+            Map<String, String> metadata = instance.getMetadata();
+            // METADATA_SERVICE_URLS_PROPERTY_NAME is a unique key exists only on instances of spring-cloud-alibaba.
+            String dubboURLsJSON = metadata.get(METADATA_SERVICE_URLS_PROPERTY_NAME);
+            if (StringUtils.isNotEmpty(dubboURLsJSON)) {
+                builder = loader.getExtension(SpringCloudMetadataServiceURLBuilder.NAME);
+            } else {
+                builder = loader.getExtension(StandardMetadataServiceURLBuilder.NAME);
+            }
+
+            List<URL> urls = builder.build(instance);
+            if (CollectionUtils.isEmpty(urls)) {
+                throw new IllegalStateException("You have enabled introspection service discovery mode for instance "
+                        + instance + ", but no metadata service can build from it.");
+            }
+
+            // Simply rely on the first metadata url, as stated in MetadataServiceURLBuilder.
+            Invoker<MetadataService> invoker = protocol.refer(MetadataService.class, urls.get(0));
+
+            return proxyFactory.getProxy(invoker);
+        });
+    }
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java
index 14c7988..e55f6f2 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java
@@ -85,6 +85,8 @@ public class ServiceInstanceMetadataUtils {
      */
     public static String METADATA_STORAGE_TYPE_PROPERTY_NAME = "dubbo.metadata.storage-type";
 
+    public static String METADATA_CLUSTER_PROPERTY_NAME = "dubbo.metadata.cluster";
+
     /**
      * Get the multiple {@link URL urls'} parameters of {@link MetadataService MetadataService's} Metadata
      *
@@ -195,6 +197,11 @@ public class ServiceInstanceMetadataUtils {
         metadata.put(METADATA_STORAGE_TYPE_PROPERTY_NAME, metadataType);
     }
 
+    public static String getRemoteCluster(ServiceInstance serviceInstance) {
+        Map<String, String> metadata = serviceInstance.getMetadata();
+        return metadata.get(METADATA_CLUSTER_PROPERTY_NAME);
+    }
+
     /**
      * Is Dubbo Service instance or not
      *
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/SubscribedServicesRevisionMetadataCustomizer.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/SubscribedServicesRevisionMetadataCustomizer.java
deleted file mode 100644
index c9e6b04..0000000
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/SubscribedServicesRevisionMetadataCustomizer.java
+++ /dev/null
@@ -1,58 +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.registry.client.metadata;
-
-import org.apache.dubbo.metadata.URLRevisionResolver;
-import org.apache.dubbo.metadata.WritableMetadataService;
-import org.apache.dubbo.registry.client.ServiceInstance;
-import org.apache.dubbo.registry.client.ServiceInstanceMetadataCustomizer;
-
-import java.util.SortedSet;
-
-import static org.apache.dubbo.metadata.WritableMetadataService.getExtension;
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.SUBSCRIBER_SERVICES_REVISION_PROPERTY_NAME;
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getMetadataStorageType;
-
-/**
- * The customizer to a add the metadata that the reversion of Dubbo subscribed services calculates.
- * <p>
- * The reversion is calculated on the methods that all Dubbo subscribed interfaces declare
- *
- * @since 2.7.5
- */
-public class SubscribedServicesRevisionMetadataCustomizer extends ServiceInstanceMetadataCustomizer {
-
-    @Override
-    protected String resolveMetadataPropertyName(ServiceInstance serviceInstance) {
-        return SUBSCRIBER_SERVICES_REVISION_PROPERTY_NAME;
-    }
-
-    @Override
-    protected String resolveMetadataPropertyValue(ServiceInstance serviceInstance) {
-
-        String metadataStorageType = getMetadataStorageType(serviceInstance);
-
-        WritableMetadataService writableMetadataService = getExtension(metadataStorageType);
-
-        SortedSet<String> subscribedURLs = writableMetadataService.getSubscribedURLs();
-
-        URLRevisionResolver resolver = new URLRevisionResolver();
-
-        return resolver.resolve(subscribedURLs);
-    }
-
-}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactory.java
deleted file mode 100644
index d599891..0000000
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactory.java
+++ /dev/null
@@ -1,58 +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.registry.client.metadata.proxy;
-
-import org.apache.dubbo.metadata.MetadataService;
-import org.apache.dubbo.registry.client.ServiceInstance;
-
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getExportedServicesRevision;
-
-/**
- * base class for remote and local implementations.
- *
- * @since 2.7.5
- */
-abstract class BaseMetadataServiceProxyFactory implements MetadataServiceProxyFactory {
-
-    private final ConcurrentMap<String, MetadataService> proxiesCache = new ConcurrentHashMap<>();
-
-    public final MetadataService getProxy(ServiceInstance serviceInstance) {
-        return proxiesCache.computeIfAbsent(createProxyCacheKey(serviceInstance), id -> createProxy(serviceInstance));
-    }
-
-    /**
-     * Create the cache key of the proxy of {@link MetadataService}
-     *
-     * @param serviceInstance {@link ServiceInstance}
-     * @return non-null
-     * @since 2.7.8
-     */
-    protected String createProxyCacheKey(ServiceInstance serviceInstance) {
-        return serviceInstance.getServiceName() + "#" + getExportedServicesRevision(serviceInstance);
-    }
-
-    /**
-     * Create the instance proxy of {@link MetadataService}
-     *
-     * @param serviceInstance {@link ServiceInstance}
-     * @return non-null
-     */
-    protected abstract MetadataService createProxy(ServiceInstance serviceInstance);
-}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/DefaultMetadataServiceProxyFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/DefaultMetadataServiceProxyFactory.java
deleted file mode 100644
index 90b3719..0000000
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/DefaultMetadataServiceProxyFactory.java
+++ /dev/null
@@ -1,85 +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.registry.client.metadata.proxy;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.extension.ExtensionLoader;
-import org.apache.dubbo.common.utils.CollectionUtils;
-import org.apache.dubbo.common.utils.StringUtils;
-import org.apache.dubbo.metadata.MetadataService;
-import org.apache.dubbo.registry.client.ServiceInstance;
-import org.apache.dubbo.registry.client.metadata.MetadataServiceURLBuilder;
-import org.apache.dubbo.registry.client.metadata.SpringCloudMetadataServiceURLBuilder;
-import org.apache.dubbo.registry.client.metadata.StandardMetadataServiceURLBuilder;
-import org.apache.dubbo.rpc.Invoker;
-import org.apache.dubbo.rpc.Protocol;
-import org.apache.dubbo.rpc.ProxyFactory;
-
-import java.util.List;
-import java.util.Map;
-
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URLS_PROPERTY_NAME;
-
-/**
- * Works on Consumer side, useful when using local metadata mode.
- *
- * Use this implementation to generate the proxy on Consumer side representing the remote MetadataService
- * exposed on the Provider side. Also see {@link RemoteMetadataServiceProxyFactory}
- *
- * @since 2.7.5
- */
-public class DefaultMetadataServiceProxyFactory extends BaseMetadataServiceProxyFactory implements MetadataServiceProxyFactory {
-
-    private ProxyFactory proxyFactory;
-
-    private Protocol protocol;
-
-    public void setProtocol(Protocol protocol) {
-        this.protocol = protocol;
-    }
-
-    public void setProxyFactory(ProxyFactory proxyFactory) {
-        this.proxyFactory = proxyFactory;
-    }
-
-
-    protected MetadataService createProxy(ServiceInstance serviceInstance) {
-        MetadataServiceURLBuilder builder = null;
-        ExtensionLoader<MetadataServiceURLBuilder> loader
-                = ExtensionLoader.getExtensionLoader(MetadataServiceURLBuilder.class);
-
-        Map<String, String> metadata = serviceInstance.getMetadata();
-        // METADATA_SERVICE_URLS_PROPERTY_NAME is a unique key exists only on instances of spring-cloud-alibaba.
-        String dubboURLsJSON = metadata.get(METADATA_SERVICE_URLS_PROPERTY_NAME);
-        if (StringUtils.isNotEmpty(dubboURLsJSON)) {
-            builder = loader.getExtension(SpringCloudMetadataServiceURLBuilder.NAME);
-        } else {
-            builder = loader.getExtension(StandardMetadataServiceURLBuilder.NAME);
-        }
-
-        List<URL> urls = builder.build(serviceInstance);
-        if (CollectionUtils.isEmpty(urls)) {
-            throw new IllegalStateException("You have enabled introspection service discovery mode for instance "
-                    + serviceInstance + ", but no metadata service can build from it.");
-        }
-
-        // Simply rely on the first metadata url, as stated in MetadataServiceURLBuilder.
-        Invoker<MetadataService> invoker = protocol.refer(MetadataService.class, urls.get(0));
-
-        return proxyFactory.getProxy(invoker);
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/MetadataServiceProxyFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/MetadataServiceProxyFactory.java
deleted file mode 100644
index e49003f..0000000
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/MetadataServiceProxyFactory.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.dubbo.registry.client.metadata.proxy;
-
-import org.apache.dubbo.common.extension.SPI;
-import org.apache.dubbo.metadata.MetadataService;
-import org.apache.dubbo.registry.client.ServiceInstance;
-
-import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
-import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader;
-
-/**
- * A factory to create a {@link MetadataService} proxy
- *
- * @see ServiceInstance
- * @see MetadataService
- * @since 2.7.5
- */
-@SPI(DEFAULT_METADATA_STORAGE_TYPE)
-public interface MetadataServiceProxyFactory {
-
-    /**
-     * Create a {@link MetadataService} proxy via the specified {@link ServiceInstance}
-     *WritableMetadataService
-     * @param serviceInstance the instance of {@link ServiceInstance}
-     * @return non-null
-     */
-    MetadataService getProxy(ServiceInstance serviceInstance);
-
-    /**
-     * Get the default extension of {@link MetadataServiceProxyFactory}
-     *
-     * @return non-null
-     */
-    static MetadataServiceProxyFactory getDefaultExtension() {
-        return getExtensionLoader(MetadataServiceProxyFactory.class).getDefaultExtension();
-    }
-
-    static MetadataServiceProxyFactory getExtension(String name) {
-        return getExtensionLoader(MetadataServiceProxyFactory.class).getExtension(name);
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/RemoteMetadataServiceProxy.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/RemoteMetadataServiceProxy.java
deleted file mode 100644
index a92cafb..0000000
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/RemoteMetadataServiceProxy.java
+++ /dev/null
@@ -1,111 +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.registry.client.metadata.proxy;
-
-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.UrlUtils;
-import org.apache.dubbo.metadata.MetadataService;
-import org.apache.dubbo.metadata.report.MetadataReport;
-import org.apache.dubbo.metadata.report.MetadataReportInstance;
-import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
-import org.apache.dubbo.registry.client.ServiceInstance;
-
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-import static java.util.Collections.unmodifiableSortedSet;
-import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
-import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
-import static org.apache.dubbo.metadata.URLRevisionResolver.UNKNOWN_REVISION;
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.EXPORTED_SERVICES_REVISION_PROPERTY_NAME;
-
-/**
- * The proxy of {@link MetadataService} is based on {@link MetadataReport}
- *
- * @since 2.7.5
- */
-public class RemoteMetadataServiceProxy implements MetadataService {
-
-    protected final Logger logger = LoggerFactory.getLogger(getClass());
-
-    private final String serviceName;
-
-    private final String revision;
-
-    public RemoteMetadataServiceProxy(ServiceInstance serviceInstance) {
-        this.serviceName = serviceInstance.getServiceName();
-        // this is ServiceInstance of registry(Provider)
-        this.revision = serviceInstance.getMetadata(EXPORTED_SERVICES_REVISION_PROPERTY_NAME, UNKNOWN_REVISION);
-    }
-
-    @Override
-    public String serviceName() {
-        return serviceName;
-    }
-
-    @Override
-    public SortedSet<String> getExportedURLs(String serviceInterface, String group, String version, String protocol) {
-
-        SortedSet<String> exportedURLs = getMetadataReport().getExportedURLs(serviceName, revision);
-        if (ALL_SERVICE_INTERFACES.equals(serviceInterface)) {
-            return exportedURLs;
-        }
-
-        return unmodifiableSortedSet(
-                exportedURLs
-                        .stream()
-                        .map(URL::valueOf)
-                        .filter(url -> serviceInterface == null || serviceInterface.equals(url.getServiceInterface()))
-                        .filter(url -> group == null || group.equals(url.getParameter(GROUP_KEY)))
-                        .filter(url -> version == null || version.equals(url.getParameter(VERSION_KEY)))
-                        .filter(url -> protocol == null || protocol.equals(url.getProtocol()))
-                        .map(URL::toFullString)
-                        .collect(TreeSet::new, Set::add, Set::addAll)
-        );
-    }
-
-    @Override
-    public String getServiceDefinition(String interfaceName, String version, String group) {
-        return getMetadataReport().getServiceDefinition(new MetadataIdentifier(interfaceName,
-                version, group, PROVIDER_SIDE, serviceName));
-    }
-
-    @Override
-    public String getServiceDefinition(String serviceDefinitionKey) {
-        String[] services = UrlUtils.parseServiceKey(serviceDefinitionKey);
-        String serviceInterface = services[0];
-        // if version or group is not exist
-        String version = null;
-        if (services.length > 1) {
-            version = services[1];
-        }
-        String group = null;
-        if (services.length > 2) {
-            group = services[2];
-        }
-        return getMetadataReport().getServiceDefinition(new MetadataIdentifier(serviceInterface,
-                version, group, PROVIDER_SIDE, serviceName));
-    }
-
-    MetadataReport getMetadataReport() {
-        return MetadataReportInstance.getMetadataReport(true);
-    }
-}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/RemoteMetadataServiceProxyFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/RemoteMetadataServiceProxyFactory.java
deleted file mode 100644
index e7188a8..0000000
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/RemoteMetadataServiceProxyFactory.java
+++ /dev/null
@@ -1,34 +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.registry.client.metadata.proxy;
-
-import org.apache.dubbo.metadata.MetadataService;
-import org.apache.dubbo.registry.client.ServiceInstance;
-
-/**
- * Works on Consumer side, useful when using remote metadata mode. Also see {@link DefaultMetadataServiceProxyFactory}
- *
- * Provider will not expose the embedded {@link MetadataService}, instead, each provider register its metadata info to a
- * remote Metadata Center. So the Consumer side need to connect to the same Metadata Center to get one instance's metadata.
- */
-public class RemoteMetadataServiceProxyFactory extends BaseMetadataServiceProxyFactory implements MetadataServiceProxyFactory {
-
-    @Override
-    public MetadataService createProxy(ServiceInstance serviceInstance) {
-        return new RemoteMetadataServiceProxy(serviceInstance);
-    }
-}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/store/InMemoryWritableMetadataService.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java
similarity index 71%
rename from dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/store/InMemoryWritableMetadataService.java
rename to dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java
index a897d76..b9e7877 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/store/InMemoryWritableMetadataService.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java
@@ -14,13 +14,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.metadata.store;
+package org.apache.dubbo.registry.client.metadata.store;
 
-import org.apache.dubbo.common.BaseServiceMetadata;
 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.StringUtils;
+import org.apache.dubbo.metadata.MetadataInfo;
+import org.apache.dubbo.metadata.MetadataInfo.ServiceInfo;
 import org.apache.dubbo.metadata.MetadataService;
 import org.apache.dubbo.metadata.WritableMetadataService;
+import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
 import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
+import org.apache.dubbo.rpc.support.ProtocolUtils;
+
+import com.google.gson.Gson;
 
 import java.util.Comparator;
 import java.util.Map;
@@ -33,11 +41,12 @@ import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
 import static java.util.Collections.emptySortedSet;
-import static java.util.Collections.unmodifiableSortedMap;
 import static java.util.Collections.unmodifiableSortedSet;
 import static org.apache.dubbo.common.URL.buildKey;
+import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
 import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty;
+import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
 
 /**
  * The {@link WritableMetadataService} implementation stores the metadata of Dubbo services in memory locally when they
@@ -47,7 +56,9 @@ import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty;
  * @see WritableMetadataService
  * @since 2.7.5
  */
-public class InMemoryWritableMetadataService extends AbstractAbstractWritableMetadataService {
+public class InMemoryWritableMetadataService implements WritableMetadataService {
+
+    final Logger logger = LoggerFactory.getLogger(getClass());
 
     private final Lock lock = new ReentrantLock();
 
@@ -57,7 +68,8 @@ public class InMemoryWritableMetadataService extends AbstractAbstractWritableMet
      * All exported {@link URL urls} {@link Map} whose key is the return value of {@link URL#getServiceKey()} method
      * and value is the {@link SortedSet sorted set} of the {@link URL URLs}
      */
-    private final ConcurrentNavigableMap<String, SortedSet<URL>> exportedServiceURLs = new ConcurrentSkipListMap<>();
+    ConcurrentNavigableMap<String, SortedSet<URL>> exportedServiceURLs = new ConcurrentSkipListMap<>();
+    MetadataInfo metadataInfo;
 
     // ==================================================================================== //
 
@@ -68,13 +80,13 @@ public class InMemoryWritableMetadataService extends AbstractAbstractWritableMet
      * whose key is the return value of {@link URL#getServiceKey()} method and value is
      * the {@link SortedSet sorted set} of the {@link URL URLs}
      */
-    private final ConcurrentNavigableMap<String, SortedSet<URL>> subscribedServiceURLs = new ConcurrentSkipListMap<>();
+    ConcurrentNavigableMap<String, SortedSet<URL>> subscribedServiceURLs = new ConcurrentSkipListMap<>();
 
-    /**
-     * The {@link Map} caches the json of {@link ServiceDefinition} with
-     * {@link BaseServiceMetadata#buildServiceKey(String, String, String) the service key}
-     */
-    private final ConcurrentNavigableMap<String, String> serviceDefinitions = new ConcurrentSkipListMap<>();
+    ConcurrentNavigableMap<String, String> serviceDefinitions = new ConcurrentSkipListMap<>();
+
+    public InMemoryWritableMetadataService() {
+        this.metadataInfo = new MetadataInfo();
+    }
 
     @Override
     public SortedSet<String> getSubscribedURLs() {
@@ -107,11 +119,15 @@ public class InMemoryWritableMetadataService extends AbstractAbstractWritableMet
 
     @Override
     public boolean exportURL(URL url) {
+        ServiceInfo serviceInfo = new ServiceInfo(url);
+        metadataInfo.addService(serviceInfo);
         return addURL(exportedServiceURLs, url);
     }
 
     @Override
     public boolean unexportURL(URL url) {
+        ServiceInfo serviceInfo = new ServiceInfo(url);
+        metadataInfo.removeService(serviceInfo);
         return removeURL(exportedServiceURLs, url);
     }
 
@@ -126,25 +142,38 @@ public class InMemoryWritableMetadataService extends AbstractAbstractWritableMet
     }
 
     @Override
-    protected void publishServiceDefinition(String key, String json) {
-        serviceDefinitions.put(key, json);
+    public void publishServiceDefinition(URL providerUrl) {
+        try {
+            String interfaceName = providerUrl.getParameter(INTERFACE_KEY);
+            if (StringUtils.isNotEmpty(interfaceName)
+                    && !ProtocolUtils.isGeneric(providerUrl.getParameter(GENERIC_KEY))) {
+                Class interfaceClass = Class.forName(interfaceName);
+                ServiceDefinition serviceDefinition = ServiceDefinitionBuilder.build(interfaceClass);
+                Gson gson = new Gson();
+                String data = gson.toJson(serviceDefinition);
+                serviceDefinitions.put(providerUrl.getServiceKey(), data);
+                return;
+            }
+            logger.error("publishProvider interfaceName is empty . providerUrl: " + providerUrl.toFullString());
+        } catch (ClassNotFoundException e) {
+            //ignore error
+            logger.error("publishProvider getServiceDescriptor error. providerUrl: " + providerUrl.toFullString(), e);
+        }
     }
 
     @Override
-    public String getServiceDefinition(String serviceDefinitionKey) {
-        return serviceDefinitions.get(serviceDefinitionKey);
-    }
-
-    public Map<String, SortedSet<URL>> getExportedServiceURLs() {
-        return unmodifiableSortedMap(exportedServiceURLs);
+    public String getServiceDefinition(String interfaceName, String version, String group) {
+        return serviceDefinitions.get(URL.buildKey(interfaceName, group, version));
     }
 
-    public Map<String, SortedSet<URL>> getSubscribedServiceURLs() {
-        return unmodifiableSortedMap(subscribedServiceURLs);
+    @Override
+    public String getServiceDefinition(String serviceKey) {
+        return serviceDefinitions.get(serviceKey);
     }
 
-    public Map<String, String> getServiceDefinitions() {
-        return unmodifiableSortedMap(serviceDefinitions);
+    @Override
+    public MetadataInfo getMetadataInfo() {
+        return metadataInfo;
     }
 
     boolean addURL(Map<String, SortedSet<URL>> serviceURLs, URL url) {
@@ -210,6 +239,7 @@ public class InMemoryWritableMetadataService extends AbstractAbstractWritableMet
                 || protocol.equals(url.getProtocol());
     }
 
+
     static class URLComparator implements Comparator<URL> {
 
         public static final URLComparator INSTANCE = new URLComparator();
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImpl.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImpl.java
new file mode 100644
index 0000000..ac0cbc2
--- /dev/null
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImpl.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.registry.client.metadata.store;
+
+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.StringUtils;
+import org.apache.dubbo.metadata.MetadataInfo;
+import org.apache.dubbo.metadata.WritableMetadataService;
+import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
+import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
+import org.apache.dubbo.metadata.report.MetadataReport;
+import org.apache.dubbo.metadata.report.MetadataReportInstance;
+import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
+import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
+import org.apache.dubbo.registry.client.ServiceInstance;
+import org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils;
+import org.apache.dubbo.remoting.Constants;
+import org.apache.dubbo.rpc.RpcException;
+
+import java.util.List;
+
+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.GROUP_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.PID_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.constants.CommonConstants.TIMESTAMP_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
+
+public class RemoteMetadataServiceImpl {
+
+    protected final Logger logger = LoggerFactory.getLogger(getClass());
+    private WritableMetadataService localMetadataService;
+
+    public RemoteMetadataServiceImpl(WritableMetadataService writableMetadataService) {
+        this.localMetadataService = writableMetadataService;
+    }
+
+    public List<MetadataReport> getMetadataReports() {
+        return MetadataReportInstance.getMetadataReports(true);
+    }
+
+    public void publishMetadata(ServiceInstance instance) {
+        SubscriberMetadataIdentifier identifier = new SubscriberMetadataIdentifier(instance.getServiceName(), ServiceInstanceMetadataUtils.getExportedServicesRevision(instance));
+        getMetadataReports().forEach(metadataReport -> {
+            metadataReport.publishAppMetadata(identifier, localMetadataService.getMetadataInfo());
+        });
+    }
+
+    public MetadataInfo getMetadata(ServiceInstance instance) {
+        SubscriberMetadataIdentifier identifier = new SubscriberMetadataIdentifier(instance.getServiceName(), ServiceInstanceMetadataUtils.getExportedServicesRevision(instance));
+        for (MetadataReport reporter : getMetadataReports()) {
+            MetadataInfo metadataInfo = reporter.getAppMetadata(identifier, instance.getMetadata());
+            if (metadataInfo != null) {
+                return metadataInfo;
+            }
+        }
+        return null;
+    }
+
+    public void publishServiceDefinition(URL url) {
+        String side = url.getParameter(SIDE_KEY);
+        if (PROVIDER_SIDE.equalsIgnoreCase(side)) {
+            //TODO, the params part is duplicate with that stored by exportURL(url), can be further optimized in the future.
+            publishProvider(url);
+        } else {
+            //TODO, only useful for ops showing the url parameters, this is duplicate with subscribeURL(url), can be removed in the future.
+            publishConsumer(url);
+        }
+    }
+
+    private void publishProvider(URL providerUrl) throws RpcException {
+        //first add into the list
+        // remove the individual param
+        providerUrl = providerUrl.removeParameters(PID_KEY, TIMESTAMP_KEY, Constants.BIND_IP_KEY,
+                Constants.BIND_PORT_KEY, TIMESTAMP_KEY);
+
+        try {
+            String interfaceName = providerUrl.getParameter(INTERFACE_KEY);
+            if (StringUtils.isNotEmpty(interfaceName)) {
+                Class interfaceClass = Class.forName(interfaceName);
+                FullServiceDefinition fullServiceDefinition = ServiceDefinitionBuilder.buildFullDefinition(interfaceClass,
+                        providerUrl.getParameters());
+                for (MetadataReport metadataReport : getMetadataReports()) {
+                    metadataReport.storeProviderMetadata(new MetadataIdentifier(providerUrl.getServiceInterface(),
+                            providerUrl.getParameter(VERSION_KEY), providerUrl.getParameter(GROUP_KEY),
+                            PROVIDER_SIDE, providerUrl.getParameter(APPLICATION_KEY)), fullServiceDefinition);
+                }
+                return;
+            }
+            logger.error("publishProvider interfaceName is empty . providerUrl: " + providerUrl.toFullString());
+        } catch (ClassNotFoundException e) {
+            //ignore error
+            logger.error("publishProvider getServiceDescriptor error. providerUrl: " + providerUrl.toFullString(), e);
+        }
+    }
+
+    private void publishConsumer(URL consumerURL) throws RpcException {
+        final URL url = consumerURL.removeParameters(PID_KEY, TIMESTAMP_KEY, Constants.BIND_IP_KEY,
+                Constants.BIND_PORT_KEY, TIMESTAMP_KEY);
+        getMetadataReports().forEach(config -> {
+            config.storeConsumerMetadata(new MetadataIdentifier(url.getServiceInterface(),
+                    url.getParameter(VERSION_KEY), url.getParameter(GROUP_KEY), CONSUMER_SIDE,
+                    url.getParameter(APPLICATION_KEY)), url.getParameters());
+        });
+    }
+
+}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
index 16f5446..9953a28 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
@@ -183,6 +183,7 @@ public class RegistryDirectory<T> extends AbstractDirectory<T> implements Notify
                 );
             }
         }
+        // FIXME, filter out unmatched urls before notify: version, group, protocol, registry
         notify(urls);
     }
 
diff --git a/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.event.EventListener b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.event.EventListener
index a7fb58d..d856dd2 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.event.EventListener
+++ b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.event.EventListener
@@ -1,4 +1 @@
-# "service-instance" has been deprecated since 2.7.8
-# service-instance=org.apache.dubbo.registry.client.event.listener.CustomizableServiceInstanceListener
-
-registry-logging=org.apache.dubbo.registry.client.event.listener.LoggingEventListener
+registry-logging=org.apache.dubbo.registry.client.event.listener.LoggingEventListener
\ No newline at end of file
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/LoggingEventListenerTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/LoggingEventListenerTest.java
index 0933092..7eda41a 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/LoggingEventListenerTest.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/LoggingEventListenerTest.java
@@ -32,7 +32,8 @@ import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import static java.util.Collections.singleton;
+import java.util.Arrays;
+
 import static org.apache.dubbo.registry.client.DefaultServiceInstanceTest.createInstance;
 
 /**
@@ -71,7 +72,7 @@ public class LoggingEventListenerTest {
         listener.onEvent(new ServiceInstanceRegisteredEvent(serviceDiscovery, createInstance()));
 
         // ServiceInstancesChangedEvent
-        listener.onEvent(new ServiceInstancesChangedEvent("test", singleton(createInstance())));
+        listener.onEvent(new ServiceInstancesChangedEvent("test", Arrays.asList(createInstance())));
 
         // ServiceInstancePreUnregisteredEvent
         listener.onEvent(new ServiceInstancePreUnregisteredEvent(serviceDiscovery, createInstance()));
diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListenerTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListenerTest.java
index 8e76e44..5095f11 100644
--- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListenerTest.java
+++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListenerTest.java
@@ -16,18 +16,8 @@
  */
 package org.apache.dubbo.registry.client.event.listener;
 
-import org.apache.dubbo.event.Event;
-import org.apache.dubbo.event.EventDispatcher;
-import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
-
 import org.junit.jupiter.api.Test;
 
-import java.util.concurrent.atomic.AtomicReference;
-
-import static java.util.Collections.emptyList;
-import static org.apache.dubbo.event.EventDispatcher.getDefaultExtension;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
 /**
  * {@link ServiceInstancesChangedListener} Test
  *
@@ -38,22 +28,5 @@ public class ServiceInstancesChangedListenerTest {
     @Test
     public void testOnEvent() {
 
-        EventDispatcher eventDispatcher = getDefaultExtension();
-
-        Event event = new ServiceInstancesChangedEvent("test", emptyList());
-
-        AtomicReference<Event> eventRef = new AtomicReference<>();
-
-        eventDispatcher.addEventListener(new ServiceInstancesChangedListener("test") {
-            @Override
-            public void onEvent(ServiceInstancesChangedEvent event) {
-                eventRef.set(event);
-            }
-        });
-
-        // Dispatch a ServiceInstancesChangedEvent
-        eventDispatcher.dispatch(event);
-
-        assertEquals(eventRef.get(), event);
     }
 }