> propertySources, String group,
String dataId, Date timestamp, boolean isRefreshable) {
this(group, dataId, getSourceMap(group, dataId, propertySources), timestamp,
isRefreshable);
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLoader.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLoader.java
new file mode 100644
index 000000000..db8a105ba
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLoader.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2015-2020 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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 com.alibaba.cloud.nacos.configdata;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+
+import com.alibaba.cloud.nacos.NacosConfigManager;
+import com.alibaba.cloud.nacos.NacosConfigProperties;
+import com.alibaba.cloud.nacos.NacosPropertySourceRepository;
+import com.alibaba.cloud.nacos.client.NacosPropertySource;
+import com.alibaba.cloud.nacos.parser.NacosDataParserHandler;
+import com.alibaba.nacos.api.config.ConfigService;
+import com.alibaba.nacos.api.exception.NacosException;
+import org.apache.commons.logging.Log;
+
+import org.springframework.boot.context.config.ConfigData;
+import org.springframework.boot.context.config.ConfigDataLoader;
+import org.springframework.boot.context.config.ConfigDataLoaderContext;
+import org.springframework.boot.context.config.ConfigDataResourceNotFoundException;
+import org.springframework.core.env.PropertySource;
+
+import static com.alibaba.cloud.nacos.configdata.NacosConfigDataResource.NacosItemConfig;
+
+/**
+ * Implementation of {@link ConfigDataLoader}.
+ *
+ *
+ * Load {@link ConfigData} via {@link NacosConfigDataResource}
+ *
+ * @author freeman
+ */
+public class NacosConfigDataLoader implements ConfigDataLoader {
+
+ private final Log log;
+
+ public NacosConfigDataLoader(Log log) {
+ this.log = log;
+ }
+
+ @Override
+ public ConfigData load(ConfigDataLoaderContext context,
+ NacosConfigDataResource resource) {
+ return doLoad(context, resource);
+ }
+
+ public ConfigData doLoad(ConfigDataLoaderContext context,
+ NacosConfigDataResource resource) {
+ try {
+ ConfigService configService = getBean(context, NacosConfigManager.class)
+ .getConfigService();
+ NacosConfigProperties properties = getBean(context,
+ NacosConfigProperties.class);
+
+ NacosItemConfig config = resource.getConfig();
+ // pull config from nacos
+ List> propertySources = pullConfig(configService,
+ config.getGroup(), config.getDataId(), config.getSuffix(),
+ properties.getTimeout());
+
+ NacosPropertySource propertySource = new NacosPropertySource(propertySources,
+ config.getGroup(), config.getDataId(), new Date(),
+ config.isRefreshEnabled());
+
+ NacosPropertySourceRepository.collectNacosPropertySource(propertySource);
+
+ // TODO Currently based on 2.4.2,
+ // compatibility needs to be done when upgrading to boot version 2.4.5
+ return new ConfigData(propertySources);
+ }
+ catch (Exception e) {
+ if (log.isDebugEnabled()) {
+ log.debug("Error getting properties from nacos: " + resource, e);
+ }
+ if (!resource.isOptional()) {
+ throw new ConfigDataResourceNotFoundException(resource, e);
+ }
+ }
+ return null;
+ }
+
+ private List> pullConfig(ConfigService configService, String group,
+ String dataId, String suffix, long timeout)
+ throws NacosException, IOException {
+ String config = configService.getConfig(dataId, group, timeout);
+ return NacosDataParserHandler.getInstance().parseNacosData(dataId, config,
+ suffix);
+ }
+
+ protected T getBean(ConfigDataLoaderContext context, Class type) {
+ if (context.getBootstrapContext().isRegistered(type)) {
+ return context.getBootstrapContext().get(type);
+ }
+ return null;
+ }
+
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLocationResolver.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLocationResolver.java
new file mode 100644
index 000000000..15ef3a402
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLocationResolver.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2013-2020 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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 com.alibaba.cloud.nacos.configdata;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.alibaba.cloud.nacos.NacosConfigManager;
+import com.alibaba.cloud.nacos.NacosConfigProperties;
+import com.alibaba.nacos.common.utils.StringUtils;
+import org.apache.commons.logging.Log;
+
+import org.springframework.boot.BootstrapRegistry.InstanceSupplier;
+import org.springframework.boot.ConfigurableBootstrapContext;
+import org.springframework.boot.context.config.ConfigDataLocation;
+import org.springframework.boot.context.config.ConfigDataLocationNotFoundException;
+import org.springframework.boot.context.config.ConfigDataLocationResolver;
+import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
+import org.springframework.boot.context.config.ConfigDataResource;
+import org.springframework.boot.context.config.ConfigDataResourceNotFoundException;
+import org.springframework.boot.context.config.Profiles;
+import org.springframework.boot.context.properties.bind.BindHandler;
+import org.springframework.boot.context.properties.bind.Bindable;
+import org.springframework.boot.context.properties.bind.Binder;
+import org.springframework.core.Ordered;
+
+import static com.alibaba.cloud.nacos.configdata.NacosConfigDataResource.NacosItemConfig;
+
+/**
+ * Implementation of {@link ConfigDataLocationResolver}, load Nacos
+ * {@link ConfigDataResource}.
+ *
+ * @author freeman
+ */
+public class NacosConfigDataLocationResolver
+ implements ConfigDataLocationResolver, Ordered {
+ /**
+ * Prefix for Config Server imports.
+ */
+ public static final String PREFIX = "nacos:";
+
+ private final Log log;
+
+ // support params
+
+ private static final String GROUP = "group";
+
+ private static final String REFRESH_ENABLED = "refreshEnabled";
+
+ public NacosConfigDataLocationResolver(Log log) {
+ this.log = log;
+ }
+
+ @Override
+ public int getOrder() {
+ return -1;
+ }
+
+ protected NacosConfigProperties loadProperties(
+ ConfigDataLocationResolverContext context) {
+ Binder binder = context.getBinder();
+ BindHandler bindHandler = getBindHandler(context);
+
+ NacosConfigProperties nacosConfigProperties;
+ if (context.getBootstrapContext().isRegistered(NacosConfigProperties.class)) {
+ nacosConfigProperties = context.getBootstrapContext()
+ .get(NacosConfigProperties.class);
+ }
+ else {
+ nacosConfigProperties = binder
+ .bind("spring.cloud.nacos", Bindable.of(NacosConfigProperties.class),
+ bindHandler)
+ .map(properties -> binder
+ .bind(NacosConfigProperties.PREFIX,
+ Bindable.ofInstance(properties), bindHandler)
+ .orElse(properties))
+ .orElseGet(() -> binder
+ .bind(NacosConfigProperties.PREFIX,
+ Bindable.of(NacosConfigProperties.class), bindHandler)
+ .orElseGet(NacosConfigProperties::new));
+ }
+
+ return nacosConfigProperties;
+ }
+
+ private BindHandler getBindHandler(ConfigDataLocationResolverContext context) {
+ return context.getBootstrapContext().getOrElse(BindHandler.class, null);
+ }
+
+ protected Log getLog() {
+ return this.log;
+ }
+
+ @Override
+ public boolean isResolvable(ConfigDataLocationResolverContext context,
+ ConfigDataLocation location) {
+ if (!location.hasPrefix(getPrefix())) {
+ return false;
+ }
+ return context.getBinder()
+ .bind(NacosConfigProperties.PREFIX + ".enabled", Boolean.class)
+ .orElse(true);
+ }
+
+ protected String getPrefix() {
+ return PREFIX;
+ }
+
+ @Override
+ public List resolve(
+ ConfigDataLocationResolverContext context, ConfigDataLocation location)
+ throws ConfigDataLocationNotFoundException,
+ ConfigDataResourceNotFoundException {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List resolveProfileSpecific(
+ ConfigDataLocationResolverContext resolverContext,
+ ConfigDataLocation location, Profiles profiles)
+ throws ConfigDataLocationNotFoundException {
+ NacosConfigProperties properties = loadProperties(resolverContext);
+
+ ConfigurableBootstrapContext bootstrapContext = resolverContext
+ .getBootstrapContext();
+
+ bootstrapContext.registerIfAbsent(NacosConfigProperties.class,
+ InstanceSupplier.of(properties));
+
+ registerConfigManager(properties, bootstrapContext);
+
+ return loadConfigDataResources(location, profiles, properties);
+ }
+
+ private List loadConfigDataResources(
+ ConfigDataLocation location, Profiles profiles,
+ NacosConfigProperties properties) {
+ List result = new ArrayList<>();
+ URI uri = getUri(location, properties);
+
+ if (StringUtils.isBlank(dataIdFor(uri))) {
+ throw new IllegalArgumentException("dataId must be specified");
+ }
+
+ NacosConfigDataResource resource = new NacosConfigDataResource(properties,
+ location.isOptional(), profiles, log,
+ new NacosItemConfig().setGroup(groupFor(uri, properties))
+ .setDataId(dataIdFor(uri)).setSuffix(suffixFor(uri, properties))
+ .setRefreshEnabled(refreshEnabledFor(uri, properties)));
+ result.add(resource);
+
+ return result;
+ }
+
+ private URI getUri(ConfigDataLocation location, NacosConfigProperties properties) {
+ String path = location.getNonPrefixedValue(getPrefix());
+ if (StringUtils.isBlank(path)) {
+ path = "/";
+ }
+ if (!path.startsWith("/")) {
+ path = "/" + path;
+ }
+ String uri = properties.getServerAddr() + path;
+ return getUri(uri);
+ }
+
+ private void registerConfigManager(NacosConfigProperties properties,
+ ConfigurableBootstrapContext bootstrapContext) {
+ if (!bootstrapContext.isRegistered(NacosConfigManager.class)) {
+ bootstrapContext.register(NacosConfigManager.class,
+ InstanceSupplier.of(new NacosConfigManager(properties)));
+ }
+ }
+
+ private URI getUri(String uris) {
+ if (!uris.startsWith("http://") && !uris.startsWith("https://")) {
+ uris = "http://" + uris;
+ }
+ URI uri;
+ try {
+ uri = new URI(uris);
+ }
+ catch (URISyntaxException e) {
+ throw new IllegalArgumentException("illegal URI: " + uris);
+ }
+ return uri;
+ }
+
+ private String groupFor(URI uri, NacosConfigProperties properties) {
+ Map queryMap = getQueryMap(uri);
+ return queryMap.containsKey(GROUP) ? queryMap.get(GROUP) : properties.getGroup();
+ }
+
+ private Map getQueryMap(URI uri) {
+ String query = uri.getQuery();
+ if (StringUtils.isBlank(query)) {
+ return Collections.emptyMap();
+ }
+ Map result = new HashMap<>(4);
+ for (String entry : query.split("&")) {
+ String[] kv = entry.split("=");
+ if (kv.length == 2) {
+ result.put(kv[0], kv[1]);
+ }
+ }
+ return result;
+ }
+
+ private String suffixFor(URI uri, NacosConfigProperties properties) {
+ String dataId = dataIdFor(uri);
+ if (dataId != null && dataId.contains(".")) {
+ return dataId.substring(dataId.lastIndexOf('.') + 1);
+ }
+ return properties.getFileExtension();
+ }
+
+ private boolean refreshEnabledFor(URI uri, NacosConfigProperties properties) {
+ Map queryMap = getQueryMap(uri);
+ return queryMap.containsKey(REFRESH_ENABLED)
+ ? Boolean.parseBoolean(queryMap.get(REFRESH_ENABLED))
+ : properties.isRefreshEnabled();
+ }
+
+ private String dataIdFor(URI uri) {
+ String path = uri.getPath();
+ // notice '/'
+ if (path == null || path.length() <= 1) {
+ return StringUtils.EMPTY;
+ }
+ String[] parts = path.substring(1).split("/");
+ if (parts.length != 1) {
+ throw new IllegalArgumentException("illegal dataId");
+ }
+ return parts[0];
+ }
+
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataResource.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataResource.java
new file mode 100644
index 000000000..c0973c20b
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataResource.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2013-2020 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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 com.alibaba.cloud.nacos.configdata;
+
+import java.util.List;
+import java.util.Objects;
+
+import com.alibaba.cloud.nacos.NacosConfigProperties;
+import org.apache.commons.logging.Log;
+
+import org.springframework.boot.context.config.ConfigDataResource;
+import org.springframework.boot.context.config.Profiles;
+import org.springframework.util.StringUtils;
+
+/**
+ * @author freeman
+ */
+public class NacosConfigDataResource extends ConfigDataResource {
+
+ private final NacosConfigProperties properties;
+
+ private final boolean optional;
+
+ private final Profiles profiles;
+
+ private final Log log;
+
+ private final NacosItemConfig config;
+
+ public NacosConfigDataResource(NacosConfigProperties properties, boolean optional,
+ Profiles profiles, Log log, NacosItemConfig config) {
+ this.properties = properties;
+ this.optional = optional;
+ this.profiles = profiles;
+ this.log = log;
+ this.config = config;
+ }
+
+ public NacosConfigProperties getProperties() {
+ return this.properties;
+ }
+
+ public boolean isOptional() {
+ return this.optional;
+ }
+
+ public String getProfiles() {
+ return StringUtils.collectionToCommaDelimitedString(getAcceptedProfiles());
+ }
+
+ List getAcceptedProfiles() {
+ return this.profiles.getAccepted();
+ }
+
+ public Log getLog() {
+ return this.log;
+ }
+
+ public NacosItemConfig getConfig() {
+ return config;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ NacosConfigDataResource that = (NacosConfigDataResource) o;
+ return optional == that.optional && Objects.equals(properties, that.properties)
+ && Objects.equals(profiles, that.profiles)
+ && Objects.equals(log, that.log) && Objects.equals(config, that.config);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(properties, optional, profiles, log, config);
+ }
+
+ @Override
+ public String toString() {
+ return "NacosConfigDataResource{" + "properties=" + properties + ", optional="
+ + optional + ", profiles=" + profiles + ", config=" + config + '}';
+ }
+
+ public static class NacosItemConfig {
+ private String group;
+ private String dataId;
+ private String suffix;
+ private boolean refreshEnabled;
+
+ public NacosItemConfig() {
+ }
+
+ public NacosItemConfig(String group, String dataId, String suffix,
+ boolean refreshEnabled) {
+ this.group = group;
+ this.dataId = dataId;
+ this.suffix = suffix;
+ this.refreshEnabled = refreshEnabled;
+ }
+
+ public NacosItemConfig setGroup(String group) {
+ this.group = group;
+ return this;
+ }
+
+ public NacosItemConfig setDataId(String dataId) {
+ this.dataId = dataId;
+ return this;
+ }
+
+ public NacosItemConfig setSuffix(String suffix) {
+ this.suffix = suffix;
+ return this;
+ }
+
+ public NacosItemConfig setRefreshEnabled(boolean refreshEnabled) {
+ this.refreshEnabled = refreshEnabled;
+ return this;
+ }
+
+ public String getGroup() {
+ return group;
+ }
+
+ public String getDataId() {
+ return dataId;
+ }
+
+ public String getSuffix() {
+ return suffix;
+ }
+
+ public boolean isRefreshEnabled() {
+ return refreshEnabled;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ NacosItemConfig that = (NacosItemConfig) o;
+ return refreshEnabled == that.refreshEnabled
+ && Objects.equals(group, that.group)
+ && Objects.equals(dataId, that.dataId)
+ && Objects.equals(suffix, that.suffix);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(group, dataId, suffix, refreshEnabled);
+ }
+
+ @Override
+ public String toString() {
+ return "NacosItemConfig{" + "group='" + group + '\'' + ", dataId='" + dataId
+ + '\'' + ", suffix='" + suffix + '\'' + ", refreshEnabled="
+ + refreshEnabled + '}';
+ }
+ }
+
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/resources/META-INF/spring.factories
index aa6bf0d92..dc58fcddd 100644
--- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/resources/META-INF/spring.factories
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/resources/META-INF/spring.factories
@@ -7,4 +7,12 @@ org.springframework.boot.diagnostics.FailureAnalyzer=\
com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer
org.springframework.boot.env.PropertySourceLoader=\
com.alibaba.cloud.nacos.parser.NacosJsonPropertySourceLoader,\
-com.alibaba.cloud.nacos.parser.NacosXmlPropertySourceLoader
\ No newline at end of file
+com.alibaba.cloud.nacos.parser.NacosXmlPropertySourceLoader
+
+# ConfigData Location Resolvers
+org.springframework.boot.context.config.ConfigDataLocationResolver=\
+com.alibaba.cloud.nacos.configdata.NacosConfigDataLocationResolver
+
+# ConfigData Loaders
+org.springframework.boot.context.config.ConfigDataLoader=\
+com.alibaba.cloud.nacos.configdata.NacosConfigDataLoader
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/test/java/com/alibaba/cloud/nacos/NacosConfigAutoConfigurationTest.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/test/java/com/alibaba/cloud/nacos/NacosConfigAutoConfigurationTest.java
new file mode 100644
index 000000000..59309e3d5
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/test/java/com/alibaba/cloud/nacos/NacosConfigAutoConfigurationTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2013-2022 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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 com.alibaba.cloud.nacos;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * NacosConfigAutoConfiguration Tester.
+ *
+ * @author freeman
+ */
+public class NacosConfigAutoConfigurationTest {
+
+ @Test
+ public void noImports_thenCreateProperties() {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ NacosConfigAutoConfiguration.class);
+
+ assertThat(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
+ NacosConfigProperties.class).length).isEqualTo(1);
+ assertThat(context.getBean(NacosConfigProperties.class).getServerAddr())
+ .isEqualTo("localhost:8848");
+ context.close();
+ }
+
+ @Test
+ public void imports_thenNoCreateProperties() {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ NacosConfigAutoConfiguration.class);
+ // mock import
+ context.registerBean(NacosConfigProperties.class, () -> {
+ NacosConfigProperties properties = new NacosConfigProperties();
+ properties.setServerAddr("localhost");
+ return properties;
+ });
+
+ assertThat(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
+ NacosConfigProperties.class).length).isEqualTo(1);
+ assertThat(context.getBean(NacosConfigProperties.class).getServerAddr())
+ .isEqualTo("localhost");
+ context.close();
+ }
+
+}
diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/test/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLocationResolverTest.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/test/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLocationResolverTest.java
new file mode 100644
index 000000000..b149c3a2e
--- /dev/null
+++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/test/java/com/alibaba/cloud/nacos/configdata/NacosConfigDataLocationResolverTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2013-2022 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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 com.alibaba.cloud.nacos.configdata;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import com.alibaba.cloud.nacos.NacosConfigProperties;
+import com.alibaba.nacos.api.config.ConfigService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.boot.ConfigurableBootstrapContext;
+import org.springframework.boot.context.config.ConfigDataLocation;
+import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
+import org.springframework.boot.context.config.Profiles;
+import org.springframework.boot.context.properties.bind.Binder;
+import org.springframework.boot.logging.DeferredLog;
+import org.springframework.mock.env.MockEnvironment;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * NacosConfigDataLocationResolver Tester.
+ *
+ * @author freeman
+ */
+public class NacosConfigDataLocationResolverTest {
+
+ private NacosConfigDataLocationResolver resolver;
+
+ private ConfigDataLocationResolverContext context = mock(
+ ConfigDataLocationResolverContext.class);
+
+ private MockEnvironment environment;
+
+ private Binder environmentBinder;
+
+ private ConfigurableBootstrapContext bootstrapContext = mock(
+ ConfigurableBootstrapContext.class);
+
+ @BeforeEach
+ void setup() {
+ this.environment = new MockEnvironment();
+ this.environmentBinder = Binder.get(this.environment);
+ this.resolver = new NacosConfigDataLocationResolver(new DeferredLog());
+ when(bootstrapContext.isRegistered(eq(ConfigService.class))).thenReturn(true);
+ when(context.getBinder()).thenReturn(environmentBinder);
+ when(context.getBootstrapContext()).thenReturn(bootstrapContext);
+ }
+
+ @Test
+ void testIsResolvable_givenIncorrectPrefix_thenReturnFalse() {
+ assertThat(
+ this.resolver.isResolvable(this.context, ConfigDataLocation.of("test:")))
+ .isFalse();
+ }
+
+ @Test
+ void testIsResolvable_givenCorrectPrefix_thenReturnTure() {
+ assertThat(
+ this.resolver.isResolvable(this.context, ConfigDataLocation.of("nacos:")))
+ .isTrue();
+ assertThat(this.resolver.isResolvable(this.context,
+ ConfigDataLocation.of("optional:nacos:"))).isTrue();
+ }
+
+ @Test
+ void testIsResolvable_givenDisable_thenReturnFalse() {
+ this.environment.setProperty(NacosConfigProperties.PREFIX + ".enabled", "false");
+ assertThat(
+ this.resolver.isResolvable(this.context, ConfigDataLocation.of("nacos:")))
+ .isFalse();
+ }
+
+ @Test
+ void testResolveProfileSpecific_givenNothing_thenReturnDefaultProfile() {
+ NacosConfigDataResource resource = testResolveProfileSpecific();
+ assertThat(resource.getProfiles()).isEqualTo("default");
+ }
+
+ @Test
+ void testStartWithASlashIsOK() {
+ String locationUri = "nacos:/app";
+ List resources = testUri(locationUri);
+ assertThat(resources).hasSize(1);
+ assertThat(resources.get(0).getConfig().getDataId()).isEqualTo("app");
+
+ locationUri = "nacos:app";
+ resources = testUri(locationUri);
+ assertThat(resources).hasSize(1);
+ assertThat(resources.get(0).getConfig().getDataId()).isEqualTo("app");
+ }
+
+ @Test
+ void testDataIdMustBeSpecified() {
+ String locationUri = "nacos:";
+ assertThatThrownBy(() -> testUri(locationUri))
+ .hasMessage("dataId must be specified");
+ }
+
+ @Test
+ void testInvalidDataId() {
+ String locationUri = "nacos:test/test.yml";
+ assertThatThrownBy(() -> testUri(locationUri)).hasMessage("illegal dataId");
+ }
+
+ @Test
+ void whenCustomizeSuffix_thenOverrideDefault() {
+ String locationUri = "nacos:app";
+ List resources = testUri(locationUri);
+ assertThat(resources).hasSize(1);
+ assertThat(resources.get(0).getConfig().getDataId()).isEqualTo("app");
+ assertThat(resources.get(0).getConfig().getSuffix()).isEqualTo("properties");
+
+ environment.setProperty("spring.cloud.nacos.config.file-extension", "yml");
+ locationUri = "nacos:app";
+ resources = testUri(locationUri);
+ assertThat(resources).hasSize(1);
+ assertThat(resources.get(0).getConfig().getDataId()).isEqualTo("app");
+ assertThat(resources.get(0).getConfig().getSuffix()).isEqualTo("yml");
+
+ locationUri = "nacos:app.json";
+ resources = testUri(locationUri);
+ assertThat(resources).hasSize(1);
+ assertThat(resources.get(0).getConfig().getDataId()).isEqualTo("app.json");
+ assertThat(resources.get(0).getConfig().getSuffix()).isEqualTo("json");
+ }
+
+ @Test
+ void testUrisInLocationShouldOverridesProperty() {
+ environment.setProperty("spring.cloud.nacos.config.group", "default");
+ environment.setProperty("spring.cloud.nacos.config.refreshEnabled", "true");
+ String locationUri = "nacos:test.yml?group=not_default&refreshEnabled=false";
+ List resources = testUri(locationUri);
+ assertThat(resources).hasSize(1);
+ NacosConfigDataResource resource = resources.get(0);
+ assertThat(resource.getConfig().getGroup()).isEqualTo("not_default");
+ assertThat(resource.getConfig().getSuffix()).isEqualTo("yml");
+ assertThat(resource.getConfig().isRefreshEnabled()).isFalse();
+ assertThat(resource.getConfig().getDataId()).isEqualTo("test.yml");
+ }
+
+ @Test
+ void testSetCommonPropertiesIsOK() {
+ environment.setProperty("spring.cloud.nacos.username", "root");
+ environment.setProperty("spring.cloud.nacos.password", "root");
+ environment.setProperty("spring.cloud.nacos.server-addr", "localhost:8888");
+ String locationUri = "nacos:test.yml";
+ List resources = testUri(locationUri);
+
+ assertThat(resources).hasSize(1);
+ NacosConfigDataResource resource = resources.get(0);
+ assertThat(resource.getProperties().getUsername()).isEqualTo("root");
+ assertThat(resource.getProperties().getPassword()).isEqualTo("root");
+ assertThat(resource.getProperties().getServerAddr()).isEqualTo("localhost:8888");
+ }
+
+ @Test
+ void testCommonPropertiesHasLowerPriority() {
+ environment.setProperty("spring.cloud.nacos.username", "root");
+ environment.setProperty("spring.cloud.nacos.password", "root");
+ environment.setProperty("spring.cloud.nacos.config.password", "not_root");
+ environment.setProperty("spring.cloud.nacos.server-addr", "localhost:8888");
+ environment.setProperty("spring.cloud.nacos.config.server-addr",
+ "localhost:9999");
+ String locationUri = "nacos:test.yml";
+ List resources = testUri(locationUri);
+
+ assertThat(resources).hasSize(1);
+ NacosConfigDataResource resource = resources.get(0);
+ assertThat(resource.getProperties().getUsername()).isEqualTo("root");
+ assertThat(resource.getProperties().getPassword()).isEqualTo("not_root");
+ assertThat(resource.getProperties().getServerAddr()).isEqualTo("localhost:9999");
+ }
+
+ private List testUri(String locationUri,
+ String... activeProfiles) {
+ Profiles profiles = mock(Profiles.class);
+ when(profiles.getActive()).thenReturn(Arrays.asList(activeProfiles));
+ return this.resolver.resolveProfileSpecific(context,
+ ConfigDataLocation.of(locationUri), profiles);
+ }
+
+ @Test
+ void whenNoneInBootstrapContext_thenCreateNewConfigClientProperties() {
+ when(bootstrapContext.isRegistered(eq(NacosConfigProperties.class)))
+ .thenReturn(false);
+ when(bootstrapContext.get(eq(NacosConfigProperties.class)))
+ .thenReturn(new NacosConfigProperties());
+ List resources = this.resolver.resolveProfileSpecific(
+ context, ConfigDataLocation.of("nacos:test.yml"), mock(Profiles.class));
+ assertThat(resources).hasSize(1);
+ verify(bootstrapContext, times(0)).get(eq(NacosConfigProperties.class));
+ NacosConfigDataResource resource = resources.get(0);
+ assertThat(resource.getConfig().getGroup()).isEqualTo("DEFAULT_GROUP");
+ assertThat(resource.getConfig().getDataId()).isEqualTo("test.yml");
+ }
+
+ private NacosConfigDataResource testResolveProfileSpecific() {
+ return testResolveProfileSpecific("default");
+ }
+
+ private NacosConfigDataResource testResolveProfileSpecific(String activeProfile) {
+ Profiles profiles = mock(Profiles.class);
+ if (activeProfile != null) {
+ when(profiles.getActive())
+ .thenReturn(Collections.singletonList(activeProfile));
+ when(profiles.getAccepted())
+ .thenReturn(Collections.singletonList(activeProfile));
+ }
+
+ List resources = this.resolver.resolveProfileSpecific(
+ context, ConfigDataLocation.of("nacos:test.yml"), profiles);
+ assertThat(resources).hasSize(1);
+ return resources.get(0);
+ }
+
+}