feat: support 2.0.0 config. (#1463)

1. 支持监听整个配置分组
2. 支持对接 TSF 时监听默认分组
3. 支持单配置刷新
hoxton
shedfreewu 2 days ago committed by GitHub
parent 84a98de136
commit 28dc95b9a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -16,4 +16,5 @@
- [feat:upgrade jacoco version.](https://github.com/Tencent/spring-cloud-tencent/pull/1306) - [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: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) - [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) - [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)

@ -104,8 +104,9 @@ public class ConfigurationModifier implements PolarisConfigurationConfigModifier
throw new RuntimeException("Config server address is blank. Please check your config in bootstrap.yml" 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"); + " with spring.cloud.polaris.address or spring.cloud.polaris.config.address");
} }
if (polarisConfigProperties.isCheckAddress()) {
checkAddressAccessible(configAddresses); checkAddressAccessible(configAddresses);
}
configuration.getConfigFile().getServerConnector().setAddresses(configAddresses); configuration.getConfigFile().getServerConnector().setAddresses(configAddresses);

@ -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.AffectedConfigurationPropertiesRebinder;
import com.tencent.cloud.polaris.config.adapter.PolarisConfigPropertyRefresher; 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.PolarisRefreshAffectedContextRefresher;
import com.tencent.cloud.polaris.config.adapter.PolarisRefreshEntireContextRefresher; import com.tencent.cloud.polaris.config.adapter.PolarisRefreshEntireContextRefresher;
import com.tencent.cloud.polaris.config.annotation.PolarisConfigAnnotationProcessor; import com.tencent.cloud.polaris.config.annotation.PolarisConfigAnnotationProcessor;
import com.tencent.cloud.polaris.config.condition.ConditionalOnReflectRefreshType; import com.tencent.cloud.polaris.config.condition.ConditionalOnReflectRefreshType;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.listener.PolarisConfigChangeEventListener; 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.logger.PolarisConfigLoggerApplicationListener;
import com.tencent.cloud.polaris.config.spring.annotation.SpringValueProcessor; 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.PlaceholderHelper;
import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; 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.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -78,46 +77,38 @@ public class PolarisConfigAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT) @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public PolarisConfigPropertyRefresher polarisRefreshContextPropertySourceAutoRefresher( public PolarisConfigPropertyRefresher polarisRefreshContextPropertySourceAutoRefresher(
PolarisConfigProperties polarisConfigProperties, ContextRefresher contextRefresher) { PolarisConfigProperties polarisConfigProperties, SpringValueRegistry springValueRegistry,
return new PolarisRefreshEntireContextRefresher(polarisConfigProperties, contextRefresher); 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) @Configuration(proxyBeanMethods = false)
@ConditionalOnReflectRefreshType @ConditionalOnReflectRefreshType
@AutoConfigureBefore(PolarisConfigAutoConfiguration.class) @AutoConfigureBefore(PolarisConfigAutoConfiguration.class)
public static class PolarisReflectRefresherAutoConfiguration { 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 @Bean
public PolarisConfigPropertyRefresher polarisReflectPropertySourceAutoRefresher( public PolarisConfigPropertyRefresher polarisReflectPropertySourceAutoRefresher(
PolarisConfigProperties polarisConfigProperties, SpringValueRegistry springValueRegistry, PolarisConfigProperties polarisConfigProperties, SpringValueRegistry springValueRegistry,
PlaceholderHelper placeholderHelper) { PlaceholderHelper placeholderHelper, ConfigFileService configFileService, ContextRefresher contextRefresher) {
return new PolarisRefreshAffectedContextRefresher(polarisConfigProperties, return new PolarisRefreshAffectedContextRefresher(polarisConfigProperties,
springValueRegistry, placeholderHelper); springValueRegistry, placeholderHelper, configFileService, contextRefresher);
}
@Bean
public PolarisConfigRefreshScopeAnnotationDetector polarisConfigRefreshScopeAnnotationDetector() {
return new PolarisConfigRefreshScopeAnnotationDetector();
}
@Bean
public PolarisConfigRefreshOptimizationListener polarisConfigRefreshOptimizationListener() {
return new PolarisConfigRefreshOptimizationListener();
} }
} }
} }

@ -37,5 +37,5 @@ public interface PolarisConfigCustomExtensionLayer {
void executeAfterLocateConfigReturning(CompositePropertySource compositePropertySource); void executeAfterLocateConfigReturning(CompositePropertySource compositePropertySource);
boolean executeRegisterPublishChangeListener(PolarisPropertySource polarisPropertySource); boolean executeRegisterPublishChangeListener(PolarisPropertySource polarisPropertySource, PolarisPropertySource effectPolarisPropertySource);
} }

