提交 c3cf0f74 authored 作者: chenshiying's avatar chenshiying

[新增] nacos 版本升级2.0.3

上级 cceef1d1
差异被折叠。
......@@ -14,7 +14,7 @@
<groupId>com.timeloit.cloud</groupId>
<artifactId>spring-cloud-timeloit</artifactId>
<version>2.3.3-SNAPSHOT</version>
<version>2.2.8-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Spring Cloud Timeloit</name>
......@@ -36,7 +36,7 @@
<properties>
<!-- Project revision -->
<revision>2.3.3-SNAPSHOT</revision>
<revision>2.2.8-SNAPSHOT</revision>
<!-- Dependency Versions -->
<spring-cloud-commons.version>2.2.5.RELEASE</spring-cloud-commons.version>
......@@ -52,7 +52,7 @@
<junit.version>4.12</junit.version>
<javax-servlet-api>3.0</javax-servlet-api>
<slf4j-api.version>1.7.25</slf4j-api.version>
<slf4j-api.version>1.7.30</slf4j-api.version>
<!-- Apache Dubbo -->
<dubbo.version>2.7.8</dubbo.version>
......
......@@ -13,7 +13,7 @@
<groupId>com.timeloit.cloud</groupId>
<artifactId>spring-cloud-timeloit-dependencies</artifactId>
<version>2.3.3-SNAPSHOT</version>
<version>2.2.8-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Spring Cloud Timeloit</name>
......@@ -21,14 +21,14 @@
<properties>
<revision>2.3.3-SNAPSHOT</revision>
<revision>2.2.8-SNAPSHOT</revision>
<sentinel.version>1.8.0</sentinel.version>
<sentinel.version>1.8.1</sentinel.version>
<oss.version>3.1.0</oss.version>
<seata.version>1.3.0</seata.version>
<nacos.client.version>1.2.1</nacos.client.version>
<nacos.client.version>2.0.3</nacos.client.version>
<nacos.config.version>0.8.0</nacos.config.version>
<spring.context.support.version>1.0.9</spring.context.support.version>
<spring.context.support.version>1.0.11</spring.context.support.version>
<!-- Maven Plugin Versions -->
<maven-source-plugin.version>2.2.1</maven-source-plugin.version>
......@@ -190,6 +190,12 @@
<!-- Own dependencies -->
<dependency>
<groupId>com.timeloit.cloud</groupId>
<artifactId>spring-cloud-timeloit-commons</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.timeloit.cloud</groupId>
<artifactId>spring-cloud-circuitbreaker-sentinel</artifactId>
......
......@@ -5,7 +5,7 @@
<parent>
<artifactId>spring-cloud-timeloit</artifactId>
<groupId>com.timeloit.cloud</groupId>
<version>2.3.3-SNAPSHOT</version>
<version>2.2.8-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
......@@ -21,6 +21,7 @@
<module>spring-cloud-timeloit-sentinel-datasource</module>
<module>spring-cloud-timeloit-sentinel-gateway</module>
<module>spring-cloud-starter-timeloit-sentinel</module>
<module>spring-cloud-timeloit-commons</module>
</modules>
</project>
......@@ -5,7 +5,7 @@
<parent>
<artifactId>spring-cloud-timeloit-starters</artifactId>
<groupId>com.timeloit.cloud</groupId>
<version>2.3.3-SNAPSHOT</version>
<version>2.2.8-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
......
......@@ -5,7 +5,7 @@
<parent>
<artifactId>spring-cloud-timeloit-starters</artifactId>
<groupId>com.timeloit.cloud</groupId>
<version>2.3.3-SNAPSHOT</version>
<version>2.2.8-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
......@@ -51,15 +51,10 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-test-support</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
......@@ -5,7 +5,7 @@
<parent>
<artifactId>spring-cloud-timeloit-starters</artifactId>
<groupId>com.timeloit.cloud</groupId>
<version>2.3.3-SNAPSHOT</version>
<version>2.2.8-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
......@@ -87,12 +87,6 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-test-support</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.powermock.modules.test.powermockito/powermock-modules-test-powermockito -->
<dependency>
<groupId>org.powermock</groupId>
......@@ -110,5 +104,4 @@
</dependencies>
</project>
......@@ -53,6 +53,7 @@ import static com.alibaba.nacos.api.PropertyKeyConst.ENDPOINT_PORT;
import static com.alibaba.nacos.api.PropertyKeyConst.MAX_RETRY;
import static com.alibaba.nacos.api.PropertyKeyConst.NAMESPACE;
import static com.alibaba.nacos.api.PropertyKeyConst.PASSWORD;
import static com.alibaba.nacos.api.PropertyKeyConst.RAM_ROLE_NAME;
import static com.alibaba.nacos.api.PropertyKeyConst.SECRET_KEY;
import static com.alibaba.nacos.api.PropertyKeyConst.SERVER_ADDR;
import static com.alibaba.nacos.api.PropertyKeyConst.USERNAME;
......@@ -202,6 +203,11 @@ public class NacosConfigProperties {
*/
private String secretKey;
/**
* access key for namespace.
*/
private String ramRoleName;
/**
* context path for nacos config server.
*/
......@@ -356,6 +362,14 @@ public class NacosConfigProperties {
this.secretKey = secretKey;
}
public String getRamRoleName() {
return ramRoleName;
}
public void setRamRoleName(String ramRoleName) {
this.ramRoleName = ramRoleName;
}
public String getEncode() {
return encode;
}
......@@ -548,6 +562,7 @@ public class NacosConfigProperties {
properties.put(NAMESPACE, Objects.toString(this.namespace, ""));
properties.put(ACCESS_KEY, Objects.toString(this.accessKey, ""));
properties.put(SECRET_KEY, Objects.toString(this.secretKey, ""));
properties.put(RAM_ROLE_NAME, Objects.toString(this.ramRoleName, ""));
properties.put(CLUSTER_NAME, Objects.toString(this.clusterName, ""));
properties.put(MAX_RETRY, Objects.toString(this.maxRetry, ""));
properties.put(CONFIG_LONG_POLL_TIMEOUT,
......@@ -560,8 +575,7 @@ public class NacosConfigProperties {
int index = endpoint.indexOf(":");
properties.put(ENDPOINT, endpoint.substring(0, index));
properties.put(ENDPOINT_PORT, endpoint.substring(index + 1));
}
else {
} else {
properties.put(ENDPOINT, endpoint);
}
......@@ -597,6 +611,7 @@ public class NacosConfigProperties {
+ ", enableRemoteSyncConfig=" + enableRemoteSyncConfig + ", endpoint='"
+ endpoint + '\'' + ", namespace='" + namespace + '\'' + ", accessKey='"
+ accessKey + '\'' + ", secretKey='" + secretKey + '\''
+ ", ramRoleName='" + ramRoleName + '\''
+ ", contextPath='" + contextPath + '\'' + ", clusterName='" + clusterName
+ '\'' + ", name='" + name + '\'' + '\'' + ", shares=" + sharedConfigs
+ ", extensions=" + extensionConfigs + ", refreshEnabled="
......
......@@ -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<PropertySource<?>> propertySources, String group,
String dataId, Date timestamp, boolean isRefreshable) {
this(group, dataId, getSourceMap(group, dataId, propertySources), timestamp,
isRefreshable);
}
private static Map<String, Object> getSourceMap(String group, String dataId,
List<PropertySource<?>> 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<String, Object>) 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;
}
......
......@@ -16,9 +16,9 @@
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;
......@@ -27,6 +27,7 @@ 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 +39,6 @@ public class NacosPropertySourceBuilder {
private static final Logger log = LoggerFactory
.getLogger(NacosPropertySourceBuilder.class);
private static final Map<String, Object> EMPTY_MAP = new LinkedHashMap();
private ConfigService configService;
private long timeout;
......@@ -71,14 +70,15 @@ public class NacosPropertySourceBuilder {
*/
NacosPropertySource build(String dataId, String group, String fileExtension,
boolean isRefreshable) {
Map<String, Object> p = loadNacosData(dataId, group, fileExtension);
NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId,
p, new Date(), isRefreshable);
List<PropertySource<?>> propertySources = loadNacosData(dataId, group,
fileExtension);
NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,
group, dataId, new Date(), isRefreshable);
NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
return nacosPropertySource;
}
private Map<String, Object> loadNacosData(String dataId, String group,
private List<PropertySource<?>> loadNacosData(String dataId, String group,
String fileExtension) {
String data = null;
try {
......@@ -87,24 +87,23 @@ 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<String, Object> 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);
log.error("get data from Nacos error,dataId:{} ", dataId, e);
}
catch (Exception e) {
log.error("parse data from Nacos error,dataId:{},data:{},", dataId, data, e);
log.error("parse data from Nacos error,dataId:{},data:{}", dataId, data, e);
}
return EMPTY_MAP;
return Collections.emptyList();
}
}
......@@ -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<NacosConfigProperties.Config> 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<NacosConfigProperties.Config> 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,
......
......@@ -36,7 +36,7 @@ import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
*
* @author xiaojing
*/
@Endpoint(id = "nacos-config")
@Endpoint(id = "nacosconfig")
public class NacosConfigEndpoint {
private final NacosConfigProperties properties;
......
......@@ -32,6 +32,16 @@ public class NacosConfigHealthIndicator extends AbstractHealthIndicator {
private final ConfigService configService;
/**
* status up .
*/
private final String STATUS_UP = "UP";
/**
* status down .
*/
private final String STATUS_DOWN = "DOWN";
public NacosConfigHealthIndicator(ConfigService configService) {
this.configService = configService;
}
......@@ -43,10 +53,10 @@ public class NacosConfigHealthIndicator extends AbstractHealthIndicator {
// Set the status to Builder
builder.status(status);
switch (status) {
case "UP":
case STATUS_UP:
builder.up();
break;
case "DOWN":
case STATUS_DOWN:
builder.down();
break;
default:
......
/*
* 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.logging;
import com.alibaba.nacos.client.logging.NacosLogging;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.GenericApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
/**
* Reload nacos log configuration file, after
* {@link org.springframework.boot.context.logging.LoggingApplicationListener}.
*
* @author mai.jh
*/
public class NacosLoggingListener implements GenericApplicationListener {
@Override
public boolean supportsEventType(ResolvableType resolvableType) {
Class<?> type = resolvableType.getRawClass();
if (type != null) {
return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(type);
}
return false;
}
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
NacosLogging.getInstance().loadConfiguration();
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 21;
}
}
......@@ -20,103 +20,87 @@ import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
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;
/**
* Nacos-specific loader, If need to support other methods of parsing,you need to do the
* following steps:
* <p>
* 1.inherit {@link AbstractPropertySourceLoader} ;<br/>
* 2. define the file{@code spring.factories} and append
* {@code org.springframework.boot.env.PropertySourceLoader=..}; <br/>
* 3.the last step validate.
* </p>
* Notice the use of {@link NacosByteArrayResource} .
*
* @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();
}
public abstract class AbstractPropertySourceLoader implements PropertySourceLoader {
/**
* Verify dataId extensions.
* @param extension file extension. json or xml or yml or yaml or properties
* @return valid or not
* symbol: dot.
*/
public final boolean checkFileExtension(String extension) {
if (this.isLegal(extension.toLowerCase())) {
return true;
}
if (this.nextParser == null) {
return false;
}
return this.nextParser.checkFileExtension(extension);
static final String DOT = ".";
/**
* Prevent interference with other loaders.Nacos-specific loader, unless the reload
* changes it.
* @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 if the resource can be loaded
*/
protected boolean canLoad(String name, Resource resource) {
return resource instanceof NacosByteArrayResource;
}
/**
* 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.
* 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
*/
public final Map<String, Object> parseNacosData(String data, String extension)
@Override
public List<PropertySource<?>> load(String name, Resource resource)
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 (!canLoad(name, resource)) {
return Collections.emptyList();
}
if (this.nextParser == null) {
throw new IllegalStateException(getTips(extension));
}
return this.nextParser.parseNacosData(data, extension);
return this.doLoad(name, resource);
}
/**
* Core logic for parsing.
* @param data config from Nacos
* @return result of Properties
* @throws IOException thrown if there is a problem parsing config.
* 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 Map<String, Object> 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 abstract List<PropertySource<?>> doLoad(String name, Resource resource)
throws IOException;
protected void flattenedMap(Map<String, Object> result, Map<String, Object> dataMap,
String parentKey) {
Set<Map.Entry<String, Object>> entries = dataMap.entrySet();
for (Iterator<Map.Entry<String, Object>> iterator = entries.iterator(); iterator
if (dataMap == null || dataMap.isEmpty()) {
return;
}
Set<Entry<String, Object>> entries = dataMap.entrySet();
for (Iterator<Entry<String, Object>> iterator = entries.iterator(); iterator
.hasNext();) {
Map.Entry<String, Object> entry = iterator.next();
String key = entry.getKey();
......@@ -145,31 +129,4 @@ public abstract class AbstractNacosDataParser {
}
}
/**
* Reload the key ending in `value` if need.
*/
protected Map<String, Object> reloadMap(Map<String, Object> map) {
if (map == null || map.isEmpty()) {
return null;
}
Map<String, Object> result = new LinkedHashMap<>(map);
for (Map.Entry<String, Object> 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);
}
}
......@@ -16,51 +16,45 @@
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;
import org.springframework.core.io.ByteArrayResource;
/**
* Nacos-specific resource.
*
* @author zkz
* @author yuhuangbin
*/
public class NacosDataJsonParser extends AbstractNacosDataParser {
public class NacosByteArrayResource extends ByteArrayResource {
protected NacosDataJsonParser() {
super("json");
}
private String filename;
@Override
protected Map<String, Object> doParse(String data) throws IOException {
if (StringUtils.isEmpty(data)) {
return null;
}
Map<String, Object> map = parseJSON2Map(data);
return this.reloadMap(map);
/**
* Create a new {@code ByteArrayResource}.
* @param byteArray the byte array to wrap
*/
public NacosByteArrayResource(byte[] byteArray) {
super(byteArray);
}
/**
* JSON to Map.
* @param json json data
* @return the map convert by json string
* @throws IOException thrown if there is a problem parsing config.
* Create a new {@code ByteArrayResource} with a description.
* @param byteArray the byte array to wrap
* @param description where the byte array comes from
*/
private Map<String, Object> parseJSON2Map(String json) throws IOException {
Map<String, Object> result = new LinkedHashMap<>(32);
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> nacosDataMap = mapper.readValue(json, LinkedHashMap.class);
public NacosByteArrayResource(byte[] byteArray, String description) {
super(byteArray, description);
}
if (CollectionUtils.isEmpty(nacosDataMap)) {
return result;
public void setFilename(String filename) {
this.filename = filename;
}
flattenedMap(result, nacosDataMap, EMPTY_STRING);
return result;
/**
* 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;
}
}
......@@ -17,63 +17,144 @@
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 com.alibaba.cloud.nacos.utils.NacosConfigUtils;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.boot.env.PropertiesPropertySourceLoader;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import static com.alibaba.cloud.nacos.parser.AbstractPropertySourceLoader.DOT;
/**
* @author zkz
*/
public final class NacosDataParserHandler {
private AbstractNacosDataParser parser;
/**
* default extension.
*/
private static final String DEFAULT_EXTENSION = "properties";
private static List<PropertySourceLoader> 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<String, Object> parseNacosData(String data, String extension)
throws IOException {
if (null == parser) {
parser = this.createParser();
public List<PropertySource<?>> parseNacosData(String configName, String configValue,
String extension) throws IOException {
if (StringUtils.isEmpty(configValue)) {
return Collections.emptyList();
}
if (StringUtils.isEmpty(extension)) {
extension = this.getFileExtension(configName);
}
for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) {
if (!canLoadFileExtension(propertySourceLoader, extension)) {
continue;
}
NacosByteArrayResource nacosByteArrayResource;
if (propertySourceLoader instanceof PropertiesPropertySourceLoader) {
// PropertiesPropertySourceLoader internal is to use the ISO_8859_1,
// the Chinese will be garbled, needs to transform into unicode.
nacosByteArrayResource = new NacosByteArrayResource(
NacosConfigUtils.selectiveConvertUnicode(configValue).getBytes(),
configName);
}
else {
nacosByteArrayResource = new NacosByteArrayResource(
configValue.getBytes(), configName);
}
nacosByteArrayResource.setFilename(getFileName(configName, extension));
List<PropertySource<?>> propertySourceList = propertySourceLoader
.load(configName, nacosByteArrayResource);
if (CollectionUtils.isEmpty(propertySourceList)) {
return Collections.emptyList();
}
return parser.parseNacosData(data, extension);
return propertySourceList.stream().filter(Objects::nonNull)
.map(propertySource -> {
if (propertySource instanceof EnumerablePropertySource) {
String[] propertyNames = ((EnumerablePropertySource) propertySource)
.getPropertyNames();
if (propertyNames != null && propertyNames.length > 0) {
Map<String, Object> 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();
}
/**
* check the validity of file extensions in dataid.
* @param dataIdAry array of dataId
* @return dataId handle success or not
* check the current extension can be processed.
* @param loader the propertySourceLoader
* @param extension file extension
* @return if can match extension
*/
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;
private boolean canLoadFileExtension(PropertySourceLoader loader, String extension) {
return Arrays.stream(loader.getFileExtensions())
.anyMatch((fileExtension) -> StringUtils.endsWithIgnoreCase(extension,
fileExtension));
}
/**
* @param name filename
* @return file extension, default {@code DEFAULT_EXTENSION} if don't get
*/
public String getFileExtension(String name) {
if (StringUtils.isEmpty(name)) {
return DEFAULT_EXTENSION;
}
// add tips
stringBuilder.append(dataId).append(",");
}
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;
return DEFAULT_EXTENSION;
}
private AbstractNacosDataParser createParser() {
return new NacosDataPropertiesParser().addNextParser(new NacosDataYamlParser())
.addNextParser(new NacosDataXmlParser())
.addNextParser(new NacosDataJsonParser());
private String getFileName(String name, String extension) {
if (StringUtils.isEmpty(extension)) {
return name;
}
if (StringUtils.isEmpty(name)) {
return extension;
}
int idx = name.lastIndexOf(DOT);
if (idx > 0 && idx < name.length() - 1) {
String ext = name.substring(idx + 1);
if (extension.equalsIgnoreCase(ext)) {
return name;
}
}
return name + DOT + extension;
}
public static NacosDataParserHandler getInstance() {
......
/*
* 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;
/**
* @author zkz
*/
public class NacosJsonPropertySourceLoader extends AbstractPropertySourceLoader {
/**
* constant.
*/
private static final String VALUE = "value";
/**
* Returns the file extensions that the loader supports (excluding the '.').
* @return the file extensions
*/
@Override
public String[] getFileExtensions() {
return new String[] { "json" };
}
/**
* 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
protected List<PropertySource<?>> doLoad(String name, Resource resource)
throws IOException {
Map<String, Object> result = new LinkedHashMap<>(32);
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> nacosDataMap = mapper.readValue(resource.getInputStream(),
LinkedHashMap.class);
flattenedMap(result, nacosDataMap, null);
return Collections.singletonList(
new OriginTrackedMapPropertySource(name, this.reloadMap(result), true));
}
/**
* Reload the key ending in `value` if need.
*/
protected Map<String, Object> reloadMap(Map<String, Object> map) {
if (map == null || map.isEmpty()) {
return null;
}
Map<String, Object> result = new LinkedHashMap<>(map);
for (Map.Entry<String, Object> 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;
}
}
......@@ -17,8 +17,9 @@
package com.alibaba.cloud.nacos.parser;
import java.io.IOException;
import java.io.StringReader;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
......@@ -28,39 +29,76 @@ 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.boot.env.OriginTrackedMapPropertySource;
import org.springframework.boot.env.PropertiesPropertySourceLoader;
import org.springframework.core.Ordered;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
/**
* With relatively few usage scenarios, only simple parsing is performed to reduce jar
* dependencies.
* Parsing for XML requires overwriting the default
* {@link PropertiesPropertySourceLoader}, because it internally rigorously validates
* ({@conde DOCTYPE}) THE XML in a way that makes it difficult to customize the
* configuration; at finally, make sure it's in the first place.
*
* @author zkz
*/
public class NacosDataXmlParser extends AbstractNacosDataParser {
public class NacosXmlPropertySourceLoader extends AbstractPropertySourceLoader
implements Ordered {
public NacosDataXmlParser() {
super("xml");
/**
* Get the order value of this object.
* <p>
* Higher values are interpreted as lower priority. As a consequence, the object with
* the lowest value has the highest priority (somewhat analogous to Servlet
* {@code load-on-startup} values).
* <p>
* Same order values will result in arbitrary sort positions for the affected objects.
* @return the order value
* @see #HIGHEST_PRECEDENCE
* @see #LOWEST_PRECEDENCE
*/
@Override
public int getOrder() {
return Integer.MIN_VALUE;
}
/**
* Returns the file extensions that the loader supports (excluding the '.').
* @return the file extensions
*/
@Override
protected Map<String, Object> doParse(String data) throws IOException {
if (StringUtils.isEmpty(data)) {
return null;
public String[] getFileExtensions() {
return new String[] { "xml" };
}
Map<String, Object> map = parseXml2Map(data);
return this.reloadMap(map);
/**
* 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
protected List<PropertySource<?>> doLoad(String name, Resource resource)
throws IOException {
Map<String, Object> nacosDataMap = parseXml2Map(resource);
return Collections.singletonList(
new OriginTrackedMapPropertySource(name, nacosDataMap, true));
}
private Map<String, Object> parseXml2Map(String xml) throws IOException {
xml = xml.replaceAll("\\r", "").replaceAll("\\n", "").replaceAll("\\t", "");
private Map<String, Object> parseXml2Map(Resource resource) throws IOException {
Map<String, Object> map = new LinkedHashMap<>(32);
try {
DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance()
.newDocumentBuilder();
Document document = documentBuilder
.parse(new InputSource(new StringReader(xml)));
Document document = documentBuilder.parse(resource.getInputStream());
if (null == document) {
return null;
}
......
/*
* 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.utils;
/**
* @author zkzlx
*/
public final class NacosConfigUtils {
private NacosConfigUtils() {
}
/**
* Convert Chinese characters to Unicode.
* @param configValue value of config
* @return new string
*/
public static String selectiveConvertUnicode(String configValue) {
StringBuilder sb = new StringBuilder();
char[] chars = configValue.toCharArray();
for (char aChar : chars) {
if (isBaseLetter(aChar)) {
sb.append(aChar);
}
else {
sb.append(String.format("\\u%04x", (int) aChar));
}
}
return sb.toString();
}
/**
* char is base latin or whitespace?
* @param ch a character
* @return true or false
*/
public static boolean isBaseLetter(char ch) {
Character.UnicodeBlock ub = Character.UnicodeBlock.of(ch);
return ub == Character.UnicodeBlock.BASIC_LATIN || Character.isWhitespace(ch);
}
/**
* char is chinese?
* @param c a character
* @return true or false
*/
public static boolean isChinese(char c) {
Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
return ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
|| ub == Character.UnicodeBlock.GENERAL_PUNCTUATION
|| ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
|| ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS;
}
}
......@@ -5,3 +5,8 @@ com.alibaba.cloud.nacos.NacosConfigAutoConfiguration,\
com.alibaba.cloud.nacos.endpoint.NacosConfigEndpointAutoConfiguration
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
org.springframework.context.ApplicationListener=\
com.alibaba.cloud.nacos.logging.NacosLoggingListener
\ No newline at end of file
......@@ -50,7 +50,8 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
*/
@RunWith(PowerMockRunner.class)
@PowerMockIgnore("javax.management.*")
@PowerMockIgnore({ "javax.management.*", "javax.xml.parsers.*",
"com.sun.org.apache.xerces.internal.jaxp.*", "org.w3c.dom.*" })
@PowerMockRunnerDelegate(SpringRunner.class)
@PrepareForTest({ NacosConfigService.class })
@SpringBootTest(classes = NacosConfigurationNoSuffixTest.TestConfig.class, properties = {
......
......@@ -49,7 +49,8 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
* @author zkz
*/
@RunWith(PowerMockRunner.class)
@PowerMockIgnore("javax.management.*")
@PowerMockIgnore({ "javax.management.*", "javax.xml.parsers.*",
"com.sun.org.apache.xerces.internal.jaxp.*", "org.w3c.dom.*" })
@PowerMockRunnerDelegate(SpringRunner.class)
@PrepareForTest({ NacosConfigService.class })
@SpringBootTest(classes = NacosConfigurationXmlJsonTest.TestConfig.class, properties = {
......@@ -83,13 +84,15 @@ public class NacosConfigurationXmlJsonTest {
throws Throwable {
if ("xmlApp.xml".equals(args[0]) && "test-group".equals(args[1])) {
return "<top>\n" + " <first>one</first>\n"
return "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<top>\n"
+ " <first>one</first>\n"
+ " <sencond value=\"two\">\n"
+ " <third>three</third>\n" + " </sencond>\n"
+ "</top>";
}
if ("test-name.xml".equals(args[0]) && "test-group".equals(args[1])) {
return "<Server port=\"8005\" shutdown=\"SHUTDOWN\"> \n"
return "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<Server port=\"8005\" shutdown=\"SHUTDOWN\"> \n"
+ " <Service name=\"Catalina\"> \n"
+ " <Connector value=\"第二个连接器\"> \n"
+ " <open>开启服务</open> \n"
......@@ -108,7 +111,8 @@ public class NacosConfigurationXmlJsonTest {
if ("test-name-dev.xml".equals(args[0])
&& "test-group".equals(args[1])) {
return "<application android:label=\"@string/app_name\" android:icon=\"@drawable/osg\">\n"
return "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<application android:label=\"@string/app_name\" android:icon=\"@drawable/osg\">\n"
+ " <activity android:name=\".osgViewer\"\n"
+ " android:label=\"@string/app_name\" android:screenOrientation=\"landscape\">\n"
+ " <intent-filter>\n"
......
......@@ -5,7 +5,7 @@
<parent>
<artifactId>spring-cloud-timeloit-starters</artifactId>
<groupId>com.timeloit.cloud</groupId>
<version>2.3.3-SNAPSHOT</version>
<version>2.2.8-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
......@@ -15,6 +15,11 @@
<dependencies>
<dependency>
<groupId>com.timeloit.cloud</groupId>
<artifactId>spring-cloud-timeloit-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
......@@ -107,12 +112,6 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-test-support</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
......
......@@ -28,9 +28,8 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import com.alibaba.nacos.api.naming.NamingMaintainService;
import com.alibaba.cloud.nacos.event.NacosDiscoveryInfoChangedEvent;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.PreservedMetadataKeys;
import com.alibaba.nacos.client.naming.utils.UtilAndComs;
......@@ -41,14 +40,12 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import static com.alibaba.nacos.api.NacosFactory.createMaintainService;
import static com.alibaba.nacos.api.NacosFactory.createNamingService;
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;
......@@ -186,17 +183,17 @@ public class NacosDiscoveryProperties {
private String secretKey;
/**
* Heart beat interval. Time unit: second.
* Heart beat interval. Time unit: millisecond.
*/
private Integer heartBeatInterval;
/**
* Heart beat timeout. Time unit: second.
* Heart beat timeout. Time unit: millisecond.
*/
private Integer heartBeatTimeout;
/**
* Ip delete timeout. Time unit: second.
* Ip delete timeout. Time unit: millisecond.
*/
private Integer ipDeleteTimeout;
......@@ -210,15 +207,23 @@ public class NacosDiscoveryProperties {
*/
private boolean ephemeral = true;
/**
* Throw exceptions during service registration if true, otherwise, log error
* (defaults to true).
*/
private boolean failFast = true;
@Autowired
private InetUtils inetUtils;
@Autowired
private Environment environment;
private NamingService namingService;
@Autowired
private NacosServiceManager nacosServiceManager;
private NamingMaintainService namingMaintainService;
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@PostConstruct
public void init() throws Exception {
......@@ -268,15 +273,19 @@ public class NacosDiscoveryProperties {
}
this.overrideFromEnv(environment);
Properties properties = getNacosProperties();
this.namingService = createNamingService(properties);
this.namingMaintainService = createMaintainService(properties);
if (nacosServiceManager.isNacosDiscoveryInfoChanged(this)) {
applicationEventPublisher
.publishEvent(new NacosDiscoveryInfoChangedEvent(this));
}
}
@PreDestroy
public void destroy() {
/**
* recommend to use {@link NacosServiceManager#getNamingService(Properties)}.
* @return NamingService
*/
@Deprecated
public NamingService namingServiceInstance() {
return nacosServiceManager.getNamingService(this.getNacosProperties());
}
public String getEndpoint() {
......@@ -483,6 +492,51 @@ public class NacosDiscoveryProperties {
this.ephemeral = ephemeral;
}
public boolean isFailFast() {
return failFast;
}
public void setFailFast(boolean failFast) {
this.failFast = failFast;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
NacosDiscoveryProperties that = (NacosDiscoveryProperties) o;
return Objects.equals(serverAddr, that.serverAddr)
&& Objects.equals(username, that.username)
&& Objects.equals(password, that.password)
&& Objects.equals(endpoint, that.endpoint)
&& Objects.equals(namespace, that.namespace)
&& Objects.equals(logName, that.logName)
&& Objects.equals(service, that.service)
&& Objects.equals(clusterName, that.clusterName)
&& Objects.equals(group, that.group) && Objects.equals(ip, that.ip)
&& Objects.equals(port, that.port)
&& Objects.equals(networkInterface, that.networkInterface)
&& Objects.equals(accessKey, that.accessKey)
&& Objects.equals(secretKey, that.secretKey)
&& Objects.equals(heartBeatInterval, that.heartBeatInterval)
&& Objects.equals(heartBeatTimeout, that.heartBeatTimeout)
&& Objects.equals(failFast, that.failFast)
&& Objects.equals(ipDeleteTimeout, that.ipDeleteTimeout);
}
@Override
public int hashCode() {
return Objects.hash(serverAddr, username, password, endpoint, namespace,
watchDelay, logName, service, weight, clusterName, group,
namingLoadCacheAtStart, registerEnabled, ip, networkInterface, port,
secure, accessKey, secretKey, heartBeatInterval, heartBeatTimeout,
ipDeleteTimeout, instanceEnabled, ephemeral, failFast);
}
@Override
public String toString() {
return "NacosDiscoveryProperties{" + "serverAddr='" + serverAddr + '\''
......@@ -496,7 +550,7 @@ public class NacosDiscoveryProperties {
+ ", port=" + port + ", secure=" + secure + ", accessKey='" + accessKey
+ '\'' + ", secretKey='" + secretKey + '\'' + ", heartBeatInterval="
+ heartBeatInterval + ", heartBeatTimeout=" + heartBeatTimeout
+ ", ipDeleteTimeout=" + ipDeleteTimeout + '}';
+ ", ipDeleteTimeout=" + ipDeleteTimeout + ", failFast=" + failFast + '}';
}
public void overrideFromEnv(Environment env) {
......@@ -546,16 +600,7 @@ public class NacosDiscoveryProperties {
}
}
public NamingService namingServiceInstance() {
return namingService;
}
@Deprecated
public NamingMaintainService namingMaintainServiceInstance() {
return namingMaintainService;
}
private Properties getNacosProperties() {
public Properties getNacosProperties() {
Properties properties = new Properties();
properties.put(SERVER_ADDR, serverAddr);
properties.put(USERNAME, Objects.toString(username, ""));
......
/*
* 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;
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author yuhuangbin
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
public class NacosServiceAutoConfiguration {
@Bean
public NacosServiceManager nacosServiceManager() {
return new NacosServiceManager();
}
}
/*
* 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;
import java.util.Objects;
import java.util.Properties;
import com.alibaba.cloud.nacos.registry.NacosRegistration;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingMaintainService;
import com.alibaba.nacos.api.naming.NamingService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.discovery.event.InstancePreRegisteredEvent;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.context.event.EventListener;
import static com.alibaba.nacos.api.NacosFactory.createMaintainService;
import static com.alibaba.nacos.api.NacosFactory.createNamingService;
import static org.springframework.beans.BeanUtils.copyProperties;
/**
* @author yuhuangbin
*/
public class NacosServiceManager {
private static final Logger log = LoggerFactory.getLogger(NacosServiceManager.class);
private NacosDiscoveryProperties nacosDiscoveryPropertiesCache;
private NamingService namingService;
private NamingMaintainService namingMaintainService;
public NamingService getNamingService(Properties properties) {
if (Objects.isNull(this.namingService)) {
buildNamingService(properties);
}
return namingService;
}
public NamingMaintainService getNamingMaintainService(Properties properties) {
if (Objects.isNull(namingMaintainService)) {
buildNamingMaintainService(properties);
}
return namingMaintainService;
}
public boolean isNacosDiscoveryInfoChanged(
NacosDiscoveryProperties nacosDiscoveryProperties) {
if (Objects.isNull(nacosDiscoveryPropertiesCache)
|| this.nacosDiscoveryPropertiesCache.equals(nacosDiscoveryProperties)) {
return false;
}
copyProperties(nacosDiscoveryProperties, nacosDiscoveryPropertiesCache);
return true;
}
private NamingMaintainService buildNamingMaintainService(Properties properties) {
if (Objects.isNull(namingMaintainService)) {
synchronized (NacosServiceManager.class) {
if (Objects.isNull(namingMaintainService)) {
namingMaintainService = createNamingMaintainService(properties);
}
}
}
return namingMaintainService;
}
private NamingService buildNamingService(Properties properties) {
if (Objects.isNull(namingService)) {
synchronized (NacosServiceManager.class) {
if (Objects.isNull(namingService)) {
namingService = createNewNamingService(properties);
}
}
}
return namingService;
}
private NamingService createNewNamingService(Properties properties) {
try {
return createNamingService(properties);
}
catch (NacosException e) {
throw new RuntimeException(e);
}
}
private NamingMaintainService createNamingMaintainService(Properties properties) {
try {
return createMaintainService(properties);
}
catch (NacosException e) {
throw new RuntimeException(e);
}
}
public void nacosServiceShutDown() throws NacosException {
if (Objects.nonNull(this.namingService)) {
this.namingService.shutDown();
this.namingService = null;
}
if (Objects.nonNull(this.namingMaintainService)) {
this.namingMaintainService.shutDown();
this.namingMaintainService = null;
}
}
@EventListener
public void onInstancePreRegisteredEvent(
InstancePreRegisteredEvent instancePreRegisteredEvent) {
Registration registration = instancePreRegisteredEvent.getRegistration();
if (Objects.isNull(nacosDiscoveryPropertiesCache)
&& registration instanceof NacosRegistration) {
NacosDiscoveryProperties nacosDiscoveryProperties = ((NacosRegistration) registration)
.getNacosDiscoveryProperties();
nacosDiscoveryPropertiesCache = new NacosDiscoveryProperties();
copyProperties(nacosDiscoveryProperties, nacosDiscoveryPropertiesCache);
}
}
}
......@@ -18,6 +18,7 @@ package com.alibaba.cloud.nacos.discovery;
import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
......@@ -41,8 +42,9 @@ public class NacosDiscoveryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public NacosServiceDiscovery nacosServiceDiscovery(
NacosDiscoveryProperties discoveryProperties) {
return new NacosServiceDiscovery(discoveryProperties);
NacosDiscoveryProperties discoveryProperties,
NacosServiceManager nacosServiceManager) {
return new NacosServiceDiscovery(discoveryProperties, nacosServiceManager);
}
}
......@@ -18,8 +18,8 @@ package com.alibaba.cloud.nacos.discovery;
import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
......@@ -31,7 +31,6 @@ import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
/**
* @author xiaojing
......@@ -56,9 +55,9 @@ public class NacosDiscoveryClientConfiguration {
@ConditionalOnMissingBean
@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.watch.enabled",
matchIfMissing = true)
public NacosWatch nacosWatch(NacosDiscoveryProperties nacosDiscoveryProperties,
ObjectProvider<TaskScheduler> taskScheduler) {
return new NacosWatch(nacosDiscoveryProperties, taskScheduler);
public NacosWatch nacosWatch(NacosServiceManager nacosServiceManager,
NacosDiscoveryProperties nacosDiscoveryProperties) {
return new NacosWatch(nacosServiceManager, nacosDiscoveryProperties);
}
}
......@@ -23,7 +23,9 @@ import java.util.Map;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceInstance;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.pojo.ListView;
......@@ -36,8 +38,12 @@ public class NacosServiceDiscovery {
private NacosDiscoveryProperties discoveryProperties;
public NacosServiceDiscovery(NacosDiscoveryProperties discoveryProperties) {
private NacosServiceManager nacosServiceManager;
public NacosServiceDiscovery(NacosDiscoveryProperties discoveryProperties,
NacosServiceManager nacosServiceManager) {
this.discoveryProperties = discoveryProperties;
this.nacosServiceManager = nacosServiceManager;
}
/**
......@@ -48,8 +54,8 @@ public class NacosServiceDiscovery {
*/
public List<ServiceInstance> getInstances(String serviceId) throws NacosException {
String group = discoveryProperties.getGroup();
List<Instance> instances = discoveryProperties.namingServiceInstance()
.selectInstances(serviceId, group, true);
List<Instance> instances = namingService().selectInstances(serviceId, group,
true);
return hostToServiceInstanceList(instances, serviceId);
}
......@@ -60,8 +66,8 @@ public class NacosServiceDiscovery {
*/
public List<String> getServices() throws NacosException {
String group = discoveryProperties.getGroup();
ListView<String> services = discoveryProperties.namingServiceInstance()
.getServicesOfServer(1, Integer.MAX_VALUE, group);
ListView<String> services = namingService().getServicesOfServer(1,
Integer.MAX_VALUE, group);
return services.getData();
}
......@@ -105,4 +111,9 @@ public class NacosServiceDiscovery {
return nacosServiceInstance;
}
private NamingService namingService() {
return nacosServiceManager
.getNamingService(discoveryProperties.getNacosProperties());
}
}
......@@ -16,60 +16,73 @@
package com.alibaba.cloud.nacos.discovery;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.listener.Event;
import com.alibaba.nacos.api.naming.listener.EventListener;
import com.alibaba.nacos.api.naming.listener.NamingEvent;
import com.alibaba.nacos.api.naming.pojo.Instance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.SmartLifecycle;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
* @author xiaojing
* @author yuhuangbin
* @author pengfei.lu
*/
public class NacosWatch implements ApplicationEventPublisherAware, SmartLifecycle {
public class NacosWatch implements ApplicationEventPublisherAware, SmartLifecycle, DisposableBean {
private static final Logger log = LoggerFactory.getLogger(NacosWatch.class);
private final NacosDiscoveryProperties properties;
private Map<String, EventListener> listenerMap = new ConcurrentHashMap<>(16);
private final TaskScheduler taskScheduler;
private final AtomicBoolean running = new AtomicBoolean(false);
private final AtomicLong nacosWatchIndex = new AtomicLong(0);
private final AtomicBoolean running = new AtomicBoolean(false);
private ApplicationEventPublisher publisher;
private ScheduledFuture<?> watchFuture;
public NacosWatch(NacosDiscoveryProperties properties) {
this(properties, getTaskScheduler());
}
private NacosServiceManager nacosServiceManager;
private final NacosDiscoveryProperties properties;
private final ThreadPoolTaskScheduler taskScheduler;
public NacosWatch(NacosDiscoveryProperties properties, TaskScheduler taskScheduler) {
public NacosWatch(NacosServiceManager nacosServiceManager,
NacosDiscoveryProperties properties) {
this.nacosServiceManager = nacosServiceManager;
this.properties = properties;
this.taskScheduler = taskScheduler;
this.taskScheduler = getTaskScheduler();
}
/**
* The constructor with {@link NacosDiscoveryProperties} bean and the optional.
* {@link TaskScheduler} bean
* @param properties {@link NacosDiscoveryProperties} bean
* @param taskScheduler the optional {@link TaskScheduler} bean
* @since 2.2.0
*/
public NacosWatch(NacosDiscoveryProperties properties,
ObjectProvider<TaskScheduler> taskScheduler) {
this(properties, taskScheduler.getIfAvailable(NacosWatch::getTaskScheduler));
@Deprecated
public NacosWatch(NacosServiceManager nacosServiceManager,
NacosDiscoveryProperties properties,
ObjectProvider<ThreadPoolTaskScheduler> taskScheduler) {
this.nacosServiceManager = nacosServiceManager;
this.properties = properties;
this.taskScheduler = taskScheduler.stream().findAny()
.orElseGet(NacosWatch::getTaskScheduler);
}
private static ThreadPoolTaskScheduler getTaskScheduler() {
......@@ -98,20 +111,76 @@ public class NacosWatch implements ApplicationEventPublisherAware, SmartLifecycl
@Override
public void start() {
if (this.running.compareAndSet(false, true)) {
EventListener eventListener = listenerMap.computeIfAbsent(buildKey(),
event -> new EventListener() {
@Override
public void onEvent(Event event) {
if (event instanceof NamingEvent) {
List<Instance> instances = ((NamingEvent) event)
.getInstances();
Optional<Instance> instanceOptional = selectCurrentInstance(
instances);
instanceOptional.ifPresent(currentInstance -> {
resetIfNeeded(currentInstance);
});
}
}
});
NamingService namingService = nacosServiceManager
.getNamingService(properties.getNacosProperties());
try {
namingService.subscribe(properties.getService(), properties.getGroup(),
Arrays.asList(properties.getClusterName()), eventListener);
}
catch (Exception e) {
log.error("namingService subscribe failed, properties:{}", properties, e);
}
this.watchFuture = this.taskScheduler.scheduleWithFixedDelay(
this::nacosServicesWatch, this.properties.getWatchDelay());
}
}
private String buildKey() {
return String.join(":", properties.getService(), properties.getGroup());
}
private void resetIfNeeded(Instance instance) {
if (!properties.getMetadata().equals(instance.getMetadata())) {
properties.setMetadata(instance.getMetadata());
}
}
private Optional<Instance> selectCurrentInstance(List<Instance> instances) {
return instances.stream()
.filter(instance -> properties.getIp().equals(instance.getIp())
&& properties.getPort() == instance.getPort())
.findFirst();
}
@Override
public void stop() {
if (this.running.compareAndSet(true, false) && this.watchFuture != null) {
if (this.running.compareAndSet(true, false)) {
if (this.watchFuture != null) {
// shutdown current user-thread,
// then the other daemon-threads will terminate automatic.
((ThreadPoolTaskScheduler) this.taskScheduler).shutdown();
this.taskScheduler.shutdown();
this.watchFuture.cancel(true);
}
EventListener eventListener = listenerMap.get(buildKey());
try {
NamingService namingService = nacosServiceManager
.getNamingService(properties.getNacosProperties());
namingService.unsubscribe(properties.getService(), properties.getGroup(),
Arrays.asList(properties.getClusterName()), eventListener);
}
catch (Exception e) {
log.error("namingService unsubscribe failed, properties:{}", properties,
e);
}
}
}
@Override
......@@ -132,4 +201,8 @@ public class NacosWatch implements ApplicationEventPublisherAware, SmartLifecycl
}
@Override
public void destroy() {
this.stop();
}
}
......@@ -31,6 +31,16 @@ import org.springframework.boot.actuate.health.HealthIndicator;
*/
public class NacosDiscoveryHealthIndicator extends AbstractHealthIndicator {
/**
* status up.
*/
private static final String STATUS_UP = "UP";
/**
* status down.
*/
private static final String STATUS_DOWN = "DOWN";
private final NamingService namingService;
public NacosDiscoveryHealthIndicator(NamingService namingService) {
......@@ -44,10 +54,10 @@ public class NacosDiscoveryHealthIndicator extends AbstractHealthIndicator {
// Set the status to Builder
builder.status(status);
switch (status) {
case "UP":
case STATUS_UP:
builder.up();
break;
case "DOWN":
case STATUS_DOWN:
builder.down();
break;
default:
......
......@@ -16,6 +16,7 @@
package com.alibaba.cloud.nacos.discovery.configclient;
import com.alibaba.cloud.nacos.NacosServiceAutoConfiguration;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration;
import com.alibaba.cloud.nacos.discovery.reactive.NacosReactiveDiscoveryClientConfiguration;
......@@ -36,7 +37,7 @@ import org.springframework.context.annotation.Configuration;
matchIfMissing = false)
@Configuration(proxyBeanMethods = false)
@ImportAutoConfiguration({ NacosDiscoveryAutoConfiguration.class,
NacosDiscoveryClientConfiguration.class,
NacosServiceAutoConfiguration.class, NacosDiscoveryClientConfiguration.class,
NacosReactiveDiscoveryClientConfiguration.class })
public class NacosDiscoveryClientConfigServiceBootstrapConfiguration {
......
/*
* 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.discovery.logging;
import com.alibaba.nacos.client.logging.NacosLogging;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.GenericApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
/**
* Reload nacos log configuration file, after
* {@link org.springframework.boot.context.logging.LoggingApplicationListener}.
*
* @author mai.jh
*/
public class NacosLoggingListener implements GenericApplicationListener {
@Override
public boolean supportsEventType(ResolvableType resolvableType) {
Class<?> type = resolvableType.getRawClass();
if (type != null) {
return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(type);
}
return false;
}
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
NacosLogging.getInstance().loadConfiguration();
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 21;
}
}
......@@ -22,7 +22,9 @@ import java.util.List;
import java.util.Map;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.pojo.ServiceInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -35,15 +37,19 @@ import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
*
* @author xiaojing
*/
@Endpoint(id = "nacos-discovery")
@Endpoint(id = "nacosdiscovery")
public class NacosDiscoveryEndpoint {
private static final Logger log = LoggerFactory
.getLogger(NacosDiscoveryEndpoint.class);
private NacosServiceManager nacosServiceManager;
private NacosDiscoveryProperties nacosDiscoveryProperties;
public NacosDiscoveryEndpoint(NacosDiscoveryProperties nacosDiscoveryProperties) {
public NacosDiscoveryEndpoint(NacosServiceManager nacosServiceManager,
NacosDiscoveryProperties nacosDiscoveryProperties) {
this.nacosServiceManager = nacosServiceManager;
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
}
......@@ -55,11 +61,17 @@ public class NacosDiscoveryEndpoint {
Map<String, Object> result = new HashMap<>();
result.put("NacosDiscoveryProperties", nacosDiscoveryProperties);
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
NamingService namingService = nacosServiceManager
.getNamingService(nacosDiscoveryProperties.getNacosProperties());
List<ServiceInfo> subscribe = Collections.emptyList();
try {
subscribe = namingService.getSubscribeServices();
for (ServiceInfo serviceInfo : subscribe) {
List<Instance> instances = namingService.getAllInstances(
serviceInfo.getName(), serviceInfo.getGroupName());
serviceInfo.setHosts(instances);
}
}
catch (Exception e) {
log.error("get subscribe services from nacos fail,", e);
......
......@@ -16,8 +16,11 @@
package com.alibaba.cloud.nacos.endpoint;
import java.util.Properties;
import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.discovery.actuate.health.NacosDiscoveryHealthIndicator;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
......@@ -26,7 +29,6 @@ import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -44,18 +46,20 @@ public class NacosDiscoveryEndpointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint
public NacosDiscoveryEndpoint nacosDiscoveryEndpoint(
NacosServiceManager nacosServiceManager,
NacosDiscoveryProperties nacosDiscoveryProperties) {
return new NacosDiscoveryEndpoint(nacosDiscoveryProperties);
return new NacosDiscoveryEndpoint(nacosServiceManager, nacosDiscoveryProperties);
}
@Bean
@ConditionalOnEnabledHealthIndicator("nacos-discovery")
public HealthIndicator nacosDiscoveryHealthIndicator(
NacosServiceManager nacosServiceManager,
NacosDiscoveryProperties nacosDiscoveryProperties) {
Properties nacosProperties = nacosDiscoveryProperties.getNacosProperties();
return new NacosDiscoveryHealthIndicator(
nacosDiscoveryProperties.namingServiceInstance());
nacosServiceManager.getNamingService(nacosProperties));
}
}
......@@ -14,31 +14,25 @@
* limitations under the License.
*/
package com.alibaba.cloud.nacos.parser;
package com.alibaba.cloud.nacos.event;
import java.util.LinkedHashMap;
import java.util.Map;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import org.springframework.beans.factory.config.YamlMapFactoryBean;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.context.ApplicationEvent;
/**
* @author zkz
* @author yuhuangbin
*/
public class NacosDataYamlParser extends AbstractNacosDataParser {
public class NacosDiscoveryInfoChangedEvent extends ApplicationEvent {
public NacosDataYamlParser() {
super(",yml,yaml,");
public NacosDiscoveryInfoChangedEvent(
NacosDiscoveryProperties nacosDiscoveryProperties) {
super(nacosDiscoveryProperties);
}
@Override
protected Map<String, Object> doParse(String data) {
YamlMapFactoryBean yamlFactory = new YamlMapFactoryBean();
yamlFactory.setResources(new ByteArrayResource(data.getBytes()));
Map<String, Object> result = new LinkedHashMap<>();
flattenedMap(result, yamlFactory.getObject(), EMPTY_STRING);
return result;
public NacosDiscoveryProperties getSource() {
return (NacosDiscoveryProperties) super.getSource();
}
}
......@@ -16,6 +16,7 @@
package com.alibaba.cloud.nacos.registry;
import com.alibaba.cloud.nacos.event.NacosDiscoveryInfoChangedEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -23,6 +24,7 @@ import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegis
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.context.event.EventListener;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
......@@ -102,4 +104,14 @@ public class NacosAutoServiceRegistration
return StringUtils.isEmpty(appName) ? super.getAppName() : appName;
}
@EventListener
public void onNacosDiscoveryInfoChangedEvent(NacosDiscoveryInfoChangedEvent event) {
restart();
}
private void restart() {
this.stop();
this.start();
}
}
......@@ -17,12 +17,12 @@
package com.alibaba.cloud.nacos.registry;
import java.net.URI;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.PreservedMetadataKeys;
import org.springframework.cloud.client.DefaultServiceInstance;
......@@ -58,12 +58,16 @@ public class NacosRegistration implements Registration, ServiceInstance {
*/
public static final String MANAGEMENT_ENDPOINT_BASE_PATH = "management.endpoints.web.base-path";
private List<NacosRegistrationCustomizer> registrationCustomizers;
private NacosDiscoveryProperties nacosDiscoveryProperties;
private ApplicationContext context;
public NacosRegistration(NacosDiscoveryProperties nacosDiscoveryProperties,
public NacosRegistration(List<NacosRegistrationCustomizer> registrationCustomizers,
NacosDiscoveryProperties nacosDiscoveryProperties,
ApplicationContext context) {
this.registrationCustomizers = registrationCustomizers;
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
this.context = context;
}
......@@ -105,6 +109,17 @@ public class NacosRegistration implements Registration, ServiceInstance {
metadata.put(PreservedMetadataKeys.IP_DELETE_TIMEOUT,
nacosDiscoveryProperties.getIpDeleteTimeout().toString());
}
customize(registrationCustomizers, this);
}
private static void customize(
List<NacosRegistrationCustomizer> registrationCustomizers,
NacosRegistration registration) {
if (registrationCustomizers != null) {
for (NacosRegistrationCustomizer customizer : registrationCustomizers) {
customizer.customize(registration);
}
}
}
@Override
......@@ -157,10 +172,6 @@ public class NacosRegistration implements Registration, ServiceInstance {
return nacosDiscoveryProperties;
}
public NamingService getNacosNamingService() {
return nacosDiscoveryProperties.namingServiceInstance();
}
@Override
public String toString() {
return "NacosRegistration{" + "nacosDiscoveryProperties="
......
/*
* 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.registry;
/**
* @author L.cm
*/
public interface NacosRegistrationCustomizer {
/**
* customize NacosRegistration.
* @param registration NacosRegistration
*/
void customize(NacosRegistration registration);
}
......@@ -17,8 +17,11 @@
package com.alibaba.cloud.nacos.registry;
import java.util.List;
import java.util.Properties;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import org.slf4j.Logger;
......@@ -37,12 +40,20 @@ import static org.springframework.util.ReflectionUtils.rethrowRuntimeException;
*/
public class NacosServiceRegistry implements ServiceRegistry<Registration> {
private static final String STATUS_UP = "UP";
private static final String STATUS_DOWN = "DOWN";
private static final Logger log = LoggerFactory.getLogger(NacosServiceRegistry.class);
private final NacosDiscoveryProperties nacosDiscoveryProperties;
public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {
private final NacosServiceManager nacosServiceManager;
public NacosServiceRegistry(NacosServiceManager nacosServiceManager,
NacosDiscoveryProperties nacosDiscoveryProperties) {
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
this.nacosServiceManager = nacosServiceManager;
}
@Override
......@@ -65,12 +76,16 @@ public class NacosServiceRegistry implements ServiceRegistry<Registration> {
instance.getIp(), instance.getPort());
}
catch (Exception e) {
if (nacosDiscoveryProperties.isFailFast()) {
log.error("nacos registry, {} register failed...{},", serviceId,
registration.toString(), e);
// rethrow a RuntimeException if the registration is failed.
// issue : https://github.com/alibaba/spring-cloud-alibaba/issues/1132
rethrowRuntimeException(e);
}
else {
log.warn("Failfast is false. {} register failed...{},", serviceId,
registration.toString(), e);
}
}
}
@Override
......@@ -101,13 +116,19 @@ public class NacosServiceRegistry implements ServiceRegistry<Registration> {
@Override
public void close() {
try {
nacosServiceManager.nacosServiceShutDown();
}
catch (NacosException e) {
log.error("Nacos namingService shutDown failed", e);
}
}
@Override
public void setStatus(Registration registration, String status) {
if (!status.equalsIgnoreCase("UP") && !status.equalsIgnoreCase("DOWN")) {
if (!STATUS_UP.equalsIgnoreCase(status)
&& !STATUS_DOWN.equalsIgnoreCase(status)) {
log.warn("can't support status {},please choose UP or DOWN", status);
return;
}
......@@ -116,7 +137,7 @@ public class NacosServiceRegistry implements ServiceRegistry<Registration> {
Instance instance = getNacosInstanceFromRegistration(registration);
if (status.equalsIgnoreCase("DOWN")) {
if (STATUS_DOWN.equalsIgnoreCase(status)) {
instance.setEnabled(false);
}
else {
......@@ -124,8 +145,9 @@ public class NacosServiceRegistry implements ServiceRegistry<Registration> {
}
try {
nacosDiscoveryProperties.namingMaintainServiceInstance()
.updateInstance(serviceId, instance);
Properties nacosProperties = nacosDiscoveryProperties.getNacosProperties();
nacosServiceManager.getNamingMaintainService(nacosProperties).updateInstance(
serviceId, nacosDiscoveryProperties.getGroup(), instance);
}
catch (Exception e) {
throw new RuntimeException("update nacos instance status fail", e);
......@@ -137,9 +159,10 @@ public class NacosServiceRegistry implements ServiceRegistry<Registration> {
public Object getStatus(Registration registration) {
String serviceName = registration.getServiceId();
String group = nacosDiscoveryProperties.getGroup();
try {
List<Instance> instances = nacosDiscoveryProperties.namingServiceInstance()
.getAllInstances(serviceName);
List<Instance> instances = namingService().getAllInstances(serviceName,
group);
for (Instance instance : instances) {
if (instance.getIp().equalsIgnoreCase(nacosDiscoveryProperties.getIp())
&& instance.getPort() == nacosDiscoveryProperties.getPort()) {
......@@ -166,7 +189,8 @@ public class NacosServiceRegistry implements ServiceRegistry<Registration> {
}
private NamingService namingService() {
return nacosDiscoveryProperties.namingServiceInstance();
return nacosServiceManager
.getNamingService(nacosDiscoveryProperties.getNacosProperties());
}
}
......@@ -16,10 +16,14 @@
package com.alibaba.cloud.nacos.registry;
import java.util.List;
import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
......@@ -47,16 +51,19 @@ public class NacosServiceRegistryAutoConfiguration {
@Bean
public NacosServiceRegistry nacosServiceRegistry(
NacosServiceManager nacosServiceManager,
NacosDiscoveryProperties nacosDiscoveryProperties) {
return new NacosServiceRegistry(nacosDiscoveryProperties);
return new NacosServiceRegistry(nacosServiceManager, nacosDiscoveryProperties);
}
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosRegistration nacosRegistration(
ObjectProvider<List<NacosRegistrationCustomizer>> registrationCustomizers,
NacosDiscoveryProperties nacosDiscoveryProperties,
ApplicationContext context) {
return new NacosRegistration(nacosDiscoveryProperties, context);
return new NacosRegistration(registrationCustomizers.getIfAvailable(),
nacosDiscoveryProperties, context);
}
@Bean
......
......@@ -20,14 +20,15 @@ import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
import com.netflix.loadbalancer.Server;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -47,6 +48,9 @@ public class NacosRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Autowired
private NacosServiceManager nacosServiceManager;
@Override
public Server choose(Object key) {
try {
......@@ -55,8 +59,8 @@ public class NacosRule extends AbstractLoadBalancerRule {
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
String name = loadBalancer.getName();
NamingService namingService = nacosDiscoveryProperties
.namingServiceInstance();
NamingService namingService = nacosServiceManager
.getNamingService(nacosDiscoveryProperties.getNacosProperties());
List<Instance> instances = namingService.selectInstances(name, group, true);
if (CollectionUtils.isEmpty(instances)) {
LOGGER.warn("no instance in service {}", name);
......
......@@ -5,6 +5,9 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration,\
com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration,\
com.alibaba.cloud.nacos.discovery.reactive.NacosReactiveDiscoveryClientConfiguration,\
com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration
com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration,\
com.alibaba.cloud.nacos.NacosServiceAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfiguration
org.springframework.context.ApplicationListener=\
com.alibaba.cloud.nacos.discovery.logging.NacosLoggingListener
......@@ -22,6 +22,7 @@ import java.util.LinkedList;
import java.util.List;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
......@@ -60,16 +61,19 @@ public class NacosServiceDiscoveryTest {
NacosDiscoveryProperties nacosDiscoveryProperties = mock(
NacosDiscoveryProperties.class);
NacosServiceManager nacosServiceManager = mock(NacosServiceManager.class);
NamingService namingService = mock(NamingService.class);
when(nacosDiscoveryProperties.namingServiceInstance()).thenReturn(namingService);
when(nacosServiceManager
.getNamingService(nacosDiscoveryProperties.getNacosProperties()))
.thenReturn(namingService);
when(nacosDiscoveryProperties.getGroup()).thenReturn("DEFAULT");
when(namingService.selectInstances(eq(serviceName), eq("DEFAULT"), eq(true)))
.thenReturn(instances);
NacosServiceDiscovery serviceDiscovery = new NacosServiceDiscovery(
nacosDiscoveryProperties);
nacosDiscoveryProperties, nacosServiceManager);
List<ServiceInstance> serviceInstances = serviceDiscovery
.getInstances(serviceName);
......@@ -99,16 +103,19 @@ public class NacosServiceDiscoveryTest {
NacosDiscoveryProperties nacosDiscoveryProperties = mock(
NacosDiscoveryProperties.class);
NacosServiceManager nacosServiceManager = mock(NacosServiceManager.class);
NamingService namingService = mock(NamingService.class);
when(nacosDiscoveryProperties.namingServiceInstance()).thenReturn(namingService);
when(nacosServiceManager
.getNamingService(nacosDiscoveryProperties.getNacosProperties()))
.thenReturn(namingService);
when(nacosDiscoveryProperties.getGroup()).thenReturn("DEFAULT");
when(namingService.getServicesOfServer(eq(1), eq(Integer.MAX_VALUE),
eq("DEFAULT"))).thenReturn(nacosServices);
NacosServiceDiscovery serviceDiscovery = new NacosServiceDiscovery(
nacosDiscoveryProperties);
nacosDiscoveryProperties, nacosServiceManager);
List<String> services = serviceDiscovery.getServices();
......
......@@ -22,6 +22,7 @@ import java.util.Map;
import java.util.Properties;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration;
import com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpoint;
import com.alibaba.nacos.api.NacosFactory;
......@@ -68,9 +69,9 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
"spring.cloud.nacos.discovery.accessKey=test-accessKey",
"spring.cloud.nacos.discovery.ip=8.8.8.8",
"spring.cloud.nacos.discovery.secretKey=test-secretKey",
"spring.cloud.nacos.discovery.heart-beat-interval=3",
"spring.cloud.nacos.discovery.heart-beat-timeout=6",
"spring.cloud.nacos.discovery.ip-delete-timeout=9" },
"spring.cloud.nacos.discovery.heart-beat-interval=3000",
"spring.cloud.nacos.discovery.heart-beat-timeout=6000",
"spring.cloud.nacos.discovery.ip-delete-timeout=9000" },
webEnvironment = RANDOM_PORT)
public class NacosAutoServiceRegistrationTests {
......@@ -86,6 +87,9 @@ public class NacosAutoServiceRegistrationTests {
@Autowired
private NacosDiscoveryProperties properties;
@Autowired
private NacosServiceManager nacosServiceManager;
@Autowired
private InetUtils inetUtils;
......@@ -182,15 +186,15 @@ public class NacosAutoServiceRegistrationTests {
}
private void checkoutNacosDiscoveryHeartBeatInterval() {
assertThat(properties.getHeartBeatInterval()).isEqualTo(Integer.valueOf(3));
assertThat(properties.getHeartBeatInterval()).isEqualTo(Integer.valueOf(3000));
}
private void checkoutNacosDiscoveryHeartBeatTimeout() {
assertThat(properties.getHeartBeatTimeout()).isEqualTo(Integer.valueOf(6));
assertThat(properties.getHeartBeatTimeout()).isEqualTo(Integer.valueOf(6000));
}
private void checkoutNacosDiscoveryIpDeleteTimeout() {
assertThat(properties.getIpDeleteTimeout()).isEqualTo(Integer.valueOf(9));
assertThat(properties.getIpDeleteTimeout()).isEqualTo(Integer.valueOf(9000));
}
private void checkoutNacosDiscoveryServiceName() {
......@@ -207,7 +211,7 @@ public class NacosAutoServiceRegistrationTests {
private void checkoutEndpoint() throws Exception {
NacosDiscoveryEndpoint nacosDiscoveryEndpoint = new NacosDiscoveryEndpoint(
properties);
nacosServiceManager, properties);
Map<String, Object> map = nacosDiscoveryEndpoint.nacosDiscovery();
assertThat(properties).isEqualTo(map.get("NacosDiscoveryProperties"));
......
/*
* 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.registry;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Properties;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration;
import com.alibaba.nacos.api.NacosFactory;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.api.support.MethodProxy;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.modules.junit4.PowerMockRunnerDelegate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.junit4.SpringRunner;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
/**
* @author L.cm
*/
@RunWith(PowerMockRunner.class)
@PowerMockIgnore("javax.management.*")
@PowerMockRunnerDelegate(SpringRunner.class)
@PrepareForTest({ NacosFactory.class })
@SpringBootTest(classes = NacosRegistrationCustomizerTest.TestConfig.class,
properties = { "spring.application.name=myTestService1",
"spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848" },
webEnvironment = RANDOM_PORT)
public class NacosRegistrationCustomizerTest {
@Autowired
private NacosAutoServiceRegistration nacosAutoServiceRegistration;
static {
try {
Method method = PowerMockito.method(NacosFactory.class, "createNamingService",
Properties.class);
MethodProxy.proxy(method, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return new MockNamingService();
}
});
}
catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void contextLoads() throws Exception {
NacosRegistration registration = nacosAutoServiceRegistration.getRegistration();
Map<String, String> metadata = registration.getMetadata();
Assert.assertEquals("test1", metadata.get("test1"));
}
@Configuration
@EnableAutoConfiguration
@ImportAutoConfiguration({ AutoServiceRegistrationConfiguration.class,
NacosDiscoveryClientConfiguration.class,
NacosServiceRegistryAutoConfiguration.class })
public static class TestConfig {
@Bean
public NacosRegistrationCustomizer nacosRegistrationCustomizer() {
return registration -> {
Map<String, String> metadata = registration.getMetadata();
metadata.put("test1", "test1");
};
}
}
}
......@@ -40,7 +40,8 @@ import org.springframework.test.context.junit4.SpringRunner;
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = NacosRibbonClientPropertyOverrideTests.TestConfiguration.class,
properties = { "spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848",
properties = { "spring.cloud.nacos.server-addr=127.0.0.1:8848",
"spring.cloud.nacos.username=nacos", "spring.cloud.nacos.password=nacos",
"spring.cloud.nacos.discovery.port=18080",
"spring.cloud.nacos.discovery.service=remoteApp",
"localApp.ribbon.NIWSServerListClassName="
......
......@@ -5,7 +5,7 @@
<parent>
<artifactId>spring-cloud-timeloit-starters</artifactId>
<groupId>com.timeloit.cloud</groupId>
<version>2.3.3-SNAPSHOT</version>
<version>2.2.8-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
......
......@@ -37,7 +37,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
......@@ -66,23 +65,6 @@ public class SentinelWebAutoConfiguration implements WebMvcConfigurer {
@Autowired
private Optional<RequestOriginParser> requestOriginParserOptional;
@Autowired
private Optional<SentinelWebInterceptor> sentinelWebInterceptorOptional;
@Override
public void addInterceptors(InterceptorRegistry registry) {
if (!sentinelWebInterceptorOptional.isPresent()) {
return;
}
SentinelProperties.Filter filterConfig = properties.getFilter();
registry.addInterceptor(sentinelWebInterceptorOptional.get())
.order(filterConfig.getOrder())
.addPathPatterns(filterConfig.getUrlPatterns());
log.info(
"[Sentinel Starter] register SentinelWebInterceptor with urlPatterns: {}.",
filterConfig.getUrlPatterns());
}
@Bean
@ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled",
matchIfMissing = true)
......@@ -122,4 +104,11 @@ public class SentinelWebAutoConfiguration implements WebMvcConfigurer {
return sentinelWebMvcConfig;
}
@Bean
@ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled",
matchIfMissing = true)
public SentinelWebMvcConfigurer sentinelWebMvcConfigurer() {
return new SentinelWebMvcConfigurer();
}
}
......@@ -14,53 +14,44 @@
* limitations under the License.
*/
package com.alibaba.cloud.nacos.parser;
package com.alibaba.cloud.sentinel;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author zkz
* @author: chao.wu
*/
public class NacosDataPropertiesParser extends AbstractNacosDataParser {
public class SentinelWebMvcConfigurer implements WebMvcConfigurer {
private static final Logger log = LoggerFactory
.getLogger(NacosDataPropertiesParser.class);
.getLogger(SentinelWebMvcConfigurer.class);
public NacosDataPropertiesParser() {
super("properties");
}
@Autowired
private SentinelProperties sentinelProperties;
@Override
protected Map<String, Object> doParse(String data) throws IOException {
Map<String, Object> result = new LinkedHashMap<>();
@Autowired
private Optional<SentinelWebInterceptor> sentinelWebInterceptorOptional;
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());
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
if (!sentinelWebInterceptorOptional.isPresent()) {
return;
}
return result;
SentinelProperties.Filter filterConfig = sentinelProperties.getFilter();
registry.addInterceptor(sentinelWebInterceptorOptional.get())
.order(filterConfig.getOrder())
.addPathPatterns(filterConfig.getUrlPatterns());
log.info(
"[Sentinel Starter] register SentinelWebInterceptor with urlPatterns: {}.",
filterConfig.getUrlPatterns());
}
}
......@@ -86,13 +86,23 @@ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor
Tracer.trace(
new IllegalStateException("RestTemplate ErrorHandler has error"));
}
return response;
}
catch (Throwable e) {
if (!BlockException.isBlockException(e)) {
Tracer.trace(e);
if (BlockException.isBlockException(e)) {
return handleBlockException(request, body, execution, (BlockException) e);
}
else {
return handleBlockException(request, body, execution, (BlockException) e);
Tracer.traceEntry(e, hostEntry);
if (e instanceof IOException) {
throw (IOException) e;
}
else if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
else {
throw new IOException(e);
}
}
}
finally {
......@@ -103,7 +113,6 @@ public class SentinelProtectInterceptor implements ClientHttpRequestInterceptor
hostEntry.exit();
}
}
return response;
}
private ClientHttpResponse handleBlockException(HttpRequest request, byte[] body,
......
......@@ -25,7 +25,7 @@ import com.alibaba.csp.sentinel.datasource.AbstractDataSource;
import com.alibaba.csp.sentinel.heartbeat.HeartbeatSenderProvider;
import com.alibaba.csp.sentinel.transport.HeartbeatSender;
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
import com.alibaba.csp.sentinel.util.function.Tuple2;
import com.alibaba.csp.sentinel.transport.endpoint.Endpoint;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
......@@ -84,8 +84,7 @@ public class SentinelHealthIndicator extends AbstractHealthIndicator {
// Check health of Dashboard
boolean dashboardUp = true;
List<Tuple2<String, Integer>> consoleServerList = TransportConfig
.getConsoleServerList();
List<Endpoint> consoleServerList = TransportConfig.getConsoleServerList();
if (CollectionUtils.isEmpty(consoleServerList)) {
// If Dashboard isn't configured, it's OK and mark the status of Dashboard
// with UNKNOWN.
......@@ -105,7 +104,7 @@ public class SentinelHealthIndicator extends AbstractHealthIndicator {
// If failed to send heartbeat message, means that the Dashboard is DOWN
dashboardUp = false;
detailMap.put("dashboard",
new Status(Status.DOWN.getCode(), String.format(
new Status(Status.UNKNOWN.getCode(), String.format(
"the dashboard servers [%s] one of them can't be connected",
consoleServerList)));
}
......@@ -138,7 +137,7 @@ public class SentinelHealthIndicator extends AbstractHealthIndicator {
// DOWN
dataSourceUp = false;
dataSourceDetailMap.put(dataSourceBeanName,
new Status(Status.DOWN.getCode(), e.getMessage()));
new Status(Status.UNKNOWN.getCode(), e.getMessage()));
}
}
......@@ -147,7 +146,7 @@ public class SentinelHealthIndicator extends AbstractHealthIndicator {
builder.up().withDetails(detailMap);
}
else {
builder.down().withDetails(detailMap);
builder.unknown().withDetails(detailMap);
}
}
......
......@@ -80,25 +80,27 @@ public final class SentinelFeign {
// using reflect get fallback and fallbackFactory properties from
// FeignClientFactoryBean because FeignClientFactoryBean is a package
// level class, we can not use it in our package
Object feignClientFactoryBean = Builder.this.applicationContext
.getBean("&" + target.type().getName());
Object feignClientFactoryBean = SentinelTargeterAspect
.getFeignClientFactoryBean();
if (feignClientFactoryBean != null) {
Class fallback = (Class) getFieldValue(feignClientFactoryBean,
"fallback");
Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean,
"fallbackFactory");
Class fallbackFactory = (Class) getFieldValue(
feignClientFactoryBean, "fallbackFactory");
String beanName = (String) getFieldValue(feignClientFactoryBean,
"contextId");
if (!StringUtils.hasText(beanName)) {
beanName = (String) getFieldValue(feignClientFactoryBean, "name");
beanName = (String) getFieldValue(feignClientFactoryBean,
"name");
}
Object fallbackInstance;
FallbackFactory fallbackFactoryInstance;
// check fallback and fallbackFactory properties
if (void.class != fallback) {
fallbackInstance = getFromContext(beanName, "fallback", fallback,
target.type());
fallbackInstance = getFromContext(beanName, "fallback",
fallback, target.type());
return new SentinelInvocationHandler(target, dispatch,
new FallbackFactory.Default(fallbackInstance));
}
......@@ -109,6 +111,7 @@ public final class SentinelFeign {
return new SentinelInvocationHandler(target, dispatch,
fallbackFactoryInstance);
}
}
return new SentinelInvocationHandler(target, dispatch);
}
......
......@@ -41,4 +41,11 @@ public class SentinelFeignAutoConfiguration {
return SentinelFeign.builder();
}
@Bean
@ConditionalOnProperty(name = "feign.sentinel.enabled")
@ConditionalOnClass(name = "org.springframework.cloud.openfeign.Targeter")
public SentinelTargeterAspect sentinelTargeterAspect() {
return new SentinelTargeterAspect();
}
}
......@@ -109,7 +109,7 @@ public class SentinelInvocationHandler implements InvocationHandler {
catch (Throwable ex) {
// fallback handle
if (!BlockException.isBlockException(ex)) {
Tracer.trace(ex);
Tracer.traceEntry(ex, entry);
}
if (fallbackFactory != null) {
try {
......
/*
* 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.sentinel.feign;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
/**
* Record FeignClientFactoryBean to threadlocal, so that SentinelFeign can get it when
* creating SentinelInvocationHandler.
*
* @see com.alibaba.cloud.sentinel.feign.SentinelFeign.Builder
* @author <a href="mailto:chenxilzx1@gmail.com">theonefx</a>
*/
@Aspect
public class SentinelTargeterAspect {
private static final ThreadLocal<Object> FEIGN_CLIENT_FACTORY_BEAN = new ThreadLocal<>();
public static Object getFeignClientFactoryBean() {
return FEIGN_CLIENT_FACTORY_BEAN.get();
}
@Around("execution(* org.springframework.cloud.openfeign.Targeter.target(..))")
public Object process(ProceedingJoinPoint pjp) throws Throwable {
Object factory = pjp.getArgs()[0];
try {
FEIGN_CLIENT_FACTORY_BEAN.set(factory);
return pjp.proceed();
}
finally {
FEIGN_CLIENT_FACTORY_BEAN.remove();
}
}
}
......@@ -31,7 +31,8 @@ import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
import com.alibaba.csp.sentinel.util.function.Tuple2;
import com.alibaba.csp.sentinel.transport.endpoint.Endpoint;
import com.alibaba.csp.sentinel.transport.endpoint.Protocol;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -134,8 +135,10 @@ public class SentinelAutoConfigurationTests {
Map<String, Object> map = sentinelEndpoint.invoke();
assertThat(map.get("logUsePid")).isEqualTo(Boolean.TRUE);
assertThat(map.get("consoleServer").toString()).isEqualTo(
Arrays.asList(Tuple2.of("localhost", 8080), Tuple2.of("localhost", 8081))
assertThat(map.get("consoleServer").toString())
.isEqualTo(Arrays
.asList(new Endpoint(Protocol.HTTP, "localhost", 8080),
new Endpoint(Protocol.HTTP, "localhost", 8081))
.toString());
assertThat(map.get("clientPort")).isEqualTo("9999");
assertThat(map.get("heartbeatIntervalMs")).isEqualTo(20000L);
......@@ -185,8 +188,10 @@ public class SentinelAutoConfigurationTests {
@Test
public void testSentinelSystemProperties() {
assertThat(LogBase.isLogNameUsePid()).isEqualTo(true);
assertThat(TransportConfig.getConsoleServerList().toString()).isEqualTo(
Arrays.asList(Tuple2.of("localhost", 8080), Tuple2.of("localhost", 8081))
assertThat(TransportConfig.getConsoleServerList().toString())
.isEqualTo(Arrays
.asList(new Endpoint(Protocol.HTTP, "localhost", 8080),
new Endpoint(Protocol.HTTP, "localhost", 8081))
.toString());
assertThat(TransportConfig.getPort()).isEqualTo("9999");
assertThat(TransportConfig.getHeartbeatIntervalMs().longValue())
......
......@@ -109,9 +109,9 @@ public class SentinelHealthIndicatorTests {
Health health = sentinelHealthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
assertThat(health.getDetails().get("dashboard")).isEqualTo(
new Status(Status.DOWN.getCode(), "localhost:8080 can't be connected"));
assertThat(health.getStatus()).isEqualTo(Status.UNKNOWN);
assertThat(health.getDetails().get("dashboard")).isEqualTo(new Status(
Status.UNKNOWN.getCode(), "localhost:8080 can't be connected"));
}
@Test
......@@ -163,13 +163,13 @@ public class SentinelHealthIndicatorTests {
Health health = sentinelHealthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
assertThat(health.getStatus()).isEqualTo(Status.UNKNOWN);
Map<String, Status> dataSourceDetailMap = (Map<String, Status>) health
.getDetails().get("dataSource");
assertThat(dataSourceDetailMap.get("ds1-sentinel-file-datasource"))
.isEqualTo(Status.UP);
assertThat(dataSourceDetailMap.get("ds2-sentinel-file-datasource"))
.isEqualTo(new Status(Status.DOWN.getCode(), "fileDataSource2 error"));
.isEqualTo(new Status(Status.UNKNOWN.getCode(), "fileDataSource2 error"));
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-timeloit-starters</artifactId>
<groupId>com.timeloit.cloud</groupId>
<version>2.2.8-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-timeloit-commons</artifactId>
<name>Spring Cloud Timeloit Commons</name>
</project>
/*
* Copyright 2013-2019 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.commons.io;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* The Charsets constants, copy from apache commons-io.
*
* @author <a href="mailto:chenxilzx1@gmail.com">theonefx</a>
*/
public final class Charsets {
private Charsets() {
}
/**
* Constructs a sorted map from canonical charset names to charset objects required of
* every implementation of the Java platform.
* <p>
* From the Java documentation
* <a href="https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">
* Standard charsets</a>:
* </p>
* @return An immutable, case-insensitive map from canonical charset names to charset
* objects.
* @see Charset#availableCharsets()
*/
public static SortedMap<String, Charset> requiredCharsets() {
// maybe cache?
final TreeMap<String, Charset> m = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
m.put(StandardCharsets.ISO_8859_1.name(), StandardCharsets.ISO_8859_1);
m.put(StandardCharsets.US_ASCII.name(), StandardCharsets.US_ASCII);
m.put(StandardCharsets.UTF_16.name(), StandardCharsets.UTF_16);
m.put(StandardCharsets.UTF_16BE.name(), StandardCharsets.UTF_16BE);
m.put(StandardCharsets.UTF_16LE.name(), StandardCharsets.UTF_16LE);
m.put(StandardCharsets.UTF_8.name(), StandardCharsets.UTF_8);
return Collections.unmodifiableSortedMap(m);
}
/**
* Returns the given Charset or the default Charset if the given Charset is null.
* @param charset A charset or null.
* @return the given Charset or the default Charset if the given Charset is null
*/
public static Charset toCharset(final Charset charset) {
return charset == null ? Charset.defaultCharset() : charset;
}
/**
* Returns a Charset for the named charset. If the name is null, return the default
* Charset.
* @param charset The name of the requested charset, may be null.
* @return a Charset for the named charset
* @throws java.nio.charset.UnsupportedCharsetException If the named charset is
* unavailable
*/
public static Charset toCharset(final String charset) {
return charset == null ? Charset.defaultCharset() : Charset.forName(charset);
}
/**
* CharEncodingISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
* <p>
* Every implementation of the Java platform is required to support this character
* encoding.
* </p>
*
* @see <a href=
* "https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard
* charsets</a>
* @deprecated Use Java 7's {@link StandardCharsets}
*/
@Deprecated
public static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
/**
* <p>
* Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of
* the Unicode character set.
* </p>
* <p>
* Every implementation of the Java platform is required to support this character
* encoding.
* </p>
*
* @see <a href=
* "https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard
* charsets</a>
* @deprecated Use Java 7's {@link StandardCharsets}
*/
@Deprecated
public static final Charset US_ASCII = StandardCharsets.US_ASCII;
/**
* <p>
* Sixteen-bit Unicode Transformation Format, The byte order specified by a mandatory
* initial byte-order mark (either order accepted on input, big-endian used on output)
* </p>
* <p>
* Every implementation of the Java platform is required to support this character
* encoding.
* </p>
*
* @see <a href=
* "https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard
* charsets</a>
* @deprecated Use Java 7's {@link StandardCharsets}
*/
@Deprecated
public static final Charset UTF_16 = StandardCharsets.UTF_16;
/**
* <p>
* Sixteen-bit Unicode Transformation Format, big-endian byte order.
* </p>
* <p>
* Every implementation of the Java platform is required to support this character
* encoding.
* </p>
*
* @see <a href=
* "https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard
* charsets</a>
* @deprecated Use Java 7's {@link StandardCharsets}
*/
@Deprecated
public static final Charset UTF_16BE = StandardCharsets.UTF_16BE;
/**
* <p>
* Sixteen-bit Unicode Transformation Format, little-endian byte order.
* </p>
* <p>
* Every implementation of the Java platform is required to support this character
* encoding.
* </p>
*
* @see <a href=
* "https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard
* charsets</a>
* @deprecated Use Java 7's {@link StandardCharsets}
*/
@Deprecated
public static final Charset UTF_16LE = StandardCharsets.UTF_16LE;
/**
* <p>
* Eight-bit Unicode Transformation Format.
* </p>
* <p>
* Every implementation of the Java platform is required to support this character
* encoding.
* </p>
*
* @see <a href=
* "https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard
* charsets</a>
* @deprecated Use Java 7's {@link StandardCharsets}
*/
@Deprecated
public static final Charset UTF_8 = StandardCharsets.UTF_8;
}
/*
* Copyright 2013-2019 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.commons.io;
import java.io.*;
import java.nio.charset.Charset;
/**
* FileUtils. copy from apache commons-io.
*
* @author <a href="mailto:chenxilzx1@gmail.com">theonefx</a>
*/
public final class FileUtils {
private FileUtils() {
}
// -----------------------------------------------------------------------
/**
* Opens a {@link FileInputStream} for the specified file, providing better
* error messages than simply calling <code>new FileInputStream(file)</code>.
* <p>
* At the end of the method either the stream will be successfully opened, or an
* exception will have been thrown.
* <p>
* An exception is thrown if the file does not exist. An exception is thrown if the
* file object exists but is a directory. An exception is thrown if the file exists
* but cannot be read.
* @param file the file to open for input, must not be {@code null}
* @return a new {@link FileInputStream} for the specified file
* @throws FileNotFoundException if the file does not exist
* @throws IOException if the file object is a directory
* @throws IOException if the file cannot be read
* @since 1.3
*/
public static FileInputStream openInputStream(final File file) throws IOException {
if (file.exists()) {
if (file.isDirectory()) {
throw new IOException("File '" + file + "' exists but is a directory");
}
if (!file.canRead()) {
throw new IOException("File '" + file + "' cannot be read");
}
}
else {
throw new FileNotFoundException("File '" + file + "' does not exist");
}
return new FileInputStream(file);
}
// -----------------------------------------------------------------------
/**
* Reads the contents of a file into a String. The file is always closed.
* @param file the file to read, must not be {@code null}
* @param encoding the encoding to use, {@code null} means platform default
* @return the file contents, never {@code null}
* @throws IOException in case of an I/O error
*/
public static String readFileToString(final File file, final Charset encoding)
throws IOException {
try (InputStream in = openInputStream(file)) {
return IOUtils.toString(in, Charsets.toCharset(encoding));
}
}
/**
* Reads the contents of a file into a String. The file is always closed.
* @param file the file to read, must not be {@code null}
* @param encoding the encoding to use, {@code null} means platform default
* @return the file contents, never {@code null}
* @throws IOException in case of an I/O error
* @throws java.nio.charset.UnsupportedCharsetException thrown instead of
* {@link java.io .UnsupportedEncodingException} in version 2.2 if the encoding is not
* supported.
*/
public static String readFileToString(final File file, final String encoding)
throws IOException {
return readFileToString(file, Charsets.toCharset(encoding));
}
/**
* Reads the contents of a file into a String using the default encoding for the VM.
* The file is always closed.
* @param file the file to read, must not be {@code null}
* @return the file contents, never {@code null}
* @throws IOException in case of an I/O error
* @deprecated 2.5 use {@link #readFileToString(File, String)} instead (and specify
* the appropriate encoding)
*/
@Deprecated
public static String readFileToString(final File file) throws IOException {
return readFileToString(file, Charset.defaultCharset());
}
}
/*
* Copyright 2013-2019 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.commons.io;
import java.io.*;
import java.nio.charset.Charset;
/**
* The IOUtils. copy from apache commons-io.
*
* @author <a href="mailto:chenxilzx1@gmail.com">theonefx</a>
*/
public final class IOUtils {
/**
* Represents the end-of-file (or stream).
* @since 2.5 (made public)
*/
public static final int EOF = -1;
/**
* The default buffer size ({@value}) to use for.
* {@link #copyLarge(InputStream, OutputStream)} and
* {@link #copyLarge(Reader, Writer)}
*/
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
private IOUtils() {
}
/**
* Gets the contents of an <code>InputStream</code> as a String using the specified
* character encoding.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedInputStream</code>.
* </p>
* @param input the <code>InputStream</code> to read from
* @param encoding the encoding to use, null means platform default
* @return the requested String
* @throws NullPointerException if the input is null
* @throws IOException if an I/O error occurs
* @since 2.3
*/
public static String toString(final InputStream input, final Charset encoding)
throws IOException {
try (StringBuilderWriter sw = new StringBuilderWriter()) {
copy(input, sw, encoding);
return sw.toString();
}
}
// copy from Reader
// -----------------------------------------------------------------------
/**
* Copies chars from a <code>Reader</code> to a <code>Writer</code>.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedReader</code>.
* <p>
* Large streams (over 2GB) will return a chars copied value of <code>-1</code> after
* the copy has completed since the correct number of chars cannot be returned as an
* int. For large streams use the <code>copyLarge(Reader, Writer)</code> method.
* @param input the <code>Reader</code> to read from
* @param output the <code>Writer</code> to write to
* @return the number of characters copied, or -1 if &gt; Integer.MAX_VALUE
* @throws NullPointerException if the input or output is null
* @throws IOException if an I/O error occurs
* @since 1.1
*/
public static int copy(final Reader input, final Writer output) throws IOException {
final long count = copyLarge(input, output);
if (count > Integer.MAX_VALUE) {
return -1;
}
return (int) count;
}
/**
* Copies bytes from an <code>InputStream</code> to chars on a <code>Writer</code>
* using the specified character encoding.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedInputStream</code>.
* <p>
* This method uses {@link InputStreamReader}.
* @param input the <code>InputStream</code> to read from
* @param output the <code>Writer</code> to write to
* @param inputEncoding the encoding to use for the input stream, null means platform
* default
* @throws NullPointerException if the input or output is null
* @throws IOException if an I/O error occurs
* @since 2.3
*/
public static void copy(final InputStream input, final Writer output,
final Charset inputEncoding) throws IOException {
final InputStreamReader in = new InputStreamReader(input,
Charsets.toCharset(inputEncoding));
copy(in, output);
}
/**
* Copies bytes from an <code>InputStream</code> to an <code>OutputStream</code> using
* an internal buffer of the given size.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedInputStream</code>.
* <p>
* @param input the <code>InputStream</code> to read from
* @param output the <code>OutputStream</code> to write to
* @param bufferSize the bufferSize used to copy from the input to the output
* @return the number of bytes copied
* @throws NullPointerException if the input or output is null
* @throws IOException if an I/O error occurs
* @since 2.5
*/
public static long copy(final InputStream input, final OutputStream output,
final int bufferSize) throws IOException {
return copyLarge(input, output, new byte[bufferSize]);
}
/**
* Copies chars from a large (over 2GB) <code>Reader</code> to a <code>Writer</code>.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedReader</code>.
* <p>
* The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
* @param input the <code>Reader</code> to read from
* @param output the <code>Writer</code> to write to
* @return the number of characters copied
* @throws NullPointerException if the input or output is null
* @throws IOException if an I/O error occurs
* @since 1.3
*/
public static long copyLarge(final Reader input, final Writer output)
throws IOException {
return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]);
}
/**
* Copies chars from a large (over 2GB) <code>Reader</code> to a <code>Writer</code>.
* <p>
* This method uses the provided buffer, so there is no need to use a
* <code>BufferedReader</code>.
* <p>
* @param input the <code>Reader</code> to read from
* @param output the <code>Writer</code> to write to
* @param buffer the buffer to be used for the copy
* @return the number of characters copied
* @throws NullPointerException if the input or output is null
* @throws IOException if an I/O error occurs
* @since 2.2
*/
public static long copyLarge(final Reader input, final Writer output,
final char[] buffer) throws IOException {
long count = 0;
int n;
while (EOF != (n = input.read(buffer))) {
output.write(buffer, 0, n);
count += n;
}
return count;
}
/**
* Copies bytes from a large (over 2GB) <code>InputStream</code> to an
* <code>OutputStream</code>.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedInputStream</code>.
* <p>
* The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
* @param input the <code>InputStream</code> to read from
* @param output the <code>OutputStream</code> to write to
* @return the number of bytes copied
* @throws NullPointerException if the input or output is null
* @throws IOException if an I/O error occurs
* @since 1.3
*/
public static long copyLarge(final InputStream input, final OutputStream output)
throws IOException {
return copy(input, output, DEFAULT_BUFFER_SIZE);
}
/**
* Copies bytes from a large (over 2GB) <code>InputStream</code> to an
* <code>OutputStream</code>.
* <p>
* This method uses the provided buffer, so there is no need to use a
* <code>BufferedInputStream</code>.
* <p>
* @param input the <code>InputStream</code> to read from
* @param output the <code>OutputStream</code> to write to
* @param buffer the buffer to use for the copy
* @return the number of bytes copied
* @throws NullPointerException if the input or output is null
* @throws IOException if an I/O error occurs
* @since 2.2
*/
public static long copyLarge(final InputStream input, final OutputStream output,
final byte[] buffer) throws IOException {
long count = 0;
int n;
while (EOF != (n = input.read(buffer))) {
output.write(buffer, 0, n);
count += n;
}
return count;
}
}
/*
* Copyright 2013-2019 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.commons.io;
import java.io.Serializable;
import java.io.Writer;
/**
* Copy from apache commons-io.
*
* @author <a href="mailto:chenxilzx1@gmail.com">theonefx</a>
*/
public class StringBuilderWriter extends Writer implements Serializable {
private static final long serialVersionUID = -146927496096066153L;
private final StringBuilder builder;
/**
* Constructs a new {@link StringBuilder} instance with default capacity.
*/
public StringBuilderWriter() {
this.builder = new StringBuilder();
}
/**
* Constructs a new {@link StringBuilder} instance with the specified capacity.
* @param capacity The initial capacity of the underlying {@link StringBuilder}
*/
public StringBuilderWriter(final int capacity) {
this.builder = new StringBuilder(capacity);
}
/**
* Constructs a new instance with the specified {@link StringBuilder}.
*
* <p>
* If {@code builder} is null a new instance with default capacity will be created.
* </p>
* @param builder The String builder. May be null.
*/
public StringBuilderWriter(final StringBuilder builder) {
this.builder = builder != null ? builder : new StringBuilder();
}
/**
* Appends a single character to this Writer.
* @param value The character to append
* @return This writer instance
*/
@Override
public Writer append(final char value) {
builder.append(value);
return this;
}
/**
* Appends a character sequence to this Writer.
* @param value The character to append
* @return This writer instance
*/
@Override
public Writer append(final CharSequence value) {
builder.append(value);
return this;
}
/**
* Appends a portion of a character sequence to the {@link StringBuilder}.
* @param value The character to append
* @param start The index of the first character
* @param end The index of the last character + 1
* @return This writer instance
*/
@Override
public Writer append(final CharSequence value, final int start, final int end) {
builder.append(value, start, end);
return this;
}
/**
* Closing this writer has no effect.
*/
@Override
public void close() {
// no-op
}
/**
* Flushing this writer has no effect.
*/
@Override
public void flush() {
// no-op
}
/**
* Writes a String to the {@link StringBuilder}.
* @param value The value to write
*/
@Override
public void write(final String value) {
if (value != null) {
builder.append(value);
}
}
/**
* Writes a portion of a character array to the {@link StringBuilder}.
* @param value The value to write
* @param offset The index of the first character
* @param length The number of characters to write
*/
@Override
public void write(final char[] value, final int offset, final int length) {
if (value != null) {
builder.append(value, offset, length);
}
}
/**
* Returns the underlying builder.
* @return The underlying builder
*/
public StringBuilder getBuilder() {
return builder;
}
/**
* Returns {@link StringBuilder#toString()}.
* @return The contents of the String builder.
*/
@Override
public String toString() {
return builder.toString();
}
}
......@@ -5,7 +5,7 @@
<parent>
<artifactId>spring-cloud-timeloit-starters</artifactId>
<groupId>com.timeloit.cloud</groupId>
<version>2.3.3-SNAPSHOT</version>
<version>2.2.8-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
......@@ -14,6 +14,10 @@
<dependencies>
<dependency>
<groupId>com.timeloit.cloud</groupId>
<artifactId>spring-cloud-timeloit-commons</artifactId>
</dependency>
<!--spring boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
......
......@@ -132,7 +132,6 @@ public class DataSourcePropertiesConfiguration {
if (!ObjectUtils.isEmpty(field.get(this))) {
return field.getName();
}
return null;
}
catch (IllegalAccessException e) {
// won't happen
......
......@@ -32,6 +32,8 @@ public class NacosDataSourceProperties extends AbstractDataSourceProperties {
private String serverAddr;
private String contextPath;
private String username;
private String password;
......@@ -71,6 +73,14 @@ public class NacosDataSourceProperties extends AbstractDataSourceProperties {
this.serverAddr = serverAddr;
}
public String getContextPath() {
return contextPath;
}
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
public String getUsername() {
return username;
}
......
......@@ -86,9 +86,8 @@ public abstract class SentinelConverter<T extends Object>
});
for (Object obj : sourceArray) {
String item = null;
try {
item = objectMapper.writeValueAsString(obj);
String item = objectMapper.writeValueAsString(obj);
Optional.ofNullable(convertRule(item))
.ifPresent(convertRule -> ruleCollection.add(convertRule));
}
......
......@@ -35,6 +35,8 @@ public class NacosDataSourceFactoryBean implements FactoryBean<NacosDataSource>
private String serverAddr;
private String contextPath;
private String username;
private String password;
......@@ -60,22 +62,27 @@ public class NacosDataSourceFactoryBean implements FactoryBean<NacosDataSource>
properties.setProperty(PropertyKeyConst.SERVER_ADDR, this.serverAddr);
}
else {
properties.setProperty(PropertyKeyConst.ENDPOINT, this.endpoint);
}
if (!StringUtils.isEmpty(this.contextPath)) {
properties.setProperty(PropertyKeyConst.CONTEXT_PATH, this.contextPath);
}
if (!StringUtils.isEmpty(this.accessKey)) {
properties.setProperty(PropertyKeyConst.ACCESS_KEY, this.accessKey);
}
if (!StringUtils.isEmpty(this.secretKey)) {
properties.setProperty(PropertyKeyConst.SECRET_KEY, this.secretKey);
properties.setProperty(PropertyKeyConst.ENDPOINT, this.endpoint);
}
if (!StringUtils.isEmpty(this.namespace)) {
properties.setProperty(PropertyKeyConst.NAMESPACE, this.namespace);
}
if (!StringUtils.isEmpty(this.username)) {
properties.setProperty(PropertyKeyConst.USERNAME, this.username);
}
if (!StringUtils.isEmpty(this.password)) {
properties.setProperty(PropertyKeyConst.PASSWORD, this.password);
}
return new NacosDataSource(properties, groupId, dataId, converter);
}
......@@ -92,6 +99,14 @@ public class NacosDataSourceFactoryBean implements FactoryBean<NacosDataSource>
this.serverAddr = serverAddr;
}
public String getContextPath() {
return contextPath;
}
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
public String getUsername() {
return username;
}
......
......@@ -16,9 +16,9 @@
package com.alibaba.cloud.sentinel.datasource.factorybean;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.datasource.zookeeper.ZookeeperDataSource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.FactoryBean;
......
......@@ -39,6 +39,8 @@ public class NacosDataSourceFactoryBeanTests {
private String serverAddr = "localhost:8848";
private String contextPath = "/my-nacos";
private String accessKey = "ak";
private String secretKey = "sk";
......@@ -56,6 +58,7 @@ public class NacosDataSourceFactoryBeanTests {
factoryBean.setDataId(dataId);
factoryBean.setGroupId(groupId);
factoryBean.setServerAddr(serverAddr);
factoryBean.setContextPath(contextPath);
factoryBean.setConverter(converter);
NacosDataSource nacosDataSource = mock(NacosDataSource.class);
......@@ -69,6 +72,7 @@ public class NacosDataSourceFactoryBeanTests {
assertThat(factoryBean.getDataId()).isEqualTo(dataId);
assertThat(factoryBean.getGroupId()).isEqualTo(groupId);
assertThat(factoryBean.getServerAddr()).isEqualTo(serverAddr);
assertThat(factoryBean.getContextPath()).isEqualTo(contextPath);
}
@Test
......
......@@ -31,11 +31,13 @@ public class NacosDataSourcePropertiesTests {
public void testNacosWithAddr() {
NacosDataSourceProperties nacosDataSourceProperties = new NacosDataSourceProperties();
nacosDataSourceProperties.setServerAddr("127.0.0.1:8848");
nacosDataSourceProperties.setContextPath("/my-nacos");
nacosDataSourceProperties.setRuleType(RuleType.FLOW);
nacosDataSourceProperties.setDataId("sentinel");
nacosDataSourceProperties.setGroupId("custom-group");
nacosDataSourceProperties.setDataType("xml");
assertThat(nacosDataSourceProperties.getContextPath()).isEqualTo("/my-nacos");
assertThat(nacosDataSourceProperties.getGroupId()).isEqualTo("custom-group");
assertThat(nacosDataSourceProperties.getDataId()).isEqualTo("sentinel");
assertThat(nacosDataSourceProperties.getDataType()).isEqualTo("xml");
......
......@@ -19,13 +19,13 @@ package com.alibaba.cloud.sentinel.datasource;
import java.io.IOException;
import java.util.List;
import com.alibaba.cloud.commons.io.FileUtils;
import com.alibaba.cloud.sentinel.datasource.converter.JsonConverter;
import com.alibaba.cloud.sentinel.datasource.converter.XmlConverter;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.apache.commons.io.FileUtils;
import org.junit.Test;
import org.springframework.util.ResourceUtils;
......
# Sentinel Spring Cloud Zuul Adapter
Zuul does not provide rateLimit function, If use default `SentinelRibbonFilter` route filter. it wrapped by Hystrix Command. so only provide Service level
circuit protect.
Sentinel can provide `ServiceId` level and `API Path` level flow control for spring cloud zuul gateway service.
*Note*: this project is for zuul 1.
## How to use
1. Add maven dependency
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
<version>x.y.z</version>
</dependency>
```
2. Set application.property
```
// default value is false
spring.cloud.sentinel.zuul.enabled=true
```
## How it works
As Zuul run as per thread per connection block model, we add filters around `route Filter` to trace sentinel statistics.
- `SentinelPreFilter`: Get an entry of resource,the first order is **ServiceId**, then **API Path**.
- `SentinelPostFilter`: When success response,exit entry.
- `SentinelErrorFilter`: When get an `Exception`, trace the exception and exit context.
the order of Filter can be changed by configuration:
```
spring.cloud.sentinel.zuul.order.post=0
spring.cloud.sentinel.zuul.order.pre=10000
spring.cloud.sentinel.zuul.order.error=-1
```
Filters create structure like:
```bash
EntranceNode: machine-root(t:3 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)
-EntranceNode: coke(t:2 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)
--coke(t:2 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)
---/coke/uri(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)
-EntranceNode: sentinel_default_context(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)
-EntranceNode: book(t:1 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)
--book(t:1 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)
---/book/uri(t:0 pq:0 bq:0 tq:0 rt:0 prq:0 1mp:0 1mb:0 1mt:0)
```
`book` and `coke` are serviceId.
`---/book/uri` is api path, the real uri is `/uri`.
## Integration with Sentinel DashBord
Start [Sentinel DashBord](https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0).
## Rule config with dataSource
Sentinel has full rule config features. see [Dynamic-Rule-Configuration](https://github.com/alibaba/Sentinel/wiki/Dynamic-Rule-Configuration)
## Custom Fallbacks
Implements `SentinelFallbackProvider` to define your own Fallback Provider when Sentinel Block Exception throwing for different rout. the default
Fallback Provider is `DefaultBlockFallbackProvider`.
By default Fallback route is `ServiveId + URI PATH`, example `/book/coke`, first `book` is serviceId, `/uri` is URI PATH, so both
can be needed.
Here is an example:
```java
// custom provider
public class MyCokeServiceBlockFallbackProvider implements SentinelFallbackProvider {
private Logger logger = LoggerFactory.getLogger(DefaultBlockFallbackProvider.class);
// you can define root as service level
@Override
public String getRoute() {
return "/coke/uri";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
if (cause instanceof BlockException) {
logger.info("get in fallback block exception:{}", cause);
return response(HttpStatus.TOO_MANY_REQUESTS, route);
} else {
return response(HttpStatus.INTERNAL_SERVER_ERROR, route);
}
}
}
```
## Custom Request Origin Parser
By default this adapter use `DefaultRequestOriginParser` to parse sentinel origin.
```java
public class CustomRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
// do custom logic.
return "";
}
}
```
## Custom UrlCleaner
By default this adapter use `DefaultUrlCleaner` to define uri resource.
```java
public class CustomUrlCleaner implements UrlCleaner {
@Override
public String clean(String originUrl) {
// do custom logic.
return originUrl;
}
}
```
\ No newline at end of file
......@@ -5,7 +5,7 @@
<parent>
<artifactId>spring-cloud-timeloit-starters</artifactId>
<groupId>com.timeloit.cloud</groupId>
<version>2.3.3-SNAPSHOT</version>
<version>2.2.8-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
......
......@@ -37,7 +37,7 @@ public final class ConfigConstants {
/**
* ConfigurationProperties for {@link SentinelZuulProperties}.
*/
public static final String ZUUl_PREFIX = "spring.cloud.sentinel.zuul";
public static final String ZUUL_PREFIX = "spring.cloud.sentinel.zuul";
/**
* ConfigurationProperties for {@link SentinelGatewayProperties}.
......
......@@ -47,7 +47,7 @@ import org.springframework.context.annotation.Configuration;
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ZuulServlet.class)
@ConditionalOnProperty(prefix = ConfigConstants.ZUUl_PREFIX, name = "enabled",
@ConditionalOnProperty(prefix = ConfigConstants.ZUUL_PREFIX, name = "enabled",
havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(SentinelZuulProperties.class)
public class SentinelZuulAutoConfiguration {
......
......@@ -28,7 +28,7 @@ import org.springframework.boot.context.properties.NestedConfigurationProperty;
/**
* @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
*/
@ConfigurationProperties(prefix = ConfigConstants.ZUUl_PREFIX)
@ConfigurationProperties(prefix = ConfigConstants.ZUUL_PREFIX)
public class SentinelZuulProperties {
@NestedConfigurationProperty
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论