diff --git a/spring-cloud-starter-tencent-polaris-config/pom.xml b/spring-cloud-starter-tencent-polaris-config/pom.xml
index a7f0e5894..b2ebf9640 100644
--- a/spring-cloud-starter-tencent-polaris-config/pom.xml
+++ b/spring-cloud-starter-tencent-polaris-config/pom.xml
@@ -13,6 +13,10 @@
spring-cloud-starter-tencent-polaris-config
Spring Cloud Starter Tencent Polaris Config
+
+ 5.1.0
+
+
@@ -58,6 +62,12 @@
+
+ com.google.inject
+ guice
+ ${com.google.inject.version}
+
+
org.springframework.cloud
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 cccea11bc..13244af84 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,6 +23,8 @@ 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.SpringValueDefinitionProcessor;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -60,4 +62,14 @@ public class PolarisConfigAutoConfiguration {
return new PolarisConfigChangeEventListener();
}
+ @Bean
+ public SpringValueProcessor springValueProcessor() {
+ return new SpringValueProcessor();
+ }
+
+ @Bean
+ public SpringValueDefinitionProcessor springValueDefinitionProcessor() {
+ return new SpringValueDefinitionProcessor();
+ }
+
}
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 fac3ba255..415529385 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,23 +18,32 @@
package com.tencent.cloud.polaris.config.adapter;
+import java.lang.reflect.Field;
+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.cloud.polaris.config.util.SpringInjector;
import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeEvent;
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;
/**
@@ -53,8 +62,18 @@ public class PolarisPropertySourceAutoRefresher
private final PolarisPropertySourceManager polarisPropertySourceManager;
+ private final boolean typeConverterHasConvertIfNecessaryWithFieldParameter;
+
+ private TypeConverter typeConverter;
+
private ApplicationContext applicationContext;
+ private final SpringValueRegistry springValueRegistry;
+
+ private ConfigurableBeanFactory beanFactory;
+
+ private final PlaceholderHelper placeholderHelper;
+
private final ContextRefresher contextRefresher;
private final AtomicBoolean registered = new AtomicBoolean(false);
@@ -66,12 +85,19 @@ public class PolarisPropertySourceAutoRefresher
this.polarisConfigProperties = polarisConfigProperties;
this.polarisPropertySourceManager = polarisPropertySourceManager;
this.contextRefresher = contextRefresher;
+ this.springValueRegistry = SpringInjector.getInstance(SpringValueRegistry.class);
+ this.placeholderHelper = SpringInjector.getInstance(PlaceholderHelper.class);
+ this.typeConverterHasConvertIfNecessaryWithFieldParameter = testTypeConverterHasConvertIfNecessaryWithFieldParameter();
+
+
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
+ this.beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
+ this.typeConverter = this.beanFactory.getTypeConverter();
}
@Override
@@ -110,32 +136,87 @@ public class PolarisPropertySourceAutoRefresher
Map source = polarisPropertySource
.getSource();
+
+ for (String changedKey : configKVFileChangeEvent.changedKeys()) {
- 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, as @Value and @ApolloJsonValue will not allow that
+ Object value = placeholderHelper
+ .resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder());
+
+ if (springValue.isJson()) {
+ value = parseJsonValue((String) value, springValue.getTargetType());
+ } else {
+ if (springValue.isField()) {
+ // org.springframework.beans.TypeConverter#convertIfNecessary(java.lang.Object, java.lang.Class, java.lang.reflect.Field) is available from Spring 3.2.0+
+ if (typeConverterHasConvertIfNecessaryWithFieldParameter) {
+ value = this.typeConverter
+ .convertIfNecessary(value, springValue.getTargetType(), springValue.getField());
+ } else {
+ value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType());
+ }
+ } else {
+ value = 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;
+ }
+ }
+
+ private boolean testTypeConverterHasConvertIfNecessaryWithFieldParameter() {
+ try {
+ TypeConverter.class.getMethod("convertIfNecessary", Object.class, Class.class, Field.class);
+ } catch (Throwable ex) {
+ return false;
+ }
+ return true;
+ }
+
}
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..da3429b7c
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/AbstractPolarisProcessor.java
@@ -0,0 +1,95 @@
+/*
+ * 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
+ */
+ protected abstract void processField(Object bean, String beanName, Field field);
+
+ /**
+ * subclass should implement this method to process 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..1428280a9
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java
@@ -0,0 +1,160 @@
+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.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 com.tencent.cloud.polaris.config.util.SpringInjector;
+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 BeanFactory beanFactory;
+ private Multimap beanName2SpringValueDefinitions;
+
+ public SpringValueProcessor() {
+ placeholderHelper = SpringInjector.getInstance(PlaceholderHelper.class);
+ springValueRegistry = SpringInjector.getInstance(SpringValueRegistry.class);
+ beanName2SpringValueDefinitions = LinkedListMultimap.create();
+ }
+
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
+ throws BeansException {
+ // 默认开启
+ if (beanFactory instanceof BeanDefinitionRegistry) {
+ beanName2SpringValueDefinitions = SpringValueDefinitionProcessor
+ .getBeanName2SpringValueDefinitions((BeanDefinitionRegistry) beanFactory);
+ }
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName)
+ throws BeansException {
+ // 默认开启
+ 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..3d1eb37dc
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/PlaceholderHelper.java
@@ -0,0 +1,184 @@
+/*
+ * 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.
+ *
+ *
+ * "${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.
+ *
+ * - ${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..6e0f3bec9
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValue.java
@@ -0,0 +1,150 @@
+/*
+ * 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 WeakReference