@ -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.ConfigFileMetadata;
import com.tencent.polaris.configuration.api.core.ConfigFileService; import com.tencent.polaris.configuration.api.core.ConfigFileService;
import com.tencent.polaris.configuration.api.core.ConfigKVFile; 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 com.tencent.polaris.configuration.client.internal.DefaultConfigFileMetadata;
import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger; 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 // this class provides customized logic for some customers to configure special business group files
private final PolarisConfigCustomExtensionLayer polarisConfigCustomExtensionLayer = PolarisServiceLoaderUtil.getPolarisConfigCustomExtensionLayer(); private final PolarisConfigCustomExtensionLayer polarisConfigCustomExtensionLayer = PolarisServiceLoaderUtil.getPolarisConfigCustomExtensionLayer();
private volatile static CompositePropertySource compositePropertySourceCache = null;
public PolarisConfigFileLocator(PolarisConfigProperties polarisConfigProperties, public PolarisConfigFileLocator(PolarisConfigProperties polarisConfigProperties,
PolarisContextProperties polarisContextProperties, ConfigFileService configFileService, Environment environment) { PolarisContextProperties polarisContextProperties, ConfigFileService configFileService, Environment environment) {
this.polarisConfigProperties = polarisConfigProperties; this.polarisConfigProperties = polarisConfigProperties;
@ -76,10 +79,20 @@ public class PolarisConfigFileLocator implements PropertySourceLocator {
this.environment = environment; 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 @Override
public PropertySource<?> locate(Environment environment) { public PropertySource<?> locate(Environment environment) {
if (polarisConfigProperties.isEnabled()) { if (polarisConfigProperties.isEnabled()) {
// use cache when refreshing context
if (compositePropertySourceCache != null) {
return compositePropertySourceCache;
}
CompositePropertySource compositePropertySource = new CompositePropertySource(POLARIS_CONFIG_PROPERTY_SOURCE_NAME); CompositePropertySource compositePropertySource = new CompositePropertySource(POLARIS_CONFIG_PROPERTY_SOURCE_NAME);
compositePropertySourceCache = compositePropertySource;
try { try {
// load custom config extension files // load custom config extension files
initCustomPolarisConfigExtensionFiles(compositePropertySource); initCustomPolarisConfigExtensionFiles(compositePropertySource);
@ -87,10 +100,13 @@ public class PolarisConfigFileLocator implements PropertySourceLocator {
initInternalConfigFiles(compositePropertySource); initInternalConfigFiles(compositePropertySource);
// load custom config files // load custom config files
List<ConfigFileGroup> configFileGroups = polarisConfigProperties.getGroups(); List<ConfigFileGroup> 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; return compositePropertySource;
} }
finally { finally {
@ -123,7 +139,10 @@ public class PolarisConfigFileLocator implements PropertySourceLocator {
List<ConfigFileMetadata> internalConfigFiles = getInternalConfigFiles(); List<ConfigFileMetadata> internalConfigFiles = getInternalConfigFiles();
for (ConfigFileMetadata configFile : internalConfigFiles) { 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); compositePropertySource.addPropertySource(polarisPropertySource);
@ -190,6 +209,29 @@ public class PolarisConfigFileLocator implements PropertySourceLocator {
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "bootstrap.yaml")); 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<String> 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<ConfigFileGroup> configFileGroups) { private void initCustomPolarisConfigFiles(CompositePropertySource compositePropertySource, List<ConfigFileGroup> configFileGroups) {
String namespace = polarisContextProperties.getNamespace(); String namespace = polarisContextProperties.getNamespace();
@ -201,46 +243,90 @@ public class PolarisConfigFileLocator implements PropertySourceLocator {
String group = configFileGroup.getName(); String group = configFileGroup.getName();
if (!StringUtils.hasText(group)) { if (!StringUtils.hasText(group)) {
throw new IllegalArgumentException("polaris config group name cannot be empty."); continue;
} }
List<String> files = configFileGroup.getFiles(); List<String> files = configFileGroup.getFiles();
if (CollectionUtils.isEmpty(files)) { 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) { compositePropertySource.addPropertySource(polarisPropertySource);
PolarisPropertySource polarisPropertySource = loadPolarisPropertySource(groupNamespace, group, fileName);
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) { public static PolarisPropertySource loadPolarisPropertySource(ConfigFileService configFileService, String namespace, String group, String fileName) {
ConfigKVFile configKVFile; ConfigKVFile configKVFile = loadConfigKVFile(configFileService, namespace, group, fileName);
// unknown extension is resolved as yaml file
if (ConfigFileFormat.isYamlFile(fileName) || ConfigFileFormat.isUnknownFile(fileName)) { Map<String, Object> map = new ConcurrentHashMap<>();
configKVFile = configFileService.getConfigYamlFile(namespace, group, fileName); 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<ConfigKVFile> 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<String, Object> map = new ConcurrentHashMap<>(); Map<String, Object> map = new ConcurrentHashMap<>();
for (String key : configKVFile.getPropertyNames()) { for (String key : compositeConfigFile.getPropertyNames()) {
map.put(key, configKVFile.getProperty(key, null)); 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;
} }
} }

@ -19,12 +19,21 @@
package com.tencent.cloud.polaris.config.adapter; package com.tencent.cloud.polaris.config.adapter;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean; 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.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.logger.PolarisConfigLoggerContext; 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.ConfigKVFile;
import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeListener; import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeListener;
import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; 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.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.core.env.PropertySource;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.util.CollectionUtils; 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 // this class provides customized logic for some customers to configure special business group files
private final PolarisConfigCustomExtensionLayer polarisConfigCustomExtensionLayer = PolarisServiceLoaderUtil.getPolarisConfigCustomExtensionLayer(); private final PolarisConfigCustomExtensionLayer polarisConfigCustomExtensionLayer = PolarisServiceLoaderUtil.getPolarisConfigCustomExtensionLayer();
public PolarisConfigPropertyAutoRefresher(PolarisConfigProperties polarisConfigProperties) { private static final Set<String> registeredPolarisPropertySets = Sets.newConcurrentHashSet();
private final ConfigFileService configFileService;
public PolarisConfigPropertyAutoRefresher(PolarisConfigProperties polarisConfigProperties,
ConfigFileService configFileService) {
this.polarisConfigProperties = polarisConfigProperties; this.polarisConfigProperties = polarisConfigProperties;
this.configFileService = configFileService;
} }
@Override @Override
@ -82,13 +98,16 @@ public abstract class PolarisConfigPropertyAutoRefresher implements ApplicationL
// register polaris config publish event // register polaris config publish event
for (PolarisPropertySource polarisPropertySource : polarisPropertySources) { for (PolarisPropertySource polarisPropertySource : polarisPropertySources) {
// group property source
if (polarisPropertySource.getConfigKVFile() instanceof CompositeConfigFile) { if (polarisPropertySource.getConfigKVFile() instanceof CompositeConfigFile) {
CompositeConfigFile configKVFile = (CompositeConfigFile) polarisPropertySource.getConfigKVFile(); CompositeConfigFile configKVFile = (CompositeConfigFile) polarisPropertySource.getConfigKVFile();
for (ConfigKVFile cf : configKVFile.getConfigKVFiles()) { for (ConfigKVFile cf : configKVFile.getConfigKVFiles()) {
PolarisPropertySource p = new PolarisPropertySource(cf.getNamespace(), cf.getFileGroup(), cf.getFileName(), cf, new HashMap<>()); PolarisPropertySource p = new PolarisPropertySource(cf.getNamespace(), cf.getFileGroup(), cf.getFileName(), cf, new HashMap<>());
registerPolarisConfigPublishChangeListener(p); registerPolarisConfigPublishChangeListener(p, polarisPropertySource);
customRegisterPolarisConfigPublishChangeListener(p); customRegisterPolarisConfigPublishChangeListener(p, polarisPropertySource);
registeredPolarisPropertySets.add(p.getPropertySourceName());
} }
registerPolarisConfigGroupChangeListener(polarisPropertySource);
} }
else { else {
registerPolarisConfigPublishChangeListener(polarisPropertySource); registerPolarisConfigPublishChangeListener(polarisPropertySource);
@ -105,14 +124,74 @@ public abstract class PolarisConfigPropertyAutoRefresher implements ApplicationL
polarisConfigCustomExtensionLayer.initRegisterConfig(polarisConfigPropertyAutoRefresher); 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<ConfigFileMetadata> oldConfigFileMetadataList = event.getOldConfigFileMetadataList();
List<ConfigFileMetadata> newConfigFileMetadataList = event.getNewConfigFileMetadataList();
Map<String, ConfigFileMetadata> added = calculateUnregister(oldConfigFileMetadataList, newConfigFileMetadataList);
if (added.isEmpty()) {
return;
}
Set<String> changedKeys = new HashSet<>();
for (Map.Entry<String, ConfigFileMetadata> 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) { public void registerPolarisConfigPublishChangeListener(PolarisPropertySource polarisPropertySource) {
LOGGER.info("{} will register polaris config publish listener", polarisPropertySource.getPropertySourceName()); registerPolarisConfigPublishChangeListener(polarisPropertySource, polarisPropertySource);
polarisPropertySource.getConfigKVFile() }
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 -> { .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<String, Object> source = polarisPropertySource.getSource(); Map<String, Object> effectSource = effectPolarisPropertySource.getSource();
Map<String, Object> 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()) { for (String changedKey : configKVFileChangeEvent.changedKeys()) {
ConfigPropertyChangeInfo configPropertyChangeInfo = configKVFileChangeEvent.getChangeInfo(changedKey); ConfigPropertyChangeInfo configPropertyChangeInfo = configKVFileChangeEvent.getChangeInfo(changedKey);
@ -134,10 +213,27 @@ public abstract class PolarisConfigPropertyAutoRefresher implements ApplicationL
switch (configPropertyChangeInfo.getChangeType()) { switch (configPropertyChangeInfo.getChangeType()) {
case MODIFIED: case MODIFIED:
case ADDED: case ADDED:
source.put(changedKey, configPropertyChangeInfo.getNewValue()); effectSource.put(changedKey, configPropertyChangeInfo.getNewValue());
if (isGroupRefresh) {
listenSource.put(changedKey, configPropertyChangeInfo.getNewValue());
}
break; break;
case DELETED: 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; break;
} }
// update the attribute with @Value annotation // update the attribute with @Value annotation
@ -149,11 +245,43 @@ public abstract class PolarisConfigPropertyAutoRefresher implements ApplicationL
} }
private void customRegisterPolarisConfigPublishChangeListener(PolarisPropertySource polarisPropertySource) { private void customRegisterPolarisConfigPublishChangeListener(PolarisPropertySource polarisPropertySource) {
customRegisterPolarisConfigPublishChangeListener(polarisPropertySource, polarisPropertySource);
}
private void customRegisterPolarisConfigPublishChangeListener(PolarisPropertySource listenPolarisPropertySource, PolarisPropertySource effectPolarisPropertySource) {
if (polarisConfigCustomExtensionLayer == null) { if (polarisConfigCustomExtensionLayer == null) {
LOGGER.debug("[SCT Config] PolarisConfigCustomExtensionLayer is not init, ignore the following execution steps"); LOGGER.debug("[SCT Config] PolarisConfigCustomExtensionLayer is not init, ignore the following execution steps");
return; return;
} }
polarisConfigCustomExtensionLayer.executeRegisterPublishChangeListener(polarisPropertySource); polarisConfigCustomExtensionLayer.executeRegisterPublishChangeListener(listenPolarisPropertySource, effectPolarisPropertySource);
}
private Map<String, ConfigFileMetadata> calculateUnregister(List<ConfigFileMetadata> oldConfigFileMetadataList,
List<ConfigFileMetadata> newConfigFileMetadataList) {
Map<String, ConfigFileMetadata> oldConfigFileMetadataMap = oldConfigFileMetadataList.stream()
.collect(Collectors.toMap(
configFileMetadata -> PolarisPropertySourceUtils.generateName(
configFileMetadata.getNamespace(),
configFileMetadata.getFileGroup(),
configFileMetadata.getFileName()),
configFileMetadata -> configFileMetadata));
Map<String, ConfigFileMetadata> newConfigFileMetadataMap = newConfigFileMetadataList.stream()
.collect(Collectors.toMap(
configFileMetadata -> PolarisPropertySourceUtils.generateName(
configFileMetadata.getNamespace(),
configFileMetadata.getFileGroup(),
configFileMetadata.getFileName()),
configFileMetadata -> configFileMetadata));
Map<String, ConfigFileMetadata> added = new HashMap<>();
for (Map.Entry<String, ConfigFileMetadata> entry : newConfigFileMetadataMap.entrySet()) {
if (!oldConfigFileMetadataMap.containsKey(entry.getKey())) {
added.put(entry.getKey(), entry.getValue());
}
}
return added;
} }
/** /**

@ -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}.
*
* <p>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;
}
}

@ -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.PlaceholderHelper;
import com.tencent.cloud.polaris.config.spring.property.SpringValue; import com.tencent.cloud.polaris.config.spring.property.SpringValue;
import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
import com.tencent.polaris.configuration.api.core.ConfigFileService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -32,6 +33,7 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter; import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent; import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
@ -58,11 +60,15 @@ public class PolarisRefreshAffectedContextRefresher extends PolarisConfigPropert
private TypeConverter typeConverter; private TypeConverter typeConverter;
private ContextRefresher contextRefresher;
public PolarisRefreshAffectedContextRefresher(PolarisConfigProperties polarisConfigProperties, public PolarisRefreshAffectedContextRefresher(PolarisConfigProperties polarisConfigProperties,
SpringValueRegistry springValueRegistry, PlaceholderHelper placeholderHelper) { SpringValueRegistry springValueRegistry, PlaceholderHelper placeholderHelper,
super(polarisConfigProperties); ConfigFileService configFileService, ContextRefresher contextRefresher) {
super(polarisConfigProperties, configFileService);
this.springValueRegistry = springValueRegistry; this.springValueRegistry = springValueRegistry;
this.placeholderHelper = placeholderHelper; this.placeholderHelper = placeholderHelper;
this.contextRefresher = contextRefresher;
} }
@Override @Override
@ -79,7 +85,20 @@ public class PolarisRefreshAffectedContextRefresher extends PolarisConfigPropert
@Override @Override
public void refreshConfigurationProperties(Set<String> changeKeys) { public void refreshConfigurationProperties(Set<String> 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) { private void updateSpringValue(SpringValue springValue) {

@ -21,8 +21,15 @@ package com.tencent.cloud.polaris.config.adapter;
import java.util.Set; import java.util.Set;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; 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.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. * 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 * @author lingxiao.wlx
*/ */
public class PolarisRefreshEntireContextRefresher extends PolarisConfigPropertyAutoRefresher { public class PolarisRefreshEntireContextRefresher extends PolarisConfigPropertyAutoRefresher implements ApplicationContextAware {
private final ContextRefresher contextRefresher; private final ContextRefresher contextRefresher;
private final SpringValueRegistry springValueRegistry;
private ConfigurableApplicationContext context;
public PolarisRefreshEntireContextRefresher(PolarisConfigProperties polarisConfigProperties, public PolarisRefreshEntireContextRefresher(PolarisConfigProperties polarisConfigProperties,
ContextRefresher contextRefresher) { SpringValueRegistry springValueRegistry, ConfigFileService configFileService, ContextRefresher contextRefresher) {
super(polarisConfigProperties);
super(polarisConfigProperties, configFileService);
this.springValueRegistry = springValueRegistry;
this.contextRefresher = contextRefresher; this.contextRefresher = contextRefresher;
} }
@ -47,6 +60,24 @@ public class PolarisRefreshEntireContextRefresher extends PolarisConfigPropertyA
@Override @Override
public void refreshConfigurationProperties(Set<String> changeKeys) { public void refreshConfigurationProperties(Set<String> 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;
} }
} }

@ -37,7 +37,7 @@ public class ReflectRefreshTypeCondition extends SpringBootCondition {
*/ */
public static final String POLARIS_CONFIG_REFRESH_TYPE = "spring.cloud.polaris.config.refresh-type"; 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 @Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {

@ -70,7 +70,7 @@ public class PolarisConfigProperties {
/** /**
* Attribute refresh type. * Attribute refresh type.
*/ */
private RefreshType refreshType = RefreshType.REFLECT; private RefreshType refreshType = RefreshType.REFRESH_CONTEXT;
/** /**
* List of injected configuration files. * List of injected configuration files.
@ -96,6 +96,8 @@ public class PolarisConfigProperties {
*/ */
private boolean internalEnabled = true; private boolean internalEnabled = true;
private boolean checkAddress = true;
public boolean isEnabled() { public boolean isEnabled() {
return enabled; return enabled;
} }
@ -192,6 +194,14 @@ public class PolarisConfigProperties {
this.internalEnabled = internalEnabled; this.internalEnabled = internalEnabled;
} }
public boolean isCheckAddress() {
return checkAddress;
}
public void setCheckAddress(boolean checkAddress) {
this.checkAddress = checkAddress;
}
@Override @Override
public String toString() { public String toString() {
return "PolarisConfigProperties{" + return "PolarisConfigProperties{" +
@ -207,6 +217,7 @@ public class PolarisConfigProperties {
", dataSource='" + dataSource + '\'' + ", dataSource='" + dataSource + '\'' +
", localFileRootPath='" + localFileRootPath + '\'' + ", localFileRootPath='" + localFileRootPath + '\'' +
", internalEnabled=" + internalEnabled + ", internalEnabled=" + internalEnabled +
", checkAddress=" + checkAddress +
'}'; '}';
} }
} }

@ -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}.
*
* <p>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.</p>
*
* @author jarvisxiong
*/
public class PolarisConfigRefreshOptimizationListener implements ApplicationListener<ContextRefreshedEvent> {
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);
}
}

@ -23,7 +23,11 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import org.springframework.beans.BeansException; 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.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.Ordered;
import org.springframework.core.PriorityOrdered; import org.springframework.core.PriorityOrdered;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
@ -34,17 +38,32 @@ import org.springframework.util.ReflectionUtils;
* *
* @author weihubeats 2022-7-10 * @author weihubeats 2022-7-10
*/ */
public abstract class PolarisProcessor implements BeanPostProcessor, PriorityOrdered { public abstract class PolarisProcessor implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware {
private ConfigurableListableBeanFactory beanFactory;
@Override @Override
public Object postProcessBeforeInitialization(Object bean, @NonNull String beanName) public Object postProcessBeforeInitialization(Object bean, @NonNull String beanName)
throws BeansException { throws BeansException {
Class<?> clazz = bean.getClass(); 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)) { for (Field field : findAllField(clazz)) {
processField(bean, beanName, field); processField(bean, beanName, field, isRefreshScope);
} }
for (Method method : findAllMethod(clazz)) { for (Method method : findAllMethod(clazz)) {
processMethod(bean, beanName, method); processMethod(bean, beanName, method, isRefreshScope);
} }
return bean; return bean;
} }
@ -60,7 +79,7 @@ public abstract class PolarisProcessor implements BeanPostProcessor, PriorityOrd
* @param beanName beanName * @param beanName beanName
* @param field field * @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. * subclass should implement this method to process method.
@ -68,7 +87,7 @@ public abstract class PolarisProcessor implements BeanPostProcessor, PriorityOrd
* @param beanName beanName * @param beanName beanName
* @param method method * @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 @Override
@ -77,15 +96,20 @@ public abstract class PolarisProcessor implements BeanPostProcessor, PriorityOrd
return Ordered.LOWEST_PRECEDENCE; return Ordered.LOWEST_PRECEDENCE;
} }
private List<Field> findAllField(Class<?> clazz) { protected List<Field> findAllField(Class<?> clazz) {
final List<Field> res = new LinkedList<>(); final List<Field> res = new LinkedList<>();
ReflectionUtils.doWithFields(clazz, res::add); ReflectionUtils.doWithFields(clazz, res::add);
return res; return res;
} }
private List<Method> findAllMethod(Class<?> clazz) { protected List<Method> findAllMethod(Class<?> clazz) {
final List<Method> res = new LinkedList<>(); final List<Method> res = new LinkedList<>();
ReflectionUtils.doWithMethods(clazz, res::add); ReflectionUtils.doWithMethods(clazz, res::add);
return res; return res;
} }
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
}
} }

@ -21,11 +21,13 @@ import java.beans.PropertyDescriptor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Member; import java.lang.reflect.Member;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import com.google.common.base.CaseFormat;
import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Multimap; 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.SpringValue;
import com.tencent.cloud.polaris.config.spring.property.SpringValueDefinition; import com.tencent.cloud.polaris.config.spring.property.SpringValueDefinition;
import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
import com.tencent.polaris.api.utils.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.config.TypedStringValue;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; 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.context.annotation.Bean;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
@ -105,23 +110,31 @@ public class SpringValueProcessor extends PolarisProcessor implements BeanDefini
@Override @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 // register @Value on field
Value value = field.getAnnotation(Value.class); Value value = field.getAnnotation(Value.class);
if (value == null) { if (value == null) {
return; return;
} }
doRegister(bean, beanName, field, value); doRegister(bean, beanName, field, value, isRefreshScope);
} }
@Override @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 //register @Value on method
Value value = method.getAnnotation(Value.class); Value value = method.getAnnotation(Value.class);
if (value == null) { if (value != null) {
processMethodValue(bean, beanName, method, value, isRefreshScope);
return; 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 //skip Configuration bean methods
if (method.getAnnotation(Bean.class) != null) { if (method.getAnnotation(Bean.class) != null) {
return; return;
@ -131,8 +144,128 @@ public class SpringValueProcessor extends PolarisProcessor implements BeanDefini
bean.getClass().getName(), method.getName(), method.getParameterTypes().length); bean.getClass().getName(), method.getName(), method.getParameterTypes().length);
return; 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<String> 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<String> 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 @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<String> keys = placeholderHelper.extractPlaceholderKeys(value.value()); Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
if (keys.isEmpty()) { if (keys.isEmpty()) {
return; return;
@ -158,10 +291,16 @@ public class SpringValueProcessor extends PolarisProcessor implements BeanDefini
if (member instanceof Field) { if (member instanceof Field) {
Field field = (Field) member; Field field = (Field) member;
springValue = new SpringValue(key, value.value(), bean, beanName, field); springValue = new SpringValue(key, value.value(), bean, beanName, field);
if (isRefreshScope) {
springValueRegistry.putRefreshScopeKey(key);
}
} }
else if (member instanceof Method) { else if (member instanceof Method) {
Method method = (Method) member; Method method = (Method) member;
springValue = new SpringValue(key, value.value(), bean, beanName, method); springValue = new SpringValue(key, value.value(), bean, beanName, method);
if (isRefreshScope) {
springValueRegistry.putRefreshScopeKey(key);
}
} }
else { else {
LOGGER.error("Polaris @Value annotation currently only support to be used on methods and fields, " LOGGER.error("Polaris @Value annotation currently only support to be used on methods and fields, "

@ -20,6 +20,7 @@ package com.tencent.cloud.polaris.config.spring.property;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; 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.Maps;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps; 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 com.tencent.polaris.client.util.NamedThreadFactory;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -54,6 +58,10 @@ public class SpringValueRegistry implements DisposableBean {
private final Object LOCK = new Object(); private final Object LOCK = new Object();
private ScheduledExecutorService executor; private ScheduledExecutorService executor;
private final TrieNode<String> refreshScopePrefixRoot = new TrieNode<>(TrieNode.ROOT_PATH);
private final Set<String> refreshScopeKeys = Sets.newConcurrentHashSet();
public void register(BeanFactory beanFactory, String key, SpringValue springValue) { public void register(BeanFactory beanFactory, String key, SpringValue springValue) {
if (!registry.containsKey(beanFactory)) { if (!registry.containsKey(beanFactory)) {
synchronized (LOCK) { synchronized (LOCK) {
@ -63,7 +71,16 @@ public class SpringValueRegistry implements DisposableBean {
} }
} }
registry.get(beanFactory).put(key, springValue); Multimap<String, SpringValue> 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 // lazy initialize
if (initialized.compareAndSet(false, true)) { 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<String> 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 @Override
public void destroy() throws Exception { public void destroy() throws Exception {
executor.shutdown(); executor.shutdown();

@ -37,12 +37,32 @@ import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeListener;
public class MockedConfigKVFile implements ConfigKVFile { public class MockedConfigKVFile implements ConfigKVFile {
private final Map<String, Object> properties; private final Map<String, Object> properties;
private String fileName;
private String fileGroup;
private String namespace;
private final List<ConfigKVFileChangeListener> listeners = new ArrayList<>(); private final List<ConfigKVFileChangeListener> listeners = new ArrayList<>();
public MockedConfigKVFile(Map<String, Object> properties) { public MockedConfigKVFile(Map<String, Object> properties) {
this.properties = properties; this.properties = properties;
} }
public MockedConfigKVFile(Map<String, Object> properties, String fileName) {
this.properties = properties;
this.fileName = fileName;
}
public MockedConfigKVFile(Map<String, Object> properties, String fileName, String fileGroup, String namespace) {
this.properties = properties;
this.fileName = fileName;
this.fileGroup = fileGroup;
this.namespace = namespace;
}
@Override @Override
public String getProperty(String s, String s1) { public String getProperty(String s, String s1) {
return String.valueOf(properties.get(s)); return String.valueOf(properties.get(s));
@ -161,16 +181,16 @@ public class MockedConfigKVFile implements ConfigKVFile {
@Override @Override
public String getNamespace() { public String getNamespace() {
return null; return namespace;
} }
@Override @Override
public String getFileGroup() { public String getFileGroup() {
return null; return fileGroup;
} }
@Override @Override
public String getFileName() { public String getFileName() {
return null; return fileName;
} }
} }

@ -18,6 +18,8 @@
package com.tencent.cloud.polaris.config.adapter; package com.tencent.cloud.polaris.config.adapter;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; 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.cloud.polaris.context.config.PolarisContextProperties;
import com.tencent.polaris.configuration.api.core.ConfigFileService; import com.tencent.polaris.configuration.api.core.ConfigFileService;
import com.tencent.polaris.configuration.api.core.ConfigKVFile; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -66,6 +69,7 @@ public class PolarisConfigFileLocatorTest {
@Test @Test
public void testLoadApplicationPropertiesFile() { public void testLoadApplicationPropertiesFile() {
clearCompositePropertySourceCache();
PolarisConfigFileLocator locator = new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties, PolarisConfigFileLocator locator = new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties,
configFileService, environment); configFileService, environment);
@ -103,6 +107,7 @@ public class PolarisConfigFileLocatorTest {
@Test @Test
public void testActiveProfileFilesPriorityBiggerThanDefault() { public void testActiveProfileFilesPriorityBiggerThanDefault() {
clearCompositePropertySourceCache();
PolarisConfigFileLocator locator = new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties, PolarisConfigFileLocator locator = new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties,
configFileService, environment); configFileService, environment);
@ -152,6 +157,7 @@ public class PolarisConfigFileLocatorTest {
@Test @Test
public void testGetCustomFiles() { public void testGetCustomFiles() {
clearCompositePropertySourceCache();
PolarisConfigFileLocator locator = new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties, PolarisConfigFileLocator locator = new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties,
configFileService, environment); configFileService, environment);
@ -202,4 +208,73 @@ public class PolarisConfigFileLocatorTest {
assertThat(propertySource.getProperty("k2")).isEqualTo("v2"); assertThat(propertySource.getProperty("k2")).isEqualTo("v2");
assertThat(propertySource.getProperty("k3")).isEqualTo("v3"); 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<String, Object> 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<ConfigFileGroup> 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<String, Object> 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<String, Object> 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
}
}
} }

@ -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 {
}
}

@ -20,17 +20,24 @@ package com.tencent.cloud.polaris.config.adapter;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper; 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.SpringValue;
import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
import com.tencent.polaris.configuration.api.core.ChangeType; 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.ConfigKVFileChangeEvent;
import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; 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.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; 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.TypeConverter;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -55,6 +63,7 @@ import static org.mockito.Mockito.when;
public class PolarisPropertiesSourceAutoRefresherTest { public class PolarisPropertiesSourceAutoRefresherTest {
private final String testNamespace = "testNamespace"; private final String testNamespace = "testNamespace";
private final String testFileGroup = "testFileGroup";
private final String testServiceName = "testServiceName"; private final String testServiceName = "testServiceName";
private final String testFileName = "application.properties"; private final String testFileName = "application.properties";
@Mock @Mock
@ -66,6 +75,12 @@ public class PolarisPropertiesSourceAutoRefresherTest {
@Mock @Mock
private PlaceholderHelper placeholderHelper; private PlaceholderHelper placeholderHelper;
@Mock
private ConfigFileService configFileService;
@Mock
private ContextRefresher contextRefresher;
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
PolarisPropertySourceManager.clearPropertySources(); PolarisPropertySourceManager.clearPropertySources();
@ -73,7 +88,8 @@ public class PolarisPropertiesSourceAutoRefresherTest {
@Test @Test
public void testConfigFileChanged() throws Exception { 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); ConfigurableApplicationContext applicationContext = mock(ConfigurableApplicationContext.class);
ConfigurableListableBeanFactory beanFactory = mock(ConfigurableListableBeanFactory.class); ConfigurableListableBeanFactory beanFactory = mock(ConfigurableListableBeanFactory.class);
TypeConverter typeConverter = mock(TypeConverter.class); TypeConverter typeConverter = mock(TypeConverter.class);
@ -121,4 +137,88 @@ public class PolarisPropertiesSourceAutoRefresherTest {
assertThat(polarisPropertySource.getProperty("k2")).isNull(); assertThat(polarisPropertySource.getProperty("k2")).isNull();
assertThat(polarisPropertySource.getProperty("k4")).isEqualTo("v4"); 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<SpringValue> 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<String, Object> 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<String, Object> 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<String, ConfigPropertyChangeInfo> 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);
}
} }

@ -18,13 +18,16 @@
package com.tencent.cloud.polaris.config.listener; package com.tencent.cloud.polaris.config.listener;
import java.lang.reflect.Field;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import com.google.common.collect.Sets; 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.cloud.polaris.config.annotation.PolarisConfigKVFileChangeListener;
import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; 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.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringExtension;
@ -47,7 +51,9 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
*/ */
@ExtendWith(SpringExtension.class) @ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = DEFINED_PORT, classes = ConfigChangeListenerTest.TestApplication.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 { public class ConfigChangeListenerTest {
private static final CountDownLatch hits = new CountDownLatch(2); private static final CountDownLatch hits = new CountDownLatch(2);
@ -58,6 +64,19 @@ public class ConfigChangeListenerTest {
@Autowired @Autowired
private TestApplication.TestConfig testConfig; 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 @Test
public void test() throws InterruptedException { public void test() throws InterruptedException {
//before change //before change

@ -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<String, Object> 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<String, ConfigPropertyChangeInfo> 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 {
}
}
}

@ -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<String, Object> 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<String, ConfigPropertyChangeInfo> 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 {
}
}
}

@ -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<String> set;
private ArrayList<String> list;
private String[] array;
private HashMap<String, String> map;
private InnerProperties inner;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HashSet<String> getSet() {
return set;
}
public void setSet(HashSet<String> set) {
this.set = set;
}
public ArrayList<String> getList() {
return list;
}
public void setList(ArrayList<String> list) {
this.list = list;
}
public String[] getArray() {
return array;
}
public void setArray(String[] array) {
this.array = array;
}
public HashMap<String, String> getMap() {
return map;
}
public void setMap(HashMap<String, String> 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;
}
}
}

@ -37,6 +37,12 @@
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId> <artifactId>spring-boot-starter-aop</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>

Loading…
Cancel
Save