diff --git a/CHANGELOG.md b/CHANGELOG.md
index 819415784..6c37a97df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,7 @@
- [feat:Add GitHub action of codecov.yml.](https://github.com/Tencent/spring-cloud-tencent/pull/328)
- [Feature: add spring cloud tencent logo](https://github.com/Tencent/spring-cloud-tencent/pull/329)
- [Feature: Optimize static metadata manager](https://github.com/Tencent/spring-cloud-tencent/pull/327)
+- [Feature: optimization refreshScope](https://github.com/Tencent/spring-cloud-tencent/pull/326)
- [Feature: support actuator for sct core components](https://github.com/Tencent/spring-cloud-tencent/pull/343)
- [test:update junit of metadata.](https://github.com/Tencent/spring-cloud-tencent/pull/340)
- [Optimize code style & unit test case](https://github.com/Tencent/spring-cloud-tencent/pull/336)
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java
index fb70489c7..e0fe81024 100644
--- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java
@@ -23,10 +23,13 @@ import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
import com.tencent.cloud.polaris.config.annotation.PolarisConfigAnnotationProcessor;
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;
+import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper;
+import com.tencent.cloud.polaris.config.spring.property.SpringValueDefinitionProcessor;
+import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -44,9 +47,10 @@ public class PolarisConfigAutoConfiguration {
public PolarisPropertySourceAutoRefresher polarisPropertySourceAutoRefresher(
PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager,
- ContextRefresher contextRefresher) {
+ SpringValueRegistry springValueRegistry,
+ PlaceholderHelper placeholderHelper) {
return new PolarisPropertySourceAutoRefresher(polarisConfigProperties,
- polarisPropertySourceManager, contextRefresher);
+ polarisPropertySourceManager, springValueRegistry, placeholderHelper);
}
@Bean
@@ -58,4 +62,25 @@ public class PolarisConfigAutoConfiguration {
public PolarisConfigChangeEventListener polarisConfigChangeEventListener() {
return new PolarisConfigChangeEventListener();
}
+
+ @Bean
+ public SpringValueRegistry springValueRegistry() {
+ return new SpringValueRegistry();
+ }
+
+ @Bean
+ public SpringValueProcessor springValueProcessor(PlaceholderHelper placeholderHelper, SpringValueRegistry springValueRegistry, PolarisConfigProperties polarisConfigProperties) {
+ return new SpringValueProcessor(placeholderHelper, springValueRegistry, polarisConfigProperties);
+ }
+
+ @Bean
+ public PlaceholderHelper placeholderHelper() {
+ return new PlaceholderHelper();
+ }
+
+ @Bean
+ public SpringValueDefinitionProcessor springValueDefinitionProcessor(PlaceholderHelper placeholderHelper, PolarisConfigProperties polarisConfigProperties) {
+ return new SpringValueDefinitionProcessor(placeholderHelper, polarisConfigProperties);
+ }
+
}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceAutoRefresher.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceAutoRefresher.java
index 25af8ff32..bbd86d030 100644
--- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceAutoRefresher.java
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceAutoRefresher.java
@@ -18,27 +18,36 @@
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.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.config.ConfigurableBeanFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
-import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
+import org.springframework.context.ConfigurableApplicationContext;
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
+ *
Refer to the Apollo project implementation:
+ *
+ * AutoUpdateConfigChangeListener
+ * @author zhangzheng
*
* @author lepdou 2022-03-28
*/
@@ -46,26 +55,30 @@ public class PolarisPropertySourceAutoRefresher
implements ApplicationListener, ApplicationContextAware {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisPropertySourceAutoRefresher.class);
-
private final PolarisConfigProperties polarisConfigProperties;
-
private final PolarisPropertySourceManager polarisPropertySourceManager;
- private final ContextRefresher contextRefresher;
+ private TypeConverter typeConverter;
+ private final SpringValueRegistry springValueRegistry;
+ private ConfigurableBeanFactory beanFactory;
+ private final PlaceholderHelper placeholderHelper;
+
private final AtomicBoolean registered = new AtomicBoolean(false);
- private ApplicationContext applicationContext;
public PolarisPropertySourceAutoRefresher(
PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager,
- ContextRefresher contextRefresher) {
+ SpringValueRegistry springValueRegistry,
+ PlaceholderHelper placeholderHelper) {
this.polarisConfigProperties = polarisConfigProperties;
this.polarisPropertySourceManager = polarisPropertySourceManager;
- this.contextRefresher = contextRefresher;
+ this.springValueRegistry = springValueRegistry;
+ this.placeholderHelper = placeholderHelper;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- this.applicationContext = applicationContext;
+ this.beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
+ this.typeConverter = this.beanFactory.getTypeConverter();
}
@Override
@@ -98,28 +111,66 @@ public class PolarisPropertySourceAutoRefresher
polarisPropertySource.getGroup(),
polarisPropertySource.getFileName());
- Map source = polarisPropertySource.getSource();
-
for (String changedKey : configKVFileChangeEvent.changedKeys()) {
- ConfigPropertyChangeInfo configPropertyChangeInfo = configKVFileChangeEvent
- .getChangeInfo(changedKey);
-
- LOGGER.info("[SCT Config] changed property = {}", configPropertyChangeInfo);
-
- switch (configPropertyChangeInfo.getChangeType()) {
- case MODIFIED:
- case ADDED:
- source.put(changedKey, configPropertyChangeInfo.getNewValue());
- break;
- case DELETED:
- source.remove(changedKey);
- break;
+ // 1. check whether the changed key is relevant
+ Collection targetValues = springValueRegistry.get(beanFactory, changedKey);
+ if (targetValues == null || targetValues.isEmpty()) {
+ continue;
+ }
+ // 2. update the value
+ for (SpringValue val : targetValues) {
+ updateSpringValue(val);
}
}
- // rebuild beans with @RefreshScope annotation
- 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;
+ }
+ }
}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/exceptions/PolarisConfigException.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/exceptions/PolarisConfigException.java
new file mode 100644
index 000000000..10312a57f
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/exceptions/PolarisConfigException.java
@@ -0,0 +1,34 @@
+/*
+ * 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.exceptions;
+
+/**
+ *@author : wh
+ *@date : 2022/6/28 09:31
+ *@description:
+ */
+public class PolarisConfigException extends RuntimeException {
+ public PolarisConfigException(String message) {
+ super(message);
+ }
+
+ public PolarisConfigException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/AbstractPolarisProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/AbstractPolarisProcessor.java
new file mode 100644
index 000000000..ac81f6b96
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/AbstractPolarisProcessor.java
@@ -0,0 +1,102 @@
+/*
+ * 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.Field;
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+import java.util.List;
+
+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.util.ReflectionUtils;
+
+/**
+ *@author : wh
+ *@date : 2022/6/28 09:18
+ *@description:
+ */
+public abstract class AbstractPolarisProcessor implements BeanPostProcessor, PriorityOrdered {
+
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName)
+ throws BeansException {
+ Class clazz = bean.getClass();
+ for (Field field : findAllField(clazz)) {
+ processField(bean, beanName, field);
+ }
+ for (Method method : findAllMethod(clazz)) {
+ processMethod(bean, beanName, method);
+ }
+ return bean;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+ return bean;
+ }
+
+
+ /**
+ * subclass should implement this method to process field.
+ * @param bean bean
+ * @param beanName beanName
+ * @param field field
+ */
+ protected abstract void processField(Object bean, String beanName, Field field);
+
+ /**
+ * subclass should implement this method to process method.
+ * @param bean bean
+ * @param beanName beanName
+ * @param method method
+ */
+ protected abstract void processMethod(Object bean, String beanName, Method method);
+
+
+ @Override
+ public int getOrder() {
+ //make it as late as possible
+ return Ordered.LOWEST_PRECEDENCE;
+ }
+
+ private List findAllField(Class clazz) {
+ final List res = new LinkedList<>();
+ ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() {
+ @Override
+ public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
+ res.add(field);
+ }
+ });
+ return res;
+ }
+
+ private List findAllMethod(Class clazz) {
+ final List res = new LinkedList<>();
+ ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
+ @Override
+ public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
+ res.add(method);
+ }
+ });
+ return res;
+ }
+}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java
new file mode 100644
index 000000000..c17a21efc
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java
@@ -0,0 +1,166 @@
+package com.tencent.cloud.polaris.config.spring.annotation;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Set;
+
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+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.SpringValueDefinition;
+import com.tencent.cloud.polaris.config.spring.property.SpringValueDefinitionProcessor;
+import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.annotation.Value;
+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;
+
+/**
+ *@author : wh
+ *@date : 2022/6/28 09:19
+ *@description:
+ */
+public class SpringValueProcessor extends AbstractPolarisProcessor implements BeanFactoryPostProcessor, BeanFactoryAware {
+
+ private static final Logger logger = LoggerFactory.getLogger(SpringValueProcessor.class);
+
+ private final PlaceholderHelper placeholderHelper;
+ private final SpringValueRegistry springValueRegistry;
+
+ private final PolarisConfigProperties polarisConfigProperties;
+
+ private BeanFactory beanFactory;
+ private Multimap beanName2SpringValueDefinitions;
+
+ public SpringValueProcessor(PlaceholderHelper placeholderHelper, SpringValueRegistry springValueRegistry, PolarisConfigProperties polarisConfigProperties) {
+ this.placeholderHelper = placeholderHelper;
+ this.springValueRegistry = springValueRegistry;
+ beanName2SpringValueDefinitions = LinkedListMultimap.create();
+ this.polarisConfigProperties = polarisConfigProperties;
+ }
+
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
+ throws BeansException {
+ if (polarisConfigProperties.isAutoRefresh() && beanFactory instanceof BeanDefinitionRegistry) {
+ beanName2SpringValueDefinitions = SpringValueDefinitionProcessor
+ .getBeanName2SpringValueDefinitions((BeanDefinitionRegistry) beanFactory);
+ }
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName)
+ throws BeansException {
+ if (polarisConfigProperties.isAutoRefresh()) {
+ super.postProcessBeforeInitialization(bean, beanName);
+ processBeanPropertyValues(bean, beanName);
+ }
+ return bean;
+ }
+
+
+ @Override
+ protected void processField(Object bean, String beanName, Field field) {
+ // register @Value on field
+ Value value = field.getAnnotation(Value.class);
+ if (value == null) {
+ return;
+ }
+
+ doRegister(bean, beanName, field, value);
+ }
+
+ @Override
+ protected void processMethod(Object bean, String beanName, Method method) {
+ //register @Value on method
+ Value value = method.getAnnotation(Value.class);
+ if (value == null) {
+ return;
+ }
+ //skip Configuration bean methods
+ if (method.getAnnotation(Bean.class) != null) {
+ return;
+ }
+ if (method.getParameterTypes().length != 1) {
+ logger.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters",
+ bean.getClass().getName(), method.getName(), method.getParameterTypes().length);
+ return;
+ }
+
+ doRegister(bean, beanName, method, value);
+ }
+
+ private void doRegister(Object bean, String beanName, Member member, Value value) {
+ Set keys = placeholderHelper.extractPlaceholderKeys(value.value());
+ if (keys.isEmpty()) {
+ return;
+ }
+
+ for (String key : keys) {
+ SpringValue springValue;
+ if (member instanceof Field) {
+ Field field = (Field) member;
+ springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
+ }
+ else if (member instanceof Method) {
+ Method method = (Method) member;
+ springValue = new SpringValue(key, value.value(), bean, beanName, method, false);
+ }
+ else {
+ logger.error("polaris @Value annotation currently only support to be used on methods and fields, "
+ + "but is used on {}", member.getClass());
+ return;
+ }
+ springValueRegistry.register(beanFactory, key, springValue);
+ logger.info("Monitoring {}", springValue);
+ }
+ }
+
+ private void processBeanPropertyValues(Object bean, String beanName) {
+ Collection propertySpringValues = beanName2SpringValueDefinitions
+ .get(beanName);
+ if (propertySpringValues == null || propertySpringValues.isEmpty()) {
+ return;
+ }
+
+ for (SpringValueDefinition definition : propertySpringValues) {
+ try {
+ PropertyDescriptor pd = BeanUtils
+ .getPropertyDescriptor(bean.getClass(), definition.getPropertyName());
+ Method method = pd.getWriteMethod();
+ if (method == null) {
+ continue;
+ }
+ SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(),
+ bean, beanName, method, false);
+ springValueRegistry.register(beanFactory, definition.getKey(), springValue);
+ logger.debug("Monitoring {}", springValue);
+ }
+ catch (Throwable ex) {
+ logger.error("Failed to enable auto update feature for {}.{}", bean.getClass(),
+ definition.getPropertyName());
+ }
+ }
+
+ // clear
+ beanName2SpringValueDefinitions.removeAll(beanName);
+ }
+
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+ this.beanFactory = beanFactory;
+ }
+}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/PlaceholderHelper.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/PlaceholderHelper.java
new file mode 100644
index 000000000..a272d2bfe
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/PlaceholderHelper.java
@@ -0,0 +1,193 @@
+/*
+ * 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 java.util.Stack;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanExpressionContext;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.beans.factory.config.Scope;
+import org.springframework.util.StringUtils;
+
+/**
+ *@author : wh
+ *@date : 2022/6/28 09:24
+ *@description:
+ */
+public class PlaceholderHelper {
+
+ private static final String PLACEHOLDER_PREFIX = "${";
+ private static final String PLACEHOLDER_SUFFIX = "}";
+ private static final String VALUE_SEPARATOR = ":";
+ private static final String SIMPLE_PLACEHOLDER_PREFIX = "{";
+ private static final String EXPRESSION_PREFIX = "#{";
+ private static final String EXPRESSION_SUFFIX = "}";
+
+
+ /**
+ * Resolve placeholder property values, e.g.
+ * @param beanFactory beanFactory
+ * @param beanName beanName
+ * @param placeholder placeholder
+ * @return "${somePropertyValue}" -> "the actual property value"
+ */
+ public Object resolvePropertyValue(ConfigurableBeanFactory beanFactory, String beanName, String placeholder) {
+ // resolve string value
+ String strVal = beanFactory.resolveEmbeddedValue(placeholder);
+
+ BeanDefinition bd = (beanFactory.containsBean(beanName) ? beanFactory
+ .getMergedBeanDefinition(beanName) : null);
+
+ // resolve expressions like "#{systemProperties.myProp}"
+ return evaluateBeanDefinitionString(beanFactory, strVal, bd);
+ }
+
+ private Object evaluateBeanDefinitionString(ConfigurableBeanFactory beanFactory, String value,
+ BeanDefinition beanDefinition) {
+ if (beanFactory.getBeanExpressionResolver() == null) {
+ return value;
+ }
+ Scope scope = (beanDefinition != null ? beanFactory
+ .getRegisteredScope(beanDefinition.getScope()) : null);
+ return beanFactory.getBeanExpressionResolver()
+ .evaluate(value, new BeanExpressionContext(beanFactory, scope));
+ }
+
+ /**
+ * Extract keys from placeholder, e.g.
+ * @param propertyString propertyString
+ * @return
+ *
+ * - ${some.key} => "some.key"
+ * - ${some.key:${some.other.key:100}} => "some.key", "some.other.key"
+ * - ${${some.key}} => "some.key"
+ * - ${${some.key:other.key}} => "some.key"
+ * - ${${some.key}:${another.key}} => "some.key", "another.key"
+ * - #{new java.text.SimpleDateFormat('${some.key}').parse('${another.key}')} => "some.key", "another.key"
+ *
+ */
+ public Set extractPlaceholderKeys(String propertyString) {
+ Set placeholderKeys = Sets.newHashSet();
+
+ if (Strings.isNullOrEmpty(propertyString) || (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString))) {
+ return placeholderKeys;
+ }
+
+ Stack stack = new Stack<>();
+ stack.push(propertyString);
+
+ while (!stack.isEmpty()) {
+ String strVal = stack.pop();
+ int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
+ if (startIndex == -1) {
+ placeholderKeys.add(strVal);
+ continue;
+ }
+ int endIndex = findPlaceholderEndIndex(strVal, startIndex);
+ if (endIndex == -1) {
+ // invalid placeholder?
+ continue;
+ }
+
+ String placeholderCandidate = strVal.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
+
+ // ${some.key:other.key}
+ if (placeholderCandidate.startsWith(PLACEHOLDER_PREFIX)) {
+ stack.push(placeholderCandidate);
+ }
+ else {
+ // some.key:${some.other.key:100}
+ int separatorIndex = placeholderCandidate.indexOf(VALUE_SEPARATOR);
+
+ if (separatorIndex == -1) {
+ stack.push(placeholderCandidate);
+ }
+ else {
+ stack.push(placeholderCandidate.substring(0, separatorIndex));
+ String defaultValuePart =
+ normalizeToPlaceholder(placeholderCandidate.substring(separatorIndex + VALUE_SEPARATOR.length()));
+ if (!Strings.isNullOrEmpty(defaultValuePart)) {
+ stack.push(defaultValuePart);
+ }
+ }
+ }
+
+ // has remaining part, e.g. ${a}.${b}
+ if (endIndex + PLACEHOLDER_SUFFIX.length() < strVal.length() - 1) {
+ String remainingPart = normalizeToPlaceholder(strVal.substring(endIndex + PLACEHOLDER_SUFFIX.length()));
+ if (!Strings.isNullOrEmpty(remainingPart)) {
+ stack.push(remainingPart);
+ }
+ }
+ }
+
+ return placeholderKeys;
+ }
+
+ private boolean isNormalizedPlaceholder(String propertyString) {
+ return propertyString.startsWith(PLACEHOLDER_PREFIX) && propertyString.contains(PLACEHOLDER_SUFFIX);
+ }
+
+ private boolean isExpressionWithPlaceholder(String propertyString) {
+ return propertyString.startsWith(EXPRESSION_PREFIX) && propertyString.contains(EXPRESSION_SUFFIX)
+ && propertyString.contains(PLACEHOLDER_PREFIX) && propertyString.contains(PLACEHOLDER_SUFFIX);
+ }
+
+ private String normalizeToPlaceholder(String strVal) {
+ int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
+ if (startIndex == -1) {
+ return null;
+ }
+ int endIndex = strVal.lastIndexOf(PLACEHOLDER_SUFFIX);
+ if (endIndex == -1) {
+ return null;
+ }
+
+ return strVal.substring(startIndex, endIndex + PLACEHOLDER_SUFFIX.length());
+ }
+
+ private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
+ int index = startIndex + PLACEHOLDER_PREFIX.length();
+ int withinNestedPlaceholder = 0;
+ while (index < buf.length()) {
+ if (StringUtils.substringMatch(buf, index, PLACEHOLDER_SUFFIX)) {
+ if (withinNestedPlaceholder > 0) {
+ withinNestedPlaceholder--;
+ index = index + PLACEHOLDER_SUFFIX.length();
+ }
+ else {
+ return index;
+ }
+ }
+ else if (StringUtils.substringMatch(buf, index, SIMPLE_PLACEHOLDER_PREFIX)) {
+ withinNestedPlaceholder++;
+ index = index + SIMPLE_PLACEHOLDER_PREFIX.length();
+ }
+ else {
+ index++;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValue.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValue.java
new file mode 100644
index 000000000..05eb38622
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValue.java
@@ -0,0 +1,152 @@
+/*
+ * 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.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+
+import org.springframework.core.MethodParameter;
+
+/**
+ *@author : wh
+ *@date : 2022/6/28 09:25
+ *@description:
+ */
+public class SpringValue {
+
+ private MethodParameter methodParameter;
+ private Field field;
+ private final WeakReference