diff --git a/dubbo-all/pom.xml b/dubbo-all/pom.xml
index c38263aa7b9..cf90e960ba0 100644
--- a/dubbo-all/pom.xml
+++ b/dubbo-all/pom.xml
@@ -408,6 +408,13 @@
compile
true
+
+ org.apache.dubbo
+ dubbo-configcenter-nacos
+ ${project.version}
+ compile
+ true
+
org.apache.dubbo
dubbo-configcenter-consul
@@ -591,6 +598,7 @@
org.apache.dubbo:dubbo-configcenter-zookeeper
org.apache.dubbo:dubbo-configcenter-consul
org.apache.dubbo:dubbo-configcenter-etcd
+ org.apache.dubbo:dubbo-configcenter-nacos
org.apache.dubbo:dubbo-metadata-report-api
org.apache.dubbo:dubbo-metadata-definition
org.apache.dubbo:dubbo-metadata-report-redis
diff --git a/dubbo-bom/pom.xml b/dubbo-bom/pom.xml
index 56568646232..45379ae3ed4 100644
--- a/dubbo-bom/pom.xml
+++ b/dubbo-bom/pom.xml
@@ -257,6 +257,11 @@
dubbo-registry-consul
${project.version}
+
+ org.apache.dubbo
+ dubbo-registry-nacos
+ ${project.version}
+
org.apache.dubbo
dubbo-registry-sofa
@@ -397,6 +402,11 @@
dubbo-configcenter-etcd
${project.version}
+
+ org.apache.dubbo
+ dubbo-configcenter-nacos
+ ${project.version}
+
org.apache.dubbo
dubbo-metadata-definition
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/Constants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/Constants.java
index c89a31421fa..921aeaa54ae 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/Constants.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/Constants.java
@@ -200,6 +200,8 @@ public class Constants {
public static final String PROPERTIES_CHAR_SEPERATOR = "-";
+ public static final String GROUP_CHAR_SEPERATOR = ":";
+
public static final String HIDE_KEY_PREFIX = ".";
public static final String DEFAULT_KEY_PREFIX = "default.";
diff --git a/dubbo-configcenter/dubbo-configcenter-nacos/pom.xml b/dubbo-configcenter/dubbo-configcenter-nacos/pom.xml
new file mode 100644
index 00000000000..bd5397c92b6
--- /dev/null
+++ b/dubbo-configcenter/dubbo-configcenter-nacos/pom.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+ dubbo-configcenter
+ org.apache.dubbo
+ ${revision}
+
+ 4.0.0
+
+ dubbo-configcenter-nacos
+ jar
+ ${project.artifactId}
+ The nacos implementation of the config-center api
+
+
+
+ org.apache.dubbo
+ dubbo-configcenter-api
+ ${project.parent.version}
+
+
+ com.alibaba.nacos
+ nacos-client
+
+
+
diff --git a/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java
new file mode 100644
index 00000000000..fdb536fa8a3
--- /dev/null
+++ b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java
@@ -0,0 +1,265 @@
+/*
+ * 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.configcenter.support.nacos;
+
+import com.alibaba.nacos.api.NacosFactory;
+import com.alibaba.nacos.api.config.ConfigService;
+import com.alibaba.nacos.api.config.listener.AbstractSharedListener;
+import com.alibaba.nacos.api.exception.NacosException;
+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.configcenter.ConfigChangeEvent;
+import org.apache.dubbo.configcenter.ConfigChangeType;
+import org.apache.dubbo.configcenter.ConfigurationListener;
+import org.apache.dubbo.configcenter.DynamicConfiguration;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import static com.alibaba.nacos.api.PropertyKeyConst.ACCESS_KEY;
+import static com.alibaba.nacos.api.PropertyKeyConst.CLUSTER_NAME;
+import static com.alibaba.nacos.api.PropertyKeyConst.ENDPOINT;
+import static com.alibaba.nacos.api.PropertyKeyConst.SECRET_KEY;
+import static com.alibaba.nacos.api.PropertyKeyConst.SERVER_ADDR;
+import static com.alibaba.nacos.api.PropertyKeyConst.NAMESPACE;
+import static com.alibaba.nacos.client.naming.utils.UtilAndComs.NACOS_NAMING_LOG_NAME;
+import static org.apache.dubbo.common.Constants.BACKUP_KEY;
+import static org.apache.dubbo.common.Constants.CONFIG_NAMESPACE_KEY;
+import static org.apache.dubbo.common.Constants.GROUP_CHAR_SEPERATOR;
+import static org.apache.dubbo.common.Constants.PROPERTIES_CHAR_SEPERATOR;
+
+/**
+ * The nacos implementation of {@link DynamicConfiguration}
+ */
+public class NacosDynamicConfiguration implements DynamicConfiguration {
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ /**
+ * The final root path would be: /$NAME_SPACE/config
+ */
+ private String rootPath;
+
+ /**
+ * The nacos configService
+ */
+
+ private ConfigService configService;
+
+ /**
+ * The map store the key to {@link NacosConfigListener} mapping
+ */
+ private final ConcurrentMap watchListenerMap;
+
+ NacosDynamicConfiguration(URL url) {
+ rootPath = url.getParameter(CONFIG_NAMESPACE_KEY, DEFAULT_GROUP) + "-config";
+ buildConfigService(url);
+ watchListenerMap = new ConcurrentHashMap<>();
+ }
+
+ private ConfigService buildConfigService(URL url) {
+ Properties nacosProperties = buildNacosProperties(url);
+ try {
+ configService = NacosFactory.createConfigService(nacosProperties);
+ } catch (NacosException e) {
+ if (logger.isErrorEnabled()) {
+ logger.error(e.getErrMsg(), e);
+ }
+ throw new IllegalStateException(e);
+ }
+ return configService;
+ }
+
+ public void publishNacosConfig(String key, String value) {
+ try {
+ String[] keyAndGroup = getKeyAndGroup(key);
+ configService.publishConfig(keyAndGroup[0], keyAndGroup[1], value);
+ } catch (NacosException e) {
+ logger.error(e.getErrMsg());
+ }
+ }
+
+ private String[] getKeyAndGroup(String key) {
+ int i = key.lastIndexOf(GROUP_CHAR_SEPERATOR);
+ if (i < 0) {
+ return new String[]{key, null};
+ } else {
+ return new String[]{key.substring(0, i), key.substring(i+1)};
+ }
+ }
+
+ private Properties buildNacosProperties(URL url) {
+ Properties properties = new Properties();
+ setServerAddr(url, properties);
+ setProperties(url, properties);
+ return properties;
+ }
+
+ private void setServerAddr(URL url, Properties properties) {
+ StringBuilder serverAddrBuilder =
+ new StringBuilder(url.getHost()) // Host
+ .append(":")
+ .append(url.getPort()); // Port
+
+ // Append backup parameter as other servers
+ String backup = url.getParameter(BACKUP_KEY);
+ if (backup != null) {
+ serverAddrBuilder.append(",").append(backup);
+ }
+ String serverAddr = serverAddrBuilder.toString();
+ properties.put(SERVER_ADDR, serverAddr);
+ }
+
+ private void setProperties(URL url, Properties properties) {
+ putPropertyIfAbsent(url, properties, NAMESPACE);
+ putPropertyIfAbsent(url, properties, NACOS_NAMING_LOG_NAME);
+ putPropertyIfAbsent(url, properties, ENDPOINT);
+ putPropertyIfAbsent(url, properties, ACCESS_KEY);
+ putPropertyIfAbsent(url, properties, SECRET_KEY);
+ putPropertyIfAbsent(url, properties, CLUSTER_NAME);
+ }
+
+ private void putPropertyIfAbsent(URL url, Properties properties, String propertyName) {
+ String propertyValue = url.getParameter(propertyName);
+ if (StringUtils.isNotEmpty(propertyValue)) {
+ properties.setProperty(propertyName, propertyValue);
+ }
+ }
+
+ /**
+ * Ignores the group parameter.
+ *
+ * @param key property key the native listener will listen on
+ * @param group to distinguish different set of properties
+ * @return
+ */
+ private NacosConfigListener createTargetListener(String key, String group) {
+ NacosConfigListener configListener = new NacosConfigListener();
+ configListener.fillContext(key, group);
+ return configListener;
+ }
+
+ @Override
+ public void addListener(String key, String group, ConfigurationListener listener) {
+ String[] keyAndGroup = getKeyAndGroup(key);
+ if (keyAndGroup[1] != null) {
+ group = keyAndGroup[1];
+ }
+ String finalGroup = group;
+ NacosConfigListener nacosConfigListener = watchListenerMap.computeIfAbsent(generateKey(key, group), k -> createTargetListener(key, finalGroup));
+ String keyInNacos = rootPath + PROPERTIES_CHAR_SEPERATOR + key;
+ nacosConfigListener.addListener(listener);
+ try {
+ configService.addListener(keyInNacos, group, nacosConfigListener);
+ System.out.println("1");
+ } catch (NacosException e) {
+ logger.error(e.getMessage());
+ }
+ }
+
+ private String generateKey(String key, String group) {
+ if (StringUtils.isNotEmpty(group)) {
+ key = key + GROUP_CHAR_SEPERATOR + group;
+ }
+ return key;
+ }
+
+ @Override
+ public void removeListener(String key, String group, ConfigurationListener listener) {
+ NacosConfigListener eventListener = watchListenerMap.get(generateKey(key, group));
+ if (eventListener != null) {
+ eventListener.removeListener(listener);
+ }
+ }
+
+ @Override
+ public String getConfig(String key, String group, long timeout) throws IllegalStateException {
+ key = generateKey(key, group);
+ return (String) getInternalProperty(rootPath + PROPERTIES_CHAR_SEPERATOR + key);
+ }
+
+ @Override
+ public Object getInternalProperty(String key) {
+ try {
+ String[] keyAndGroup = getKeyAndGroup(key);
+ return configService.getConfig(keyAndGroup[0], keyAndGroup[1], 5000L);
+ } catch (NacosException e) {
+ logger.error(e.getMessage());
+ }
+ return null;
+ }
+
+ public class NacosConfigListener extends AbstractSharedListener {
+
+ private Set listeners = new CopyOnWriteArraySet<>();
+ /**
+ * cache data to store old value
+ */
+ private Map cacheData = new ConcurrentHashMap<>();
+
+ @Override
+ public Executor getExecutor() {
+ return null;
+ }
+
+ /**
+ * receive
+ *
+ * @param dataId data ID
+ * @param group group
+ * @param configInfo content
+ */
+ @Override
+ public void innerReceive(String dataId, String group, String configInfo) {
+ String oldValue = cacheData.get(dataId);
+ ConfigChangeEvent event = new ConfigChangeEvent(dataId, configInfo, getChangeType(configInfo, oldValue));
+ if (configInfo == null) {
+ cacheData.remove(dataId);
+ } else {
+ cacheData.put(dataId, configInfo);
+ }
+ listeners.forEach(listener -> listener.process(event));
+ }
+
+ void addListener(ConfigurationListener configurationListener) {
+
+ this.listeners.add(configurationListener);
+ }
+
+ void removeListener(ConfigurationListener configurationListener) {
+ this.listeners.remove(configurationListener);
+ }
+
+ private ConfigChangeType getChangeType(String configInfo, String oldValue) {
+ if (StringUtils.isBlank(configInfo)) {
+ return ConfigChangeType.DELETED;
+ }
+ if (StringUtils.isBlank(oldValue)) {
+ return ConfigChangeType.ADDED;
+ }
+ return ConfigChangeType.MODIFIED;
+ }
+ }
+
+}
diff --git a/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfigurationFactory.java b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfigurationFactory.java
new file mode 100644
index 00000000000..2246741c1cb
--- /dev/null
+++ b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfigurationFactory.java
@@ -0,0 +1,40 @@
+/*
+ * 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.configcenter.support.nacos;
+
+import com.alibaba.nacos.api.PropertyKeyConst;
+import org.apache.dubbo.common.Constants;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.configcenter.AbstractDynamicConfigurationFactory;
+import org.apache.dubbo.configcenter.DynamicConfiguration;
+
+/**
+ * The nacos implementation of {@link AbstractDynamicConfigurationFactory}
+ */
+public class NacosDynamicConfigurationFactory extends AbstractDynamicConfigurationFactory {
+
+ @Override
+ protected DynamicConfiguration createDynamicConfiguration(URL url) {
+ URL nacosURL = url;
+ if (Constants.DUBBO.equals(url.getParameter(PropertyKeyConst.NAMESPACE))) {
+ // Nacos use empty string as default name space, replace default namespace "dubbo" to ""
+ nacosURL = url.removeParameter(PropertyKeyConst.NAMESPACE);
+ }
+ return new NacosDynamicConfiguration(nacosURL);
+ }
+}
diff --git a/dubbo-configcenter/dubbo-configcenter-nacos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfigurationFactory b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfigurationFactory
new file mode 100644
index 00000000000..b9c75a4c980
--- /dev/null
+++ b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.configcenter.DynamicConfigurationFactory
@@ -0,0 +1 @@
+nacos=org.apache.dubbo.configcenter.support.nacos.NacosDynamicConfigurationFactory
\ No newline at end of file
diff --git a/dubbo-configcenter/dubbo-configcenter-nacos/src/test/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfigurationTest.java b/dubbo-configcenter/dubbo-configcenter-nacos/src/test/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfigurationTest.java
new file mode 100644
index 00000000000..f5ac1d38ccb
--- /dev/null
+++ b/dubbo-configcenter/dubbo-configcenter-nacos/src/test/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfigurationTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.configcenter.support.nacos;
+
+import org.apache.dubbo.common.Constants;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.configcenter.ConfigChangeEvent;
+import org.apache.dubbo.configcenter.ConfigurationListener;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Disabled;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Unit test for nacos config center support
+ */
+@Disabled
+public class NacosDynamicConfigurationTest {
+
+ private static NacosDynamicConfiguration config;
+
+ @Test
+ public void testGetConfig() throws Exception {
+
+ put("dubbo-config-org.apache.dubbo.nacos.testService.configurators", "hello");
+ Thread.sleep(200);
+ put("dubbo-config-dubbo.properties:test", "aaa=bbb");
+ Thread.sleep(200);
+ Assertions.assertEquals("hello", config.getConfig("org.apache.dubbo.nacos.testService.configurators"));
+ Assertions.assertEquals("aaa=bbb", config.getConfig("dubbo.properties", "test"));
+ }
+
+ @Test
+ public void testAddListener() throws Exception {
+ CountDownLatch latch = new CountDownLatch(4);
+ TestListener listener1 = new TestListener(latch);
+ TestListener listener2 = new TestListener(latch);
+ TestListener listener3 = new TestListener(latch);
+ TestListener listener4 = new TestListener(latch);
+
+
+ config.addListener("AService.configurators", listener1);
+ config.addListener("AService.configurators", listener2);
+ config.addListener("testapp.tagrouters", listener3);
+ config.addListener("testapp.tagrouters", listener4);
+
+ put("dubbo-config-AService.configurators", "new value1");
+ Thread.sleep(200);
+ put("dubbo-config-testapp.tagrouters", "new value2");
+ Thread.sleep(200);
+ put("dubbo-config-testapp", "new value3");
+ Thread.sleep(5000);
+
+ latch.await();
+
+ Assertions.assertEquals(1, listener1.getCount("dubbo-config-AService.configurators"));
+ Assertions.assertEquals(1, listener2.getCount("dubbo-config-AService.configurators"));
+ Assertions.assertEquals(1, listener3.getCount("dubbo-config-testapp.tagrouters"));
+ Assertions.assertEquals(1, listener4.getCount("dubbo-config-testapp.tagrouters"));
+
+ Assertions.assertEquals("new value1", listener1.getValue());
+ Assertions.assertEquals("new value1", listener2.getValue());
+ Assertions.assertEquals("new value2", listener3.getValue());
+ Assertions.assertEquals("new value2", listener4.getValue());
+
+ }
+
+ private void put(String key, String value) {
+ try {
+ config.publishNacosConfig(key, value);
+ } catch (Exception e) {
+ System.out.println("Error put value to nacos.");
+ }
+ }
+
+ @BeforeAll
+ public static void setUp() {
+ String urlForDubbo = "nacos://" + "127.0.0.1:8848" + "/org.apache.dubbo.nacos.testService";
+ // timeout in 15 seconds.
+ URL url = URL.valueOf(urlForDubbo)
+ .addParameter(Constants.SESSION_TIMEOUT_KEY, 15000);
+ config = new NacosDynamicConfiguration(url);
+ }
+
+ @AfterAll
+ public static void tearDown() {
+ }
+
+ private class TestListener implements ConfigurationListener {
+ private CountDownLatch latch;
+ private String value;
+ private Map countMap = new HashMap<>();
+
+ public TestListener(CountDownLatch latch) {
+ this.latch = latch;
+ }
+
+ @Override
+ public void process(ConfigChangeEvent event) {
+ System.out.println(this + ": " + event);
+ Integer count = countMap.computeIfAbsent(event.getKey(), k -> new Integer(0));
+ countMap.put(event.getKey(), ++count);
+ value = event.getValue();
+ latch.countDown();
+ }
+
+ public int getCount(String key) {
+ return countMap.get(key);
+ }
+
+ public String getValue() {
+ return value;
+ }
+ }
+
+}
diff --git a/dubbo-configcenter/pom.xml b/dubbo-configcenter/pom.xml
index c6fb983bb72..0de20d319c7 100644
--- a/dubbo-configcenter/pom.xml
+++ b/dubbo-configcenter/pom.xml
@@ -35,5 +35,6 @@
dubbo-configcenter-apollo
dubbo-configcenter-consul
dubbo-configcenter-etcd
+ dubbo-configcenter-nacos
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/AbstractConfiguratorListener.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/AbstractConfiguratorListener.java
index 8b0b43fad9d..3aea7e6177e 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/AbstractConfiguratorListener.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/AbstractConfiguratorListener.java
@@ -16,6 +16,7 @@
*/
package org.apache.dubbo.registry.integration;
+import org.apache.dubbo.common.Constants;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.StringUtils;
@@ -41,7 +42,7 @@ public abstract class AbstractConfiguratorListener implements ConfigurationListe
protected final void initWith(String key) {
DynamicConfiguration dynamicConfiguration = DynamicConfiguration.getDynamicConfiguration();
dynamicConfiguration.addListener(key, this);
- String rawConfig = dynamicConfiguration.getConfig(key);
+ String rawConfig = dynamicConfiguration.getConfig(key, Constants.DUBBO);
if (!StringUtils.isEmpty(rawConfig)) {
process(new ConfigChangeEvent(key, rawConfig));
}