feature:add @ConditionalOnConfigReflectEnabled annotation. (#539)

pull/559/head
Haotian Zhang 2 years ago committed by GitHub
parent 05130c9327
commit 74287e3001
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -26,4 +26,5 @@
- [Feature: remove location metadata](https://github.com/Tencent/spring-cloud-tencent/pull/536)
- [Feature: remove location metadata](https://github.com/Tencent/spring-cloud-tencent/pull/542)
- [add feature-env plugin & add spring cloud gateway staining plugin](https://github.com/Tencent/spring-cloud-tencent/pull/533)
- [feature:add @ConditionalOnConfigReflectEnabled annotation](https://github.com/Tencent/spring-cloud-tencent/pull/539)
- [fix:set error handler named EnhancedRestTemplateReporter for RestTemplate](https://github.com/Tencent/spring-cloud-tencent/pull/545)

@ -18,11 +18,13 @@
package com.tencent.cloud.polaris.config;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceAutoRefresher;
import com.tencent.cloud.polaris.config.adapter.AffectedConfigurationPropertiesRebinder;
import com.tencent.cloud.polaris.config.adapter.PolarisConfigPropertyRefresher;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
import com.tencent.cloud.polaris.config.adapter.SmartConfigurationPropertiesRebinder;
import com.tencent.cloud.polaris.config.adapter.PolarisRefreshAffectedContextRefresher;
import com.tencent.cloud.polaris.config.adapter.PolarisRefreshEntireContextRefresher;
import com.tencent.cloud.polaris.config.annotation.PolarisConfigAnnotationProcessor;
import com.tencent.cloud.polaris.config.condition.ConditionalOnNonDefaultBehavior;
import com.tencent.cloud.polaris.config.condition.ConditionalOnReflectRefreshType;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.listener.PolarisConfigChangeEventListener;
import com.tencent.cloud.polaris.config.spring.annotation.SpringValueProcessor;
@ -30,6 +32,7 @@ import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper;
import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
@ -49,17 +52,6 @@ import org.springframework.context.annotation.Configuration;
@ConditionalOnProperty(value = "spring.cloud.polaris.config.enabled", matchIfMissing = true)
public class PolarisConfigAutoConfiguration {
@Bean
public PolarisPropertySourceAutoRefresher polarisPropertySourceAutoRefresher(
PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager,
SpringValueRegistry springValueRegistry,
PlaceholderHelper placeholderHelper,
ContextRefresher contextRefresher) {
return new PolarisPropertySourceAutoRefresher(polarisConfigProperties,
polarisPropertySourceManager, springValueRegistry, placeholderHelper, contextRefresher);
}
@Bean
public PolarisConfigAnnotationProcessor polarisConfigAnnotationProcessor() {
return new PolarisConfigAnnotationProcessor();
@ -71,28 +63,46 @@ public class PolarisConfigAutoConfiguration {
}
@Bean
public SpringValueRegistry springValueRegistry() {
return new SpringValueRegistry();
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
@ConditionalOnReflectRefreshType
public ConfigurationPropertiesRebinder affectedConfigurationPropertiesRebinder(
ConfigurationPropertiesBeans beans) {
return new AffectedConfigurationPropertiesRebinder(beans);
}
@Bean
public PlaceholderHelper placeholderHelper() {
return new PlaceholderHelper();
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public PolarisConfigPropertyRefresher polarisRefreshContextPropertySourceAutoRefresher(PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager, ContextRefresher contextRefresher) {
return new PolarisRefreshEntireContextRefresher(polarisConfigProperties, polarisPropertySourceManager, contextRefresher);
}
@Bean
public SpringValueProcessor springValueProcessor(PlaceholderHelper placeholderHelper, SpringValueRegistry springValueRegistry, PolarisConfigProperties polarisConfigProperties) {
return new SpringValueProcessor(placeholderHelper, springValueRegistry, polarisConfigProperties);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnReflectRefreshType
@AutoConfigureBefore(PolarisConfigAutoConfiguration.class)
public static class PolarisReflectRefresherAutoConfiguration {
@Bean
public SpringValueRegistry springValueRegistry() {
return new SpringValueRegistry();
}
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
@ConditionalOnNonDefaultBehavior
public ConfigurationPropertiesRebinder smartConfigurationPropertiesRebinder(
ConfigurationPropertiesBeans beans) {
// If using default behavior, not use SmartConfigurationPropertiesRebinder.
// Minimize te possibility of making mistakes.
return new SmartConfigurationPropertiesRebinder(beans);
}
@Bean
public PlaceholderHelper placeholderHelper() {
return new PlaceholderHelper();
}
@Bean
public SpringValueProcessor springValueProcessor(PlaceholderHelper placeholderHelper,
SpringValueRegistry springValueRegistry, PolarisConfigProperties polarisConfigProperties) {
return new SpringValueProcessor(placeholderHelper, springValueRegistry, polarisConfigProperties);
}
@Bean
public PolarisConfigPropertyRefresher polarisReflectPropertySourceAutoRefresher(PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager, SpringValueRegistry springValueRegistry,
PlaceholderHelper placeholderHelper) {
return new PolarisRefreshAffectedContextRefresher(polarisConfigProperties, polarisPropertySourceManager,
springValueRegistry, placeholderHelper);
}
}
}

@ -17,10 +17,10 @@
*/
package com.tencent.cloud.polaris.config;
import com.tencent.cloud.polaris.config.adapter.AffectedConfigurationPropertiesRebinder;
import com.tencent.cloud.polaris.config.adapter.PolarisConfigFileLocator;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
import com.tencent.cloud.polaris.config.adapter.SmartConfigurationPropertiesRebinder;
import com.tencent.cloud.polaris.config.condition.ConditionalOnNonDefaultBehavior;
import com.tencent.cloud.polaris.config.condition.ConditionalOnReflectRefreshType;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
@ -89,11 +89,9 @@ public class PolarisConfigBootstrapAutoConfiguration {
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
@ConditionalOnNonDefaultBehavior
public ConfigurationPropertiesRebinder smartConfigurationPropertiesRebinder(
@ConditionalOnReflectRefreshType
public ConfigurationPropertiesRebinder affectedConfigurationPropertiesRebinder(
ConfigurationPropertiesBeans beans) {
// If using default behavior, not use SmartConfigurationPropertiesRebinder.
// Minimize te possibility of making mistakes.
return new SmartConfigurationPropertiesRebinder(beans);
return new AffectedConfigurationPropertiesRebinder(beans);
}
}

@ -0,0 +1,82 @@
/*
* 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.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans;
import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.CollectionUtils;
/**
* Optimize {@link ConfigurationPropertiesRebinder}, only rebuild affected beans.
*
* @author weihubeats
*/
public class AffectedConfigurationPropertiesRebinder extends ConfigurationPropertiesRebinder {
private ApplicationContext applicationContext;
private Map<String, ConfigurationPropertiesBean> propertiesBeans = new HashMap<>();
public AffectedConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) {
super(beans);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
super.setApplicationContext(applicationContext);
this.applicationContext = applicationContext;
propertiesBeans = ConfigurationPropertiesBean.getAll(applicationContext);
}
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (this.applicationContext.equals(event.getSource())) {
rebindAffectedBeans(event);
}
}
private void rebindAffectedBeans(EnvironmentChangeEvent event) {
Set<String> changedKeys = event.getKeys();
if (CollectionUtils.isEmpty(changedKeys)) {
return;
}
propertiesBeans.forEach((name, bean) -> {
changedKeys.forEach(key -> {
String propertiesPrefix = Objects.requireNonNull(AnnotationUtils.getValue(bean.getAnnotation()))
.toString();
if (key.startsWith(propertiesPrefix)) {
rebind(name);
}
});
});
}
}

@ -0,0 +1,116 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.config.adapter;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeListener;
import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.lang.NonNull;
import org.springframework.util.CollectionUtils;
/**
* 1. Listen to the Polaris server configuration publishing event 2. Write the changed
* configuration content to propertySource 3. Refresh the context through contextRefresher
*
* @author lepdou
*/
public abstract class PolarisConfigPropertyAutoRefresher
implements ApplicationListener<ApplicationReadyEvent>, PolarisConfigPropertyRefresher {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisConfigPropertyAutoRefresher.class);
private final PolarisConfigProperties polarisConfigProperties;
private final PolarisPropertySourceManager polarisPropertySourceManager;
private final AtomicBoolean registered = new AtomicBoolean(false);
public PolarisConfigPropertyAutoRefresher(
PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager) {
this.polarisConfigProperties = polarisConfigProperties;
this.polarisPropertySourceManager = polarisPropertySourceManager;
}
@Override
public void onApplicationEvent(@NonNull ApplicationReadyEvent event) {
registerPolarisConfigPublishEvent();
}
private void registerPolarisConfigPublishEvent() {
if (!polarisConfigProperties.isAutoRefresh()) {
return;
}
List<PolarisPropertySource> polarisPropertySources = polarisPropertySourceManager.getAllPropertySources();
if (CollectionUtils.isEmpty(polarisPropertySources)) {
return;
}
if (!registered.compareAndSet(false, true)) {
return;
}
// register polaris config publish event
for (PolarisPropertySource polarisPropertySource : polarisPropertySources) {
polarisPropertySource.getConfigKVFile()
.addChangeListener((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;
}
// update the attribute with @Value annotation
refreshSpringValue(changedKey);
}
// update @ConfigurationProperties beans
refreshConfigurationProperties(configKVFileChangeEvent.changedKeys());
});
}
}
}

@ -0,0 +1,44 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.config.adapter;
import java.util.Set;
/**
* PolarisConfigPropertyRefresher refresh spring value filed and configurationProperties bean
* when config exchange.
*
* @author lingxiao.wlx
*/
public interface PolarisConfigPropertyRefresher {
/**
* refresh the attribute with @Value annotation.
*
* @param changedKey changedKey
*/
void refreshSpringValue(String changedKey);
/**
* refresh @ConfigurationProperties beans.
*
* @param changeKeys changeKeys
*/
void refreshConfigurationProperties(Set<String> changeKeys);
}

@ -1,217 +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.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.enums.RefreshType;
import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper;
import com.tencent.cloud.polaris.config.spring.property.SpringValue;
import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeListener;
import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.lang.NonNull;
import org.springframework.util.CollectionUtils;
/**
* 1. Listen to the Polaris server configuration publishing event 2. Write the changed
* configuration content to propertySource 3. Refresh the context through contextRefresher
*
* @author lepdou 2022-03-28
*/
public class PolarisPropertySourceAutoRefresher
implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware, BeanFactoryAware {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisPropertySourceAutoRefresher.class);
private final PolarisConfigProperties polarisConfigProperties;
private final PolarisPropertySourceManager polarisPropertySourceManager;
private final ContextRefresher contextRefresher;
private final AtomicBoolean registered = new AtomicBoolean(false);
private final SpringValueRegistry springValueRegistry;
private final PlaceholderHelper placeholderHelper;
private ConfigurableApplicationContext context;
private TypeConverter typeConverter;
private ConfigurableBeanFactory beanFactory;
public PolarisPropertySourceAutoRefresher(
PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager,
SpringValueRegistry springValueRegistry,
PlaceholderHelper placeholderHelper,
ContextRefresher contextRefresher) {
this.polarisConfigProperties = polarisConfigProperties;
this.polarisPropertySourceManager = polarisPropertySourceManager;
this.springValueRegistry = springValueRegistry;
this.placeholderHelper = placeholderHelper;
this.contextRefresher = contextRefresher;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = (ConfigurableApplicationContext) applicationContext;
this.beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
this.typeConverter = this.beanFactory.getTypeConverter();
}
@Override
public void onApplicationEvent(@NonNull ApplicationReadyEvent event) {
registerPolarisConfigPublishEvent();
}
private void registerPolarisConfigPublishEvent() {
if (!polarisConfigProperties.isAutoRefresh()) {
return;
}
List<PolarisPropertySource> polarisPropertySources = polarisPropertySourceManager.getAllPropertySources();
if (CollectionUtils.isEmpty(polarisPropertySources)) {
return;
}
if (!registered.compareAndSet(false, true)) {
return;
}
// register polaris config publish event
for (PolarisPropertySource polarisPropertySource : polarisPropertySources) {
polarisPropertySource.getConfigKVFile()
.addChangeListener((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;
}
if (polarisConfigProperties.getRefreshType() == RefreshType.REFLECT) {
Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, changedKey);
if (targetValues == null || targetValues.isEmpty()) {
continue;
}
// update the attribute with @Value annotation
for (SpringValue val : targetValues) {
updateSpringValue(val);
}
}
}
if (polarisConfigProperties.getRefreshType() == RefreshType.REFLECT) {
// update @ConfigurationProperties beans
context.publishEvent(new EnvironmentChangeEvent(context, configKVFileChangeEvent.changedKeys()));
}
else {
contextRefresher.refresh();
}
});
}
}
private void updateSpringValue(SpringValue springValue) {
try {
Object value = resolvePropertyValue(springValue);
springValue.update(value);
LOGGER.info("Auto update polaris changed value successfully, new value: {}, {}", value,
springValue);
}
catch (Throwable ex) {
LOGGER.error("Auto update polaris changed value failed, {}", springValue.toString(), ex);
}
}
/**
* Logic transplanted from DefaultListableBeanFactory.
*
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency(org.springframework.beans.factory.config.DependencyDescriptor,
* java.lang.String, java.util.Set, org.springframework.beans.TypeConverter)
*/
private Object resolvePropertyValue(SpringValue springValue) {
// value will never be null
Object value = placeholderHelper
.resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder());
if (springValue.isJson()) {
value = parseJsonValue((String) value, springValue.getTargetType());
}
else {
value = springValue.isField() ? this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), springValue.getField()) :
this.typeConverter.convertIfNecessary(value, springValue.getTargetType(),
springValue.getMethodParameter());
}
return value;
}
private Object parseJsonValue(String json, Class<?> targetType) {
try {
return JacksonUtils.json2JavaBean(json, targetType);
}
catch (Throwable ex) {
LOGGER.error("Parsing json '{}' to type {} failed!", json, targetType, ex);
throw ex;
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
}
}

