From a418ae8693c796ca3ff65625a6291c5a51e5cb54 Mon Sep 17 00:00:00 2001 From: lepdou Date: Wed, 30 Mar 2022 16:06:39 +0800 Subject: [PATCH] support polaris config center --- CHANGELOG.md | 2 + pom.xml | 3 +- ...sCircuitBreakerBootstrapConfiguration.java | 58 +++++++ .../PolarisFeignClientAutoConfiguration.java | 23 --- .../main/resources/META-INF/spring.factories | 2 + .../feign/PolarisFeignClientTest.java | 3 +- .../pom.xml | 38 +++++ .../polaris/config/ConfigurationModifier.java | 62 +++++++ .../PolarisConfigAutoConfiguration.java | 49 ++++++ ...larisConfigBootstrapAutoConfiguration.java | 73 +++++++++ .../adapter/PolarisConfigFileLocator.java | 153 ++++++++++++++++++ .../config/adapter/PolarisPropertySource.java | 78 +++++++++ .../PolarisPropertySourceAutoRefresher.java | 141 ++++++++++++++++ .../adapter/PolarisPropertySourceManager.java | 44 +++++ .../config/config/ConfigFileGroup.java | 60 +++++++ .../config/PolarisConfigProperties.java | 85 ++++++++++ .../config/enums/ConfigFileFormat.java | 84 ++++++++++ ...itional-spring-configuration-metadata.json | 32 ++++ .../main/resources/META-INF/spring.factories | 4 + .../cloud/polaris/PolarisProperties.java | 2 +- ...PolarisDiscoveryAutoConfigurationTest.java | 3 +- ...larisDiscoveryClientConfigurationTest.java | 3 +- .../PolarisServiceDiscoveryTest.java | 3 +- ...ctiveDiscoveryClientConfigurationTest.java | 3 +- ...arisRibbonServerListConfigurationTest.java | 4 +- .../polaris/ribbon/PolarisServerListTest.java | 3 +- .../PolarisRibbonAutoConfigurationTest.java | 4 +- .../common/constant/ContextConstant.java | 5 + .../cloud/common/util/AddressUtils.java | 48 ++++++ spring-cloud-tencent-dependencies/pom.xml | 9 +- .../polaris-config-example/pom.xml | 36 +++++ .../config/example/ConfigController.java | 55 +++++++ .../cloud/polaris/config/example/Person.java | 58 +++++++ .../PolarisConfigExampleApplication.java | 35 ++++ .../cloud/polaris/config/example/README-zh.md | 76 +++++++++ .../config/example/polaris-config-ui.png | Bin 0 -> 116372 bytes .../src/main/resources/bootstrap.yml | 14 ++ .../src/main/resources/log4j.properties | 15 ++ .../src/main/resources/logback-spring.xml | 28 ++++ .../src/main/resources/bootstrap.yml | 2 + .../polaris-discovery-example/pom.xml | 2 +- spring-cloud-tencent-examples/pom.xml | 5 +- spring-cloud-tencent-polaris-context/pom.xml | 7 +- .../polaris/context/InetUtilsProperties.java | 52 ++++++ .../context/PolarisContextConfiguration.java | 28 ++-- .../context/PolarisContextProperties.java | 34 ++-- .../spring-configuration-metadata.json | 6 + .../main/resources/META-INF/spring.factories | 3 +- .../context/PolarisContextGetHostTest.java | 6 +- .../src/test/resources/application-test.yml | 4 - .../src/test/resources/bootstrap.yml | 7 + 51 files changed, 1474 insertions(+), 80 deletions(-) create mode 100644 spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerBootstrapConfiguration.java create mode 100644 spring-cloud-starter-tencent-polaris-config/pom.xml create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/ConfigurationModifier.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocator.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySource.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceAutoRefresher.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceManager.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/config/ConfigFileGroup.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/config/PolarisConfigProperties.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/enums/ConfigFileFormat.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/AddressUtils.java create mode 100644 spring-cloud-tencent-examples/polaris-config-example/pom.xml create mode 100644 spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/ConfigController.java create mode 100644 spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/Person.java create mode 100644 spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/PolarisConfigExampleApplication.java create mode 100644 spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/README-zh.md create mode 100644 spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/polaris-config-ui.png create mode 100644 spring-cloud-tencent-examples/polaris-config-example/src/main/resources/bootstrap.yml create mode 100644 spring-cloud-tencent-examples/polaris-config-example/src/main/resources/log4j.properties create mode 100644 spring-cloud-tencent-examples/polaris-config-example/src/main/resources/logback-spring.xml create mode 100644 spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/InetUtilsProperties.java delete mode 100644 spring-cloud-tencent-polaris-context/src/test/resources/application-test.yml create mode 100644 spring-cloud-tencent-polaris-context/src/test/resources/bootstrap.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index e2ba0556..d74a8718 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,3 +10,5 @@ - [send heartbeat if healthcheck url not configured](https://github.com/Tencent/spring-cloud-tencent/pull/54) - [feat:optimize project structure and checkstyle](https://github.com/Tencent/spring-cloud-tencent/pull/56) - [feat:divide storage and transfer of metadata.](https://github.com/Tencent/spring-cloud-tencent/pull/63) +- [feat:support polaris config center.](https://github.com/Tencent/spring-cloud-tencent/pull/69) + diff --git a/pom.xml b/pom.xml index bc98fa02..d70a4568 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,7 @@ spring-cloud-tencent-dependencies spring-cloud-tencent-examples spring-cloud-tencent-coverage + spring-cloud-starter-tencent-polaris-config @@ -292,4 +293,4 @@ - \ No newline at end of file + diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerBootstrapConfiguration.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerBootstrapConfiguration.java new file mode 100644 index 00000000..3718ba4b --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisCircuitBreakerBootstrapConfiguration.java @@ -0,0 +1,58 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.circuitbreaker; + +import com.tencent.cloud.common.constant.ContextConstant; +import com.tencent.cloud.polaris.context.PolarisConfigModifier; +import com.tencent.polaris.factory.config.ConfigurationImpl; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Auto configuration at bootstrap phase. + * + * @author lepdou 2022-03-29 + */ +@ConditionalOnProperty(value = "spring.cloud.polaris.circuitbreaker.enabled", + havingValue = "true", matchIfMissing = true) +@Configuration(proxyBeanMethods = false) +public class PolarisCircuitBreakerBootstrapConfiguration { + + @Bean + public CircuitBreakerConfigModifier circuitBreakerConfigModifier() { + return new CircuitBreakerConfigModifier(); + } + + public static class CircuitBreakerConfigModifier implements PolarisConfigModifier { + + @Override + public void modify(ConfigurationImpl configuration) { + // Turn on circuitbreaker configuration + configuration.getConsumer().getCircuitBreaker().setEnable(true); + } + + @Override + public int getOrder() { + return ContextConstant.ModifierOrder.CIRCUIT_BREAKER_ORDER; + } + + } + +} diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisFeignClientAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisFeignClientAutoConfiguration.java index 634abf6e..b03d1491 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisFeignClientAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/java/com/tencent/cloud/polaris/circuitbreaker/PolarisFeignClientAutoConfiguration.java @@ -17,14 +17,11 @@ package com.tencent.cloud.polaris.circuitbreaker; -import com.tencent.cloud.common.constant.ContextConstant.ModifierOrder; import com.tencent.cloud.polaris.circuitbreaker.feign.PolarisFeignBeanPostProcessor; -import com.tencent.cloud.polaris.context.PolarisConfigModifier; import com.tencent.cloud.polaris.context.PolarisContextConfiguration; import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.client.api.SDKContext; import com.tencent.polaris.factory.api.DiscoveryAPIFactory; -import com.tencent.polaris.factory.config.ConfigurationImpl; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; @@ -61,24 +58,4 @@ public class PolarisFeignClientAutoConfiguration { return new PolarisFeignBeanPostProcessor(consumerAPI); } - @Bean - public CircuitBreakerConfigModifier circuitBreakerConfigModifier() { - return new CircuitBreakerConfigModifier(); - } - - public static class CircuitBreakerConfigModifier implements PolarisConfigModifier { - - @Override - public void modify(ConfigurationImpl configuration) { - // Enable circuit breaker. - configuration.getConsumer().getCircuitBreaker().setEnable(true); - } - - @Override - public int getOrder() { - return ModifierOrder.CIRCUIT_BREAKER_ORDER; - } - - } - } diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories index 4ceef0fb..04fa47a1 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/main/resources/META-INF/spring.factories @@ -1,2 +1,4 @@ +org.springframework.cloud.bootstrap.BootstrapConfiguration=\ + com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreakerBootstrapConfiguration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.tencent.cloud.polaris.circuitbreaker.PolarisFeignClientAutoConfiguration diff --git a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignClientTest.java b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignClientTest.java index b37c1792..757e1200 100644 --- a/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignClientTest.java +++ b/spring-cloud-starter-tencent-polaris-circuitbreaker/src/test/java/com/tencent/cloud/polaris/circuitbreaker/feign/PolarisFeignClientTest.java @@ -18,6 +18,7 @@ package com.tencent.cloud.polaris.circuitbreaker.feign; import com.tencent.cloud.polaris.circuitbreaker.PolarisFeignClientAutoConfiguration; +import com.tencent.cloud.polaris.context.PolarisContextConfiguration; import feign.Client; import org.junit.Test; import org.junit.jupiter.api.Assertions; @@ -36,7 +37,7 @@ import org.springframework.test.context.junit4.SpringRunner; */ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestPolarisFeignApp.class) -@ContextConfiguration(classes = { PolarisFeignClientAutoConfiguration.class }) +@ContextConfiguration(classes = { PolarisFeignClientAutoConfiguration.class, PolarisContextConfiguration.class }) public class PolarisFeignClientTest { @Autowired diff --git a/spring-cloud-starter-tencent-polaris-config/pom.xml b/spring-cloud-starter-tencent-polaris-config/pom.xml new file mode 100644 index 00000000..d257cd08 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/pom.xml @@ -0,0 +1,38 @@ + + + + spring-cloud-tencent + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-starter-tencent-polaris-config + + + + + com.tencent.cloud + spring-cloud-tencent-polaris-context + + + + + + com.tencent.polaris + polaris-configuration-factory + + + + + + org.springframework.cloud + spring-cloud-context + + + + + diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/ConfigurationModifier.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/ConfigurationModifier.java new file mode 100644 index 00000000..a02d090a --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/ConfigurationModifier.java @@ -0,0 +1,62 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config; + +import java.util.List; + +import com.tencent.cloud.common.constant.ContextConstant; +import com.tencent.cloud.common.util.AddressUtils; +import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; +import com.tencent.cloud.polaris.context.PolarisConfigModifier; +import com.tencent.polaris.factory.config.ConfigurationImpl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StringUtils; + +/** + * Read configuration from spring cloud's configuration file and override polaris.yaml. + * + * @author lepdou 2022-03-10 + */ +public class ConfigurationModifier implements PolarisConfigModifier { + + @Autowired + private PolarisConfigProperties polarisConfigProperties; + + @Override + public void modify(ConfigurationImpl configuration) { + configuration.getConfigFile().getServerConnector().setConnectorType("polaris"); + + if (StringUtils.isEmpty(polarisConfigProperties.getAddresses())) { + return; + } + + // override polaris config server address + List addresses = AddressUtils + .parseAddressList(polarisConfigProperties.getAddresses()); + + configuration.getConfigFile().getServerConnector().setAddresses(addresses); + } + + @Override + public int getOrder() { + return ContextConstant.ModifierOrder.CONFIG_ORDER; + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java new file mode 100644 index 00000000..eab848b5 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java @@ -0,0 +1,49 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config; + +import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceAutoRefresher; +import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager; +import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.context.refresh.ContextRefresher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * polaris config module auto configuration at init application context phase. + * + * @author lepdou 2022-03-28 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(value = "spring.cloud.polaris.config.enabled", + matchIfMissing = true) +public class PolarisConfigAutoConfiguration { + + @Bean + public PolarisPropertySourceAutoRefresher polarisPropertySourceAutoRefresher( + PolarisConfigProperties polarisConfigProperties, + PolarisPropertySourceManager polarisPropertySourceManager, + ContextRefresher contextRefresher) { + return new PolarisPropertySourceAutoRefresher(polarisConfigProperties, + polarisPropertySourceManager, contextRefresher); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java new file mode 100644 index 00000000..8ca4ad98 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java @@ -0,0 +1,73 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config; + +import com.tencent.cloud.polaris.config.adapter.PolarisConfigFileLocator; +import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager; +import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; +import com.tencent.cloud.polaris.context.PolarisContextProperties; +import com.tencent.polaris.client.api.SDKContext; +import com.tencent.polaris.configuration.api.core.ConfigFileService; +import com.tencent.polaris.configuration.factory.ConfigFileServiceFactory; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * polaris config module auto configuration at bootstrap phase. + * + * @author lepdou 2022-03-10 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(value = "spring.cloud.polaris.config.enabled", + matchIfMissing = true) +public class PolarisConfigBootstrapAutoConfiguration { + + @Bean + public PolarisConfigProperties polarisProperties() { + return new PolarisConfigProperties(); + } + + @Bean + public ConfigFileService configFileService(SDKContext sdkContext) { + return ConfigFileServiceFactory.createConfigFileService(sdkContext); + } + + @Bean + public PolarisPropertySourceManager polarisPropertySourceManager() { + return new PolarisPropertySourceManager(); + } + + @Bean + public PolarisConfigFileLocator polarisConfigFileLocator( + PolarisConfigProperties polarisConfigProperties, + PolarisContextProperties polarisContextProperties, + ConfigFileService configFileService, + PolarisPropertySourceManager polarisPropertySourceManager) { + return new PolarisConfigFileLocator(polarisConfigProperties, + polarisContextProperties, configFileService, + polarisPropertySourceManager); + } + + @Bean + public ConfigurationModifier configurationModifier() { + return new ConfigurationModifier(); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocator.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocator.java new file mode 100644 index 00000000..6ab0b453 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocator.java @@ -0,0 +1,153 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.adapter; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.tencent.cloud.polaris.config.config.ConfigFileGroup; +import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; +import com.tencent.cloud.polaris.config.enums.ConfigFileFormat; +import com.tencent.cloud.polaris.context.PolarisContextProperties; +import com.tencent.polaris.configuration.api.core.ConfigFileService; +import com.tencent.polaris.configuration.api.core.ConfigKVFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.cloud.bootstrap.config.PropertySourceLocator; +import org.springframework.core.annotation.Order; +import org.springframework.core.env.CompositePropertySource; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertySource; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +/** + * Spring cloud reserved core configuration loading SPI. + *

+ * This SPI is implemented to interface with Polaris configuration center + * + * @author lepdou 2022-03-10 + */ +@Order(0) +public class PolarisConfigFileLocator implements PropertySourceLocator { + + private static final Logger LOGGER = LoggerFactory + .getLogger(PolarisConfigFileLocator.class); + + private static final String POLARIS_CONFIG_PROPERTY_SOURCE_NAME = "polaris-config"; + + private final PolarisConfigProperties polarisConfigProperties; + + private final PolarisContextProperties polarisContextProperties; + + private final ConfigFileService configFileService; + + private final PolarisPropertySourceManager polarisPropertySourceManager; + + public PolarisConfigFileLocator(PolarisConfigProperties polarisConfigProperties, + PolarisContextProperties polarisContextProperties, + ConfigFileService configFileService, + PolarisPropertySourceManager polarisPropertySourceManager) { + this.polarisConfigProperties = polarisConfigProperties; + this.polarisContextProperties = polarisContextProperties; + this.configFileService = configFileService; + this.polarisPropertySourceManager = polarisPropertySourceManager; + } + + @Override + public PropertySource locate(Environment environment) { + CompositePropertySource compositePropertySource = new CompositePropertySource( + POLARIS_CONFIG_PROPERTY_SOURCE_NAME); + + List configFileGroups = polarisConfigProperties.getGroups(); + if (CollectionUtils.isEmpty(configFileGroups)) { + return compositePropertySource; + } + + initPolarisConfigFiles(compositePropertySource, configFileGroups); + + return compositePropertySource; + } + + private void initPolarisConfigFiles(CompositePropertySource compositePropertySource, + List configFileGroups) { + String namespace = polarisContextProperties.getNamespace(); + + for (ConfigFileGroup configFileGroup : configFileGroups) { + String group = configFileGroup.getName(); + + if (StringUtils.isEmpty(group)) { + throw new IllegalArgumentException( + "polaris config group name cannot be empty."); + } + + List files = configFileGroup.getFiles(); + if (CollectionUtils.isEmpty(files)) { + return; + } + + for (String fileName : files) { + PolarisPropertySource polarisPropertySource = loadPolarisPropertySource( + namespace, group, fileName); + + compositePropertySource.addPropertySource(polarisPropertySource); + + polarisPropertySourceManager.addPropertySource(polarisPropertySource); + + LOGGER.info( + "[SCT Config] Load and inject polaris config file success. namespace = {}, group = {}, fileName = {}", + namespace, group, fileName); + } + } + } + + private PolarisPropertySource loadPolarisPropertySource(String namespace, + String group, String fileName) { + ConfigKVFile configKVFile; + // unknown extension is resolved as properties file + if (ConfigFileFormat.isPropertyFile(fileName) + || ConfigFileFormat.isUnknownFile(fileName)) { + configKVFile = configFileService.getConfigPropertiesFile(namespace, group, + fileName); + } + else if (ConfigFileFormat.isYamlFile(fileName)) { + configKVFile = configFileService.getConfigYamlFile(namespace, group, + fileName); + } + else { + LOGGER.warn( + "[SCT Config] Unsupported config file. namespace = {}, group = {}, fileName = {}", + namespace, group, fileName); + + throw new IllegalStateException( + "Only configuration files in the format of properties / yaml / yaml" + + " can be injected into the spring context"); + } + + Map map = new ConcurrentHashMap<>(); + for (String key : configKVFile.getPropertyNames()) { + map.put(key, configKVFile.getProperty(key, null)); + } + + return new PolarisPropertySource(namespace, group, fileName, configKVFile, map); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySource.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySource.java new file mode 100644 index 00000000..76b234e7 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySource.java @@ -0,0 +1,78 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.adapter; + +import java.util.Map; + +import com.tencent.polaris.configuration.api.core.ConfigKVFile; + +import org.springframework.core.env.MapPropertySource; + +/** + * a polaris config file will be wrapped as polaris property source. + * + * @author lepdou 2022-03-10 + */ +public class PolarisPropertySource extends MapPropertySource { + + private final String namespace; + + private final String group; + + private final String fileName; + + private final ConfigKVFile configKVFile; + + public PolarisPropertySource(String namespace, String group, String fileName, + ConfigKVFile configKVFile, Map source) { + super(namespace + "-" + group + "-" + fileName, source); + + this.namespace = namespace; + this.group = group; + this.fileName = fileName; + this.configKVFile = configKVFile; + } + + public String getNamespace() { + return namespace; + } + + public String getGroup() { + return group; + } + + public String getFileName() { + return fileName; + } + + public String getPropertySourceName() { + return namespace + "-" + group + "-" + fileName; + } + + ConfigKVFile getConfigKVFile() { + return configKVFile; + } + + @Override + public String toString() { + return "PolarisPropertySource{" + "namespace='" + namespace + '\'' + ", group='" + + group + '\'' + ", fileName='" + fileName + '\'' + '}'; + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceAutoRefresher.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceAutoRefresher.java new file mode 100644 index 00000000..fac3ba25 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceAutoRefresher.java @@ -0,0 +1,141 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.adapter; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; +import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeEvent; +import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeListener; +import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.BeansException; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.cloud.context.refresh.ContextRefresher; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationListener; +import org.springframework.util.CollectionUtils; + +/** + * 1. Listen to the Polaris server configuration publishing event 2. Write the changed + * configuration content to propertySource 3. Refresh the context through contextRefresher + * + * @author lepdou 2022-03-28 + */ +public class PolarisPropertySourceAutoRefresher + implements ApplicationListener, ApplicationContextAware { + + private static final Logger LOGGER = LoggerFactory + .getLogger(PolarisPropertySourceAutoRefresher.class); + + private final PolarisConfigProperties polarisConfigProperties; + + private final PolarisPropertySourceManager polarisPropertySourceManager; + + private ApplicationContext applicationContext; + + private final ContextRefresher contextRefresher; + + private final AtomicBoolean registered = new AtomicBoolean(false); + + public PolarisPropertySourceAutoRefresher( + PolarisConfigProperties polarisConfigProperties, + PolarisPropertySourceManager polarisPropertySourceManager, + ContextRefresher contextRefresher) { + this.polarisConfigProperties = polarisConfigProperties; + this.polarisPropertySourceManager = polarisPropertySourceManager; + this.contextRefresher = contextRefresher; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + registerPolarisConfigPublishEvent(); + } + + private void registerPolarisConfigPublishEvent() { + if (!polarisConfigProperties.isAutoRefresh()) { + return; + } + + List polarisPropertySources = polarisPropertySourceManager + .getAllPropertySources(); + if (CollectionUtils.isEmpty(polarisPropertySources)) { + return; + } + + if (!registered.compareAndSet(false, true)) { + return; + } + + // register polaris config publish event + for (PolarisPropertySource polarisPropertySource : polarisPropertySources) { + polarisPropertySource.getConfigKVFile() + .addChangeListener(new ConfigKVFileChangeListener() { + @Override + public void onChange( + ConfigKVFileChangeEvent configKVFileChangeEvent) { + LOGGER.info( + "[SCT Config] received polaris config change event and will refresh spring context." + + "namespace = {}, group = {}, fileName = {}", + polarisPropertySource.getNamespace(), + polarisPropertySource.getGroup(), + polarisPropertySource.getFileName()); + + Map source = polarisPropertySource + .getSource(); + + for (String changedKey : configKVFileChangeEvent + .changedKeys()) { + ConfigPropertyChangeInfo configPropertyChangeInfo = configKVFileChangeEvent + .getChangeInfo(changedKey); + + LOGGER.info("[SCT Config] changed property = {}", + configPropertyChangeInfo); + + switch (configPropertyChangeInfo.getChangeType()) { + case MODIFIED: + case ADDED: + source.put(changedKey, + configPropertyChangeInfo.getNewValue()); + break; + case DELETED: + source.remove(changedKey); + break; + } + } + + // rebuild beans with @RefreshScope annotation + contextRefresher.refresh(); + } + }); + } + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceManager.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceManager.java new file mode 100644 index 00000000..6fff4906 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceManager.java @@ -0,0 +1,44 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.adapter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The manager of polaris property source. + * + * @author lepdou 2022-03-28 + */ +public class PolarisPropertySourceManager { + + private final Map polarisPropertySources = new ConcurrentHashMap<>(); + + public void addPropertySource(PolarisPropertySource polarisPropertySource) { + polarisPropertySources.putIfAbsent(polarisPropertySource.getPropertySourceName(), + polarisPropertySource); + } + + public List getAllPropertySources() { + return new ArrayList<>(polarisPropertySources.values()); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/config/ConfigFileGroup.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/config/ConfigFileGroup.java new file mode 100644 index 00000000..05f1d7c5 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/config/ConfigFileGroup.java @@ -0,0 +1,60 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.config; + +import java.util.List; + +/** + * the config file group. + * + * @author lepdou 2022-03-28 + */ +public class ConfigFileGroup { + + /** + * group name. + */ + private String name; + + /** + * the files belong to group. + */ + private List files; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getFiles() { + return files; + } + + public void setFiles(List files) { + this.files = files; + } + + @Override + public String toString() { + return "ConfigFileGroup{" + "name='" + name + '\'' + ", file=" + files + '}'; + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/config/PolarisConfigProperties.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/config/PolarisConfigProperties.java new file mode 100644 index 00000000..8ef59261 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/config/PolarisConfigProperties.java @@ -0,0 +1,85 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.config; + +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * polaris config module bootstrap configs. + * + * @author lepdou 2022-03-10 + */ +@ConfigurationProperties("spring.cloud.polaris.config") +public class PolarisConfigProperties { + + /** + * Whether to open the configuration center. + */ + private boolean enabled = true; + + /** + * Configuration center service address list. + */ + private String addresses; + + /** + * Whether to automatically update to the spring context when the configuration file. + * is updated + */ + private boolean autoRefresh = true; + + /** + * List of injected configuration files. + */ + private List groups; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getAddresses() { + return addresses; + } + + public void setAddresses(String addresses) { + this.addresses = addresses; + } + + public boolean isAutoRefresh() { + return autoRefresh; + } + + public void setAutoRefresh(boolean autoRefresh) { + this.autoRefresh = autoRefresh; + } + + public List getGroups() { + return groups; + } + + public void setGroups(List groups) { + this.groups = groups; + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/enums/ConfigFileFormat.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/enums/ConfigFileFormat.java new file mode 100644 index 00000000..2b7be577 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/enums/ConfigFileFormat.java @@ -0,0 +1,84 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.enums; + +/** + * the format of config file. + * + * @author lepdou 2022-03-28 + */ +public enum ConfigFileFormat { + + /** + * property format. + */ + PROPERTY(".property"), + /** + * yaml format. + */ + YAML(".yaml"), + /** + * yml format. + */ + YML(".yml"), + /** + * xml format. + */ + XML(".xml"), + /** + * json format. + */ + JSON(".json"), + /** + * text format. + */ + TEXT(".text"), + /** + * html format. + */ + html(".html"), + /** + * unknown format. + */ + UNKNOWN(".unknown"); + + private final String extension; + + ConfigFileFormat(String extension) { + this.extension = extension; + } + + public static boolean isPropertyFile(String fileName) { + return fileName.endsWith(PROPERTY.extension); + } + + public static boolean isYamlFile(String fileName) { + return fileName.endsWith(YAML.extension) || fileName.endsWith(YML.extension); + } + + public static boolean isUnknownFile(String fileName) { + for (ConfigFileFormat format : ConfigFileFormat.values()) { + if (fileName.endsWith(format.extension)) { + return false; + } + } + return true; + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 00000000..d2461eb7 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,32 @@ +{ + "properties": [ + { + "name": "spring.cloud.polaris.config.enabled", + "type": "java.lang.Boolean", + "defaultValue": "true", + "description": "The switch of polaris configuration module.", + "sourceType": "com.tencent.cloud.polaris.config.config.PolarisConfigProperties" + }, + { + "name": "spring.cloud.polaris.config.addresses", + "type": "java.lang.String", + "defaultValue": "", + "description": "The polaris configuration server addresses.", + "sourceType": "com.tencent.cloud.polaris.config.config.PolarisConfigProperties" + }, + { + "name": "spring.cloud.polaris.config.auto-refresh", + "type": "java.lang.Boolean", + "defaultValue": "true", + "description": "Whether to automatically update to the spring context when the configuration file is updated.", + "sourceType": "com.tencent.cloud.polaris.config.config.PolarisConfigProperties" + }, + { + "name": "spring.cloud.polaris.config.groups", + "type": "com.tencent.cloud.polaris.config.config.ConfigFileGroup", + "defaultValue": "", + "description": "List of imported config files.", + "sourceType": "com.tencent.cloud.polaris.config.config.PolarisConfigProperties" + } + ] +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/spring.factories b/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..a0c33067 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/spring.factories @@ -0,0 +1,4 @@ +org.springframework.cloud.bootstrap.BootstrapConfiguration=\ + com.tencent.cloud.polaris.config.PolarisConfigBootstrapAutoConfiguration +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.tencent.cloud.polaris.config.PolarisConfigAutoConfiguration diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/PolarisProperties.java b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/PolarisProperties.java index f9e4e39d..f387cf14 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/PolarisProperties.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/main/java/com/tencent/cloud/polaris/PolarisProperties.java @@ -43,7 +43,7 @@ public class PolarisProperties { /** * Namespace, separation registry of different environments. */ - @Value("${spring.cloud.polaris.discovery.namespace:#{'default'}}") + @Value("${spring.cloud.polaris.discovery.namespace:${spring.cloud.polaris.namespace:#{'default'}}}") private String namespace; /** diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryAutoConfigurationTest.java b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryAutoConfigurationTest.java index b8d6b319..9c864be9 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryAutoConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryAutoConfigurationTest.java @@ -48,7 +48,8 @@ public class PolarisDiscoveryAutoConfigurationTest { private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() .withConfiguration(AutoConfigurations.of(PolarisContextConfiguration.class, PolarisDiscoveryAutoConfiguration.class, - PolarisDiscoveryClientConfiguration.class)) + PolarisDiscoveryClientConfiguration.class, + PolarisContextConfiguration.class)) .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) .withPropertyValues("server.port=" + PORT) .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081"); diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClientConfigurationTest.java b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClientConfigurationTest.java index c4738115..0de51124 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClientConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisDiscoveryClientConfigurationTest.java @@ -44,7 +44,8 @@ public class PolarisDiscoveryClientConfigurationTest { private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() .withConfiguration(AutoConfigurations.of(PolarisContextConfiguration.class, - PolarisDiscoveryClientConfiguration.class)) + PolarisDiscoveryClientConfiguration.class, + PolarisContextConfiguration.class)) .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) .withPropertyValues("server.port=" + PORT) .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081"); diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisServiceDiscoveryTest.java b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisServiceDiscoveryTest.java index de38a3b1..abd3ac98 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisServiceDiscoveryTest.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/PolarisServiceDiscoveryTest.java @@ -53,7 +53,8 @@ public class PolarisServiceDiscoveryTest { .withConfiguration(AutoConfigurations.of(PolarisContextConfiguration.class, PolarisServiceDiscoveryTest.PolarisPropertiesConfiguration.class, PolarisDiscoveryClientConfiguration.class, - PolarisDiscoveryAutoConfiguration.class)) + PolarisDiscoveryAutoConfiguration.class, + PolarisContextConfiguration.class)) .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) .withPropertyValues("server.port=" + PORT) .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081") diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClientConfigurationTest.java b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClientConfigurationTest.java index 25dff7d4..1069b364 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClientConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/discovery/reactive/PolarisReactiveDiscoveryClientConfigurationTest.java @@ -46,7 +46,8 @@ public class PolarisReactiveDiscoveryClientConfigurationTest { private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() .withConfiguration(AutoConfigurations.of(PolarisContextConfiguration.class, PolarisReactiveDiscoveryClientConfiguration.class, - PolarisDiscoveryClientConfiguration.class)) + PolarisDiscoveryClientConfiguration.class, + PolarisContextConfiguration.class)) .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) .withPropertyValues("server.port=" + PORT) .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081"); diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisRibbonServerListConfigurationTest.java b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisRibbonServerListConfigurationTest.java index 99660fb6..eea15316 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisRibbonServerListConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisRibbonServerListConfigurationTest.java @@ -19,6 +19,7 @@ package com.tencent.cloud.polaris.ribbon; import com.netflix.client.config.DefaultClientConfigImpl; import com.netflix.client.config.IClientConfig; +import com.tencent.cloud.polaris.context.PolarisContextConfiguration; import com.tencent.cloud.polaris.discovery.PolarisDiscoveryClientConfiguration; import com.tencent.cloud.polaris.discovery.PolarisDiscoveryHandler; import org.junit.Test; @@ -46,7 +47,8 @@ public class PolarisRibbonServerListConfigurationTest { private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() .withConfiguration(AutoConfigurations.of(PolarisRibbonClientTest.class, - PolarisDiscoveryClientConfiguration.class)) + PolarisDiscoveryClientConfiguration.class, + PolarisContextConfiguration.class)) .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) .withPropertyValues("server.port=" + PORT) .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081") diff --git a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisServerListTest.java b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisServerListTest.java index b7ffb7e6..92cc4fb3 100644 --- a/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisServerListTest.java +++ b/spring-cloud-starter-tencent-polaris-discovery/src/test/java/com/tencent/cloud/polaris/ribbon/PolarisServerListTest.java @@ -58,7 +58,8 @@ public class PolarisServerListTest { .withConfiguration(AutoConfigurations.of(PolarisContextConfiguration.class, PolarisServerListTest.PolarisPropertiesConfiguration.class, PolarisDiscoveryClientConfiguration.class, - PolarisDiscoveryAutoConfiguration.class)) + PolarisDiscoveryAutoConfiguration.class, + PolarisContextConfiguration.class)) .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) .withPropertyValues("server.port=" + PORT) .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081") diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/config/PolarisRibbonAutoConfigurationTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/config/PolarisRibbonAutoConfigurationTest.java index cd92a08d..af4645e6 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/config/PolarisRibbonAutoConfigurationTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/config/PolarisRibbonAutoConfigurationTest.java @@ -17,6 +17,7 @@ package com.tencent.cloud.polaris.router.config; +import com.tencent.cloud.polaris.context.PolarisContextConfiguration; import com.tencent.polaris.router.api.core.RouterAPI; import org.junit.Test; @@ -38,7 +39,8 @@ public class PolarisRibbonAutoConfigurationTest { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(PolarisRibbonTest.class, - PolarisRibbonAutoConfiguration.class)) + PolarisRibbonAutoConfiguration.class, + PolarisContextConfiguration.class)) .withPropertyValues("spring.application.name=" + SERVICE_PROVIDER) .withPropertyValues("server.port=" + PORT) .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081"); diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/ContextConstant.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/ContextConstant.java index 7b0c0b85..54f20387 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/ContextConstant.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/ContextConstant.java @@ -44,6 +44,11 @@ public final class ContextConstant { */ public static Integer CIRCUIT_BREAKER_ORDER = 1; + /** + * Order of configuration modifier. + */ + public static Integer CONFIG_ORDER = 1; + } } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/AddressUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/AddressUtils.java new file mode 100644 index 00000000..4cfcb11a --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/AddressUtils.java @@ -0,0 +1,48 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.common.util; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +/** + * the utils of parse address. + * + * @author lepdou 2022-03-29 + */ +public final class AddressUtils { + + private static final String ADDRESS_SEPARATOR = ","; + + private AddressUtils() { + + } + + public static List parseAddressList(String addressInfo) { + List addressList = new ArrayList<>(); + String[] addresses = addressInfo.split(ADDRESS_SEPARATOR); + for (String address : addresses) { + URI uri = URI.create(address.trim()); + addressList.add(uri.getAuthority()); + } + return addressList; + } + +} diff --git a/spring-cloud-tencent-dependencies/pom.xml b/spring-cloud-tencent-dependencies/pom.xml index d16a6fb6..c20150a2 100644 --- a/spring-cloud-tencent-dependencies/pom.xml +++ b/spring-cloud-tencent-dependencies/pom.xml @@ -96,7 +96,6 @@ ${revision} - com.tencent.cloud spring-cloud-starter-tencent-polaris-ratelimit @@ -115,6 +114,12 @@ ${revision} + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-config + ${revision} + + com.tencent.cloud spring-cloud-tencent-polaris-context @@ -239,4 +244,4 @@ - \ No newline at end of file + diff --git a/spring-cloud-tencent-examples/polaris-config-example/pom.xml b/spring-cloud-tencent-examples/polaris-config-example/pom.xml new file mode 100644 index 00000000..d782d5d8 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-config-example/pom.xml @@ -0,0 +1,36 @@ + + + + + spring-cloud-tencent-examples + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + polaris-config-example + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-config + + + + + diff --git a/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/ConfigController.java b/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/ConfigController.java new file mode 100644 index 00000000..d06a732e --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/ConfigController.java @@ -0,0 +1,55 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.example; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.core.env.Environment; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * the endpoint for get config. + * + * @author lepdou 2022-03-10 + */ +@RestController +@RefreshScope +public class ConfigController { + + @Value("${timeout:1000}") + private int timeout; + + @Autowired + private Person person; + + @Autowired + private Environment environment; + + @GetMapping("/timeout") + public int timeout() { + environment.getProperty("timeout", "1000"); + return timeout; + } + + @GetMapping("/person") + public String person() { + return person.toString(); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/Person.java b/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/Person.java new file mode 100644 index 00000000..492af0a8 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/Person.java @@ -0,0 +1,58 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.example; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * example property object. + * + * @author lepdou 2022-03-28 + */ +@Component +@ConfigurationProperties(prefix = "teacher") +public class Person { + + private String name; + + private int age; + + String getName() { + return name; + } + + void setName(String name) { + this.name = name; + } + + int getAge() { + return age; + } + + void setAge(int age) { + this.age = age; + } + + @Override + public String toString() { + return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; + } + +} diff --git a/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/PolarisConfigExampleApplication.java b/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/PolarisConfigExampleApplication.java new file mode 100644 index 00000000..a632cf23 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/PolarisConfigExampleApplication.java @@ -0,0 +1,35 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.config.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * example application starter. + * + * @author lepdou 2022-03-10 + */ +@SpringBootApplication +public class PolarisConfigExampleApplication { + + public static void main(String[] args) { + SpringApplication.run(PolarisConfigExampleApplication.class, args); + } + +} diff --git a/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/README-zh.md b/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/README-zh.md new file mode 100644 index 00000000..3b1048a6 --- /dev/null +++ b/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/README-zh.md @@ -0,0 +1,76 @@ +# Polaris Config Example 使用指南 + +## 1. bootstrap.yml 配置 + +> 注意是在 bootstrap.yml 里配置,而不是在 application.yml 里配置。因为配置中心相关的配置是在 bootstrap 阶段依赖的配置。 + +```` yaml +spring: + application: + name: polaris-config-example + cloud: + polaris: + namespace: dev + config: + addresses: grpc://9.134.122.18:8093 # the address of polaris config server + auto-refresh: true # auto refresh when config file changed + groups: + - name: ${spring.application.name} # group name + files: [ "config/application.properties", "config/bootstrap.yml" ] # config/application.properties takes precedence over config/bootstrap.yml +```` + +## 2. 在北极星服务端创建配置文件 + +### 2.1 创建 namespace (dev) +### 2.2 创建配置文件分组(polaris-config-example) + +北极星的配置文件分组概念为一组配置文件的集合,推荐应用名=分组名,例如在我们的示例中,新建一个 polaris-config-example 的分组。 +把 polaris-config-example 应用的配置文件都放在 polaris-config-example 分组下,这样便于配置管理。 + +### 2.3 创建两个配置文件 config/application.properties 、config/bootstrap.yml + +北极星配置中心的控制台,配置文件名可以通过 / 来按树状目录结构展示,通过树状结构可以清晰的管理配置文件。 + +#### 2.3.1 config/application.properties 文件内容 + + ```` properties +timeout = 3000 +```` + +#### 2.3.2 config/bootstrap.yml 文件内容 + +````yaml +teacher: + name : 张三 + age: 38 +```` + +页面样例如下图所示: + +![](polaris-config-ui.png) + +## 3. 运行 PolarisConfigExampleApplication + +## 4. 访问接口 + +```` +curl "http://localhost:48084/timeout" + +curl "http://localhost:48084/person" +```` + +## 5. 动态推送能力 + +### 5.1 管控台动态修改并发布 config/application.properties + + ```` properties +timeout = 5000 +```` + +### 5.2 再次访问接口 +```` +curl "http://localhost:48084/timeout" +```` + + + diff --git a/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/polaris-config-ui.png b/spring-cloud-tencent-examples/polaris-config-example/src/main/java/com/tencent/cloud/polaris/config/example/polaris-config-ui.png new file mode 100644 index 0000000000000000000000000000000000000000..ebc5a0c0685add833b80398afe158c6313960be9 GIT binary patch literal 116372 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

KFN5nov7T#yQ{b5QS^- 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 z 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 V(zwO=-8}er + + logback + + + %d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + + + true + + + applog/%d{yyyy-MM-dd}/%d{yyyy-MM-dd}.log + + + + + %d{yyyy-MM-dd HH:mm:ss} -%msg%n + + + + + + + + + diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/resources/bootstrap.yml index 66478159..cc7b8178 100644 --- a/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/polaris-discovery-example/discovery-callee-service/src/main/resources/bootstrap.yml @@ -8,6 +8,8 @@ spring: address: grpc://127.0.0.1:8091 discovery: ip-address: 127.0.0.1 + inetutils: + default-ip-address: 198.1.1.1 # consul: # port: 8500 # host: 127.0.0.1 diff --git a/spring-cloud-tencent-examples/polaris-discovery-example/pom.xml b/spring-cloud-tencent-examples/polaris-discovery-example/pom.xml index 55d11638..865038c7 100644 --- a/spring-cloud-tencent-examples/polaris-discovery-example/pom.xml +++ b/spring-cloud-tencent-examples/polaris-discovery-example/pom.xml @@ -35,4 +35,4 @@ spring-cloud-starter-openfeign - \ No newline at end of file + diff --git a/spring-cloud-tencent-examples/pom.xml b/spring-cloud-tencent-examples/pom.xml index 73e7d84e..61106d50 100644 --- a/spring-cloud-tencent-examples/pom.xml +++ b/spring-cloud-tencent-examples/pom.xml @@ -20,10 +20,11 @@ polaris-ratelimit-example polaris-circuitbreaker-example polaris-gateway-example - + polaris-config-example + true - \ No newline at end of file + diff --git a/spring-cloud-tencent-polaris-context/pom.xml b/spring-cloud-tencent-polaris-context/pom.xml index a97c9420..a6fe5ac9 100644 --- a/spring-cloud-tencent-polaris-context/pom.xml +++ b/spring-cloud-tencent-polaris-context/pom.xml @@ -37,11 +37,6 @@ connector-polaris-grpc - - com.tencent.polaris - connector-polaris-grpc - - com.tencent.polaris connector-consul @@ -110,4 +105,4 @@ - \ No newline at end of file + diff --git a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/InetUtilsProperties.java b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/InetUtilsProperties.java new file mode 100644 index 00000000..cad84c34 --- /dev/null +++ b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/InetUtilsProperties.java @@ -0,0 +1,52 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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.tencent.cloud.polaris.context; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Because polaris-context is initialized in the bootstrap phase, the initialization of + * InetUtilsProperties is required. The impact on user usage is that + * spring.cloud.inetutils.defaultIpAddress needs to be configured in bootstrap.yml to take + * effect. + * + * @see org.springframework.cloud.commons.util.InetUtilsProperties + */ +@ConfigurationProperties(InetUtilsProperties.PREFIX) +public class InetUtilsProperties { + + /** + * Prefix for the Inet Utils properties. + */ + public static final String PREFIX = "spring.cloud.inetutils"; + + /** + * The default IP address. Used in case of errors. + */ + private String defaultIpAddress = "127.0.0.1"; + + String getDefaultIpAddress() { + return defaultIpAddress; + } + + void setDefaultIpAddress(String defaultIpAddress) { + this.defaultIpAddress = defaultIpAddress; + } + +} diff --git a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextConfiguration.java b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextConfiguration.java index 94bd493c..e54768eb 100644 --- a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextConfiguration.java +++ b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextConfiguration.java @@ -17,11 +17,10 @@ package com.tencent.cloud.polaris.context; -import java.net.URI; -import java.util.ArrayList; import java.util.List; import com.tencent.cloud.common.constant.ContextConstant.ModifierOrder; +import com.tencent.cloud.common.util.AddressUtils; import com.tencent.polaris.api.exception.PolarisException; import com.tencent.polaris.client.api.SDKContext; import com.tencent.polaris.factory.config.ConfigurationImpl; @@ -37,11 +36,10 @@ import org.springframework.context.annotation.Bean; * * @author Haotian Zhang */ -@EnableConfigurationProperties(PolarisContextProperties.class) +@EnableConfigurationProperties({ PolarisContextProperties.class, + InetUtilsProperties.class }) public class PolarisContextConfiguration { - private static final String ADDRESS_SEPARATOR = ","; - @Bean(name = "polarisContext", initMethod = "init", destroyMethod = "destroy") @ConditionalOnMissingBean public SDKContext polarisContext(PolarisContextProperties properties) @@ -62,10 +60,14 @@ public class PolarisContextConfiguration { @Override public void modify(ConfigurationImpl configuration) { - if (!StringUtils.isBlank(properties.getAddress())) { - configuration.getGlobal().getServerConnector() - .setAddresses(getAddressList(properties.getAddress())); + if (StringUtils.isBlank(properties.getAddress())) { + return; } + + List addresses = AddressUtils + .parseAddressList(properties.getAddress()); + + configuration.getGlobal().getServerConnector().setAddresses(addresses); } @Override @@ -73,16 +75,6 @@ public class PolarisContextConfiguration { return ModifierOrder.FIRST; } - private List getAddressList(String addressInfo) { - List addressList = new ArrayList<>(); - String[] addresses = addressInfo.split(ADDRESS_SEPARATOR); - for (String address : addresses) { - URI uri = URI.create(address.trim()); - addressList.add(uri.getAuthority()); - } - return addressList; - } - } } diff --git a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextProperties.java b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextProperties.java index 9235de4a..a3cce9e4 100644 --- a/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextProperties.java +++ b/spring-cloud-tencent-polaris-context/src/main/java/com/tencent/cloud/polaris/context/PolarisContextProperties.java @@ -29,8 +29,8 @@ import com.tencent.polaris.factory.config.ConfigurationImpl; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.cloud.commons.util.InetUtilsProperties; import org.springframework.core.env.Environment; import org.springframework.util.CollectionUtils; @@ -43,10 +43,16 @@ import org.springframework.util.CollectionUtils; public class PolarisContextProperties { /** - * polaris server adress. + * polaris server address. */ private String address; + /** + * polaris namespace. + */ + @Value("${spring.cloud.polaris.namespace:#{'default'}}") + private String namespace; + @Autowired private InetUtilsProperties inetUtilsProperties; @@ -56,14 +62,6 @@ public class PolarisContextProperties { @Autowired private List modifierList; - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - protected Configuration configuration() { ConfigurationImpl configuration = (ConfigurationImpl) ConfigAPIFactory .defaultConfig(ConfigProvider.DEFAULT_CONFIG); @@ -91,4 +89,20 @@ public class PolarisContextProperties { return environment.getProperty("spring.cloud.client.ip-address"); } + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + } diff --git a/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring-configuration-metadata.json b/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring-configuration-metadata.json index 8c326e0c..7274683d 100644 --- a/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring-configuration-metadata.json +++ b/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring-configuration-metadata.json @@ -12,6 +12,12 @@ "type": "java.lang.String", "description": "polaris server address list that can be separated by \",\"", "sourceType": "com.tencent.cloud.polaris.context.PolarisContextProperties" + }, + { + "name": "spring.cloud.polaris.namespace", + "type": "java.lang.String", + "description": "polaris namespace", + "sourceType": "com.tencent.cloud.polaris.context.PolarisContextProperties" } ], "hints": [] diff --git a/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring.factories index 3d931b1c..609b809d 100644 --- a/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-tencent-polaris-context/src/main/resources/META-INF/spring.factories @@ -1,2 +1 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - com.tencent.cloud.polaris.context.PolarisContextConfiguration +org.springframework.cloud.bootstrap.BootstrapConfiguration=com.tencent.cloud.polaris.context.PolarisContextConfiguration diff --git a/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/PolarisContextGetHostTest.java b/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/PolarisContextGetHostTest.java index 0ead596e..dce22530 100644 --- a/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/PolarisContextGetHostTest.java +++ b/spring-cloud-tencent-polaris-context/src/test/java/com/tencent/cloud/polaris/context/PolarisContextGetHostTest.java @@ -24,12 +24,14 @@ import org.junit.platform.commons.util.StringUtils; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest(classes = PolarisContextApplication.class, - properties = { "spring.config.location = classpath:application-test.yml" }) + properties = { "spring.config.location = classpath:bootstrap.yml" }) +@ImportAutoConfiguration({ PolarisContextConfiguration.class }) public class PolarisContextGetHostTest { @Autowired @@ -39,7 +41,7 @@ public class PolarisContextGetHostTest { public void testGetConfigHost() { String bindIP = polarisContext.getConfig().getGlobal().getAPI().getBindIP(); Assert.assertFalse(StringUtils.isBlank(bindIP)); - Assert.assertNotEquals(bindIP, "127.0.0.1"); + Assert.assertEquals(bindIP, "192.168.1.1"); } } diff --git a/spring-cloud-tencent-polaris-context/src/test/resources/application-test.yml b/spring-cloud-tencent-polaris-context/src/test/resources/application-test.yml deleted file mode 100644 index 512acd15..00000000 --- a/spring-cloud-tencent-polaris-context/src/test/resources/application-test.yml +++ /dev/null @@ -1,4 +0,0 @@ -spring: - cloud: - polaris: - address: grpc://127.0.0.1:8091 \ No newline at end of file diff --git a/spring-cloud-tencent-polaris-context/src/test/resources/bootstrap.yml b/spring-cloud-tencent-polaris-context/src/test/resources/bootstrap.yml new file mode 100644 index 00000000..f85882ee --- /dev/null +++ b/spring-cloud-tencent-polaris-context/src/test/resources/bootstrap.yml @@ -0,0 +1,7 @@ +spring: + cloud: + polaris: + address: grpc://127.0.0.1:8091 + namespace: dev + inetutils: + defaultIpAddress: 192.168.1.1