feature: support Polaris configuration center extension plugin interface and support dynamic modification of log levels. (#1105)

Co-authored-by: 杨镌颖 <531948963@qq.com>
Co-authored-by: melodyl <lovemycode@qq.com>
pull/1112/head
Haotian Zhang 10 months ago committed by GitHub
parent 0aa3d7d820
commit 43ec0f91ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -27,3 +27,4 @@
- [refactor:optimize discovery properties initialization.](https://github.com/Tencent/spring-cloud-tencent/pull/1079)
- [fix:upgrade spring version.](https://github.com/Tencent/spring-cloud-tencent/pull/1085)
- [fix:Update README-zh.md](https://github.com/Tencent/spring-cloud-tencent/pull/1094).
- [feature: support Polaris configuration center extension plugin interface and support dynamic modification of log levels.](https://github.com/Tencent/spring-cloud-tencent/pull/1105)

@ -29,6 +29,7 @@ import com.tencent.cloud.polaris.config.condition.ConditionalOnReflectRefreshTyp
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.listener.PolarisConfigChangeEventListener;
import com.tencent.cloud.polaris.config.listener.PolarisConfigRefreshOptimizationListener;
import com.tencent.cloud.polaris.config.logger.PolarisConfigLoggerApplicationListener;
import com.tencent.cloud.polaris.config.spring.annotation.SpringValueProcessor;
import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper;
import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
@ -62,6 +63,12 @@ public class PolarisConfigAutoConfiguration {
return new PolarisConfigChangeEventListener();
}
@Bean
public PolarisConfigLoggerApplicationListener polarisConfigLoggerApplicationListener() {
return new PolarisConfigLoggerApplicationListener();
}
@Bean
@Primary
@ConditionalOnReflectRefreshType

@ -0,0 +1,38 @@
/*
* 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.polaris.configuration.api.core.ConfigFileService;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.Environment;
/**
* @Date Jul 23, 2023 2:57:49 PM
* @author juanyinyang
*/
public interface PolarisConfigCustomExtensionLayer {
void initRegisterConfig(PolarisConfigPropertyAutoRefresher polarisConfigPropertyAutoRefresher);
void initConfigFiles(Environment environment, CompositePropertySource compositePropertySource, PolarisPropertySourceManager polarisPropertySourceManager, ConfigFileService configFileService);
void executeAfterLocateConfigReturning(CompositePropertySource compositePropertySource);
boolean executeRegisterPublishChangeListener(PolarisPropertySource polarisPropertySource);
}

@ -17,12 +17,24 @@
package com.tencent.cloud.polaris.config.adapter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.tencent.cloud.polaris.config.config.ConfigFileGroup;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.enums.ConfigFileFormat;
import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import com.tencent.polaris.configuration.api.core.ConfigFileMetadata;
import com.tencent.polaris.configuration.api.core.ConfigFileService;
import com.tencent.polaris.configuration.api.core.ConfigKVFile;
import com.tencent.polaris.configuration.client.internal.DefaultConfigFileMetadata;
import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.core.annotation.Order;
@ -30,6 +42,7 @@ import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* Spring cloud reserved core configuration loading SPI.
@ -41,38 +54,185 @@ import org.springframework.util.CollectionUtils;
@Order(0)
public class PolarisConfigFileLocator implements PropertySourceLocator {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisConfigFileLocator.class);
private static final String POLARIS_CONFIG_PROPERTY_SOURCE_NAME = "polaris-config";
private final PolarisConfigProperties polarisConfigProperties;
private final PolarisConfigFilePuller polarisConfigFilePuller;
private final PolarisContextProperties polarisContextProperties;
private final ConfigFileService configFileService;
private final PolarisPropertySourceManager polarisPropertySourceManager;
private final Environment environment;
// this class provides customized logic for some customers to configure special business group files
private final PolarisConfigCustomExtensionLayer polarisConfigCustomExtensionLayer = PolarisServiceLoaderUtil.getPolarisConfigCustomExtensionLayer();
public PolarisConfigFileLocator(PolarisConfigProperties polarisConfigProperties,
PolarisContextProperties polarisContextProperties, ConfigFileService configFileService,
PolarisPropertySourceManager polarisPropertySourceManager, Environment environment) {
public PolarisConfigFileLocator(PolarisConfigProperties polarisConfigProperties, PolarisContextProperties polarisContextProperties, ConfigFileService configFileService, PolarisPropertySourceManager polarisPropertySourceManager, Environment environment) {
this.polarisConfigProperties = polarisConfigProperties;
this.polarisConfigFilePuller = PolarisConfigFilePuller.get(polarisContextProperties, configFileService,
polarisPropertySourceManager);
this.polarisContextProperties = polarisContextProperties;
this.configFileService = configFileService;
this.polarisPropertySourceManager = polarisPropertySourceManager;
this.environment = environment;
}
@Override
public PropertySource<?> locate(Environment environment) {
CompositePropertySource compositePropertySource = new CompositePropertySource(
POLARIS_CONFIG_PROPERTY_SOURCE_NAME);
CompositePropertySource compositePropertySource = new CompositePropertySource(POLARIS_CONFIG_PROPERTY_SOURCE_NAME);
try {
// load custom config extension files
initCustomPolarisConfigExtensionFiles(compositePropertySource);
// load spring boot default config files
initInternalConfigFiles(compositePropertySource);
// load custom config files
List<ConfigFileGroup> configFileGroups = polarisConfigProperties.getGroups();
if (CollectionUtils.isEmpty(configFileGroups)) {
return compositePropertySource;
}
initCustomPolarisConfigFiles(compositePropertySource, configFileGroups);
return compositePropertySource;
}
finally {
afterLocatePolarisConfigExtension(compositePropertySource);
}
}
private void initCustomPolarisConfigExtensionFiles(CompositePropertySource compositePropertySource) {
if (polarisConfigCustomExtensionLayer == null) {
LOGGER.debug("[SCT Config] PolarisAdaptorTsfConfigExtensionLayer is not init, ignore the following execution steps");
return;
}
polarisConfigCustomExtensionLayer.initConfigFiles(environment, compositePropertySource, polarisPropertySourceManager, configFileService);
}
private void afterLocatePolarisConfigExtension(CompositePropertySource compositePropertySource) {
if (polarisConfigCustomExtensionLayer == null) {
LOGGER.debug("[SCT Config] PolarisAdaptorTsfConfigExtensionLayer is not init, ignore the following execution steps");
return;
}
polarisConfigCustomExtensionLayer.executeAfterLocateConfigReturning(compositePropertySource);
}
private void initInternalConfigFiles(CompositePropertySource compositePropertySource) {
List<ConfigFileMetadata> internalConfigFiles = getInternalConfigFiles();
for (ConfigFileMetadata configFile : internalConfigFiles) {
PolarisPropertySource polarisPropertySource = loadPolarisPropertySource(configFile.getNamespace(), configFile.getFileGroup(), configFile.getFileName());
compositePropertySource.addPropertySource(polarisPropertySource);
// load spring boot default config files
polarisPropertySourceManager.addPropertySource(polarisPropertySource);
LOGGER.info("[SCT Config] Load and inject polaris config file. file = {}", configFile);
}
}
private List<ConfigFileMetadata> getInternalConfigFiles() {
String namespace = polarisContextProperties.getNamespace();
String serviceName = polarisContextProperties.getService();
if (!StringUtils.hasText(serviceName)) {
serviceName = environment.getProperty("spring.application.name");
}
List<ConfigFileMetadata> internalConfigFiles = new LinkedList<>();
// priority: application-${profile} > application > boostrap-${profile} > boostrap
String[] activeProfiles = environment.getActiveProfiles();
String[] defaultProfiles = environment.getDefaultProfiles();
String serviceName = environment.getProperty("spring.application.name");
polarisConfigFilePuller.initInternalConfigFiles(
compositePropertySource, activeProfiles, defaultProfiles, serviceName);
List<String> profileList = new ArrayList<>();
if (ArrayUtils.isNotEmpty(activeProfiles)) {
profileList.addAll(Arrays.asList(activeProfiles));
}
else if (ArrayUtils.isNotEmpty(defaultProfiles)) {
profileList.addAll(Arrays.asList(defaultProfiles));
}
// build application config files
buildInternalApplicationConfigFiles(internalConfigFiles, namespace, serviceName, profileList);
// build bootstrap config files
buildInternalBootstrapConfigFiles(internalConfigFiles, namespace, serviceName, profileList);
// load custom config files
List<ConfigFileGroup> configFileGroups = polarisConfigProperties.getGroups();
if (CollectionUtils.isEmpty(configFileGroups)) {
return compositePropertySource;
return internalConfigFiles;
}
private void buildInternalApplicationConfigFiles(List<ConfigFileMetadata> internalConfigFiles, String namespace, String serviceName, List<String> profileList) {
for (String profile : profileList) {
if (!StringUtils.hasText(profile)) {
continue;
}
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "application-" + profile + ".properties"));
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "application-" + profile + ".yml"));
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "application-" + profile + ".yaml"));
}
polarisConfigFilePuller.initCustomPolarisConfigFiles(compositePropertySource, configFileGroups);
return compositePropertySource;
// build default config properties files.
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "application.properties"));
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "application.yml"));
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "application.yaml"));
}
private void buildInternalBootstrapConfigFiles(List<ConfigFileMetadata> internalConfigFiles, String namespace, String serviceName, List<String> profileList) {
for (String profile : profileList) {
if (!StringUtils.hasText(profile)) {
continue;
}
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "bootstrap-" + profile + ".properties"));
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "bootstrap-" + profile + ".yml"));
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "bootstrap-" + profile + ".yaml"));
}
// build default config properties files.
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "bootstrap.properties"));
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "bootstrap.yml"));
internalConfigFiles.add(new DefaultConfigFileMetadata(namespace, serviceName, "bootstrap.yaml"));
}
private void initCustomPolarisConfigFiles(CompositePropertySource compositePropertySource, List<ConfigFileGroup> configFileGroups) {
String namespace = polarisContextProperties.getNamespace();
for (ConfigFileGroup configFileGroup : configFileGroups) {
String group = configFileGroup.getName();
if (!StringUtils.hasText(group)) {
throw new IllegalArgumentException("polaris config group name cannot be empty.");
}
List<String> files = configFileGroup.getFiles();
if (CollectionUtils.isEmpty(files)) {
return;
}
for (String fileName : files) {
PolarisPropertySource polarisPropertySource = loadPolarisPropertySource(namespace, group, fileName);
compositePropertySource.addPropertySource(polarisPropertySource);
polarisPropertySourceManager.addPropertySource(polarisPropertySource);
LOGGER.info("[SCT Config] Load and inject polaris config file success. namespace = {}, group = {}, fileName = {}", namespace, group, fileName);
}
}
}
private PolarisPropertySource loadPolarisPropertySource(String namespace, String group, String fileName) {
ConfigKVFile configKVFile;
// unknown extension is resolved as properties file
if (ConfigFileFormat.isPropertyFile(fileName) || ConfigFileFormat.isUnknownFile(fileName)) {
configKVFile = configFileService.getConfigPropertiesFile(namespace, group, fileName);
}
else if (ConfigFileFormat.isYamlFile(fileName)) {
configKVFile = configFileService.getConfigYamlFile(namespace, group, fileName);
}
else {
LOGGER.warn("[SCT Config] Unsupported config file. namespace = {}, group = {}, fileName = {}", namespace, group, fileName);
throw new IllegalStateException("Only configuration files in the format of properties / yaml / yaml" + " can be injected into the spring context");
}
Map<String, Object> map = new ConcurrentHashMap<>();
for (String key : configKVFile.getPropertyNames()) {
map.put(key, configKVFile.getProperty(key, null));
}
return new PolarisPropertySource(namespace, group, fileName, configKVFile, map);
}
}