@ -0,0 +1,123 @@
/*
* 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.Collection;
import java.util.Set;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper;
import com.tencent.cloud.polaris.config.spring.property.SpringValue;
import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.lang.NonNull;
/**
* 1. The refresh of @Value properties is implemented through reflection.
* 2. Implement @ConfigurationProperties bean property refresh via EnvironmentChangeEvent,
* while rebuilding only beans with property changes.
*
* @author lingxiao.wlx
*/
public class PolarisRefreshAffectedContextRefresher extends PolarisConfigPropertyAutoRefresher implements ApplicationContextAware {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisRefreshAffectedContextRefresher.class);
private final SpringValueRegistry springValueRegistry;
private final PlaceholderHelper placeholderHelper;
private ConfigurableApplicationContext context;
private ConfigurableBeanFactory beanFactory;
private TypeConverter typeConverter;
public PolarisRefreshAffectedContextRefresher(PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager, SpringValueRegistry springValueRegistry,
PlaceholderHelper placeholderHelper) {
super(polarisConfigProperties, polarisPropertySourceManager);
this.springValueRegistry = springValueRegistry;
this.placeholderHelper = placeholderHelper;
}
@Override
public void refreshSpringValue(String changedKey) {
Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, changedKey);
if (targetValues == null || targetValues.isEmpty()) {
return;
}
// update the attribute with @Value annotation
for (SpringValue val : targetValues) {
updateSpringValue(val);
}
}
@Override
public void refreshConfigurationProperties(Set<String> changeKeys) {
context.publishEvent(new EnvironmentChangeEvent(context, changeKeys));
}
private void updateSpringValue(SpringValue springValue) {
try {
Object value = resolvePropertyValue(springValue);
springValue.update(value);
LOGGER.info("[SCT Config] Auto update polaris changed value successfully, new value: {}, {}", value,
springValue);
}
catch (Throwable ex) {
LOGGER.error("[SCT Config] Auto update polaris changed value failed, {}", springValue.toString(), ex);
}
}
/**
* Logic transplanted from DefaultListableBeanFactory.
*
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency(org.springframework.beans.factory.config.DependencyDescriptor,
* java.lang.String, java.util.Set, org.springframework.beans.TypeConverter)
*/
private Object resolvePropertyValue(SpringValue springValue) {
// value will never be null
Object value = placeholderHelper
.resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder());
value = springValue.isField() ? this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), springValue.getField()) :
this.typeConverter.convertIfNecessary(value, springValue.getTargetType(),
springValue.getMethodParameter());
return value;
}
@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
this.context = (ConfigurableApplicationContext) applicationContext;
this.beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
this.typeConverter = this.beanFactory.getTypeConverter();
}
}

