From 28dc95b9a0c6a3aeea6fcead82e36798f0de39f5 Mon Sep 17 00:00:00 2001 From: shedfreewu <49236872+shedfreewu@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:52:24 +0800 Subject: [PATCH] feat: support 2.0.0 config. (#1463) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 支持监听整个配置分组 2. 支持对接 TSF 时监听默认分组 3. 支持单配置刷新 --- CHANGELOG.md | 3 +- .../polaris/config/ConfigurationModifier.java | 5 +- .../PolarisConfigAutoConfiguration.java | 55 ++- .../PolarisConfigCustomExtensionLayer.java | 2 +- .../adapter/PolarisConfigFileLocator.java | 136 +++++-- .../PolarisConfigPropertyAutoRefresher.java | 148 +++++++- ...sConfigRefreshScopeAnnotationDetector.java | 94 ----- ...olarisRefreshAffectedContextRefresher.java | 25 +- .../PolarisRefreshEntireContextRefresher.java | 39 +- .../ReflectRefreshTypeCondition.java | 2 +- .../config/PolarisConfigProperties.java | 13 +- ...arisConfigRefreshOptimizationListener.java | 136 ------- .../spring/annotation/PolarisProcessor.java | 38 +- .../annotation/SpringValueProcessor.java | 151 +++++++- .../spring/property/SpringValueRegistry.java | 43 ++- .../config/adapter/MockedConfigKVFile.java | 26 +- .../adapter/PolarisConfigFileLocatorTest.java | 75 ++++ ...figRefreshScopeAnnotationDetectorTest.java | 101 ------ ...arisPropertiesSourceAutoRefresherTest.java | 102 +++++- .../listener/ConfigChangeListenerTest.java | 21 +- ...hOptimizationListenerNotTriggeredTest.java | 153 -------- ...reshOptimizationListenerTriggeredTest.java | 155 -------- .../RefreshScopeSpringProcessorTest.java | 335 ++++++++++++++++++ spring-cloud-tencent-rpc-enhancement/pom.xml | 6 + 24 files changed, 1126 insertions(+), 738 deletions(-) delete mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigRefreshScopeAnnotationDetector.java delete mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListener.java delete mode 100644 spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigRefreshScopeAnnotationDetectorTest.java delete mode 100644 spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListenerNotTriggeredTest.java delete mode 100644 spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListenerTriggeredTest.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/spring/annotation/RefreshScopeSpringProcessorTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f8a4057d..570767af8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,4 +16,5 @@ - [feat:upgrade jacoco version.](https://github.com/Tencent/spring-cloud-tencent/pull/1306) - [fix:fix no registry when lossless is disabled.](https://github.com/Tencent/spring-cloud-tencent/pull/1313) - [fix: memory not released while using wildcard api call with circuitbreaker enabled](https://github.com/Tencent/spring-cloud-tencent/pull/1335) -- [feat: support 2.0.0](https://github.com/Tencent/spring-cloud-tencent/pull/1458) \ No newline at end of file +- [feat: support 2.0.0](https://github.com/Tencent/spring-cloud-tencent/pull/1458) +- [feat: support 2.0.0 config](https://github.com/Tencent/spring-cloud-tencent/pull/1463) \ No newline at end of file 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 index 69025b48e..2136bb2c4 100644 --- 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 @@ -104,8 +104,9 @@ public class ConfigurationModifier implements PolarisConfigurationConfigModifier throw new RuntimeException("Config server address is blank. Please check your config in bootstrap.yml" + " with spring.cloud.polaris.address or spring.cloud.polaris.config.address"); } - - checkAddressAccessible(configAddresses); + if (polarisConfigProperties.isCheckAddress()) { + checkAddressAccessible(configAddresses); + } configuration.getConfigFile().getServerConnector().setAddresses(configAddresses); 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 index 7b39f4bd1..8e266fe96 100644 --- 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 @@ -20,18 +20,17 @@ package com.tencent.cloud.polaris.config; import com.tencent.cloud.polaris.config.adapter.AffectedConfigurationPropertiesRebinder; import com.tencent.cloud.polaris.config.adapter.PolarisConfigPropertyRefresher; -import com.tencent.cloud.polaris.config.adapter.PolarisConfigRefreshScopeAnnotationDetector; import com.tencent.cloud.polaris.config.adapter.PolarisRefreshAffectedContextRefresher; import com.tencent.cloud.polaris.config.adapter.PolarisRefreshEntireContextRefresher; import com.tencent.cloud.polaris.config.annotation.PolarisConfigAnnotationProcessor; import com.tencent.cloud.polaris.config.condition.ConditionalOnReflectRefreshType; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; import com.tencent.cloud.polaris.config.listener.PolarisConfigChangeEventListener; -import com.tencent.cloud.polaris.config.listener.PolarisConfigRefreshOptimizationListener; import com.tencent.cloud.polaris.config.logger.PolarisConfigLoggerApplicationListener; import com.tencent.cloud.polaris.config.spring.annotation.SpringValueProcessor; import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper; import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; +import com.tencent.polaris.configuration.api.core.ConfigFileService; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -78,46 +77,38 @@ public class PolarisConfigAutoConfiguration { @Bean @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) public PolarisConfigPropertyRefresher polarisRefreshContextPropertySourceAutoRefresher( - PolarisConfigProperties polarisConfigProperties, ContextRefresher contextRefresher) { - return new PolarisRefreshEntireContextRefresher(polarisConfigProperties, contextRefresher); + PolarisConfigProperties polarisConfigProperties, SpringValueRegistry springValueRegistry, + ConfigFileService configFileService, ContextRefresher contextRefresher) { + return new PolarisRefreshEntireContextRefresher(polarisConfigProperties, + springValueRegistry, configFileService, contextRefresher); + } + + @Bean + public SpringValueRegistry springValueRegistry() { + return new SpringValueRegistry(); + } + + @Bean + public PlaceholderHelper placeholderHelper() { + return new PlaceholderHelper(); + } + + @Bean + public SpringValueProcessor springValueProcessor(PlaceholderHelper placeholderHelper, + SpringValueRegistry springValueRegistry, PolarisConfigProperties polarisConfigProperties) { + return new SpringValueProcessor(placeholderHelper, springValueRegistry, polarisConfigProperties); } @Configuration(proxyBeanMethods = false) @ConditionalOnReflectRefreshType @AutoConfigureBefore(PolarisConfigAutoConfiguration.class) public static class PolarisReflectRefresherAutoConfiguration { - @Bean - public SpringValueRegistry springValueRegistry() { - return new SpringValueRegistry(); - } - - @Bean - public PlaceholderHelper placeholderHelper() { - return new PlaceholderHelper(); - } - - @Bean - public SpringValueProcessor springValueProcessor(PlaceholderHelper placeholderHelper, - SpringValueRegistry springValueRegistry, PolarisConfigProperties polarisConfigProperties) { - return new SpringValueProcessor(placeholderHelper, springValueRegistry, polarisConfigProperties); - } - @Bean public PolarisConfigPropertyRefresher polarisReflectPropertySourceAutoRefresher( PolarisConfigProperties polarisConfigProperties, SpringValueRegistry springValueRegistry, - PlaceholderHelper placeholderHelper) { + PlaceholderHelper placeholderHelper, ConfigFileService configFileService, ContextRefresher contextRefresher) { return new PolarisRefreshAffectedContextRefresher(polarisConfigProperties, - springValueRegistry, placeholderHelper); - } - - @Bean - public PolarisConfigRefreshScopeAnnotationDetector polarisConfigRefreshScopeAnnotationDetector() { - return new PolarisConfigRefreshScopeAnnotationDetector(); - } - - @Bean - public PolarisConfigRefreshOptimizationListener polarisConfigRefreshOptimizationListener() { - return new PolarisConfigRefreshOptimizationListener(); + springValueRegistry, placeholderHelper, configFileService, contextRefresher); } } } diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigCustomExtensionLayer.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigCustomExtensionLayer.java index 592368c68..ca813e235 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigCustomExtensionLayer.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigCustomExtensionLayer.java @@ -37,5 +37,5 @@ public interface PolarisConfigCustomExtensionLayer { void executeAfterLocateConfigReturning(CompositePropertySource compositePropertySource); - boolean executeRegisterPublishChangeListener(PolarisPropertySource polarisPropertySource); + boolean executeRegisterPublishChangeListener(PolarisPropertySource polarisPropertySource, PolarisPropertySource effectPolarisPropertySource); } 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 index 56a3d08ad..3e9d8cc6a 100644 --- 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 @@ -31,6 +31,7 @@ import com.tencent.cloud.polaris.context.config.PolarisContextProperties; import com.tencent.polaris.configuration.api.core.ConfigFileMetadata; import com.tencent.polaris.configuration.api.core.ConfigFileService; import com.tencent.polaris.configuration.api.core.ConfigKVFile; +import com.tencent.polaris.configuration.client.internal.CompositeConfigFile; import com.tencent.polaris.configuration.client.internal.DefaultConfigFileMetadata; import org.apache.commons.lang.ArrayUtils; import org.slf4j.Logger; @@ -68,6 +69,8 @@ public class PolarisConfigFileLocator implements PropertySourceLocator { // this class provides customized logic for some customers to configure special business group files private final PolarisConfigCustomExtensionLayer polarisConfigCustomExtensionLayer = PolarisServiceLoaderUtil.getPolarisConfigCustomExtensionLayer(); + private volatile static CompositePropertySource compositePropertySourceCache = null; + public PolarisConfigFileLocator(PolarisConfigProperties polarisConfigProperties, PolarisContextProperties polarisContextProperties, ConfigFileService configFileService, Environment environment) { this.polarisConfigProperties = polarisConfigProperties; @@ -76,10 +79,20 @@ public class PolarisConfigFileLocator implements PropertySourceLocator { this.environment = environment; } + /** + * order: spring boot default config files > custom config files > tsf default config group. + * @param environment The current Environment. + * @return The PropertySource to be added to the Environment. + */ @Override public PropertySource locate(Environment environment) { if (polarisConfigProperties.isEnabled()) { + // use cache when refreshing context + if (compositePropertySourceCache != null) { + return compositePropertySourceCache; + } CompositePropertySource compositePropertySource = new CompositePropertySource(POLARIS_CONFIG_PROPERTY_SOURCE_NAME); + compositePropertySourceCache = compositePropertySource; try { // load custom config extension files initCustomPolarisConfigExtensionFiles(compositePropertySource); @@ -87,10 +100,13 @@ public class PolarisConfigFileLocator implements PropertySourceLocator { initInternalConfigFiles(compositePropertySource); // load custom config files List configFileGroups = polarisConfigProperties.getGroups(); - if (CollectionUtils.isEmpty(configFileGroups)) { - return compositePropertySource; + + if (!CollectionUtils.isEmpty(configFileGroups)) { + initCustomPolarisConfigFiles(compositePropertySource, configFileGroups); } - initCustomPolarisConfigFiles(compositePropertySource, configFileGroups); + // load tsf default config group + initTsfConfigGroups(compositePropertySource); + return compositePropertySource; } finally { @@ -123,7 +139,10 @@ public class PolarisConfigFileLocator implements PropertySourceLocator { List internalConfigFiles = getInternalConfigFiles(); for (ConfigFileMetadata configFile : internalConfigFiles) { - PolarisPropertySource polarisPropertySource = loadPolarisPropertySource(configFile.getNamespace(), configFile.getFileGroup(), configFile.getFileName()); + if (StringUtils.isEmpty(configFile.getFileGroup())) { + continue; + } + PolarisPropertySource polarisPropertySource = loadPolarisPropertySource(configFileService, configFile.getNamespace(), configFile.getFileGroup(), configFile.getFileName()); compositePropertySource.addPropertySource(polarisPropertySource); @@ -190,6 +209,29 @@ public class PolarisConfigFileLocator implements PropertySourceLocator { internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "bootstrap.yaml")); } + private void initTsfConfigGroups(CompositePropertySource compositePropertySource) { + String tsfId = environment.getProperty("tsf_id"); + String tsfNamespaceName = environment.getProperty("tsf_namespace_name"); + String tsfGroupName = environment.getProperty("tsf_group_name"); + + if (StringUtils.isEmpty(tsfId) || StringUtils.isEmpty(tsfNamespaceName) || StringUtils.isEmpty(tsfGroupName)) { + return; + } + String namespace = polarisContextProperties.getNamespace(); + List tsfConfigGroups = Arrays.asList( + tsfId + "." + tsfGroupName + ".application_config_group", + tsfId + "." + tsfNamespaceName + ".global_config_group"); + for (String tsfConfigGroup : tsfConfigGroups) { + PolarisPropertySource polarisPropertySource = loadGroupPolarisPropertySource(configFileService, namespace, tsfConfigGroup); + if (polarisPropertySource == null) { + // not register to polaris + continue; + } + compositePropertySource.addPropertySource(polarisPropertySource); + PolarisPropertySourceManager.addPropertySource(polarisPropertySource); + } + } + private void initCustomPolarisConfigFiles(CompositePropertySource compositePropertySource, List configFileGroups) { String namespace = polarisContextProperties.getNamespace(); @@ -201,46 +243,90 @@ public class PolarisConfigFileLocator implements PropertySourceLocator { String group = configFileGroup.getName(); if (!StringUtils.hasText(group)) { - throw new IllegalArgumentException("polaris config group name cannot be empty."); + continue; } List files = configFileGroup.getFiles(); + if (CollectionUtils.isEmpty(files)) { - return; + PolarisPropertySource polarisPropertySource = loadGroupPolarisPropertySource(configFileService, namespace, group); + if (polarisPropertySource == null) { + continue; + } + compositePropertySource.addPropertySource(polarisPropertySource); + PolarisPropertySourceManager.addPropertySource(polarisPropertySource); + LOGGER.info("[SCT Config] Load and inject polaris config file success. namespace = {}, group = {}", namespace, group); } + else { + for (String fileName : files) { + PolarisPropertySource polarisPropertySource = loadPolarisPropertySource(configFileService, groupNamespace, group, fileName); - for (String fileName : files) { - PolarisPropertySource polarisPropertySource = loadPolarisPropertySource(groupNamespace, group, fileName); + compositePropertySource.addPropertySource(polarisPropertySource); - compositePropertySource.addPropertySource(polarisPropertySource); + PolarisPropertySourceManager.addPropertySource(polarisPropertySource); - PolarisPropertySourceManager.addPropertySource(polarisPropertySource); - - LOGGER.info("[SCT Config] Load and inject polaris config file success. namespace = {}, group = {}, fileName = {}", groupNamespace, group, fileName); + LOGGER.info("[SCT Config] Load and inject polaris config file success. namespace = {}, group = {}, fileName = {}", groupNamespace, group, fileName); + } } } } - private PolarisPropertySource loadPolarisPropertySource(String namespace, String group, String fileName) { - ConfigKVFile configKVFile; - // unknown extension is resolved as yaml file - if (ConfigFileFormat.isYamlFile(fileName) || ConfigFileFormat.isUnknownFile(fileName)) { - configKVFile = configFileService.getConfigYamlFile(namespace, group, fileName); + public static PolarisPropertySource loadPolarisPropertySource(ConfigFileService configFileService, String namespace, String group, String fileName) { + ConfigKVFile configKVFile = loadConfigKVFile(configFileService, namespace, group, fileName); + + Map map = new ConcurrentHashMap<>(); + for (String key : configKVFile.getPropertyNames()) { + map.put(key, configKVFile.getProperty(key, null)); } - else if (ConfigFileFormat.isPropertyFile(fileName)) { - configKVFile = configFileService.getConfigPropertiesFile(namespace, group, fileName); + + return new PolarisPropertySource(namespace, group, fileName, configKVFile, map); + } + + public static PolarisPropertySource loadGroupPolarisPropertySource(ConfigFileService configFileService, String namespace, String group) { + List configKVFiles = new ArrayList<>(); + + com.tencent.polaris.configuration.api.core.ConfigFileGroup remoteGroup = configFileService.getConfigFileGroup(namespace, group); + if (remoteGroup == null) { + return null; } - 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"); + for (ConfigFileMetadata configFile : remoteGroup.getConfigFileMetadataList()) { + String fileName = configFile.getFileName(); + ConfigKVFile configKVFile = loadConfigKVFile(configFileService, namespace, group, fileName); + configKVFiles.add(configKVFile); } + CompositeConfigFile compositeConfigFile = new CompositeConfigFile(configKVFiles); + Map map = new ConcurrentHashMap<>(); - for (String key : configKVFile.getPropertyNames()) { - map.put(key, configKVFile.getProperty(key, null)); + for (String key : compositeConfigFile.getPropertyNames()) { + String value = compositeConfigFile.getProperty(key, null); + map.put(key, value); } - return new PolarisPropertySource(namespace, group, fileName, configKVFile, map); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("namespace='" + namespace + '\'' + + ", group='" + group + '\'' + ", fileName='" + compositeConfigFile + '\'' + + ", map='" + map + '\''); + } + + return new PolarisPropertySource(namespace, group, "", compositeConfigFile, map); + } + + public static ConfigKVFile loadConfigKVFile(ConfigFileService configFileService, 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"); + } + return configKVFile; } } diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigPropertyAutoRefresher.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigPropertyAutoRefresher.java index 1d983c1b3..15d4e993d 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigPropertyAutoRefresher.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigPropertyAutoRefresher.java @@ -19,12 +19,21 @@ package com.tencent.cloud.polaris.config.adapter; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; +import com.google.common.collect.Sets; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; import com.tencent.cloud.polaris.config.logger.PolarisConfigLoggerContext; +import com.tencent.cloud.polaris.config.utils.PolarisPropertySourceUtils; +import com.tencent.polaris.configuration.api.core.ConfigFileGroup; +import com.tencent.polaris.configuration.api.core.ConfigFileMetadata; +import com.tencent.polaris.configuration.api.core.ConfigFileService; import com.tencent.polaris.configuration.api.core.ConfigKVFile; import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeListener; import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; @@ -34,6 +43,7 @@ import org.slf4j.LoggerFactory; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationListener; +import org.springframework.core.env.PropertySource; import org.springframework.lang.NonNull; import org.springframework.util.CollectionUtils; @@ -54,8 +64,14 @@ public abstract class PolarisConfigPropertyAutoRefresher implements ApplicationL // this class provides customized logic for some customers to configure special business group files private final PolarisConfigCustomExtensionLayer polarisConfigCustomExtensionLayer = PolarisServiceLoaderUtil.getPolarisConfigCustomExtensionLayer(); - public PolarisConfigPropertyAutoRefresher(PolarisConfigProperties polarisConfigProperties) { + private static final Set registeredPolarisPropertySets = Sets.newConcurrentHashSet(); + + private final ConfigFileService configFileService; + + public PolarisConfigPropertyAutoRefresher(PolarisConfigProperties polarisConfigProperties, + ConfigFileService configFileService) { this.polarisConfigProperties = polarisConfigProperties; + this.configFileService = configFileService; } @Override @@ -82,13 +98,16 @@ public abstract class PolarisConfigPropertyAutoRefresher implements ApplicationL // register polaris config publish event for (PolarisPropertySource polarisPropertySource : polarisPropertySources) { + // group property source if (polarisPropertySource.getConfigKVFile() instanceof CompositeConfigFile) { CompositeConfigFile configKVFile = (CompositeConfigFile) polarisPropertySource.getConfigKVFile(); for (ConfigKVFile cf : configKVFile.getConfigKVFiles()) { PolarisPropertySource p = new PolarisPropertySource(cf.getNamespace(), cf.getFileGroup(), cf.getFileName(), cf, new HashMap<>()); - registerPolarisConfigPublishChangeListener(p); - customRegisterPolarisConfigPublishChangeListener(p); + registerPolarisConfigPublishChangeListener(p, polarisPropertySource); + customRegisterPolarisConfigPublishChangeListener(p, polarisPropertySource); + registeredPolarisPropertySets.add(p.getPropertySourceName()); } + registerPolarisConfigGroupChangeListener(polarisPropertySource); } else { registerPolarisConfigPublishChangeListener(polarisPropertySource); @@ -105,14 +124,74 @@ public abstract class PolarisConfigPropertyAutoRefresher implements ApplicationL polarisConfigCustomExtensionLayer.initRegisterConfig(polarisConfigPropertyAutoRefresher); } + private void registerPolarisConfigGroupChangeListener(PolarisPropertySource polarisPropertySource) { + ConfigFileGroup configFileGroup = configFileService.getConfigFileGroup( + polarisPropertySource.getNamespace(), polarisPropertySource.getGroup()); + + if (configFileGroup == null) { + return; + } + configFileGroup.addChangeListener(event -> { + try { + LOGGER.debug("ConfigFileGroup receive onChange event:{}", event); + List oldConfigFileMetadataList = event.getOldConfigFileMetadataList(); + List newConfigFileMetadataList = event.getNewConfigFileMetadataList(); + + Map added = calculateUnregister(oldConfigFileMetadataList, newConfigFileMetadataList); + if (added.isEmpty()) { + return; + } + Set changedKeys = new HashSet<>(); + + for (Map.Entry entry : added.entrySet()) { + if (registeredPolarisPropertySets.contains(entry.getKey())) { + continue; + } + registeredPolarisPropertySets.add(entry.getKey()); + LOGGER.info("[SCT Config] add polaris config file:{}", entry.getKey()); + ConfigFileMetadata configFileMetadata = entry.getValue(); + PolarisPropertySource p = PolarisConfigFileLocator.loadPolarisPropertySource( + configFileService, configFileMetadata.getNamespace(), + configFileMetadata.getFileGroup(), configFileMetadata.getFileName()); + LOGGER.info("[SCT Config] changed property = {}", p.getSource().keySet()); + changedKeys.addAll(p.getSource().keySet()); + this.registerPolarisConfigPublishChangeListener(p, polarisPropertySource); + PolarisPropertySourceManager.addPropertySource(p); + for (String changedKey : p.getSource().keySet()) { + polarisPropertySource.getSource().put(changedKey, p.getSource().get(changedKey)); + refreshSpringValue(changedKey); + } + } + refreshConfigurationProperties(changedKeys); + } + catch (Exception e) { + LOGGER.error("[SCT Config] receive onChange exception,", e); + } + }); + } + public void registerPolarisConfigPublishChangeListener(PolarisPropertySource polarisPropertySource) { - LOGGER.info("{} will register polaris config publish listener", polarisPropertySource.getPropertySourceName()); - polarisPropertySource.getConfigKVFile() + registerPolarisConfigPublishChangeListener(polarisPropertySource, polarisPropertySource); + } + + public void registerPolarisConfigPublishChangeListener(PolarisPropertySource listenPolarisPropertySource, PolarisPropertySource effectPolarisPropertySource) { + LOGGER.info("{} will register polaris config publish listener, effect source:{}", + listenPolarisPropertySource.getPropertySourceName(), effectPolarisPropertySource.getPropertySourceName()); + listenPolarisPropertySource.getConfigKVFile() .addChangeListener((ConfigKVFileChangeListener) configKVFileChangeEvent -> { - LOGGER.info("[SCT Config] received polaris config change event and will refresh spring context." + " namespace = {}, group = {}, fileName = {}", polarisPropertySource.getNamespace(), polarisPropertySource.getGroup(), polarisPropertySource.getFileName()); + LOGGER.info("[SCT Config] received polaris config change event and will refresh spring context." + " namespace = {}, group = {}, fileName = {}", + listenPolarisPropertySource.getNamespace(), listenPolarisPropertySource.getGroup(), listenPolarisPropertySource.getFileName()); - Map source = polarisPropertySource.getSource(); + Map effectSource = effectPolarisPropertySource.getSource(); + Map listenSource = listenPolarisPropertySource.getSource(); + boolean isGroupRefresh = !listenPolarisPropertySource.equals(effectPolarisPropertySource); + + PolarisPropertySource newGroupSource = null; + if (isGroupRefresh) { + newGroupSource = PolarisConfigFileLocator.loadGroupPolarisPropertySource(configFileService, + effectPolarisPropertySource.getNamespace(), effectPolarisPropertySource.getGroup()); + } for (String changedKey : configKVFileChangeEvent.changedKeys()) { ConfigPropertyChangeInfo configPropertyChangeInfo = configKVFileChangeEvent.getChangeInfo(changedKey); @@ -134,10 +213,27 @@ public abstract class PolarisConfigPropertyAutoRefresher implements ApplicationL switch (configPropertyChangeInfo.getChangeType()) { case MODIFIED: case ADDED: - source.put(changedKey, configPropertyChangeInfo.getNewValue()); + effectSource.put(changedKey, configPropertyChangeInfo.getNewValue()); + if (isGroupRefresh) { + listenSource.put(changedKey, configPropertyChangeInfo.getNewValue()); + } break; case DELETED: - source.remove(changedKey); + if (isGroupRefresh) { + // when the key is deleted, the value should load from group source + Object newValue = Optional.ofNullable(newGroupSource).map(PropertySource::getSource). + map(source -> source.get(changedKey)).orElse(null); + if (newValue != null) { + effectSource.put(changedKey, newValue); + } + else { + effectSource.remove(changedKey); + } + listenSource.remove(changedKey); + } + else { + effectSource.remove(changedKey); + } break; } // update the attribute with @Value annotation @@ -149,11 +245,43 @@ public abstract class PolarisConfigPropertyAutoRefresher implements ApplicationL } private void customRegisterPolarisConfigPublishChangeListener(PolarisPropertySource polarisPropertySource) { + customRegisterPolarisConfigPublishChangeListener(polarisPropertySource, polarisPropertySource); + } + + private void customRegisterPolarisConfigPublishChangeListener(PolarisPropertySource listenPolarisPropertySource, PolarisPropertySource effectPolarisPropertySource) { if (polarisConfigCustomExtensionLayer == null) { LOGGER.debug("[SCT Config] PolarisConfigCustomExtensionLayer is not init, ignore the following execution steps"); return; } - polarisConfigCustomExtensionLayer.executeRegisterPublishChangeListener(polarisPropertySource); + polarisConfigCustomExtensionLayer.executeRegisterPublishChangeListener(listenPolarisPropertySource, effectPolarisPropertySource); + } + + private Map calculateUnregister(List oldConfigFileMetadataList, + List newConfigFileMetadataList) { + + + Map oldConfigFileMetadataMap = oldConfigFileMetadataList.stream() + .collect(Collectors.toMap( + configFileMetadata -> PolarisPropertySourceUtils.generateName( + configFileMetadata.getNamespace(), + configFileMetadata.getFileGroup(), + configFileMetadata.getFileName()), + configFileMetadata -> configFileMetadata)); + + Map newConfigFileMetadataMap = newConfigFileMetadataList.stream() + .collect(Collectors.toMap( + configFileMetadata -> PolarisPropertySourceUtils.generateName( + configFileMetadata.getNamespace(), + configFileMetadata.getFileGroup(), + configFileMetadata.getFileName()), + configFileMetadata -> configFileMetadata)); + Map added = new HashMap<>(); + for (Map.Entry entry : newConfigFileMetadataMap.entrySet()) { + if (!oldConfigFileMetadataMap.containsKey(entry.getKey())) { + added.put(entry.getKey(), entry.getValue()); + } + } + return added; } /** diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigRefreshScopeAnnotationDetector.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigRefreshScopeAnnotationDetector.java deleted file mode 100644 index 5c26342ce..000000000 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigRefreshScopeAnnotationDetector.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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.lang.annotation.Annotation; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.core.Ordered; -import org.springframework.core.PriorityOrdered; -import org.springframework.lang.NonNull; - -/** - * Mainly used to detect whether the annotation class {@link org.springframework.cloud.context.config.annotation.RefreshScope} - * exists, and whether the user has configured beans using this annotation in their business system. - * If the annotation {@code @RefreshScope} exists and is used, the auto-optimization will be triggered - * in listener {@link com.tencent.cloud.polaris.config.listener.PolarisConfigRefreshOptimizationListener}. - * - *

This bean will only be created and initialized when the config refresh type is {@code RefreshType.REFLECT}. - * - * @author jarvisxiong - */ -@SuppressWarnings({"unchecked", "rawtypes"}) -public class PolarisConfigRefreshScopeAnnotationDetector implements BeanPostProcessor, InitializingBean, PriorityOrdered { - - private final AtomicBoolean isRefreshScopeAnnotationUsed = new AtomicBoolean(false); - - private Class refreshScopeAnnotationClass; - - private String annotatedRefreshScopeBeanName; - - @Override - public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) - throws BeansException { - return bean; - } - - @Override - public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) - throws BeansException { - if (isRefreshScopeAnnotationUsed() || refreshScopeAnnotationClass == null) { - return bean; - } - Annotation[] refreshScopeAnnotations = bean.getClass().getAnnotationsByType(refreshScopeAnnotationClass); - if (refreshScopeAnnotations.length > 0) { - if (isRefreshScopeAnnotationUsed.compareAndSet(false, true)) { - annotatedRefreshScopeBeanName = beanName; - } - } - return bean; - } - - @Override - public void afterPropertiesSet() { - try { - refreshScopeAnnotationClass = Class.forName( - "org.springframework.cloud.context.config.annotation.RefreshScope", - false, - getClass().getClassLoader()); - } - catch (ClassNotFoundException ignored) { - } - } - - @Override - public int getOrder() { - return Ordered.LOWEST_PRECEDENCE; - } - - public boolean isRefreshScopeAnnotationUsed() { - return isRefreshScopeAnnotationUsed.get(); - } - - public String getAnnotatedRefreshScopeBeanName() { - return annotatedRefreshScopeBeanName; - } -} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisRefreshAffectedContextRefresher.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisRefreshAffectedContextRefresher.java index 146648ca3..5ace6f243 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisRefreshAffectedContextRefresher.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisRefreshAffectedContextRefresher.java @@ -25,6 +25,7 @@ import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper; import com.tencent.cloud.polaris.config.spring.property.SpringValue; import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; +import com.tencent.polaris.configuration.api.core.ConfigFileService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,6 +33,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.TypeConverter; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.cloud.context.environment.EnvironmentChangeEvent; +import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; @@ -58,11 +60,15 @@ public class PolarisRefreshAffectedContextRefresher extends PolarisConfigPropert private TypeConverter typeConverter; + private ContextRefresher contextRefresher; + public PolarisRefreshAffectedContextRefresher(PolarisConfigProperties polarisConfigProperties, - SpringValueRegistry springValueRegistry, PlaceholderHelper placeholderHelper) { - super(polarisConfigProperties); + SpringValueRegistry springValueRegistry, PlaceholderHelper placeholderHelper, + ConfigFileService configFileService, ContextRefresher contextRefresher) { + super(polarisConfigProperties, configFileService); this.springValueRegistry = springValueRegistry; this.placeholderHelper = placeholderHelper; + this.contextRefresher = contextRefresher; } @Override @@ -79,7 +85,20 @@ public class PolarisRefreshAffectedContextRefresher extends PolarisConfigPropert @Override public void refreshConfigurationProperties(Set changeKeys) { - context.publishEvent(new EnvironmentChangeEvent(context, changeKeys)); + boolean needRefreshContext = false; + for (String changedKey : changeKeys) { + boolean inRefreshScope = springValueRegistry.isRefreshScopeKey(changedKey); + if (inRefreshScope) { + needRefreshContext = true; + break; + } + } + if (needRefreshContext) { + contextRefresher.refresh(); + } + else { + context.publishEvent(new EnvironmentChangeEvent(context, changeKeys)); + } } private void updateSpringValue(SpringValue springValue) { diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisRefreshEntireContextRefresher.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisRefreshEntireContextRefresher.java index 3c2ca5ac7..93cebb03c 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisRefreshEntireContextRefresher.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisRefreshEntireContextRefresher.java @@ -21,8 +21,15 @@ package com.tencent.cloud.polaris.config.adapter; import java.util.Set; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; +import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; +import com.tencent.polaris.configuration.api.core.ConfigFileService; +import org.springframework.beans.BeansException; +import org.springframework.cloud.context.environment.EnvironmentChangeEvent; import org.springframework.cloud.context.refresh.ContextRefresher; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; /** * The default implement of Spring Cloud refreshes the entire Spring Context. @@ -30,13 +37,19 @@ import org.springframework.cloud.context.refresh.ContextRefresher; * * @author lingxiao.wlx */ -public class PolarisRefreshEntireContextRefresher extends PolarisConfigPropertyAutoRefresher { +public class PolarisRefreshEntireContextRefresher extends PolarisConfigPropertyAutoRefresher implements ApplicationContextAware { private final ContextRefresher contextRefresher; + private final SpringValueRegistry springValueRegistry; + + private ConfigurableApplicationContext context; + public PolarisRefreshEntireContextRefresher(PolarisConfigProperties polarisConfigProperties, - ContextRefresher contextRefresher) { - super(polarisConfigProperties); + SpringValueRegistry springValueRegistry, ConfigFileService configFileService, ContextRefresher contextRefresher) { + + super(polarisConfigProperties, configFileService); + this.springValueRegistry = springValueRegistry; this.contextRefresher = contextRefresher; } @@ -47,6 +60,24 @@ public class PolarisRefreshEntireContextRefresher extends PolarisConfigPropertyA @Override public void refreshConfigurationProperties(Set changeKeys) { - contextRefresher.refresh(); + boolean needRefreshContext = false; + for (String changedKey : changeKeys) { + boolean inRefreshScope = springValueRegistry.isRefreshScopeKey(changedKey); + if (inRefreshScope) { + needRefreshContext = true; + break; + } + } + if (needRefreshContext) { + contextRefresher.refresh(); + } + else { + context.publishEvent(new EnvironmentChangeEvent(context, changeKeys)); + } + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.context = (ConfigurableApplicationContext) applicationContext; } } diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/ReflectRefreshTypeCondition.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/ReflectRefreshTypeCondition.java index b6dd08764..3a9f48abe 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/ReflectRefreshTypeCondition.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/ReflectRefreshTypeCondition.java @@ -37,7 +37,7 @@ public class ReflectRefreshTypeCondition extends SpringBootCondition { */ public static final String POLARIS_CONFIG_REFRESH_TYPE = "spring.cloud.polaris.config.refresh-type"; - private static final RefreshType DEFAULT_REFRESH_TYPE = RefreshType.REFLECT; + private static final RefreshType DEFAULT_REFRESH_TYPE = RefreshType.REFRESH_CONTEXT; @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { 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 index 4586134ee..1a5a994ec 100644 --- 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 @@ -70,7 +70,7 @@ public class PolarisConfigProperties { /** * Attribute refresh type. */ - private RefreshType refreshType = RefreshType.REFLECT; + private RefreshType refreshType = RefreshType.REFRESH_CONTEXT; /** * List of injected configuration files. @@ -96,6 +96,8 @@ public class PolarisConfigProperties { */ private boolean internalEnabled = true; + private boolean checkAddress = true; + public boolean isEnabled() { return enabled; } @@ -192,6 +194,14 @@ public class PolarisConfigProperties { this.internalEnabled = internalEnabled; } + public boolean isCheckAddress() { + return checkAddress; + } + + public void setCheckAddress(boolean checkAddress) { + this.checkAddress = checkAddress; + } + @Override public String toString() { return "PolarisConfigProperties{" + @@ -207,6 +217,7 @@ public class PolarisConfigProperties { ", dataSource='" + dataSource + '\'' + ", localFileRootPath='" + localFileRootPath + '\'' + ", internalEnabled=" + internalEnabled + + ", checkAddress=" + checkAddress + '}'; } } diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListener.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListener.java deleted file mode 100644 index 598978112..000000000 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListener.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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.listener; - -import java.util.Collections; - -import com.tencent.cloud.polaris.config.adapter.PolarisConfigRefreshScopeAnnotationDetector; -import com.tencent.cloud.polaris.config.adapter.PolarisRefreshEntireContextRefresher; -import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; -import com.tencent.cloud.polaris.config.enums.RefreshType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.ConstructorArgumentValues; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.cloud.context.refresh.ContextRefresher; -import org.springframework.context.ApplicationListener; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.core.env.MapPropertySource; -import org.springframework.core.env.MutablePropertySources; -import org.springframework.lang.NonNull; - -import static com.tencent.cloud.polaris.config.condition.ReflectRefreshTypeCondition.POLARIS_CONFIG_REFRESH_TYPE; - -/** - * When {@link PolarisConfigRefreshScopeAnnotationDetector} detects that - * the annotation {@code @RefreshScope} exists and is used, but the config refresh type - * {@code spring.cloud.polaris.config.refresh-type} is still {@code RefreshType.REFLECT}, then the framework will - * automatically switch the config refresh type to {@code RefreshType.REFRESH_CONTEXT}. - * - *

The purpose of this optimization is to omit additional configuration, and facilitate for users to use the - * dynamic configuration refresh strategy of Spring Cloud Context.

- * - * @author jarvisxiong - */ -public class PolarisConfigRefreshOptimizationListener implements ApplicationListener { - - private static final Logger LOGGER = LoggerFactory.getLogger(PolarisConfigRefreshOptimizationListener.class); - - private static final String CONFIG_REFRESH_TYPE_PROPERTY = "configRefreshTypeProperty"; - - private static final String REFLECT_REBINDER_BEAN_NAME = "affectedConfigurationPropertiesRebinder"; - - private static final String REFLECT_REFRESHER_BEAN_NAME = "polarisReflectPropertySourceAutoRefresher"; - - private static final String REFRESH_CONTEXT_REFRESHER_BEAN_NAME = "polarisRefreshContextPropertySourceAutoRefresher"; - - - @Override - public void onApplicationEvent(@NonNull ContextRefreshedEvent event) { - ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) event.getApplicationContext(); - PolarisConfigRefreshScopeAnnotationDetector detector = applicationContext.getBean(PolarisConfigRefreshScopeAnnotationDetector.class); - boolean isRefreshScopeAnnotationUsed = detector.isRefreshScopeAnnotationUsed(); - String annotatedRefreshScopeBeanName = detector.getAnnotatedRefreshScopeBeanName(); - // using System.setProperty to set spring.cloud.polaris.config.refresh-type - String value = System.getProperty("spring.cloud.polaris.config.refresh-type"); - boolean isSystemSetRefreshType = RefreshType.REFRESH_CONTEXT.toString().equalsIgnoreCase(value); - // a bean is using @RefreshScope, but the config refresh type is still [reflect], switch automatically - if (isRefreshScopeAnnotationUsed || isSystemSetRefreshType) { - if (isRefreshScopeAnnotationUsed) { - LOGGER.warn("Detected that the bean [{}] is using @RefreshScope annotation, but the config refresh type is still [reflect]. " + "[SCT] will automatically switch to [refresh_context].", annotatedRefreshScopeBeanName); - } - if (isSystemSetRefreshType) { - LOGGER.warn("Detected that using System.setProperty to set spring.cloud.polaris.config.refresh-type = refresh_context, but the config refresh type is still [reflect]. " + "[SCT] will automatically switch to [refresh_context]."); - } - switchConfigRefreshTypeProperty(applicationContext); - modifyPolarisConfigPropertiesBean(applicationContext); - // remove related bean of type [reflect] - removeRelatedBeansOfReflect(applicationContext); - // register a new refresher bean of type [refresh_context] - registerRefresherBeanOfRefreshContext(applicationContext); - // add the new refresher to context as a listener - addRefresherBeanAsListener(applicationContext); - } - } - - private void switchConfigRefreshTypeProperty(ConfigurableApplicationContext applicationContext) { - MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources(); - propertySources.addFirst(new MapPropertySource(CONFIG_REFRESH_TYPE_PROPERTY, Collections.singletonMap(POLARIS_CONFIG_REFRESH_TYPE, RefreshType.REFRESH_CONTEXT))); - } - - private void modifyPolarisConfigPropertiesBean(ConfigurableApplicationContext applicationContext) { - PolarisConfigProperties polarisConfigProperties = applicationContext.getBean(PolarisConfigProperties.class); - polarisConfigProperties.setRefreshType(RefreshType.REFRESH_CONTEXT); - } - - private void removeRelatedBeansOfReflect(ConfigurableApplicationContext applicationContext) { - try { - DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory(); - beanFactory.removeBeanDefinition(REFLECT_REFRESHER_BEAN_NAME); - beanFactory.removeBeanDefinition(REFLECT_REBINDER_BEAN_NAME); - } - catch (BeansException e) { - // If there is a removeBean exception in this code, do not affect the main process startup. Some user usage may cause the polarisReflectPropertySourceAutoRefresher to not load, and the removeBeanDefinition will report an error - LOGGER.debug("removeRelatedBeansOfReflect occur error:", e); - } - } - - private void registerRefresherBeanOfRefreshContext(ConfigurableApplicationContext applicationContext) { - DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory(); - AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition(); - beanDefinition.setBeanClass(PolarisRefreshEntireContextRefresher.class); - PolarisConfigProperties polarisConfigProperties = beanFactory.getBean(PolarisConfigProperties.class); - ContextRefresher contextRefresher = beanFactory.getBean(ContextRefresher.class); - ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues(); - constructorArgumentValues.addIndexedArgumentValue(0, polarisConfigProperties); - constructorArgumentValues.addIndexedArgumentValue(1, contextRefresher); - beanFactory.registerBeanDefinition(REFRESH_CONTEXT_REFRESHER_BEAN_NAME, beanDefinition); - } - - - private void addRefresherBeanAsListener(ConfigurableApplicationContext applicationContext) { - PolarisRefreshEntireContextRefresher refresher = (PolarisRefreshEntireContextRefresher) applicationContext.getBean(REFRESH_CONTEXT_REFRESHER_BEAN_NAME); - applicationContext.addApplicationListener(refresher); - } -} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/PolarisProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/PolarisProcessor.java index cfbc5385b..137c9530e 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/PolarisProcessor.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/PolarisProcessor.java @@ -23,7 +23,11 @@ import java.util.LinkedList; import java.util.List; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; import org.springframework.lang.NonNull; @@ -34,17 +38,32 @@ import org.springframework.util.ReflectionUtils; * * @author weihubeats 2022-7-10 */ -public abstract class PolarisProcessor implements BeanPostProcessor, PriorityOrdered { +public abstract class PolarisProcessor implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware { + + private ConfigurableListableBeanFactory beanFactory; @Override public Object postProcessBeforeInitialization(Object bean, @NonNull String beanName) throws BeansException { Class clazz = bean.getClass(); + + boolean isRefreshScope = false; + + try { + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); + if ("refresh".equals(beanDefinition.getScope())) { + isRefreshScope = true; + } + } + catch (Exception ignored) { + // ignore + } + for (Field field : findAllField(clazz)) { - processField(bean, beanName, field); + processField(bean, beanName, field, isRefreshScope); } for (Method method : findAllMethod(clazz)) { - processMethod(bean, beanName, method); + processMethod(bean, beanName, method, isRefreshScope); } return bean; } @@ -60,7 +79,7 @@ public abstract class PolarisProcessor implements BeanPostProcessor, PriorityOrd * @param beanName beanName * @param field field */ - protected abstract void processField(Object bean, String beanName, Field field); + protected abstract void processField(Object bean, String beanName, Field field, boolean isRefreshScope); /** * subclass should implement this method to process method. @@ -68,7 +87,7 @@ public abstract class PolarisProcessor implements BeanPostProcessor, PriorityOrd * @param beanName beanName * @param method method */ - protected abstract void processMethod(Object bean, String beanName, Method method); + protected abstract void processMethod(Object bean, String beanName, Method method, boolean isRefreshScope); @Override @@ -77,15 +96,20 @@ public abstract class PolarisProcessor implements BeanPostProcessor, PriorityOrd return Ordered.LOWEST_PRECEDENCE; } - private List findAllField(Class clazz) { + protected List findAllField(Class clazz) { final List res = new LinkedList<>(); ReflectionUtils.doWithFields(clazz, res::add); return res; } - private List findAllMethod(Class clazz) { + protected List findAllMethod(Class clazz) { final List res = new LinkedList<>(); ReflectionUtils.doWithMethods(clazz, res::add); return res; } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.beanFactory = (ConfigurableListableBeanFactory) applicationContext.getAutowireCapableBeanFactory(); + } } diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java index 562fc67f2..96912ea31 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java @@ -21,11 +21,13 @@ import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; +import com.google.common.base.CaseFormat; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; @@ -35,6 +37,7 @@ import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper; import com.tencent.cloud.polaris.config.spring.property.SpringValue; import com.tencent.cloud.polaris.config.spring.property.SpringValueDefinition; import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; +import com.tencent.polaris.api.utils.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,6 +53,8 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.context.annotation.Bean; import org.springframework.lang.NonNull; @@ -105,23 +110,31 @@ public class SpringValueProcessor extends PolarisProcessor implements BeanDefini @Override - protected void processField(Object bean, String beanName, Field field) { + protected void processField(Object bean, String beanName, Field field, boolean isRefreshScope) { // register @Value on field Value value = field.getAnnotation(Value.class); if (value == null) { return; } - doRegister(bean, beanName, field, value); + doRegister(bean, beanName, field, value, isRefreshScope); } @Override - protected void processMethod(Object bean, String beanName, Method method) { + protected void processMethod(Object bean, String beanName, Method method, boolean isRefreshScope) { //register @Value on method Value value = method.getAnnotation(Value.class); - if (value == null) { + if (value != null) { + processMethodValue(bean, beanName, method, value, isRefreshScope); return; } + + if (method.getAnnotation(RefreshScope.class) != null) { + processMethodRefreshScope(bean, method); + } + } + + private void processMethodValue(Object bean, String beanName, Method method, Value value, boolean isRefreshScope) { //skip Configuration bean methods if (method.getAnnotation(Bean.class) != null) { return; @@ -131,8 +144,128 @@ public class SpringValueProcessor extends PolarisProcessor implements BeanDefini bean.getClass().getName(), method.getName(), method.getParameterTypes().length); return; } + doRegister(bean, beanName, method, value, isRefreshScope); + } + + /** + * @RefreshScope on method. + * method parameter with @Value ${@link com.tencent.cloud.polaris.config.spring.annotation.RefreshScopeSpringProcessorTest.TestConfig3#testBean3}. + * method parameter class with @ConfigurationProperties ${@link com.tencent.cloud.polaris.config.spring.annotation.RefreshScopeSpringProcessorTest.TestConfig4#testBean4}. + * @ConfigurationProperties outside method may effect @RefreshScope bean${@link com.tencent.cloud.polaris.config.spring.annotation.RefreshScopeSpringProcessorTest.TestConfig5#testBean5()}. + * @param bean spring bean. + * @param method method. + */ + private void processMethodRefreshScope(Object bean, Method method) { + // must have @Bean annotation + if (method.getAnnotation(Bean.class) == null) { + return; + } + + for (Parameter parameter : method.getParameters()) { + Value value = parameter.getAnnotation(Value.class); + if (value != null) { + // method parameter with @Value + Set keys = placeholderHelper.extractPlaceholderKeys(value.value()); + springValueRegistry.putRefreshScopeKeys(keys); + } + // method parameter class with @ConfigurationProperties + ConfigurationProperties configurationProperties = parameter.getType().getAnnotation(ConfigurationProperties.class); + parseConfigurationPropertiesKeys(configurationProperties, parameter.getType()); + } + + // analyze all fields of the class containing the method. + for (Field field : findAllField(bean.getClass())) { + Value value = field.getAnnotation(Value.class); + if (value != null) { + // field with @Value + Set keys = placeholderHelper.extractPlaceholderKeys(value.value()); + springValueRegistry.putRefreshScopeKeys(keys); + continue; + } + // field class with @ConfigurationProperties + ConfigurationProperties configurationProperties = field.getType().getAnnotation(ConfigurationProperties.class); + parseConfigurationPropertiesKeys(configurationProperties, field.getType()); + } + } + + /** + * parse refresh scope keys from @ConfigurationProperties. + * @param configurationProperties @ConfigurationProperties annotation object. + * @param clazz class of @ConfigurationProperties bean. + */ + private void parseConfigurationPropertiesKeys(ConfigurationProperties configurationProperties, Class clazz) { + if (configurationProperties != null) { + // get prefix from @ConfigurationProperties prefix or value. + String prefix = configurationProperties.value(); + if (StringUtils.isEmpty(prefix)) { + prefix = configurationProperties.prefix(); + } + if (StringUtils.isNotEmpty(prefix)) { + prefix += "."; + } + parseConfigKeys(clazz, prefix); + } + } + + /** + * parse all fields of the configClazz. + * if the field is primitive or wrapper, add it to refresh scope key map. + * ${@link com.tencent.cloud.polaris.config.spring.annotation.RefreshScopeSpringProcessorTest.TestBeanProperties2#name} + * if the field is collection, add it to refresh scope prefix trie node. + * ${@link com.tencent.cloud.polaris.config.spring.annotation.RefreshScopeSpringProcessorTest.TestBeanProperties2#list} + * if the field is complex type, recursive parse. + * ${@link com.tencent.cloud.polaris.config.spring.annotation.RefreshScopeSpringProcessorTest.TestBeanProperties2#inner} + * @param configClazz class or subclass of @ConfigurationProperties bean. + * @param prefix prefix or subclass's prefix of @ConfigurationProperties bean. + */ + private void parseConfigKeys(Class configClazz, String prefix) { + for (Field field : findAllField(configClazz)) { + if (isPrimitiveOrWrapper(field.getType())) { + // lowerCamel format + springValueRegistry.putRefreshScopeKey(prefix + field.getName()); + // lower-hyphen format + springValueRegistry.putRefreshScopeKey( + prefix + CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, field.getName())); + } + else if (isCollection(field.getType())) { + springValueRegistry.putRefreshScopePrefixKey(prefix + field.getName()); + springValueRegistry.putRefreshScopePrefixKey( + prefix + CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, field.getName())); + } + else { + // complex type, recursive parse + parseConfigKeys(field.getType(), prefix + field.getName() + "."); + parseConfigKeys(field.getType(), + prefix + CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, field.getName()) + "."); + } + } + } - doRegister(bean, beanName, method, value); + /** + * whether the class is primitive or wrapper. + * @param clazz the class under analysis. + * @return true if the class is primitive or wrapper, otherwise false. + */ + private static boolean isPrimitiveOrWrapper(Class clazz) { + return clazz.isPrimitive() || + clazz == String.class || + clazz == Boolean.class || + clazz == Character.class || + clazz == Byte.class || + clazz == Short.class || + clazz == Integer.class || + clazz == Long.class || + clazz == Float.class || + clazz == Double.class; + } + + /** + * whether the class is collection(array, collection, map). + * @param clazz the class under analysis. + * @return true if the class is collection(array, collection, map), otherwise false. + */ + private static boolean isCollection(Class clazz) { + return clazz.isArray() || Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz); } @Override @@ -147,7 +280,7 @@ public class SpringValueProcessor extends PolarisProcessor implements BeanDefini } } - private void doRegister(Object bean, String beanName, Member member, Value value) { + private void doRegister(Object bean, String beanName, Member member, Value value, boolean isRefreshScope) { Set keys = placeholderHelper.extractPlaceholderKeys(value.value()); if (keys.isEmpty()) { return; @@ -158,10 +291,16 @@ public class SpringValueProcessor extends PolarisProcessor implements BeanDefini if (member instanceof Field) { Field field = (Field) member; springValue = new SpringValue(key, value.value(), bean, beanName, field); + if (isRefreshScope) { + springValueRegistry.putRefreshScopeKey(key); + } } else if (member instanceof Method) { Method method = (Method) member; springValue = new SpringValue(key, value.value(), bean, beanName, method); + if (isRefreshScope) { + springValueRegistry.putRefreshScopeKey(key); + } } else { LOGGER.error("Polaris @Value annotation currently only support to be used on methods and fields, " diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueRegistry.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueRegistry.java index 9e2c2657d..724ea7357 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueRegistry.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueRegistry.java @@ -20,6 +20,7 @@ package com.tencent.cloud.polaris.config.spring.property; import java.util.Collection; import java.util.Iterator; import java.util.Map; +import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -29,6 +30,9 @@ import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; +import com.google.common.collect.Sets; +import com.tencent.polaris.api.pojo.TrieNode; +import com.tencent.polaris.api.utils.TrieUtil; import com.tencent.polaris.client.util.NamedThreadFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,6 +58,10 @@ public class SpringValueRegistry implements DisposableBean { private final Object LOCK = new Object(); private ScheduledExecutorService executor; + private final TrieNode refreshScopePrefixRoot = new TrieNode<>(TrieNode.ROOT_PATH); + + private final Set refreshScopeKeys = Sets.newConcurrentHashSet(); + public void register(BeanFactory beanFactory, String key, SpringValue springValue) { if (!registry.containsKey(beanFactory)) { synchronized (LOCK) { @@ -63,7 +71,16 @@ public class SpringValueRegistry implements DisposableBean { } } - registry.get(beanFactory).put(key, springValue); + Multimap multimap = registry.get(beanFactory); + for (SpringValue existingValue : multimap.get(key)) { + // if the spring value is already registered, remove it + if (existingValue.getBeanName().equals(springValue.getBeanName())) { + multimap.remove(key, existingValue); + break; + } + + } + multimap.put(key, springValue); // lazy initialize if (initialized.compareAndSet(false, true)) { @@ -102,6 +119,30 @@ public class SpringValueRegistry implements DisposableBean { } } + public void putRefreshScopePrefixKey(String key) { + TrieUtil.buildConfigTrieNode(key, refreshScopePrefixRoot); + } + + public void putRefreshScopeKey(String key) { + refreshScopeKeys.add(key); + } + + public void putRefreshScopeKeys(Set keys) { + refreshScopeKeys.addAll(keys); + } + + /** + * first check if the key is in refreshScopeKeys, if not, check the key by TrieUtil. + * @param key changed key. + * @return true if the key is refresh scope key, otherwise false. + */ + public boolean isRefreshScopeKey(String key) { + if (refreshScopeKeys.contains(key)) { + return true; + } + return TrieUtil.checkConfig(refreshScopePrefixRoot, key); + } + @Override public void destroy() throws Exception { executor.shutdown(); diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/MockedConfigKVFile.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/MockedConfigKVFile.java index bad8894a1..256f5e4b7 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/MockedConfigKVFile.java +++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/MockedConfigKVFile.java @@ -37,12 +37,32 @@ import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeListener; public class MockedConfigKVFile implements ConfigKVFile { private final Map properties; + + private String fileName; + + private String fileGroup; + + private String namespace; + private final List listeners = new ArrayList<>(); public MockedConfigKVFile(Map properties) { this.properties = properties; } + public MockedConfigKVFile(Map properties, String fileName) { + this.properties = properties; + this.fileName = fileName; + } + + public MockedConfigKVFile(Map properties, String fileName, String fileGroup, String namespace) { + this.properties = properties; + this.fileName = fileName; + this.fileGroup = fileGroup; + this.namespace = namespace; + + } + @Override public String getProperty(String s, String s1) { return String.valueOf(properties.get(s)); @@ -161,16 +181,16 @@ public class MockedConfigKVFile implements ConfigKVFile { @Override public String getNamespace() { - return null; + return namespace; } @Override public String getFileGroup() { - return null; + return fileGroup; } @Override public String getFileName() { - return null; + return fileName; } } diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocatorTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocatorTest.java index 42dbd028f..85c762c2f 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocatorTest.java +++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigFileLocatorTest.java @@ -18,6 +18,8 @@ package com.tencent.cloud.polaris.config.adapter; +import java.lang.reflect.Field; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -29,6 +31,7 @@ import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; import com.tencent.cloud.polaris.context.config.PolarisContextProperties; import com.tencent.polaris.configuration.api.core.ConfigFileService; import com.tencent.polaris.configuration.api.core.ConfigKVFile; +import com.tencent.polaris.configuration.client.internal.RevisableConfigFileGroup; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -66,6 +69,7 @@ public class PolarisConfigFileLocatorTest { @Test public void testLoadApplicationPropertiesFile() { + clearCompositePropertySourceCache(); PolarisConfigFileLocator locator = new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties, configFileService, environment); @@ -103,6 +107,7 @@ public class PolarisConfigFileLocatorTest { @Test public void testActiveProfileFilesPriorityBiggerThanDefault() { + clearCompositePropertySourceCache(); PolarisConfigFileLocator locator = new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties, configFileService, environment); @@ -152,6 +157,7 @@ public class PolarisConfigFileLocatorTest { @Test public void testGetCustomFiles() { + clearCompositePropertySourceCache(); PolarisConfigFileLocator locator = new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties, configFileService, environment); @@ -202,4 +208,73 @@ public class PolarisConfigFileLocatorTest { assertThat(propertySource.getProperty("k2")).isEqualTo("v2"); assertThat(propertySource.getProperty("k3")).isEqualTo("v3"); } + + + @Test + public void testGetCustomGroupFiles() { + clearCompositePropertySourceCache(); + PolarisConfigFileLocator locator = new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties, + configFileService, environment); + + when(polarisContextProperties.getNamespace()).thenReturn(testNamespace); + when(polarisContextProperties.getService()).thenReturn(testServiceName); + + Map emptyMap = new HashMap<>(); + ConfigKVFile emptyConfigFile = new MockedConfigKVFile(emptyMap); + + when(configFileService.getConfigPropertiesFile(testNamespace, testServiceName, "application.properties")).thenReturn(emptyConfigFile); + when(configFileService.getConfigYamlFile(testNamespace, testServiceName, "application.yml")).thenReturn(emptyConfigFile); + when(configFileService.getConfigYamlFile(testNamespace, testServiceName, "application.yaml")).thenReturn(emptyConfigFile); + when(configFileService.getConfigPropertiesFile(testNamespace, testServiceName, "bootstrap.properties")).thenReturn(emptyConfigFile); + when(configFileService.getConfigYamlFile(testNamespace, testServiceName, "bootstrap.yml")).thenReturn(emptyConfigFile); + when(configFileService.getConfigYamlFile(testNamespace, testServiceName, "bootstrap.yaml")).thenReturn(emptyConfigFile); + + List customFiles = new LinkedList<>(); + ConfigFileGroup configFileGroup = new ConfigFileGroup(); + String customGroup = "group2"; + configFileGroup.setName(customGroup); + String customFile1 = "file1.properties"; + String customFile2 = "file2.yaml"; + customFiles.add(configFileGroup); + + when(polarisConfigProperties.isEnabled()).thenReturn(true); + when(polarisConfigProperties.getGroups()).thenReturn(customFiles); + when(polarisConfigProperties.isInternalEnabled()).thenReturn(true); + when(environment.getActiveProfiles()).thenReturn(new String[] {}); + + // file1.properties + Map file1Map = new HashMap<>(); + file1Map.put("k1", "v1"); + file1Map.put("k2", "v2"); + ConfigKVFile file1 = new MockedConfigKVFile(file1Map, customFile1); + when(configFileService.getConfigPropertiesFile(testNamespace, customGroup, customFile1)).thenReturn(file1); + + // file2.yaml + Map file2Map = new HashMap<>(); + file2Map.put("k1", "v11"); + file2Map.put("k3", "v3"); + ConfigKVFile file2 = new MockedConfigKVFile(file2Map, customFile2); + when(configFileService.getConfigYamlFile(testNamespace, customGroup, customFile2)).thenReturn(file2); + + RevisableConfigFileGroup revisableConfigFileGroup = new RevisableConfigFileGroup(testNamespace, customGroup, Arrays.asList(file1, file2), "v1"); + when(configFileService.getConfigFileGroup(testNamespace, customGroup)).thenReturn(revisableConfigFileGroup); + + PropertySource propertySource = locator.locate(environment); + + assertThat(propertySource.getProperty("k1")).isEqualTo("v1"); + assertThat(propertySource.getProperty("k2")).isEqualTo("v2"); + assertThat(propertySource.getProperty("k3")).isEqualTo("v3"); + } + + private void clearCompositePropertySourceCache() { + try { + Class clazz = PolarisConfigFileLocator.class; + Field field = clazz.getDeclaredField("compositePropertySourceCache"); + field.setAccessible(true); + field.set(null, null); + } + catch (Exception e) { + // ignore + } + } } diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigRefreshScopeAnnotationDetectorTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigRefreshScopeAnnotationDetectorTest.java deleted file mode 100644 index 31e3c1d57..000000000 --- a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigRefreshScopeAnnotationDetectorTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 com.tencent.cloud.polaris.config.PolarisConfigAutoConfiguration; -import com.tencent.cloud.polaris.config.PolarisConfigBootstrapAutoConfiguration; -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration; -import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; -import org.springframework.cloud.context.config.annotation.RefreshScope; - -import static org.assertj.core.api.Assertions.as; -import static org.assertj.core.api.Assertions.assertThat; - -/** - * test for {@link PolarisConfigRefreshScopeAnnotationDetector}. - */ -@SuppressWarnings("rawtypes") -public class PolarisConfigRefreshScopeAnnotationDetectorTest { - - private static Class refreshScopeAnnotationClass = null; - - static { - try { - refreshScopeAnnotationClass = Class.forName( - "org.springframework.cloud.context.config.annotation.RefreshScope", - false, - PolarisConfigRefreshScopeAnnotationDetectorTest.class.getClassLoader()); - } - catch (ClassNotFoundException ignored) { - } - } - - @Test - public void testUseRefreshScope() { - ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(PolarisConfigBootstrapAutoConfiguration.class)) - .withConfiguration(AutoConfigurations.of(PolarisConfigAutoConfiguration.class)) - .withConfiguration(AutoConfigurations.of(RefreshAutoConfiguration.class)) - .withConfiguration(AutoConfigurations.of(ConfigurationPropertiesRebinderAutoConfiguration.class)) - .withBean("testBeanWithRefreshScope", TestBeanWithRefreshScope.class) - .withPropertyValues("spring.application.name=" + "polarisConfigRefreshScopeAnnotationDetectorTest") - .withPropertyValues("server.port=" + 8080) - .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081") - .withPropertyValues("spring.cloud.polaris.config.connect-remote-server=false"); - contextRunner.run(context -> { - assertThat(context).hasSingleBean(PolarisConfigRefreshScopeAnnotationDetector.class); - PolarisConfigRefreshScopeAnnotationDetector detector = context.getBean(PolarisConfigRefreshScopeAnnotationDetector.class); - assertThat(detector.isRefreshScopeAnnotationUsed()).isTrue(); - assertThat(detector.getAnnotatedRefreshScopeBeanName()).isEqualTo("scopedTarget.testBeanWithRefreshScope"); - assertThat(detector).extracting("refreshScopeAnnotationClass", as(InstanceOfAssertFactories.type(Class.class))) - .isEqualTo(refreshScopeAnnotationClass); - }); - } - - @Test - public void testNotUseRefreshScope() { - ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(PolarisConfigBootstrapAutoConfiguration.class)) - .withConfiguration(AutoConfigurations.of(PolarisConfigAutoConfiguration.class)) - .withConfiguration(AutoConfigurations.of(RefreshAutoConfiguration.class)) - .withConfiguration(AutoConfigurations.of(ConfigurationPropertiesRebinderAutoConfiguration.class)) - .withPropertyValues("spring.application.name=" + "polarisConfigRefreshScopeAnnotationDetectorTest") - .withPropertyValues("server.port=" + 8080) - .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081") - .withPropertyValues("spring.cloud.polaris.config.connect-remote-server=false"); - contextRunner.run(context -> { - assertThat(context).hasSingleBean(PolarisConfigRefreshScopeAnnotationDetector.class); - PolarisConfigRefreshScopeAnnotationDetector detector = context.getBean(PolarisConfigRefreshScopeAnnotationDetector.class); - assertThat(detector.isRefreshScopeAnnotationUsed()).isFalse(); - assertThat(detector.getAnnotatedRefreshScopeBeanName()).isNull(); - assertThat(detector).extracting("refreshScopeAnnotationClass", as(InstanceOfAssertFactories.type(Class.class))) - .isEqualTo(refreshScopeAnnotationClass); - }); - } - - @RefreshScope - protected static class TestBeanWithRefreshScope { - - } -} diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertiesSourceAutoRefresherTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertiesSourceAutoRefresherTest.java index 0d10255fa..fffb294c7 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertiesSourceAutoRefresherTest.java +++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertiesSourceAutoRefresherTest.java @@ -20,17 +20,24 @@ package com.tencent.cloud.polaris.config.adapter; import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper; import com.tencent.cloud.polaris.config.spring.property.SpringValue; import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; import com.tencent.polaris.configuration.api.core.ChangeType; +import com.tencent.polaris.configuration.api.core.ConfigFileService; import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeEvent; import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; +import com.tencent.polaris.configuration.client.internal.CompositeConfigFile; +import com.tencent.polaris.configuration.client.internal.RevisableConfigFileGroup; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -39,6 +46,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.TypeConverter; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.context.ConfigurableApplicationContext; import static org.assertj.core.api.Assertions.assertThat; @@ -55,6 +63,7 @@ import static org.mockito.Mockito.when; public class PolarisPropertiesSourceAutoRefresherTest { private final String testNamespace = "testNamespace"; + private final String testFileGroup = "testFileGroup"; private final String testServiceName = "testServiceName"; private final String testFileName = "application.properties"; @Mock @@ -66,6 +75,12 @@ public class PolarisPropertiesSourceAutoRefresherTest { @Mock private PlaceholderHelper placeholderHelper; + @Mock + private ConfigFileService configFileService; + + @Mock + private ContextRefresher contextRefresher; + @BeforeEach public void setUp() { PolarisPropertySourceManager.clearPropertySources(); @@ -73,7 +88,8 @@ public class PolarisPropertiesSourceAutoRefresherTest { @Test public void testConfigFileChanged() throws Exception { - PolarisRefreshAffectedContextRefresher refresher = new PolarisRefreshAffectedContextRefresher(polarisConfigProperties, springValueRegistry, placeholderHelper); + PolarisRefreshAffectedContextRefresher refresher = new PolarisRefreshAffectedContextRefresher( + polarisConfigProperties, springValueRegistry, placeholderHelper, configFileService, contextRefresher); ConfigurableApplicationContext applicationContext = mock(ConfigurableApplicationContext.class); ConfigurableListableBeanFactory beanFactory = mock(ConfigurableListableBeanFactory.class); TypeConverter typeConverter = mock(TypeConverter.class); @@ -121,4 +137,88 @@ public class PolarisPropertiesSourceAutoRefresherTest { assertThat(polarisPropertySource.getProperty("k2")).isNull(); assertThat(polarisPropertySource.getProperty("k4")).isEqualTo("v4"); } + + @Test + public void testConfigFileGroupChanged() throws Exception { + PolarisRefreshAffectedContextRefresher refresher = new PolarisRefreshAffectedContextRefresher( + polarisConfigProperties, springValueRegistry, placeholderHelper, configFileService, contextRefresher); + ConfigurableApplicationContext applicationContext = mock(ConfigurableApplicationContext.class); + ConfigurableListableBeanFactory beanFactory = mock(ConfigurableListableBeanFactory.class); + TypeConverter typeConverter = mock(TypeConverter.class); + when(beanFactory.getTypeConverter()).thenReturn(typeConverter); + when(applicationContext.getBeanFactory()).thenReturn(beanFactory); + refresher.setApplicationContext(applicationContext); + when(typeConverter.convertIfNecessary(any(), any(), (Field) any())).thenReturn("v11"); + Collection springValues = new ArrayList<>(); + MockedConfigChange mockedConfigChange = new MockedConfigChange(); + mockedConfigChange.setK1("v1"); + Field field = mockedConfigChange.getClass().getDeclaredField("k1"); + SpringValue springValue = new SpringValue("v1", "placeholder", mockedConfigChange, "mockedConfigChange", field); + + springValues.add(springValue); + + when(springValueRegistry.get(any(), any())).thenReturn(springValues); + + when(polarisConfigProperties.isAutoRefresh()).thenReturn(true); + + Map content = new ConcurrentHashMap<>(); + content.put("k1", "v1"); + content.put("k2", "v2"); + content.put("k3", "v3"); + MockedConfigKVFile file = new MockedConfigKVFile(content, testFileName, testFileGroup, testNamespace); + when(configFileService.getConfigPropertiesFile(testNamespace, testFileGroup, testFileName)) + .thenReturn(file); + + CompositeConfigFile compositeConfigFile = new CompositeConfigFile(Collections.singletonList(file)); + PolarisPropertySource polarisPropertySource = new PolarisPropertySource(testNamespace, testFileGroup, testFileName, + compositeConfigFile, new ConcurrentHashMap<>(content)); + + PolarisPropertySourceManager.addPropertySource(polarisPropertySource); + + RevisableConfigFileGroup revisableConfigFileGroup = new RevisableConfigFileGroup(testNamespace, testFileGroup, Collections.singletonList(file), "v1"); + when(configFileService.getConfigFileGroup(testNamespace, testFileGroup)).thenReturn(revisableConfigFileGroup); + + refresher.onApplicationEvent(null); + + Map content2 = new ConcurrentHashMap<>(); + content2.put("k1", "v1.1"); + content2.put("k3.1", "v3.1"); + MockedConfigKVFile file2 = new MockedConfigKVFile(content2, "file2.properties", testFileGroup, testNamespace); + + when(configFileService.getConfigPropertiesFile(testNamespace, testFileGroup, "file2.properties")) + .thenReturn(file2); + + revisableConfigFileGroup.updateConfigFileList(Arrays.asList(file, file2), "v2"); + Thread.sleep(5000); + assertThat(polarisPropertySource.getProperty("k1")).isEqualTo("v1.1"); + assertThat(polarisPropertySource.getProperty("k2")).isEqualTo("v2"); + assertThat(polarisPropertySource.getProperty("k3")).isEqualTo("v3"); + assertThat(polarisPropertySource.getProperty("k3.1")).isEqualTo("v3.1"); + + // delete event + revisableConfigFileGroup.updateConfigFileList(Collections.singletonList(file), "v3"); + ConfigPropertyChangeInfo changeInfo = new ConfigPropertyChangeInfo("k1", "v1.1", null, ChangeType.DELETED); + ConfigPropertyChangeInfo changeInfo2 = new ConfigPropertyChangeInfo("k3.1", "v3.1", null, ChangeType.DELETED); + ConfigPropertyChangeInfo changeInfo3 = new ConfigPropertyChangeInfo("k4", null, "v4", ChangeType.ADDED); + Map changeInfos = new TreeMap<>(); + changeInfos.put("k1", changeInfo); + changeInfos.put("k3.1", changeInfo2); + changeInfos.put("k4", changeInfo3); + ConfigKVFileChangeEvent event = new ConfigKVFileChangeEvent(changeInfos); + file2.fireChangeListener(event); + Thread.sleep(5000); + + assertThat(polarisPropertySource.getProperty("k1")).isEqualTo("v1"); + assertThat(polarisPropertySource.getProperty("k3.1")).isNull(); + assertThat(polarisPropertySource.getProperty("k4")).isEqualTo("v4"); + + revisableConfigFileGroup.updateConfigFileList(Arrays.asList(file, file2), "v4"); + Thread.sleep(5000); + + // no exception + MockedConfigKVFile file3 = new MockedConfigKVFile(Collections.emptyMap(), "file3.properties", testFileGroup, testNamespace); + revisableConfigFileGroup.updateConfigFileList(Arrays.asList(file, file2, file3), "v5"); + Thread.sleep(5000); + + } } diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/ConfigChangeListenerTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/ConfigChangeListenerTest.java index 465af4353..0c5e2eb43 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/ConfigChangeListenerTest.java +++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/ConfigChangeListenerTest.java @@ -18,13 +18,16 @@ package com.tencent.cloud.polaris.config.listener; +import java.lang.reflect.Field; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import com.google.common.collect.Sets; +import com.tencent.cloud.polaris.config.adapter.PolarisConfigFileLocator; import com.tencent.cloud.polaris.config.annotation.PolarisConfigKVFileChangeListener; import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -35,6 +38,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.context.environment.EnvironmentChangeEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.CompositePropertySource; import org.springframework.stereotype.Component; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -47,7 +51,9 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen */ @ExtendWith(SpringExtension.class) @SpringBootTest(webEnvironment = DEFINED_PORT, classes = ConfigChangeListenerTest.TestApplication.class, - properties = {"server.port=48081", "spring.config.location = classpath:application-test.yml"}) + properties = {"server.port=48081", "spring.config.location = classpath:application-test.yml", + "spring.cloud.polaris.config.connect-remote-server=true", "spring.cloud.polaris.config.check-address=false" +}) public class ConfigChangeListenerTest { private static final CountDownLatch hits = new CountDownLatch(2); @@ -58,6 +64,19 @@ public class ConfigChangeListenerTest { @Autowired private TestApplication.TestConfig testConfig; + @BeforeAll + public static void setUp() { + try { + Class clazz = PolarisConfigFileLocator.class; + Field field = clazz.getDeclaredField("compositePropertySourceCache"); + field.setAccessible(true); + field.set(null, new CompositePropertySource("mock")); + } + catch (Exception e) { + // ignore + } + } + @Test public void test() throws InterruptedException { //before change diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListenerNotTriggeredTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListenerNotTriggeredTest.java deleted file mode 100644 index 191af7110..000000000 --- a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListenerNotTriggeredTest.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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.listener; - -import java.util.HashMap; -import java.util.Map; - -import com.tencent.cloud.polaris.config.adapter.MockedConfigKVFile; -import com.tencent.cloud.polaris.config.adapter.PolarisPropertySource; -import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager; -import com.tencent.cloud.polaris.config.adapter.PolarisRefreshAffectedContextRefresher; -import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; -import com.tencent.cloud.polaris.config.enums.RefreshType; -import com.tencent.polaris.configuration.api.core.ChangeType; -import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeEvent; -import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mockito; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.context.refresh.ContextRefresher; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; -import org.springframework.context.support.AbstractApplicationContext; -import org.springframework.stereotype.Component; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static com.tencent.cloud.polaris.config.condition.ReflectRefreshTypeCondition.POLARIS_CONFIG_REFRESH_TYPE; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - -/** - * test for {@link PolarisConfigRefreshOptimizationListener}. - */ -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = DEFINED_PORT, classes = PolarisConfigRefreshOptimizationListenerNotTriggeredTest.TestApplication.class, - properties = { - "server.port=48081", - "spring.cloud.polaris.address=grpc://127.0.0.1:10081", - "spring.cloud.polaris.config.connect-remote-server=false", - "spring.cloud.polaris.config.refresh-type=reflect", - "spring.config.location = classpath:application-test.yml" - }) -public class PolarisConfigRefreshOptimizationListenerNotTriggeredTest { - - private static final String REFLECT_REFRESHER_BEAN_NAME = "polarisReflectPropertySourceAutoRefresher"; - - private static final String TEST_NAMESPACE = "testNamespace"; - - private static final String TEST_SERVICE_NAME = "testServiceName"; - - private static final String TEST_FILE_NAME = "application.properties"; - - @Autowired - private ConfigurableApplicationContext context; - - @BeforeAll - static void beforeAll() { - PolarisPropertySourceManager.clearPropertySources(); - } - - @Test - public void testNotSwitchConfigRefreshType() { - RefreshType actualRefreshType = context.getEnvironment() - .getProperty(POLARIS_CONFIG_REFRESH_TYPE, RefreshType.class); - assertThat(actualRefreshType).isEqualTo(RefreshType.REFLECT); - PolarisConfigProperties polarisConfigProperties = context.getBean(PolarisConfigProperties.class); - assertThat(polarisConfigProperties.getRefreshType()).isEqualTo(RefreshType.REFLECT); - assertThat(context.containsBean(REFLECT_REFRESHER_BEAN_NAME)).isTrue(); - PolarisRefreshAffectedContextRefresher refresher = context - .getBean(REFLECT_REFRESHER_BEAN_NAME, PolarisRefreshAffectedContextRefresher.class); - assertThat(((AbstractApplicationContext) context).getApplicationListeners().contains(refresher)).isTrue(); - } - - @Test - public void testConfigFileChanged() { - Map content = new HashMap<>(); - content.put("k1", "v1"); - content.put("k2", "v2"); - content.put("k3", "v3"); - MockedConfigKVFile file = new MockedConfigKVFile(content); - - PolarisPropertySource polarisPropertySource = new PolarisPropertySource(TEST_NAMESPACE, TEST_SERVICE_NAME, TEST_FILE_NAME, - file, content); - PolarisPropertySourceManager.addPropertySource(polarisPropertySource); - - PolarisRefreshAffectedContextRefresher refresher = context.getBean(PolarisRefreshAffectedContextRefresher.class); - PolarisRefreshAffectedContextRefresher spyRefresher = Mockito.spy(refresher); - - refresher.setRegistered(false); - spyRefresher.onApplicationEvent(null); - - ConfigPropertyChangeInfo changeInfo = new ConfigPropertyChangeInfo("k1", "v1", "v11", ChangeType.MODIFIED); - ConfigPropertyChangeInfo changeInfo2 = new ConfigPropertyChangeInfo("k4", null, "v4", ChangeType.ADDED); - ConfigPropertyChangeInfo changeInfo3 = new ConfigPropertyChangeInfo("k2", "v2", null, ChangeType.DELETED); - Map changeInfos = new HashMap<>(); - changeInfos.put("k1", changeInfo); - changeInfos.put("k2", changeInfo3); - changeInfos.put("k4", changeInfo2); - ConfigKVFileChangeEvent event = new ConfigKVFileChangeEvent(changeInfos); - file.fireChangeListener(event); - - ContextRefresher mockContextRefresher = context.getBean(ContextRefresher.class); - when(mockContextRefresher.refresh()).thenReturn(event.changedKeys()); - - Mockito.verify(spyRefresher, Mockito.times(1)) - .refreshSpringValue("k1"); - Mockito.verify(spyRefresher, Mockito.times(1)) - .refreshSpringValue("k2"); - Mockito.verify(spyRefresher, Mockito.times(1)) - .refreshSpringValue("k4"); - Mockito.verify(spyRefresher, Mockito.times(1)) - .refreshConfigurationProperties(event.changedKeys()); - } - - @SpringBootApplication - protected static class TestApplication { - - @Primary - @Bean - public ContextRefresher contextRefresher() { - return mock(ContextRefresher.class); - } - - @Component - protected static class TestBeanWithoutRefreshScope { - - } - } -} diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListenerTriggeredTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListenerTriggeredTest.java deleted file mode 100644 index ef273483e..000000000 --- a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListenerTriggeredTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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.listener; - -import java.util.HashMap; -import java.util.Map; - -import com.tencent.cloud.polaris.config.adapter.MockedConfigKVFile; -import com.tencent.cloud.polaris.config.adapter.PolarisPropertySource; -import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager; -import com.tencent.cloud.polaris.config.adapter.PolarisRefreshEntireContextRefresher; -import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; -import com.tencent.cloud.polaris.config.enums.RefreshType; -import com.tencent.polaris.configuration.api.core.ChangeType; -import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeEvent; -import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mockito; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.context.config.annotation.RefreshScope; -import org.springframework.cloud.context.refresh.ContextRefresher; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; -import org.springframework.context.support.AbstractApplicationContext; -import org.springframework.stereotype.Component; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static com.tencent.cloud.polaris.config.condition.ReflectRefreshTypeCondition.POLARIS_CONFIG_REFRESH_TYPE; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - -/** - * test for {@link PolarisConfigRefreshOptimizationListener}. - */ -@ExtendWith(SpringExtension.class) -@SpringBootTest(webEnvironment = DEFINED_PORT, classes = PolarisConfigRefreshOptimizationListenerTriggeredTest.TestApplication.class, - properties = { - "server.port=48081", - "spring.cloud.polaris.address=grpc://127.0.0.1:10081", - "spring.cloud.polaris.config.connect-remote-server=false", - "spring.cloud.polaris.config.refresh-type=reflect", - "spring.config.location = classpath:application-test.yml" - }) -public class PolarisConfigRefreshOptimizationListenerTriggeredTest { - - private static final String REFRESH_CONTEXT_REFRESHER_BEAN_NAME = "polarisRefreshContextPropertySourceAutoRefresher"; - - private static final String TEST_NAMESPACE = "testNamespace"; - - private static final String TEST_SERVICE_NAME = "testServiceName"; - - private static final String TEST_FILE_NAME = "application.properties"; - - @Autowired - private ConfigurableApplicationContext context; - - @BeforeEach - public void setUp() { - PolarisPropertySourceManager.clearPropertySources(); - } - - @Test - public void testSwitchConfigRefreshType() { - RefreshType actualRefreshType = context.getEnvironment() - .getProperty(POLARIS_CONFIG_REFRESH_TYPE, RefreshType.class); - assertThat(actualRefreshType).isEqualTo(RefreshType.REFRESH_CONTEXT); - PolarisConfigProperties polarisConfigProperties = context.getBean(PolarisConfigProperties.class); - assertThat(polarisConfigProperties.getRefreshType()).isEqualTo(RefreshType.REFRESH_CONTEXT); - assertThat(context.containsBean(REFRESH_CONTEXT_REFRESHER_BEAN_NAME)).isTrue(); - PolarisRefreshEntireContextRefresher refresher = context - .getBean(REFRESH_CONTEXT_REFRESHER_BEAN_NAME, PolarisRefreshEntireContextRefresher.class); - assertThat(((AbstractApplicationContext) context).getApplicationListeners().contains(refresher)).isTrue(); - } - - @Test - public void testConfigFileChanged() { - Map content = new HashMap<>(); - content.put("k1", "v1"); - content.put("k2", "v2"); - content.put("k3", "v3"); - MockedConfigKVFile file = new MockedConfigKVFile(content); - - PolarisPropertySource polarisPropertySource = new PolarisPropertySource(TEST_NAMESPACE, TEST_SERVICE_NAME, TEST_FILE_NAME, - file, content); - PolarisPropertySourceManager.addPropertySource(polarisPropertySource); - - PolarisRefreshEntireContextRefresher refresher = context.getBean(PolarisRefreshEntireContextRefresher.class); - PolarisRefreshEntireContextRefresher spyRefresher = Mockito.spy(refresher); - - refresher.setRegistered(false); - spyRefresher.onApplicationEvent(null); - - ConfigPropertyChangeInfo changeInfo = new ConfigPropertyChangeInfo("k1", "v1", "v11", ChangeType.MODIFIED); - ConfigPropertyChangeInfo changeInfo2 = new ConfigPropertyChangeInfo("k4", null, "v4", ChangeType.ADDED); - ConfigPropertyChangeInfo changeInfo3 = new ConfigPropertyChangeInfo("k2", "v2", null, ChangeType.DELETED); - Map changeInfos = new HashMap<>(); - changeInfos.put("k1", changeInfo); - changeInfos.put("k2", changeInfo3); - changeInfos.put("k4", changeInfo2); - ConfigKVFileChangeEvent event = new ConfigKVFileChangeEvent(changeInfos); - file.fireChangeListener(event); - - ContextRefresher mockContextRefresher = context.getBean(ContextRefresher.class); - when(mockContextRefresher.refresh()).thenReturn(event.changedKeys()); - - Mockito.verify(spyRefresher, Mockito.times(1)) - .refreshSpringValue("k1"); - Mockito.verify(spyRefresher, Mockito.times(1)) - .refreshSpringValue("k2"); - Mockito.verify(spyRefresher, Mockito.times(1)) - .refreshSpringValue("k4"); - Mockito.verify(spyRefresher, Mockito.times(1)) - .refreshConfigurationProperties(event.changedKeys()); - } - - @SpringBootApplication - protected static class TestApplication { - - @Primary - @Bean - public ContextRefresher contextRefresher() { - return mock(ContextRefresher.class); - } - - @Component - @RefreshScope - protected static class TestBeanWithRefreshScope { - - } - } -} diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/spring/annotation/RefreshScopeSpringProcessorTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/spring/annotation/RefreshScopeSpringProcessorTest.java new file mode 100644 index 000000000..7507ab6f3 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/spring/annotation/RefreshScopeSpringProcessorTest.java @@ -0,0 +1,335 @@ +/* + * 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.spring.annotation; + +import java.io.IOException; +import java.net.ServerSocket; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Objects; + +import com.tencent.cloud.polaris.config.PolarisConfigBootstrapAutoConfiguration; +import com.tencent.cloud.polaris.config.enums.RefreshType; +import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for {@link SpringValueProcessor}. + * + * @author Shedfree Wu + */ +public class RefreshScopeSpringProcessorTest { + + private static ServerSocket serverSocket; + + @BeforeAll + static void beforeAll() { + new Thread(() -> { + try { + serverSocket = new ServerSocket(8093); + serverSocket.accept(); + } + catch (IOException e) { + e.printStackTrace(); + } + }).start(); + } + + @AfterAll + static void afterAll() throws IOException { + if (Objects.nonNull(serverSocket)) { + serverSocket.close(); + } + } + + @Test + public void springValueFiledProcessorTest() { + ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(PolarisConfigBootstrapAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(RefreshAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(ValueTest.class)) + .withConfiguration(AutoConfigurations.of(TestConfig2.class)) + .withConfiguration(AutoConfigurations.of(TestConfig3.class)) + .withConfiguration(AutoConfigurations.of(TestConfig4.class)) + .withConfiguration(AutoConfigurations.of(TestConfig5.class)) + .withConfiguration(AutoConfigurations.of(TestBeanProperties1.class)) + .withConfiguration(AutoConfigurations.of(TestBeanProperties2.class)) + .withConfiguration(AutoConfigurations.of(PolarisConfigAutoConfiguration.class)) + .withAllowBeanDefinitionOverriding(true) + .withPropertyValues("spring.application.name=" + "conditionalOnConfigReflectEnabledTest") + .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081") + .withPropertyValues("spring.cloud.polaris.config.refresh-type=" + RefreshType.REFLECT) + .withPropertyValues("spring.cloud.polaris.config.enabled=true") + .withPropertyValues("timeout=10000"); + contextRunner.run(context -> { + SpringValueRegistry springValueRegistry = context.getBean(SpringValueRegistry.class); + + assertThat(springValueRegistry.isRefreshScopeKey("key.not.exist")).isFalse(); + // @RefreshScope on @Component bean, @Value on field + assertThat(springValueRegistry.isRefreshScopeKey("timeout")).isTrue(); + // not exact match + assertThat(springValueRegistry.isRefreshScopeKey("timeout.test")).isFalse(); + // @RefreshScope on @Component bean, @Value on method + assertThat(springValueRegistry.isRefreshScopeKey("name")).isTrue(); + // @RefreshScope and @Bean on method, @Value on field + assertThat(springValueRegistry.isRefreshScopeKey("test.bean.name")).isTrue(); + // @RefreshScope and @Bean on method, @Value on method + assertThat(springValueRegistry.isRefreshScopeKey("test.bean.timeout")).isTrue(); + // @RefreshScope and @Bean on method, @Value on parameter + assertThat(springValueRegistry.isRefreshScopeKey("test.param.name")).isTrue(); + // @RefreshScope and @Bean on method, @ConfigurationProperties bean on method parameter + assertThat(springValueRegistry.isRefreshScopeKey("test.properties1.name")).isTrue(); + // @RefreshScope and @Bean on method, @ConfigurationProperties bean in class + assertThat(springValueRegistry.isRefreshScopeKey("test.properties2.name")).isTrue(); + assertThat(springValueRegistry.isRefreshScopeKey("test.properties2.inner.name")).isTrue(); + assertThat(springValueRegistry.isRefreshScopeKey("test.properties2.set")).isTrue(); + assertThat(springValueRegistry.isRefreshScopeKey("test.properties2.list")).isTrue(); + assertThat(springValueRegistry.isRefreshScopeKey("test.properties2.list[0]")).isTrue(); + assertThat(springValueRegistry.isRefreshScopeKey("test.properties2.array")).isTrue(); + assertThat(springValueRegistry.isRefreshScopeKey("test.properties2.array[0]")).isTrue(); + assertThat(springValueRegistry.isRefreshScopeKey("test.properties2.map")).isTrue(); + assertThat(springValueRegistry.isRefreshScopeKey("test.properties2.map.key")).isTrue(); + + assertThat(springValueRegistry.isRefreshScopeKey("test.properties2.notExist")).isFalse(); + // @RefreshScope and @Bean on method, @Value bean in class + assertThat(springValueRegistry.isRefreshScopeKey("test.bean5.name")).isTrue(); + }); + } + + + @Configuration + @EnableAutoConfiguration + static class PolarisConfigAutoConfiguration { + + @Autowired + private BeanFactory beanFactory; + + public BeanFactory getBeanFactory() { + return beanFactory; + } + + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + } + + @Component + @RefreshScope + private static class ValueTest { + + ValueTest() { + } + + private static String name; + @Value("${timeout:1000}") + private int timeout; + + public int getTimeout() { + return timeout; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + @Value("${name:1000}") + public void setName(String name) { + ValueTest.name = name; + } + } + + @Configuration + static class TestConfig2 { + @Bean + @RefreshScope + public TestBean testBean2() { + return new TestBean(); + } + } + + @Configuration + static class TestConfig3 { + @Bean + @RefreshScope + public TestBean testBean3(@Value("${test.param.name:}") String name) { + return new TestBean(); + } + } + + @Configuration + static class TestConfig4 { + @Bean + @RefreshScope + public TestBean testBean4(TestBeanProperties1 testBeanProperties1) { + return new TestBean(); + } + } + + @Configuration + static class TestConfig5 { + + @Autowired + private TestBeanProperties2 testBeanProperties2; + + @Value("${test.bean5.name:}") + private String name; + + @Bean + @RefreshScope + public TestBean testBean5() { + TestBean testBean = new TestBean(); + testBean.setName(testBeanProperties2.getName()); + return testBean; + } + } + + static class TestBean { + + @Value("${test.bean.name:}") + private String name; + + private int timeout; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getTimeout() { + return timeout; + } + + @Value("${test.bean.timeout:0}") + public void setTimeout(int timeout) { + this.timeout = timeout; + } + } + + @Component + @ConfigurationProperties(prefix = "test.properties1") + static class TestBeanProperties1 { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Component + @ConfigurationProperties("test.properties2") + static class TestBeanProperties2 { + private String name; + + private HashSet set; + + private ArrayList list; + + private String[] array; + + private HashMap map; + + private InnerProperties inner; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public HashSet getSet() { + return set; + } + + public void setSet(HashSet set) { + this.set = set; + } + + public ArrayList getList() { + return list; + } + + public void setList(ArrayList list) { + this.list = list; + } + + public String[] getArray() { + return array; + } + + public void setArray(String[] array) { + this.array = array; + } + + public HashMap getMap() { + return map; + } + + public void setMap(HashMap map) { + this.map = map; + } + + public InnerProperties getInner() { + return inner; + } + + public void setInner(InnerProperties inner) { + this.inner = inner; + } + } + + static class InnerProperties { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +} diff --git a/spring-cloud-tencent-rpc-enhancement/pom.xml b/spring-cloud-tencent-rpc-enhancement/pom.xml index abf4786ca..cd5c4f53e 100644 --- a/spring-cloud-tencent-rpc-enhancement/pom.xml +++ b/spring-cloud-tencent-rpc-enhancement/pom.xml @@ -37,6 +37,12 @@ org.springframework.boot spring-boot-starter-aop + + + org.springframework.boot + spring-boot-starter-logging + +