@ -23,6 +23,7 @@ import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.logger.PolarisConfigLoggerContext;
import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeListener;
import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo;
import org.slf4j.Logger;
@ -39,8 +40,7 @@ import org.springframework.util.CollectionUtils;
*
* @author lepdou
*/
public abstract class PolarisConfigPropertyAutoRefresher
implements ApplicationListener<ApplicationReadyEvent>, PolarisConfigPropertyRefresher {
public abstract class PolarisConfigPropertyAutoRefresher implements ApplicationListener<ApplicationReadyEvent>, PolarisConfigPropertyRefresher {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisConfigPropertyAutoRefresher.class);
@ -50,9 +50,10 @@ public abstract class PolarisConfigPropertyAutoRefresher
private final AtomicBoolean registered = new AtomicBoolean(false);
public PolarisConfigPropertyAutoRefresher(
PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager) {
// this class provides customized logic for some customers to configure special business group files
private final PolarisConfigCustomExtensionLayer polarisConfigCustomExtensionLayer = PolarisServiceLoaderUtil.getPolarisConfigCustomExtensionLayer();
public PolarisConfigPropertyAutoRefresher(PolarisConfigProperties polarisConfigProperties, PolarisPropertySourceManager polarisPropertySourceManager) {
this.polarisConfigProperties = polarisConfigProperties;
this.polarisPropertySourceManager = polarisPropertySourceManager;
}
@ -76,41 +77,71 @@ public abstract class PolarisConfigPropertyAutoRefresher
return;
}
// custom register polaris config
customInitRegisterPolarisConfig(this);
// register polaris config publish event
for (PolarisPropertySource polarisPropertySource : polarisPropertySources) {
polarisPropertySource.getConfigKVFile()
.addChangeListener((ConfigKVFileChangeListener) configKVFileChangeEvent -> {
LOGGER.info(
"[SCT Config] received polaris config change event and will refresh spring context."
+ " namespace = {}, group = {}, fileName = {}",
polarisPropertySource.getNamespace(),
polarisPropertySource.getGroup(),
polarisPropertySource.getFileName());
Map<String, Object> source = polarisPropertySource.getSource();
for (String changedKey : configKVFileChangeEvent.changedKeys()) {
ConfigPropertyChangeInfo configPropertyChangeInfo = configKVFileChangeEvent
.getChangeInfo(changedKey);
LOGGER.info("[SCT Config] changed property = {}", configPropertyChangeInfo);
switch (configPropertyChangeInfo.getChangeType()) {
case MODIFIED:
case ADDED:
source.put(changedKey, configPropertyChangeInfo.getNewValue());
break;
case DELETED:
source.remove(changedKey);
break;
registerPolarisConfigPublishChangeListener(polarisPropertySource);
customRegisterPolarisConfigPublishChangeListener(polarisPropertySource);
}
}
private void customInitRegisterPolarisConfig(PolarisConfigPropertyAutoRefresher polarisConfigPropertyAutoRefresher) {
if (polarisConfigCustomExtensionLayer == null) {
LOGGER.debug("[SCT Config] PolarisAdaptorTsfConfigExtensionLayer is not init, ignore the following execution steps");
return;
}
polarisConfigCustomExtensionLayer.initRegisterConfig(polarisConfigPropertyAutoRefresher);
}
public void registerPolarisConfigPublishChangeListener(PolarisPropertySource polarisPropertySource) {
polarisPropertySource.getConfigKVFile()
.addChangeListener((ConfigKVFileChangeListener) configKVFileChangeEvent -> {
LOGGER.info("[SCT Config] received polaris config change event and will refresh spring context." + " namespace = {}, group = {}, fileName = {}", polarisPropertySource.getNamespace(), polarisPropertySource.getGroup(), polarisPropertySource.getFileName());
Map<String, Object> source = polarisPropertySource.getSource();
for (String changedKey : configKVFileChangeEvent.changedKeys()) {
ConfigPropertyChangeInfo configPropertyChangeInfo = configKVFileChangeEvent.getChangeInfo(changedKey);
LOGGER.info("[SCT Config] changed property = {}", configPropertyChangeInfo);
// new ability to dynamically change log levels
try {
if (changedKey.startsWith("logging.level") && changedKey.length() >= 14) {
String loggerName = changedKey.substring(14);
String newValue = (String) configPropertyChangeInfo.getNewValue();
LOGGER.info("[SCT Config] set logging.level loggerName:{}, newValue:{}", loggerName, newValue);
PolarisConfigLoggerContext.setLevel(loggerName, newValue);
}
// update the attribute with @Value annotation
refreshSpringValue(changedKey);
}
// update @ConfigurationProperties beans
refreshConfigurationProperties(configKVFileChangeEvent.changedKeys());
});
catch (Exception e) {
LOGGER.error("[SCT Config] set logging.level exception,", e);
}
switch (configPropertyChangeInfo.getChangeType()) {
case MODIFIED:
case ADDED:
source.put(changedKey, configPropertyChangeInfo.getNewValue());
break;
case DELETED:
source.remove(changedKey);
break;
}
// update the attribute with @Value annotation
refreshSpringValue(changedKey);
}
// update @ConfigurationProperties beans
refreshConfigurationProperties(configKVFileChangeEvent.changedKeys());
});
}
private void customRegisterPolarisConfigPublishChangeListener(PolarisPropertySource polarisPropertySource) {
if (polarisConfigCustomExtensionLayer == null) {
LOGGER.debug("[SCT Config] PolarisAdaptorTsfConfigExtensionLayer is not init, ignore the following execution steps");
return;
}
polarisConfigCustomExtensionLayer.executeRegisterPublishChangeListener(polarisPropertySource);
}
}

@ -39,8 +39,7 @@ public class PolarisPropertySource extends MapPropertySource {
private final ConfigKVFile configKVFile;
public PolarisPropertySource(String namespace, String group, String fileName, ConfigKVFile configKVFile,
Map<String, Object> source) {
public PolarisPropertySource(String namespace, String group, String fileName, ConfigKVFile configKVFile, Map<String, Object> source) {
super(namespace + "-" + group + "-" + fileName, source);
this.namespace = namespace;
@ -71,7 +70,6 @@ public class PolarisPropertySource extends MapPropertySource {
@Override
public String toString() {
return "PolarisPropertySource{" + "namespace='" + namespace + '\'' + ", group='" + group + '\'' + ", fileName='"
+ fileName + '\'' + '}';
return "PolarisPropertySource{" + "namespace='" + namespace + '\'' + ", group='" + group + '\'' + ", fileName='" + fileName + '\'' + '}';
}
}

@ -0,0 +1,50 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.config.adapter;
import java.util.Iterator;
import java.util.ServiceLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author juanyinyang
* @Date 2023810 4:11:05
*/
public final class PolarisServiceLoaderUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisServiceLoaderUtil.class);
private PolarisServiceLoaderUtil() {
}
// this class provides customized logic for some customers to configure special business group files
private static PolarisConfigCustomExtensionLayer polarisConfigCustomExtensionLayer;
static {
ServiceLoader<PolarisConfigCustomExtensionLayer> polarisConfigCustomExtensionLayerLoader = ServiceLoader.load(PolarisConfigCustomExtensionLayer.class);
Iterator<PolarisConfigCustomExtensionLayer> polarisConfigCustomExtensionLayerIterator = polarisConfigCustomExtensionLayerLoader.iterator();
// Generally, there is only one implementation class. If there are multiple, the last one is loaded
while (polarisConfigCustomExtensionLayerIterator.hasNext()) {
polarisConfigCustomExtensionLayer = polarisConfigCustomExtensionLayerIterator.next();
LOGGER.info("[SCT Config] PolarisConfigFileLocator init polarisConfigCustomExtensionLayer:{}", polarisConfigCustomExtensionLayer);
}
}
public static PolarisConfigCustomExtensionLayer getPolarisConfigCustomExtensionLayer() {
return polarisConfigCustomExtensionLayer;
}
}

@ -27,6 +27,7 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.tencent.cloud.polaris.config.listener.ConfigChangeEvent;
import com.tencent.cloud.polaris.config.listener.ConfigChangeListener;
import com.tencent.cloud.polaris.config.listener.SyncConfigChangeListener;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
@ -90,8 +91,19 @@ public class PolarisConfigAnnotationProcessor implements BeanPostProcessor, Prio
ReflectionUtils.makeAccessible(method);
String[] annotatedInterestedKeys = annotation.interestedKeys();
String[] annotatedInterestedKeyPrefixes = annotation.interestedKeyPrefixes();
boolean isAsync = annotation.async();
ConfigChangeListener configChangeListener = changeEvent -> ReflectionUtils.invokeMethod(method, bean, changeEvent);
ConfigChangeListener configChangeListener = new SyncConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
ReflectionUtils.invokeMethod(method, bean, changeEvent);
}
@Override
public boolean isAsync() {
return isAsync;
}
};
Set<String> interestedKeys =
annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null;