@ -0,0 +1,53 @@
/*
* 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.Set;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import org.springframework.cloud.context.refresh.ContextRefresher;
/**
* The default implement of Spring Cloud refreshes the entire Spring Context.
* The disadvantage is that the entire context is rebuilt, which has a large impact and low performance.
*
* @author lingxiao.wlx
*/
public class PolarisRefreshEntireContextRefresher extends PolarisConfigPropertyAutoRefresher {
private final ContextRefresher contextRefresher;
public PolarisRefreshEntireContextRefresher(PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager,
ContextRefresher contextRefresher) {
super(polarisConfigProperties, polarisPropertySourceManager);
this.contextRefresher = contextRefresher;
}
@Override
public void refreshSpringValue(String changedKey) {
// do nothing,all config will be refreshed by contextRefresher.refresh
}
@Override
public void refreshConfigurationProperties(Set<String> changeKeys) {
contextRefresher.refresh();
}
}

@ -1,122 +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.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import com.tencent.cloud.polaris.config.enums.RefreshBehavior;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans;
import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
import static com.tencent.cloud.polaris.config.condition.NonDefaultBehaviorCondition.POLARIS_CONFIG_REFRESH_BEHAVIOR;
import static com.tencent.cloud.polaris.config.enums.RefreshBehavior.ALL_BEANS;
/**
* Extend {@link ConfigurationPropertiesRebinder}.
* <p>
* Spring team doesn't seem to support single {@link ConfigurationPropertiesBean} refresh.
* <p>
* SmartConfigurationPropertiesRebinder can refresh specific
* {@link ConfigurationPropertiesBean} base on the change keys.
* <p>
* <strong> NOTE: We still use Spring's default behavior (full refresh) as default
* behavior, This feature can be considered an advanced feature, it may not be as stable
* as the default behavior. </strong>
* <code><a href=https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/refresh/SmartConfigurationPropertiesRebinder.java>
* SmartConfigurationPropertiesRebinder</a></code>
*
* @author weihubeats 2022-7-10
*/
public class SmartConfigurationPropertiesRebinder extends ConfigurationPropertiesRebinder {
private static final String BEANS = "beans";
private Map<String, ConfigurationPropertiesBean> beanMap;
private ApplicationContext applicationContext;
private RefreshBehavior refreshBehavior;
public SmartConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) {
super(beans);
fillBeanMap(beans);
}
@SuppressWarnings("unchecked")
private void fillBeanMap(ConfigurationPropertiesBeans beans) {
this.beanMap = new HashMap<>();
Field field = ReflectionUtils.findField(beans.getClass(), BEANS);
if (field != null) {
field.setAccessible(true);
this.beanMap.putAll((Map<String, ConfigurationPropertiesBean>) Optional
.ofNullable(ReflectionUtils.getField(field, beans))
.orElse(Collections.emptyMap()));
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
super.setApplicationContext(applicationContext);
this.applicationContext = applicationContext;
this.refreshBehavior = this.applicationContext.getEnvironment().getProperty(
POLARIS_CONFIG_REFRESH_BEHAVIOR, RefreshBehavior.class,
ALL_BEANS);
}
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (this.applicationContext.equals(event.getSource())
// Backwards compatible
|| event.getKeys().equals(event.getSource())) {
switch (refreshBehavior) {
case SPECIFIC_BEAN:
rebindSpecificBean(event);
break;
default:
rebind();
break;
}
}
}
private void rebindSpecificBean(EnvironmentChangeEvent event) {
Set<String> refreshedSet = new HashSet<>();
beanMap.forEach((name, bean) -> event.getKeys().forEach(changeKey -> {
String prefix = AnnotationUtils.getValue(bean.getAnnotation()).toString();
// prevent multiple refresh one ConfigurationPropertiesBean.
if (changeKey.startsWith(prefix) && refreshedSet.add(name)) {
rebind(name);
}
}));
}
}

