From c07701d98ef19a8c26a3ed1450dbf86727e612fa Mon Sep 17 00:00:00 2001 From: zkzlx Date: Wed, 17 Jun 2020 18:54:00 +0800 Subject: [PATCH] [enhance issue #1492 ]Improved nacos configuration parsing, based on PropertySourceLoader --- .../alibaba/cloud/examples/Application.java | 82 +++++++- .../src/main/resources/bootstrap.properties | 23 ++- .../nacos/client/NacosPropertySource.java | 30 +++ .../client/NacosPropertySourceBuilder.java | 26 +-- .../client/NacosPropertySourceLocator.java | 11 +- .../nacos/parser/AbstractNacosDataParser.java | 175 ------------------ .../parser/AbstractPropertySourceLoader.java | 128 +++++++++++++ .../parser/JsonPropertySourceLoader.java | 87 +++++++++ .../nacos/parser/NacosByteArrayResource.java | 60 ++++++ .../nacos/parser/NacosDataJsonParser.java | 66 ------- .../nacos/parser/NacosDataParserHandler.java | 116 ++++++++---- .../parser/NacosDataPropertiesParser.java | 66 ------- .../nacos/parser/NacosDataXmlParser.java | 129 ------------- .../nacos/parser/NacosDataYamlParser.java | 44 ----- .../main/resources/META-INF/spring.factories | 4 +- 15 files changed, 498 insertions(+), 549 deletions(-) delete mode 100644 spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/AbstractNacosDataParser.java create mode 100644 spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/AbstractPropertySourceLoader.java create mode 100644 spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/JsonPropertySourceLoader.java create mode 100644 spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosByteArrayResource.java delete mode 100644 spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataJsonParser.java delete mode 100644 spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataPropertiesParser.java delete mode 100644 spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataXmlParser.java delete mode 100644 spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataYamlParser.java diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/src/main/java/com/alibaba/cloud/examples/Application.java b/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/src/main/java/com/alibaba/cloud/examples/Application.java index ce6930545..b2a9e9b13 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/src/main/java/com/alibaba/cloud/examples/Application.java +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/src/main/java/com/alibaba/cloud/examples/Application.java @@ -18,11 +18,13 @@ package com.alibaba.cloud.examples; import java.io.IOException; import java.io.StringReader; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; import com.alibaba.cloud.nacos.NacosConfigManager; +import com.alibaba.fastjson.JSON; import com.alibaba.nacos.api.config.listener.Listener; import org.springframework.beans.factory.annotation.Autowired; @@ -34,7 +36,9 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -61,8 +65,10 @@ class UserConfig { private int age; private String name; + private String hr; private Map map; + private List users; public int getAge() { return age; @@ -88,18 +94,77 @@ class UserConfig { this.map = map; } + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } + + public String getHr() { + return hr; + } + + public void setHr(String hr) { + this.hr = hr; + } + @Override public String toString() { - return "UserConfig{" + "age=" + age + ", name='" + name + '\'' + ", map=" + map - + '}'; + return "UserConfig{" + + "age=" + age + + ", name='" + name + '\'' + + ", map=" + map + + ", hr='" + hr + '\'' + + ", users=" + JSON.toJSONString(users) + + '}'; } + public static class User { + private String name; + private String hr; + private String avg; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getHr() { + return hr; + } + + public void setHr(String hr) { + this.hr = hr; + } + + public String getAvg() { + return avg; + } + + public void setAvg(String avg) { + this.avg = avg; + } + + @Override + public String toString() { + return "User{" + + "name='" + name + '\'' + + ", hr=" + hr + + ", avg=" + avg + + '}'; + } + } } @Component class SampleRunner implements ApplicationRunner { - @Value("${user.name}") + @Value("${user.name:zz}") String userName; @Value("${user.age:25}") @@ -150,13 +215,17 @@ class SampleRunner implements ApplicationRunner { @RefreshScope class SampleController { + @Autowired UserConfig userConfig; @Autowired private NacosConfigManager nacosConfigManager; - @Value("${user.name}") + @Autowired + private Environment environment; + + @Value("${user.name:zz}") String userName; @Value("${user.age:25}") @@ -168,6 +237,11 @@ class SampleController { + userConfig + "!" + nacosConfigManager.getConfigService(); } + @RequestMapping("/get/{name}") + public String getValue(@PathVariable(value = "name")String name) { + return String.valueOf(environment.getProperty(name)); + } + @RequestMapping("/bool") public boolean bool() { return (Boolean) (userConfig.getMap().get("2")); diff --git a/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/src/main/resources/bootstrap.properties b/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/src/main/resources/bootstrap.properties index cea784761..9e500a4ee 100644 --- a/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/src/main/resources/bootstrap.properties +++ b/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/src/main/resources/bootstrap.properties @@ -5,19 +5,26 @@ spring.cloud.nacos.config.server-addr=127.0.0.1:8848 spring.cloud.nacos.username=nacos spring.cloud.nacos.password=nacos +#spring.cloud.nacos.config.namespace=public +spring.cloud.nacos.config.name=test-aaa +spring.cloud.nacos.config.file-extension=yaml + #spring.cloud.nacos.config.refreshable-dataids=common.properties #spring.cloud.nacos.config.shared-data-ids=common.properties,base-common.properties -spring.cloud.nacos.config.shared-configs[0]= common333.properties -spring.cloud.nacos.config.shared-configs[1].data-id= common111.properties -spring.cloud.nacos.config.shared-configs[1].group= GROUP_APP1 -spring.cloud.nacos.config.shared-configs[1].refresh= true -spring.cloud.nacos.config.shared-configs[2]= common222.properties +#spring.cloud.nacos.config.shared-configs[0]= common333.properties +#spring.cloud.nacos.config.shared-configs[1].data-id= common111.properties +#spring.cloud.nacos.config.shared-configs[1].group= GROUP_APP1 +#spring.cloud.nacos.config.shared-configs[1].refresh= true +#spring.cloud.nacos.config.shared-configs[2]= common222.properties +spring.cloud.nacos.config.shared-configs[0].data-id= test2.yaml +spring.cloud.nacos.config.shared-configs[0].refresh=true #spring.cloud.nacos.config.ext-config[0]=ext.properties spring.cloud.nacos.config.extension-configs[0].data-id= extension1.properties -spring.cloud.nacos.config.extension-configs[0].refresh= true -spring.cloud.nacos.config.extension-configs[1]= extension2.properties -spring.cloud.nacos.config.extension-configs[2].data-id= extension3.json +spring.cloud.nacos.config.extension-configs[0].refresh=true +spring.cloud.nacos.config.extension-configs[1].data-id= test1.yml +spring.cloud.nacos.config.extension-configs[1].refresh= true + diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySource.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySource.java index 2d83fa5dd..2abdc3499 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySource.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySource.java @@ -16,12 +16,16 @@ package com.alibaba.cloud.nacos.client; +import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.Map; import com.alibaba.cloud.nacos.NacosConfigProperties; import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.util.CollectionUtils; /** * @author xiaojing @@ -58,6 +62,32 @@ public class NacosPropertySource extends MapPropertySource { this.isRefreshable = isRefreshable; } + NacosPropertySource(List> propertySources, String group, + String dataId, Date timestamp, boolean isRefreshable) { + this(group, dataId, getSourceMap(group, dataId, propertySources), timestamp, + isRefreshable); + } + + private static Map getSourceMap(String group, String dataId, + List> propertySources) { + if (CollectionUtils.isEmpty(propertySources)) { + return Collections.emptyMap(); + } + // If only one, return the internal element, otherwise wrap it. + if (propertySources.size() == 1) { + PropertySource propertySource = propertySources.get(0); + if (propertySource != null && propertySource.getSource() instanceof Map) { + return (Map) propertySource.getSource(); + } + } + // If it is multiple, it will be returned as it is, and the internal elements + // cannot be directly retrieved, so the user needs to implement the retrieval + // logic by himself + return Collections.singletonMap( + String.join(NacosConfigProperties.COMMAS, dataId, group), + propertySources); + } + public String getGroup() { return this.group; } diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySourceBuilder.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySourceBuilder.java index 7c3dbaa7b..09ab3c447 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySourceBuilder.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySourceBuilder.java @@ -16,17 +16,19 @@ package com.alibaba.cloud.nacos.client; +import java.util.Collections; import java.util.Date; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.List; import com.alibaba.cloud.nacos.NacosPropertySourceRepository; import com.alibaba.cloud.nacos.parser.NacosDataParserHandler; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.exception.NacosException; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.env.PropertySource; import org.springframework.util.StringUtils; /** @@ -38,8 +40,6 @@ public class NacosPropertySourceBuilder { private static final Logger log = LoggerFactory .getLogger(NacosPropertySourceBuilder.class); - private static final Map EMPTY_MAP = new LinkedHashMap(); - private ConfigService configService; private long timeout; @@ -71,14 +71,15 @@ public class NacosPropertySourceBuilder { */ NacosPropertySource build(String dataId, String group, String fileExtension, boolean isRefreshable) { - Map p = loadNacosData(dataId, group, fileExtension); - NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId, - p, new Date(), isRefreshable); + List> propertySources = loadNacosData(dataId, group, + fileExtension); + NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources, + group, dataId, new Date(), isRefreshable); NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource); return nacosPropertySource; } - private Map loadNacosData(String dataId, String group, + private List> loadNacosData(String dataId, String group, String fileExtension) { String data = null; try { @@ -87,16 +88,15 @@ public class NacosPropertySourceBuilder { log.warn( "Ignore the empty nacos configuration and get it based on dataId[{}] & group[{}]", dataId, group); - return EMPTY_MAP; + return Collections.emptyList(); } if (log.isDebugEnabled()) { log.debug(String.format( "Loading nacos data, dataId: '%s', group: '%s', data: %s", dataId, group, data)); } - Map dataMap = NacosDataParserHandler.getInstance() - .parseNacosData(data, fileExtension); - return dataMap == null ? EMPTY_MAP : dataMap; + return NacosDataParserHandler.getInstance().parseNacosData(dataId, data, + fileExtension); } catch (NacosException e) { log.error("get data from Nacos error,dataId:{}, ", dataId, e); @@ -104,7 +104,7 @@ public class NacosPropertySourceBuilder { catch (Exception e) { log.error("parse data from Nacos error,dataId:{},data:{},", dataId, data, e); } - return EMPTY_MAP; + return Collections.emptyList(); } } diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySourceLocator.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySourceLocator.java index ef075bcb6..b243aa972 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySourceLocator.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/client/NacosPropertySourceLocator.java @@ -101,7 +101,6 @@ public class NacosPropertySourceLocator implements PropertySourceLocator { loadSharedConfiguration(composite); loadExtConfiguration(composite); loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env); - return composite; } @@ -156,16 +155,15 @@ public class NacosPropertySourceLocator implements PropertySourceLocator { private void loadNacosConfiguration(final CompositePropertySource composite, List configs) { for (NacosConfigProperties.Config config : configs) { - String dataId = config.getDataId(); - String fileExtension = dataId.substring(dataId.lastIndexOf(DOT) + 1); - loadNacosDataIfPresent(composite, dataId, config.getGroup(), fileExtension, + loadNacosDataIfPresent(composite, config.getDataId(), config.getGroup(), + NacosDataParserHandler.getInstance() + .getFileExtension(config.getDataId()), config.isRefresh()); } } private void checkConfiguration(List configs, String tips) { - String[] dataIds = new String[configs.size()]; for (int i = 0; i < configs.size(); i++) { String dataId = configs.get(i).getDataId(); if (dataId == null || dataId.trim().length() == 0) { @@ -173,10 +171,7 @@ public class NacosPropertySourceLocator implements PropertySourceLocator { "the [ spring.cloud.nacos.config.%s[%s] ] must give a dataId", tips, i)); } - dataIds[i] = dataId; } - // Just decide that the current dataId must have a suffix - NacosDataParserHandler.getInstance().checkDataId(dataIds); } private void loadNacosDataIfPresent(final CompositePropertySource composite, diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/AbstractNacosDataParser.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/AbstractNacosDataParser.java deleted file mode 100644 index 6f94cb6c0..000000000 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/AbstractNacosDataParser.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2013-2018 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.parser; - -import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -import org.springframework.util.StringUtils; - -/** - * @author zkz - */ -public abstract class AbstractNacosDataParser { - - protected static final String DOT = "."; - - protected static final String VALUE = "value"; - - protected static final String EMPTY_STRING = ""; - - private String extension; - - private AbstractNacosDataParser nextParser; - - protected AbstractNacosDataParser(String extension) { - if (StringUtils.isEmpty(extension)) { - throw new IllegalArgumentException("extension cannot be empty"); - } - this.extension = extension.toLowerCase(); - } - - /** - * Verify dataId extensions. - * @param extension file extension. json or xml or yml or yaml or properties - * @return valid or not - */ - public final boolean checkFileExtension(String extension) { - if (this.isLegal(extension.toLowerCase())) { - return true; - } - if (this.nextParser == null) { - return false; - } - return this.nextParser.checkFileExtension(extension); - - } - - /** - * Parsing nacos configuration content. - * @param data config data from Nacos - * @param extension file extension. json or xml or yml or yaml or properties - * @return result of Properties - * @throws IOException thrown if there is a problem parsing config. - */ - public final Map parseNacosData(String data, String extension) - throws IOException { - if (extension == null || extension.length() < 1) { - throw new IllegalStateException("The file extension cannot be empty"); - } - if (this.isLegal(extension.toLowerCase())) { - return this.doParse(data); - } - if (this.nextParser == null) { - throw new IllegalStateException(getTips(extension)); - } - return this.nextParser.parseNacosData(data, extension); - } - - /** - * Core logic for parsing. - * @param data config from Nacos - * @return result of Properties - * @throws IOException thrown if there is a problem parsing config. - */ - protected abstract Map doParse(String data) throws IOException; - - protected AbstractNacosDataParser setNextParser(AbstractNacosDataParser nextParser) { - this.nextParser = nextParser; - return this; - } - - public AbstractNacosDataParser addNextParser(AbstractNacosDataParser nextParser) { - if (this.nextParser == null) { - this.nextParser = nextParser; - } - else { - this.nextParser.addNextParser(nextParser); - } - return this; - } - - protected boolean isLegal(String extension) { - return this.extension.equalsIgnoreCase(extension) - || this.extension.contains(extension); - } - - protected void flattenedMap(Map result, Map dataMap, - String parentKey) { - Set> entries = dataMap.entrySet(); - for (Iterator> iterator = entries.iterator(); iterator - .hasNext();) { - Map.Entry entry = iterator.next(); - String key = entry.getKey(); - Object value = entry.getValue(); - - String fullKey = StringUtils.isEmpty(parentKey) ? key : key.startsWith("[") - ? parentKey.concat(key) : parentKey.concat(DOT).concat(key); - - if (value instanceof Map) { - Map map = (Map) value; - flattenedMap(result, map, fullKey); - continue; - } - else if (value instanceof Collection) { - int count = 0; - Collection collection = (Collection) value; - for (Object object : collection) { - flattenedMap(result, - Collections.singletonMap("[" + (count++) + "]", object), - fullKey); - } - continue; - } - - result.put(fullKey, value); - } - } - - /** - * Reload the key ending in `value` if need. - */ - protected Map reloadMap(Map map) { - if (map == null || map.isEmpty()) { - return null; - } - Map result = new LinkedHashMap<>(map); - for (Map.Entry entry : map.entrySet()) { - String key = entry.getKey(); - if (key.contains(DOT)) { - int idx = key.lastIndexOf(DOT); - String suffix = key.substring(idx + 1); - if (VALUE.equalsIgnoreCase(suffix)) { - result.put(key.substring(0, idx), entry.getValue()); - } - } - } - return result; - } - - public static String getTips(String fileName) { - return String.format( - "[%s] must contains file extension with properties|yaml|yml|xml|json", - fileName); - } - -} diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/AbstractPropertySourceLoader.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/AbstractPropertySourceLoader.java new file mode 100644 index 000000000..7218c2243 --- /dev/null +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/AbstractPropertySourceLoader.java @@ -0,0 +1,128 @@ +/* + * Copyright 2013-2018 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.parser; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.springframework.boot.env.PropertySourceLoader; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.Resource; +import org.springframework.util.StringUtils; + +import static com.alibaba.cloud.nacos.parser.NacosDataParserHandler.DOT; + +/** + * Nacos-specific loader, If need to support other methods of parsing,you need to do the + * following steps: + *

+ * 1.inherit {@link AbstractPropertySourceLoader} ;
+ * 2. define the file{@code spring.factories} and append + * {@code org.springframework.boot.env.PropertySourceLoader=..};
+ * 3.the last step validate. + *

+ * Notice the use of {@link NacosByteArrayResource} . + * + * @author zkz + */ +public abstract class AbstractPropertySourceLoader implements PropertySourceLoader { + + /** + * Prevent interference with other loaders.Nacos-specific loader, unless the reload + * changes it. + * @param name + * @param resource + * @return + */ + protected boolean canLoad(String name, Resource resource) { + return resource instanceof NacosByteArrayResource; + } + + /** + * Load the resource into one or more property sources. Implementations may either + * return a list containing a single source, or in the case of a multi-document format + * such as yaml a source for each document in the resource. + * @param name the root name of the property source. If multiple documents are loaded + * an additional suffix should be added to the name for each source loaded. + * @param resource the resource to load + * @return a list property sources + * @throws IOException if the source cannot be loaded + */ + @Override + public List> load(String name, Resource resource) + throws IOException { + if (!canLoad(name, resource)) { + return Collections.emptyList(); + } + return this.doLoad(name, resource); + } + + /** + * Load the resource into one or more property sources. Implementations may either + * return a list containing a single source, or in the case of a multi-document format + * such as yaml a source for each document in the resource. + * @param name the root name of the property source. If multiple documents are loaded + * an additional suffix should be added to the name for each source loaded. + * @param resource the resource to load + * @return a list property sources + * @throws IOException if the source cannot be loaded + */ + protected abstract List> doLoad(String name, Resource resource) + throws IOException; + + protected void flattenedMap(Map result, Map dataMap, + String parentKey) { + if (dataMap == null || dataMap.isEmpty()) { + return; + } + Set> entries = dataMap.entrySet(); + for (Iterator> iterator = entries.iterator(); iterator + .hasNext();) { + Map.Entry entry = iterator.next(); + String key = entry.getKey(); + Object value = entry.getValue(); + + String fullKey = StringUtils.isEmpty(parentKey) ? key : key.startsWith("[") + ? parentKey.concat(key) : parentKey.concat(DOT).concat(key); + + if (value instanceof Map) { + Map map = (Map) value; + flattenedMap(result, map, fullKey); + continue; + } + else if (value instanceof Collection) { + int count = 0; + Collection collection = (Collection) value; + for (Object object : collection) { + flattenedMap(result, + Collections.singletonMap("[" + (count++) + "]", object), + fullKey); + } + continue; + } + + result.put(fullKey, value); + } + } + +} diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/JsonPropertySourceLoader.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/JsonPropertySourceLoader.java new file mode 100644 index 000000000..ad767ce42 --- /dev/null +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/JsonPropertySourceLoader.java @@ -0,0 +1,87 @@ +/* + * Copyright 2013-2018 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.parser; + +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.boot.env.OriginTrackedMapPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.Resource; + +import static com.alibaba.cloud.nacos.parser.NacosDataParserHandler.DOT; +import static com.alibaba.cloud.nacos.parser.NacosDataParserHandler.VALUE; + +/** + * @author zkz + */ +public class JsonPropertySourceLoader extends AbstractPropertySourceLoader { + + /** + * Returns the file extensions that the loader supports (excluding the '.'). + * @return the file extensions + */ + @Override + public String[] getFileExtensions() { + return new String[] { "json" }; + } + + /** + * @param name + * @param resource + * @return + */ + @Override + protected List> doLoad(String name, Resource resource) + throws IOException { + Map result = new LinkedHashMap<>(32); + ObjectMapper mapper = new ObjectMapper(); + Map nacosDataMap = mapper.readValue(resource.getInputStream(), + LinkedHashMap.class); + flattenedMap(result, this.reloadMap(nacosDataMap), null); + return Collections.singletonList( + new OriginTrackedMapPropertySource(name, nacosDataMap, true)); + + } + + /** + * Reload the key ending in `value` if need. + */ + protected Map reloadMap(Map map) { + if (map == null || map.isEmpty()) { + return null; + } + Map result = new LinkedHashMap<>(map); + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + if (key.contains(DOT)) { + int idx = key.lastIndexOf(DOT); + String suffix = key.substring(idx + 1); + if (VALUE.equalsIgnoreCase(suffix)) { + result.put(key.substring(0, idx), entry.getValue()); + } + } + } + return result; + } + +} diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosByteArrayResource.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosByteArrayResource.java new file mode 100644 index 000000000..e0820344b --- /dev/null +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosByteArrayResource.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013-2018 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.parser; + +import org.springframework.core.io.ByteArrayResource; + +/** + * Nacos-specific resource + * + * @author zkz + */ +public class NacosByteArrayResource extends ByteArrayResource { + + private String filename; + + /** + * Create a new {@code ByteArrayResource}. + * @param byteArray the byte array to wrap + */ + public NacosByteArrayResource(byte[] byteArray) { + super(byteArray); + } + + /** + * Create a new {@code ByteArrayResource} with a description. + * @param byteArray the byte array to wrap + * @param description where the byte array comes from + */ + public NacosByteArrayResource(byte[] byteArray, String description) { + super(byteArray, description); + } + + public void setFilename(String filename) { + this.filename = filename; + } + + /** + * This implementation always returns {@code null}, assuming that this resource type + * does not have a filename. + */ + @Override + public String getFilename() { + return null == this.filename ? this.getDescription() : this.filename; + } + +} diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataJsonParser.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataJsonParser.java deleted file mode 100644 index d33cf62f8..000000000 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataJsonParser.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2013-2018 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.parser; - -import java.io.IOException; -import java.util.LinkedHashMap; -import java.util.Map; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * @author zkz - * @author yuhuangbin - */ -public class NacosDataJsonParser extends AbstractNacosDataParser { - - protected NacosDataJsonParser() { - super("json"); - } - - @Override - protected Map doParse(String data) throws IOException { - if (StringUtils.isEmpty(data)) { - return null; - } - Map map = parseJSON2Map(data); - return this.reloadMap(map); - } - - /** - * JSON to Map. - * @param json json data - * @return the map convert by json string - * @throws IOException thrown if there is a problem parsing config. - */ - private Map parseJSON2Map(String json) throws IOException { - Map result = new LinkedHashMap<>(32); - - ObjectMapper mapper = new ObjectMapper(); - Map nacosDataMap = mapper.readValue(json, LinkedHashMap.class); - - if (CollectionUtils.isEmpty(nacosDataMap)) { - return result; - } - flattenedMap(result, nacosDataMap, EMPTY_STRING); - return result; - } - -} diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataParserHandler.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataParserHandler.java index f5787e791..120abd0b1 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataParserHandler.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataParserHandler.java @@ -17,63 +17,109 @@ package com.alibaba.cloud.nacos.parser; import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.springframework.boot.env.OriginTrackedMapPropertySource; +import org.springframework.boot.env.PropertySourceLoader; +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedCaseInsensitiveMap; +import org.springframework.util.StringUtils; /** * @author zkz */ public final class NacosDataParserHandler { - private AbstractNacosDataParser parser; + public static final String DOT = "."; + + public static final String VALUE = "value"; + + public static final String DEFAULT_EXTENSION = "properties"; + + private static List propertySourceLoaders; private NacosDataParserHandler() { - parser = this.createParser(); + propertySourceLoaders = SpringFactoriesLoader + .loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); } /** * Parsing nacos configuration content. - * @param data config from Nacos - * @param extension file extension. json or xml or yml or yaml or properties - * @return result of LinkedHashMap + * @param configName name of nacos-config + * @param configValue value from nacos-config + * @param extension identifies the type of configValue + * @return result of Map * @throws IOException thrown if there is a problem parsing config. */ - public Map parseNacosData(String data, String extension) - throws IOException { - if (null == parser) { - parser = this.createParser(); + public List> parseNacosData(String configName, String configValue, + String extension) throws IOException { + if (StringUtils.isEmpty(configValue)) { + return Collections.emptyList(); } - return parser.parseNacosData(data, extension); + if (StringUtils.isEmpty(extension)) { + extension = this.getFileExtension(configName); + } + for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) { + if (!canLoadFileExtension(propertySourceLoader, extension)) { + continue; + } + NacosByteArrayResource nacosByteArrayResource = new NacosByteArrayResource( + configValue.getBytes(), configName); + nacosByteArrayResource.setFilename(configName + DOT + extension); + List> propertySourceList = propertySourceLoader + .load(configName, nacosByteArrayResource); + if (CollectionUtils.isEmpty(propertySourceList)) { + return Collections.emptyList(); + } + return propertySourceList.stream().filter(Objects::nonNull) + .map(propertySource -> { + if (propertySource instanceof EnumerablePropertySource) { + String[] propertyNames = ((EnumerablePropertySource) propertySource) + .getPropertyNames(); + if (propertyNames != null && propertyNames.length > 0) { + Map map = new LinkedHashMap<>(); + Arrays.stream(propertyNames).forEach(name -> { + map.put(name, propertySource.getProperty(name)); + }); + return new OriginTrackedMapPropertySource( + propertySource.getName(), map, true); + } + } + return propertySource; + }).collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + private boolean canLoadFileExtension(PropertySourceLoader loader, String extension) { + return Arrays.stream(loader.getFileExtensions()) + .anyMatch((fileExtension) -> StringUtils.endsWithIgnoreCase(extension, + fileExtension)); } /** - * check the validity of file extensions in dataid. - * @param dataIdAry array of dataId - * @return dataId handle success or not + * @param name filename + * @return file extension, default {@code DEFAULT_EXTENSION} if don't get */ - public boolean checkDataId(String... dataIdAry) { - StringBuilder stringBuilder = new StringBuilder(); - for (String dataId : dataIdAry) { - int idx = dataId.lastIndexOf(AbstractNacosDataParser.DOT); - if (idx > 0 && idx < dataId.length() - 1) { - String extension = dataId.substring(idx + 1); - if (parser.checkFileExtension(extension)) { - break; - } - } - // add tips - stringBuilder.append(dataId).append(","); + public String getFileExtension(String name) { + if (StringUtils.isEmpty(name)) { + return DEFAULT_EXTENSION; } - if (stringBuilder.length() > 0) { - String result = stringBuilder.substring(0, stringBuilder.length() - 1); - throw new IllegalStateException(AbstractNacosDataParser.getTips(result)); + int idx = name.lastIndexOf(DOT); + if (idx > 0 && idx < name.length() - 1) { + return name.substring(idx + 1); } - return true; - } - - private AbstractNacosDataParser createParser() { - return new NacosDataPropertiesParser().addNextParser(new NacosDataYamlParser()) - .addNextParser(new NacosDataXmlParser()) - .addNextParser(new NacosDataJsonParser()); + return DEFAULT_EXTENSION; } public static NacosDataParserHandler getInstance() { diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataPropertiesParser.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataPropertiesParser.java deleted file mode 100644 index 6dec190e8..000000000 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataPropertiesParser.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2013-2018 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.parser; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.StringReader; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.util.StringUtils; - -/** - * @author zkz - */ -public class NacosDataPropertiesParser extends AbstractNacosDataParser { - - private static final Logger log = LoggerFactory - .getLogger(NacosDataPropertiesParser.class); - - public NacosDataPropertiesParser() { - super("properties"); - } - - @Override - protected Map doParse(String data) throws IOException { - Map result = new LinkedHashMap<>(); - - try (BufferedReader reader = new BufferedReader(new StringReader(data))) { - for (String line = reader.readLine(); line != null; line = reader - .readLine()) { - String dataLine = line.trim(); - if (StringUtils.isEmpty(dataLine) || dataLine.startsWith("#")) { - continue; - } - int index = dataLine.indexOf("="); - if (index == -1) { - log.warn("the config data is invalid {}", dataLine); - continue; - } - String key = dataLine.substring(0, index); - String value = dataLine.substring(index + 1); - result.put(key.trim(), value.trim()); - } - } - return result; - } - -} diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataXmlParser.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataXmlParser.java deleted file mode 100644 index eab890b9b..000000000 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataXmlParser.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2013-2018 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.parser; - -import java.io.IOException; -import java.io.StringReader; -import java.util.LinkedHashMap; -import java.util.Map; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; - -import org.w3c.dom.Document; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; - -import org.springframework.util.StringUtils; - -/** - * With relatively few usage scenarios, only simple parsing is performed to reduce jar - * dependencies. - * - * @author zkz - */ -public class NacosDataXmlParser extends AbstractNacosDataParser { - - public NacosDataXmlParser() { - super("xml"); - } - - @Override - protected Map doParse(String data) throws IOException { - if (StringUtils.isEmpty(data)) { - return null; - } - Map map = parseXml2Map(data); - return this.reloadMap(map); - } - - private Map parseXml2Map(String xml) throws IOException { - xml = xml.replaceAll("\\r", "").replaceAll("\\n", "").replaceAll("\\t", ""); - Map map = new LinkedHashMap<>(32); - try { - DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance() - .newDocumentBuilder(); - Document document = documentBuilder - .parse(new InputSource(new StringReader(xml))); - if (null == document) { - return null; - } - parseNodeList(document.getChildNodes(), map, ""); - } - catch (Exception e) { - throw new IOException("The xml content parse error.", e.getCause()); - } - return map; - } - - private void parseNodeList(NodeList nodeList, Map map, - String parentKey) { - if (nodeList == null || nodeList.getLength() < 1) { - return; - } - parentKey = parentKey == null ? "" : parentKey; - for (int i = 0; i < nodeList.getLength(); i++) { - Node node = nodeList.item(i); - String value = node.getNodeValue(); - value = value == null ? "" : value.trim(); - String name = node.getNodeName(); - name = name == null ? "" : name.trim(); - - if (StringUtils.isEmpty(name)) { - continue; - } - - String key = StringUtils.isEmpty(parentKey) ? name : parentKey + DOT + name; - NamedNodeMap nodeMap = node.getAttributes(); - parseNodeAttr(nodeMap, map, key); - if (node.getNodeType() == Node.ELEMENT_NODE && node.hasChildNodes()) { - parseNodeList(node.getChildNodes(), map, key); - continue; - } - if (value.length() < 1) { - continue; - } - map.put(parentKey, value); - } - } - - private void parseNodeAttr(NamedNodeMap nodeMap, Map map, - String parentKey) { - if (null == nodeMap || nodeMap.getLength() < 1) { - return; - } - for (int i = 0; i < nodeMap.getLength(); i++) { - Node node = nodeMap.item(i); - if (null == node) { - continue; - } - if (node.getNodeType() == Node.ATTRIBUTE_NODE) { - if (StringUtils.isEmpty(node.getNodeName())) { - continue; - } - if (StringUtils.isEmpty(node.getNodeValue())) { - continue; - } - map.put(String.join(DOT, parentKey, node.getNodeName()), - node.getNodeValue()); - } - } - } - -} diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataYamlParser.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataYamlParser.java deleted file mode 100644 index b963a0971..000000000 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/parser/NacosDataYamlParser.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2013-2018 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.parser; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.springframework.beans.factory.config.YamlMapFactoryBean; -import org.springframework.core.io.ByteArrayResource; - -/** - * @author zkz - */ -public class NacosDataYamlParser extends AbstractNacosDataParser { - - public NacosDataYamlParser() { - super(",yml,yaml,"); - } - - @Override - protected Map doParse(String data) { - YamlMapFactoryBean yamlFactory = new YamlMapFactoryBean(); - yamlFactory.setResources(new ByteArrayResource(data.getBytes())); - - Map result = new LinkedHashMap<>(); - flattenedMap(result, yamlFactory.getObject(), EMPTY_STRING); - return result; - } - -} 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 9977a8549..c8799777c 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 @@ -4,4 +4,6 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.alibaba.cloud.nacos.NacosConfigAutoConfiguration,\ com.alibaba.cloud.nacos.endpoint.NacosConfigEndpointAutoConfiguration org.springframework.boot.diagnostics.FailureAnalyzer=\ -com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer \ No newline at end of file +com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer +org.springframework.boot.env.PropertySourceLoader=\ +com.alibaba.cloud.nacos.parser.JsonPropertySourceLoader \ No newline at end of file