@ -54,4 +54,6 @@ public @interface PolarisConfigKVFileChangeListener {
* @return interested key-prefixed in the listener
*/
String[] interestedKeyPrefixes() default {};
boolean async() default true;
}

@ -21,8 +21,10 @@ import java.util.List;
import com.tencent.cloud.polaris.config.enums.RefreshType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* polaris config module bootstrap configs.
*
@ -30,26 +32,29 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
*/
@ConfigurationProperties("spring.cloud.polaris.config")
public class PolarisConfigProperties {
/**
* Whether to open the configuration center.
*/
@Value("${spring.cloud.polaris.config.enabled:#{'true'}}")
private boolean enabled = true;
/**
* Configuration center service address list.
*/
@Value("${spring.cloud.polaris.config.address:}")
private String address;
/**
* Polaris config grpc port.
*/
@Value("${spring.cloud.polaris.config.port:#{'8093'}}")
private int port = 8093;
/**
* Whether to automatically update to the spring context when the configuration file.
* is updated
*/
@Value("${spring.cloud.polaris.config.autoRefresh:#{'true'}}")
private boolean autoRefresh = true;
private boolean shutdownIfConnectToConfigServerFailed = true;

@ -70,11 +70,11 @@ public final class PolarisConfigListenerContext {
/**
* All custom interested keys defined in application .
*/
private static final Map<ConfigChangeListener, Set<String>> interestedKeys = Maps.newHashMap();
private static final Map<ConfigChangeListener, Set<String>> interestedKeys = Maps.newConcurrentMap();
/**
* All custom interested key prefixes defined in application .
*/
private static final Map<ConfigChangeListener, Set<String>> interestedKeyPrefixes = Maps.newHashMap();
private static final Map<ConfigChangeListener, Set<String>> interestedKeyPrefixes = Maps.newConcurrentMap();
/**
* Cache all latest configuration information for users in the application environment .
*/
@ -87,7 +87,7 @@ public final class PolarisConfigListenerContext {
* Get or Created new execute server .
* @return execute service instance of {@link ExecutorService}
*/
private static ExecutorService executor() {
public static ExecutorService executor() {
if (EAR.get() == null) {
synchronized (PolarisConfigListenerContext.class) {
int coreThreadSize = Runtime.getRuntime().availableProcessors();
@ -185,6 +185,15 @@ public final class PolarisConfigListenerContext {
Map<String, ConfigPropertyChangeInfo> modifiedChanges = new HashMap<>(interestedChangedKeys.size());
interestedChangedKeys.parallelStream().forEach(key -> modifiedChanges.put(key, changes.get(key)));
ConfigChangeEvent event = new ConfigChangeEvent(modifiedChanges, interestedChangedKeys);
if (listener instanceof SyncConfigChangeListener) {
SyncConfigChangeListener l = (SyncConfigChangeListener) listener;
if (!l.isAsync()) {
listener.onChange(event);
continue;
}
}
PolarisConfigListenerContext.executor().execute(() -> listener.onChange(event));
}
}

@ -28,6 +28,7 @@ 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;
@ -65,17 +66,24 @@ public class PolarisConfigRefreshOptimizationListener implements ApplicationList
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);
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) {
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 (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]
@ -89,8 +97,7 @@ public class PolarisConfigRefreshOptimizationListener implements ApplicationList
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)));
propertySources.addFirst(new MapPropertySource(CONFIG_REFRESH_TYPE_PROPERTY, Collections.singletonMap(POLARIS_CONFIG_REFRESH_TYPE, RefreshType.REFRESH_CONTEXT)));
}
private void modifyPolarisConfigPropertiesBean(ConfigurableApplicationContext applicationContext) {
@ -99,9 +106,15 @@ public class PolarisConfigRefreshOptimizationListener implements ApplicationList
}
private void removeRelatedBeansOfReflect(ConfigurableApplicationContext applicationContext) {
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
beanFactory.removeBeanDefinition(REFLECT_REFRESHER_BEAN_NAME);
beanFactory.removeBeanDefinition(REFLECT_REBINDER_BEAN_NAME);
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) {
@ -118,9 +131,9 @@ public class PolarisConfigRefreshOptimizationListener implements ApplicationList
beanFactory.registerBeanDefinition(REFRESH_CONTEXT_REFRESHER_BEAN_NAME, beanDefinition);
}
private void addRefresherBeanAsListener(ConfigurableApplicationContext applicationContext) {
PolarisRefreshEntireContextRefresher refresher = (PolarisRefreshEntireContextRefresher) applicationContext
.getBean(REFRESH_CONTEXT_REFRESHER_BEAN_NAME);
PolarisRefreshEntireContextRefresher refresher = (PolarisRefreshEntireContextRefresher) applicationContext.getBean(REFRESH_CONTEXT_REFRESHER_BEAN_NAME);
applicationContext.addApplicationListener(refresher);
}
}