@ -34,7 +34,7 @@ import org.springframework.context.annotation.Conditional;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(NonDefaultBehaviorCondition.class)
public @interface ConditionalOnNonDefaultBehavior {
@Conditional(ReflectRefreshTypeCondition.class)
public @interface ConditionalOnReflectRefreshType {
}

@ -18,7 +18,7 @@
package com.tencent.cloud.polaris.config.condition;
import com.tencent.cloud.polaris.config.enums.RefreshBehavior;
import com.tencent.cloud.polaris.config.enums.RefreshType;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
@ -26,31 +26,28 @@ import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* Extend SpringBootCondition.
* The refresh type of reflect condition.
*
* @author weihubeats 2022-7-13
*/
public class NonDefaultBehaviorCondition extends SpringBootCondition {
public class ReflectRefreshTypeCondition extends SpringBootCondition {
/**
* refresh behavior config.
* the property key of refresh type.
*/
public static final String POLARIS_CONFIG_REFRESH_BEHAVIOR = "spring.cloud.polaris.config.refresh-behavior";
public static final String POLARIS_CONFIG_REFRESH_TYPE = "spring.cloud.polaris.config.refresh-type";
/**
* refresh behavior config default value.
*/
private static final RefreshBehavior DEFAULT_REFRESH_BEHAVIOR = RefreshBehavior.ALL_BEANS;
private static final RefreshType DEFAULT_REFRESH_TYPE = RefreshType.REFRESH_CONTEXT;
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
RefreshBehavior behavior = context.getEnvironment().getProperty(
POLARIS_CONFIG_REFRESH_BEHAVIOR, RefreshBehavior.class,
DEFAULT_REFRESH_BEHAVIOR);
if (DEFAULT_REFRESH_BEHAVIOR == behavior) {
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
RefreshType refreshType = context.getEnvironment()
.getProperty(POLARIS_CONFIG_REFRESH_TYPE, RefreshType.class, DEFAULT_REFRESH_TYPE);
if (refreshType == DEFAULT_REFRESH_TYPE) {
return ConditionOutcome.noMatch("no matched");
}
return ConditionOutcome.match("matched");
}
}

@ -9,6 +9,7 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.lang.NonNull;
import org.springframework.util.ReflectionUtils;
/**
@ -19,9 +20,9 @@ import org.springframework.util.ReflectionUtils;
public abstract class PolarisProcessor implements BeanPostProcessor, PriorityOrdered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
public Object postProcessBeforeInitialization(Object bean, @NonNull String beanName)
throws BeansException {
Class clazz = bean.getClass();
Class<?> clazz = bean.getClass();
for (Field field : findAllField(clazz)) {
processField(bean, beanName, field);
}
@ -32,7 +33,7 @@ public abstract class PolarisProcessor implements BeanPostProcessor, PriorityOrd
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
return bean;
}
@ -59,15 +60,15 @@ public abstract class PolarisProcessor implements BeanPostProcessor, PriorityOrd
return Ordered.LOWEST_PRECEDENCE;
}
private List<Field> findAllField(Class clazz) {
private List<Field> findAllField(Class<?> clazz) {
final List<Field> res = new LinkedList<>();
ReflectionUtils.doWithFields(clazz, field -> res.add(field));
ReflectionUtils.doWithFields(clazz, res::add);
return res;
}
private List<Method> findAllMethod(Class clazz) {
private List<Method> findAllMethod(Class<?> clazz) {
final List<Method> res = new LinkedList<>();
ReflectionUtils.doWithMethods(clazz, method -> res.add(method));
ReflectionUtils.doWithMethods(clazz, res::add);
return res;
}
}

@ -27,6 +27,7 @@ import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.lang.NonNull;
/**
* Spring value processor of field or method which has @Value and xml config placeholders.
@ -68,7 +69,7 @@ public class SpringValueProcessor extends PolarisProcessor implements BeanFactor
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
public Object postProcessBeforeInitialization(Object bean, @NonNull String beanName)
throws BeansException {
if (polarisConfigProperties.isAutoRefresh()) {
super.postProcessBeforeInitialization(bean, beanName);

@ -71,7 +71,7 @@ public class PlaceholderHelper {
if (beanFactory.getBeanExpressionResolver() == null) {
return value;
}
Scope scope = (beanDefinition != null ? beanFactory
Scope scope = (beanDefinition != null && beanDefinition.getScope() != null ? beanFactory
.getRegisteredScope(beanDefinition.getScope()) : null);
return beanFactory.getBeanExpressionResolver()
.evaluate(value, new BeanExpressionContext(beanFactory, scope));
@ -92,7 +92,7 @@ public class PlaceholderHelper {
public Set<String> extractPlaceholderKeys(String propertyString) {
Set<String> placeholderKeys = Sets.newHashSet();
if (Strings.isNullOrEmpty(propertyString) || (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString))) {
if (!isPlaceholder(propertyString)) {
return placeholderKeys;
}
@ -147,6 +147,11 @@ public class PlaceholderHelper {
return placeholderKeys;
}
private boolean isPlaceholder(String propertyString) {
return !Strings.isNullOrEmpty(propertyString) &&
(isNormalizedPlaceholder(propertyString) || isExpressionWithPlaceholder(propertyString));
}
private boolean isNormalizedPlaceholder(String propertyString) {
return propertyString.startsWith(PLACEHOLDER_PREFIX) && propertyString.contains(PLACEHOLDER_SUFFIX);
}

@ -97,7 +97,7 @@ public class SpringValue {
private void injectMethod(Object newVal)
throws InvocationTargetException, IllegalAccessException {
Object bean = beanRef.get();
if (bean == null) {
if (bean == null || methodParameter.getMethod() == null) {
return;
}
methodParameter.getMethod().invoke(bean, newVal);
@ -150,7 +150,12 @@ public class SpringValue {
.format("key: %s, beanName: %s, field: %s.%s", key, beanName, bean.getClass()
.getName(), field.getName());
}
return String.format("key: %s, beanName: %s, method: %s.%s", key, beanName, bean.getClass().getName(),
methodParameter.getMethod().getName());
if (null != methodParameter.getMethod()) {
return String.format("key: %s, beanName: %s, method: %s.%s", key, beanName, bean.getClass().getName(),
methodParameter.getMethod().getName());
}
else {
return String.format("key: %s, beanName: %s", key, beanName);
}
}
}

@ -54,12 +54,6 @@
"defaultValue": true,
"description": "Whether to preferentially load the remote configuration."
},
{
"name": "spring.cloud.polaris.config.refresh-behavior",
"type": "com.tencent.cloud.polaris.config.enums.RefreshBehavior",
"defaultValue": "all_beans",
"description": "ConfigurationPropertiesBean refresh behavior."
},
{
"name": "spring.cloud.polaris.config.refresh-type",
"type": "com.tencent.cloud.polaris.config.enums.RefreshType",

@ -26,7 +26,6 @@ import java.util.Map;
import com.google.common.collect.Lists;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.enums.RefreshType;
import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper;
import com.tencent.cloud.polaris.config.spring.property.SpringValue;
import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
@ -41,7 +40,6 @@ import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.ConfigurableApplicationContext;
import static org.mockito.ArgumentMatchers.any;
@ -49,8 +47,9 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* test for {@link PolarisPropertySourceAutoRefresher}.
*@author lepdou 2022-06-11
* test for {@link PolarisRefreshAffectedContextRefresher}.
*
* @author lepdou 2022-06-11
*/
@RunWith(MockitoJUnitRunner.class)
public class PolarisPropertiesSourceAutoRefresherTest {
@ -62,8 +61,6 @@ public class PolarisPropertiesSourceAutoRefresherTest {
private PolarisConfigProperties polarisConfigProperties;
@Mock
private PolarisPropertySourceManager polarisPropertySourceManager;
@Mock
private ContextRefresher contextRefresher;
@Mock
private SpringValueRegistry springValueRegistry;
@ -73,10 +70,8 @@ public class PolarisPropertiesSourceAutoRefresherTest {
@Test
public void testConfigFileChanged() throws Exception {
PolarisPropertySourceAutoRefresher refresher = new PolarisPropertySourceAutoRefresher(polarisConfigProperties,
polarisPropertySourceManager, springValueRegistry, placeholderHelper, contextRefresher);
when(polarisConfigProperties.getRefreshType()).thenReturn(RefreshType.REFLECT);
PolarisRefreshAffectedContextRefresher refresher = new PolarisRefreshAffectedContextRefresher(polarisConfigProperties,
polarisPropertySourceManager, springValueRegistry, placeholderHelper);
ConfigurableApplicationContext applicationContext = mock(ConfigurableApplicationContext.class);
ConfigurableListableBeanFactory beanFactory = mock(ConfigurableListableBeanFactory.class);
TypeConverter typeConverter = mock(TypeConverter.class);

@ -0,0 +1,86 @@
/*
* 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.condition;
import com.tencent.cloud.polaris.config.PolarisConfigAutoConfiguration;
import com.tencent.cloud.polaris.config.PolarisConfigBootstrapAutoConfiguration;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
import com.tencent.cloud.polaris.config.adapter.PolarisRefreshAffectedContextRefresher;
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.cloud.polaris.config.spring.annotation.SpringValueProcessor;
import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper;
import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
import org.junit.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.refresh.ContextRefresher;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link ConditionalOnReflectRefreshType}.
*
* @author lingxiao.wlx
*/
public class ConditionalOnReflectRefreshTypeTest {
@Test
public void testReflectEnabled() {
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=" + "conditionalOnConfigReflectEnabledTest")
.withPropertyValues("server.port=" + 8080)
.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");
contextRunner.run(context -> {
assertThat(context).hasSingleBean(PlaceholderHelper.class);
assertThat(context).hasSingleBean(SpringValueRegistry.class);
assertThat(context).hasSingleBean(SpringValueProcessor.class);
assertThat(context).hasSingleBean(PolarisRefreshAffectedContextRefresher.class);
});
}
@Test
public void testWithoutReflectEnabled() {
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=" + "conditionalOnConfigReflectEnabledTest")
.withPropertyValues("server.port=" + 8080)
.withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081")
.withPropertyValues("spring.cloud.polaris.config.enabled=true");
contextRunner.run(context -> {
assertThat(context).hasSingleBean(PolarisConfigProperties.class);
assertThat(context).hasSingleBean(PolarisPropertySourceManager.class);
assertThat(context).hasSingleBean(ContextRefresher.class);
assertThat(context).hasSingleBean(PolarisRefreshEntireContextRefresher.class);
});
}
}

@ -0,0 +1,153 @@
/*
* Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.config.spring.annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import com.tencent.cloud.polaris.config.PolarisConfigBootstrapAutoConfiguration;
import com.tencent.cloud.polaris.config.enums.RefreshType;
import com.tencent.cloud.polaris.config.spring.property.SpringValue;
import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
import com.tencent.polaris.api.utils.CollectionUtils;
import org.junit.Assert;
import org.junit.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.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* Test for {@link SpringValueProcessor}.
*
* @author lingxiao.wlx
*/
public class SpringValueProcessorTest {
@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(PolarisConfigAutoConfiguration.class))
.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);
PolarisConfigAutoConfiguration polarisConfigAutoConfiguration = context.getBean(PolarisConfigAutoConfiguration.class);
BeanFactory beanFactory = polarisConfigAutoConfiguration.beanFactory;
Collection<SpringValue> timeout = springValueRegistry.get(beanFactory, "timeout");
Assert.assertFalse(CollectionUtils.isEmpty(timeout));
Optional<SpringValue> springValueOptional = timeout.stream().findAny();
Assert.assertTrue(springValueOptional.isPresent());
SpringValue springValue = springValueOptional.get();
Assert.assertEquals("${timeout:1000}", springValue.getPlaceholder());
Assert.assertTrue(springValue.isField());
Assert.assertTrue(Objects.nonNull(springValue.getField()));
Assert.assertEquals("timeout", springValue.getField().getName());
Assert.assertEquals(int.class, springValue.getTargetType());
ValueTest bean = context.getBean(ValueTest.class);
Assert.assertEquals(10000, bean.timeout);
});
}
@Test
public void springValueMethodProcessorTest() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(PolarisConfigBootstrapAutoConfiguration.class))
.withConfiguration(AutoConfigurations.of(RefreshAutoConfiguration.class))
.withConfiguration(AutoConfigurations.of(ValueTest.class))
.withConfiguration(AutoConfigurations.of(PolarisConfigAutoConfiguration.class))
.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("name=test");
contextRunner.run(context -> {
SpringValueRegistry springValueRegistry = context.getBean(SpringValueRegistry.class);
PolarisConfigAutoConfiguration polarisConfigAutoConfiguration = context.getBean(PolarisConfigAutoConfiguration.class);
BeanFactory beanFactory = polarisConfigAutoConfiguration.beanFactory;
Collection<SpringValue> name = springValueRegistry.get(beanFactory, "name");
Assert.assertFalse(CollectionUtils.isEmpty(name));
Optional<SpringValue> springValueOptional = name.stream().findAny();
Assert.assertTrue(springValueOptional.isPresent());
SpringValue springValue = springValueOptional.get();
Method method = springValue.getMethodParameter().getMethod();
Assert.assertTrue(Objects.nonNull(method));
Assert.assertEquals("setName", method.getName());
Assert.assertEquals("${name:1000}", springValue.getPlaceholder());
Assert.assertFalse(springValue.isField());
Assert.assertEquals(String.class, springValue.getTargetType());
Assert.assertEquals("test", ValueTest.name);
});
}
@Configuration
@EnableAutoConfiguration
static class PolarisConfigAutoConfiguration {
@Autowired
private BeanFactory beanFactory;
public BeanFactory getBeanFactory() {
return beanFactory;
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
}
@Component
private static class ValueTest {
@Value("${timeout:1000}")
private int timeout;
private static String name;
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
@Value("${name:1000}")
public void setName(String name) {
ValueTest.name = name;
}
}
}

@ -0,0 +1,67 @@
/*
* 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.property;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
/**
* Test example.
*
* @author lingxiao.wlx
*/
public class Person implements BeanFactoryAware {
private String name;
private String age;
private BeanFactory beanFactory;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public BeanFactory getBeanFactory() {
return beanFactory;
}
@Override
public String toString() {
return "User{" + "name='" + name + '\'' + ", age=" + age + '}';
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}

@ -0,0 +1,88 @@
/*
* 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.property;
import java.util.Set;
import com.tencent.polaris.api.utils.CollectionUtils;
import org.junit.Assert;
import org.junit.Test;
/**
* Test for {@link PlaceholderHelper}.
*
* @author lingxiao.wlx
*/
public class PlaceholderHelperTest {
private static final PlaceholderHelper PLACEHOLDER_HELPER = new PlaceholderHelper();
@Test
public void extractNormalPlaceholderKeysTest() {
final String placeholderCase = "${some.key}";
final String placeholderCase1 = "${some.key:${some.other.key:100}}";
final String placeholderCase2 = "${${some.key}}";
final String placeholderCase3 = "${${some.key:other.key}}";
final String placeholderCase4 = "${${some.key}:${another.key}}";
final String placeholderCase5 = "#{new java.text.SimpleDateFormat('${some.key}').parse('${another.key}')}";
Set<String> placeholderKeys = PLACEHOLDER_HELPER.extractPlaceholderKeys(placeholderCase);
Assert.assertEquals(1, placeholderKeys.size());
Assert.assertTrue(placeholderKeys.contains("some.key"));
Set<String> placeholderKeys1 = PLACEHOLDER_HELPER.extractPlaceholderKeys(placeholderCase1);
Assert.assertEquals(2, placeholderKeys1.size());
Assert.assertTrue(placeholderKeys1.contains("some.key"));
Assert.assertTrue(placeholderKeys1.contains("some.other.key"));
Set<String> placeholderKeys2 = PLACEHOLDER_HELPER.extractPlaceholderKeys(placeholderCase2);
Assert.assertEquals(1, placeholderKeys2.size());
Assert.assertTrue(placeholderKeys2.contains("some.key"));
Set<String> placeholderKeys3 = PLACEHOLDER_HELPER.extractPlaceholderKeys(placeholderCase3);
Assert.assertEquals(1, placeholderKeys3.size());
Assert.assertTrue(placeholderKeys3.contains("some.key"));
Set<String> placeholderKeys4 = PLACEHOLDER_HELPER.extractPlaceholderKeys(placeholderCase4);
Assert.assertEquals(2, placeholderKeys4.size());
Assert.assertTrue(placeholderKeys4.contains("some.key"));
Assert.assertTrue(placeholderKeys4.contains("another.key"));
Set<String> placeholderKeys5 = PLACEHOLDER_HELPER.extractPlaceholderKeys(placeholderCase5);
Assert.assertEquals(2, placeholderKeys5.size());
Assert.assertTrue(placeholderKeys5.contains("some.key"));
Assert.assertTrue(placeholderKeys5.contains("another.key"));
}
@Test
public void extractIllegalPlaceholderKeysTest() {
final String placeholderCase = "${some.key";
final String placeholderCase1 = "{some.key}";
final String placeholderCase2 = "some.key";
Set<String> placeholderKeys = PLACEHOLDER_HELPER.extractPlaceholderKeys(placeholderCase);
Assert.assertTrue(CollectionUtils.isEmpty(placeholderKeys));
Set<String> placeholderKeys1 = PLACEHOLDER_HELPER.extractPlaceholderKeys(placeholderCase1);
Assert.assertTrue(CollectionUtils.isEmpty(placeholderKeys1));
Set<String> placeholderKeys2 = PLACEHOLDER_HELPER.extractPlaceholderKeys(placeholderCase2);
Assert.assertTrue(CollectionUtils.isEmpty(placeholderKeys2));
}
}

@ -0,0 +1,75 @@
/*
* 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.property;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import com.tencent.polaris.api.utils.CollectionUtils;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Test for {@link SpringValueDefinitionProcessor}.
*
* @author lingxiao.wlx
*/
public class SpringValueDefinitionProcessorTest {
@Test
public void springValueDefinitionProcessorTest() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Person person = context.getBean(Person.class);
SpringValueRegistry springValueRegistry = context.getBean(SpringValueRegistry.class);
BeanFactory beanFactory = person.getBeanFactory();
Collection<SpringValue> name = springValueRegistry.get(beanFactory, "name");
Assert.assertFalse(CollectionUtils.isEmpty(name));
Optional<SpringValue> nameSpringValueOptional = name.stream().findAny();
Assert.assertTrue(nameSpringValueOptional.isPresent());
SpringValue nameSpringValue = nameSpringValueOptional.get();
Method method = nameSpringValue.getMethodParameter().getMethod();
Assert.assertTrue(Objects.nonNull(method));
Assert.assertEquals("setName", method.getName());
Assert.assertEquals("${name:test}", nameSpringValue.getPlaceholder());
Assert.assertFalse(nameSpringValue.isField());
Assert.assertEquals(String.class, nameSpringValue.getTargetType());
Collection<SpringValue> age = springValueRegistry.get(beanFactory, "age");
Assert.assertFalse(CollectionUtils.isEmpty(age));
Optional<SpringValue> ageSpringValueOptional = age.stream().findAny();
Assert.assertTrue(ageSpringValueOptional.isPresent());
SpringValue ageSpringValue = ageSpringValueOptional.get();
Method method1 = ageSpringValue.getMethodParameter().getMethod();
Assert.assertTrue(Objects.nonNull(method1));
Assert.assertEquals("setAge", method1.getName());
Assert.assertEquals("${age:10}", ageSpringValue.getPlaceholder());
Assert.assertFalse(ageSpringValue.isField());
Assert.assertEquals(String.class, ageSpringValue.getTargetType());
}
}

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:application-test.properties"/>
<!--Person-->
<bean class="com.tencent.cloud.polaris.config.spring.property.Person">
<property name="name" value="${name:test}"/>
<property name="age" value="${age:10}"/>
</bean>
<!--SpringValueDefinitionProcessor to process xml config placeholders -->
<bean class="com.tencent.cloud.polaris.config.spring.property.SpringValueDefinitionProcessor">
<constructor-arg index="0" ref="helper"/>
<constructor-arg index="1" ref="polarisConfigProperties"/>
</bean>
<!--Placeholder helper functions -->
<bean id="helper" class="com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper"/>
<!--PolarisConfigProperties -->
<bean id="polarisConfigProperties" class="com.tencent.cloud.polaris.config.config.PolarisConfigProperties">
<property name="autoRefresh" value="true"/>
</bean>
<!--springValueRegistry -->
<bean id="springValueRegistry" class="com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry"/>
<!--Spring value processor of method -->
<bean class="com.tencent.cloud.polaris.config.spring.annotation.SpringValueProcessor">
<constructor-arg index="0" ref="helper"/>
<constructor-arg index="1" ref="springValueRegistry"/>
<constructor-arg index="2" ref="polarisConfigProperties"/>
</bean>
</beans>

@ -12,8 +12,7 @@ spring:
groups:
- name: ${spring.application.name} # group name
files: [ "config/application.properties", "config/bootstrap.yml" ] # config/application.properties takes precedence over config/bootstrap.yml
refresh-behavior: SPECIFIC_BEAN
refresh-type: REFRESH_CONTEXT
refresh-type: refresh_context
management:
endpoints:
web:

Loading…
Cancel
Save