@ -0,0 +1,24 @@
/*
* 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;
public interface SyncConfigChangeListener extends ConfigChangeListener {
default boolean isAsync() {
return true;
}
}

@ -0,0 +1,45 @@
/*
* 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.logger;
/**
* @author juanyinyang
*/
public enum Level {
/** log level. */
TRACE("TRACE"), DEBUG("DEBUG"), INFO("INFO"), WARN("WARN"), ERROR("ERROR"), FATAL("FATAL"), OFF("OFF");
private String level;
Level(String level) {
this.level = level;
}
public String getLevel() {
return level;
}
public static Level levelOf(String level) {
for (Level l : Level.values()) {
if (l.level.equalsIgnoreCase(level)) {
return l;
}
}
return null;
}
}

@ -0,0 +1,52 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.config.logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
/**
* @author juanyinyang
*/
public class PolarisConfigLoggerApplicationListener implements ApplicationListener<ApplicationEvent> {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisConfigLoggerApplicationListener.class);
/**
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
try {
// Initialize application loggingSystem.
if (event instanceof ApplicationStartedEvent) {
ApplicationStartedEvent startedEvent = (ApplicationStartedEvent) event;
ClassLoader classLoader = startedEvent.getSpringApplication().getClassLoader();
LoggingSystem loggingSystem = LoggingSystem.get(classLoader);
LOGGER.info("PolarisConfigLoggerApplicationListener onApplicationEvent init loggingSystem:{}", loggingSystem);
PolarisConfigLoggerContext.setLogSystem(loggingSystem);
}
}
catch (Exception e) {
LOGGER.error("PolarisConfigLoggerApplicationListener onApplicationEvent exception:", e);
}
}
}

@ -0,0 +1,106 @@
/*
* 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.logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.util.Assert;
import static org.springframework.boot.logging.LoggingSystem.ROOT_LOGGER_NAME;
/**
* @author juanyinyang
*/
public final class PolarisConfigLoggerContext {
private static LoggingSystem loggingSystem;
private PolarisConfigLoggerContext() {
}
protected static void setLogSystem(LoggingSystem logSystem) {
Assert.notNull(logSystem, "Logging System should not be null");
PolarisConfigLoggerContext.loggingSystem = logSystem;
}
public static void setLevel(String loggerName, String level) {
if (loggingSystem == null) {
printLog("[SCT Config] PolarisConfigLoggerContext logger: [" + loggerName + "] change to target level fail. caused by internal exception:" + level, Level.WARN);
return;
}
Level loggerLevel = Level.levelOf(level);
if (loggerLevel == null) {
printLog("[SCT Config] PolarisConfigLoggerContext logger: [" + loggerName + "] change to target level fail. caused by level is not support, level:" + level, Level.WARN);
return;
}
LogLevel logLevel = null;
switch (loggerLevel) {
case TRACE:
logLevel = LogLevel.TRACE;
break;
case DEBUG:
logLevel = LogLevel.DEBUG;
break;
case OFF:
logLevel = LogLevel.OFF;
break;
case INFO:
logLevel = LogLevel.INFO;
break;
case WARN:
logLevel = LogLevel.WARN;
break;
case ERROR:
logLevel = LogLevel.ERROR;
break;
case FATAL:
logLevel = LogLevel.FATAL;
break;
default:
printLog("[SCT Config] PolarisConfigLoggerContext logger: [" + loggerName + "] setLevel fail. caused by level is not support, level: " + level, Level.WARN);
}
loggingSystem.setLogLevel(loggerName, logLevel);
printLog("[SCT Config] PolarisConfigLoggerContext logger: [" + loggerName + "] changed to level:" + level, Level.INFO);
}
/**
* print log.
*/
private static void printLog(String message, Level level) {
Logger logger = LoggerFactory.getLogger(ROOT_LOGGER_NAME);
if (level.ordinal() <= Level.INFO.ordinal()) {
if (logger != null) {
logger.info(message);
}
else {
StdLog.info(message);
}
}
else {
if (logger != null) {
logger.warn(message);
}
else {
StdLog.warn(message);
}
}
}
}

@ -0,0 +1,96 @@
/*
* 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.logger;
import java.io.PrintStream;
import java.util.Calendar;
/**
* internal output logger.
* @author juanyinyang
*/
public final class StdLog {
private static final String CLASS_INFO = StdLog.class.getName();
protected static boolean debugEnabled = false;
/**
* *enable info level log.
*/
protected static boolean infoEnabled = true;
/**
* quite mode will out put nothing.
*/
protected static boolean quietMode = false;
private static final String DEBUG_FIX = "StdLog:DEBUG: ";
private static final String INFO_FIX = "StdLog:INFO: ";
private static final String WARN_FIX = "StdLog:WARN: ";
private StdLog() {
}
public static void setQuietMode(boolean quietMode) {
StdLog.quietMode = quietMode;
}
public static void setInfoEnabled(boolean infoEnabled) {
StdLog.infoEnabled = infoEnabled;
}
public static void setDebugEnabled(boolean debugEnabled) {
StdLog.debugEnabled = debugEnabled;
}
public static void debug(String msg) {
if (debugEnabled && !quietMode) {
println(System.out, DEBUG_FIX + msg);
}
}
public static void info(String msg) {
if (infoEnabled && !quietMode) {
println(System.out, INFO_FIX + msg);
}
}
public static void warn(String msg) {
if (infoEnabled && !quietMode) {
println(System.err, WARN_FIX + msg);
}
}
public static void warn(String msg, Throwable t) {
if (quietMode) {
return;
}
println(System.err, WARN_FIX + msg);
if (t != null) {
t.printStackTrace();
}
}
private static void println(PrintStream out, String msg) {
out.println(Calendar.getInstance().getTime().toString() + " " + CLASS_INFO + " " + msg);
}
}

@ -104,7 +104,7 @@ public class ConfigChangeListenerTest {
@PolarisConfigKVFileChangeListener(interestedKeys = {"timeout"})
public void configChangedListener(ConfigChangeEvent event) {
ConfigPropertyChangeInfo changeInfo = event.getChange("timeout");
timeout = Integer.parseInt(changeInfo.getNewValue());
timeout = Integer.parseInt(changeInfo.getNewValue().toString());
changeCnt++;
hits.countDown();
}
@ -112,7 +112,7 @@ public class ConfigChangeListenerTest {
@PolarisConfigKVFileChangeListener(interestedKeyPrefixes = {"timeout"})
public void configChangedListener2(ConfigChangeEvent event) {
ConfigPropertyChangeInfo changeInfo = event.getChange("timeout");
timeout = Integer.parseInt(changeInfo.getNewValue());
timeout = Integer.parseInt(changeInfo.getNewValue().toString());
changeCnt++;
hits.countDown();
}

@ -73,7 +73,7 @@
<revision>1.12.0-2022.0.2-SNAPSHOT</revision>
<!-- Dependencies -->
<polaris.version>1.14.0-SNAPSHOT</polaris.version>
<polaris.version>1.14.0</polaris.version>
<guava.version>32.0.1-jre</guava.version>
<mocktio.version>4.9.0</mocktio.version>
<byte-buddy.version>1.12.19</byte-buddy.version>

@ -42,7 +42,25 @@ public final class PersonConfigChangeListener {
Set<String> changedKeys = event.changedKeys();
for (String changedKey : changedKeys) {
System.out.printf("%s = %s \n", changedKey, event.getChange(changedKey));
System.out.printf("%s = %s , ThreadId: %s\n", changedKey, event.getChange(changedKey), Thread.currentThread().getId());
}
}
@PolarisConfigKVFileChangeListener(interestedKeyPrefixes = "teacher", async = false)
public void syncListen(ConfigChangeEvent event) {
Set<String> changedKeys = event.changedKeys();
for (String changedKey : changedKeys) {
System.out.printf("%s = %s , ThreadId: %s\n", changedKey, event.getChange(changedKey), Thread.currentThread().getId());
}
}
@PolarisConfigKVFileChangeListener(interestedKeyPrefixes = "teacher", async = false)
public void syncListen2(ConfigChangeEvent event) {
Set<String> changedKeys = event.changedKeys();
for (String changedKey : changedKeys) {
System.out.printf("%s = %s , ThreadId: %s\n", changedKey, event.getChange(changedKey), Thread.currentThread().getId());
}
}
}

@ -31,6 +31,7 @@ import com.tencent.polaris.factory.ConfigAPIFactory;
import com.tencent.polaris.factory.config.ConfigurationImpl;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.CollectionUtils;
@ -45,26 +46,31 @@ public class PolarisContextProperties {
/**
* polaris server address.
*/
@Value("${spring.cloud.polaris.address:}")
private String address;
/**
* current server local ip address.
*/
@Value("${spring.cloud.polaris.localIpAddress:}")
private String localIpAddress;
/**
* current server local port.
*/
@Value("${spring.cloud.polaris.localPort:}")
private Integer localPort;
/**
* If polaris enabled.
*/
@Value("${spring.cloud.polaris.enabled:#{'true'}}")
private Boolean enabled;
/**
* polaris namespace.
*/
@Value("${spring.cloud.polaris.namespace:#{'default'}}")
private String namespace = "default";
/**

Loading…
Cancel
Save