diff --git a/CHANGELOG.md b/CHANGELOG.md
index 24da631e5..dba467085 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,4 +3,8 @@
- [Bugfix: optimize ratelimit actuator](https://github.com/Tencent/spring-cloud-tencent/pull/413)
- [Feature: add rate limit filter debug log](https://github.com/Tencent/spring-cloud-tencent/pull/417)
+- [Feature: Optimized configuration update](https://github.com/Tencent/spring-cloud-tencent/pull/423)
+- [Feature: add feature-env plugin & add spring cloud gateway staining plugin](https://github.com/Tencent/spring-cloud-tencent/pull/428)
+- [Optimize: add EncodeTransferMedataRestTemplateInterceptor to RestTemplate](https://github.com/Tencent/spring-cloud-tencent/pull/434)
+- [Optimize: Specification apollo code reference notes](https://github.com/Tencent/spring-cloud-tencent/pull/442)
- [Feature: graceful service registration after ApplicationReadyEventg](https://github.com/Tencent/spring-cloud-tencent/pull/441)
diff --git a/pom.xml b/pom.xml
index d13aac07e..0839b7199 100644
--- a/pom.xml
+++ b/pom.xml
@@ -47,6 +47,7 @@
spring-cloud-starter-tencent-polaris-ratelimitspring-cloud-starter-tencent-polaris-circuitbreakerspring-cloud-starter-tencent-polaris-router
+ spring-cloud-tencent-plugin-startersspring-cloud-tencent-dependenciesspring-cloud-tencent-examplesspring-cloud-tencent-coverage
diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java
index 6384fa3b5..fa14d9ae4 100644
--- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java
+++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java
@@ -18,8 +18,9 @@
package com.tencent.cloud.metadata.config;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
-import java.util.Map;
import com.netflix.zuul.ZuulFilter;
import com.tencent.cloud.common.constant.MetadataConstant;
@@ -30,18 +31,15 @@ import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateIntercept
import com.tencent.cloud.metadata.core.EncodeTransferMedataScgFilter;
import com.tencent.cloud.metadata.core.EncodeTransferMetadataZuulFilter;
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.beans.factory.SmartInitializingSingleton;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cloud.gateway.filter.GlobalFilter;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
-import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import static javax.servlet.DispatcherType.ASYNC;
@@ -143,9 +141,10 @@ public class MetadataTransferAutoConfiguration {
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
- protected static class MetadataTransferRestTemplateConfig implements ApplicationContextAware {
+ protected static class MetadataTransferRestTemplateConfig {
- private ApplicationContext context;
+ @Autowired(required = false)
+ private List restTemplates = Collections.emptyList();
@Bean
public EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor() {
@@ -153,59 +152,12 @@ public class MetadataTransferAutoConfiguration {
}
@Bean
- BeanPostProcessor encodeTransferMetadataRestTemplatePostProcessor(
- EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor) {
- // Coping with multiple bean injection scenarios
- Map beans = this.context.getBeansOfType(RestTemplate.class);
- // If the restTemplate has been created when the
- // MetadataRestTemplatePostProcessor Bean
- // is initialized, then manually set the interceptor.
- if (!CollectionUtils.isEmpty(beans)) {
- for (RestTemplate restTemplate : beans.values()) {
- List interceptors = restTemplate.getInterceptors();
- // Avoid setting interceptor repeatedly.
- if (!interceptors.contains(encodeTransferMedataRestTemplateInterceptor)) {
- interceptors.add(encodeTransferMedataRestTemplateInterceptor);
- restTemplate.setInterceptors(interceptors);
- }
- }
- }
- return new EncodeTransferMetadataRestTemplatePostProcessor(encodeTransferMedataRestTemplateInterceptor);
- }
-
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- this.context = applicationContext;
- }
-
- public static class EncodeTransferMetadataRestTemplatePostProcessor
- implements BeanPostProcessor {
-
- private final EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor;
-
- EncodeTransferMetadataRestTemplatePostProcessor(
- EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor) {
- this.encodeTransferMedataRestTemplateInterceptor = encodeTransferMedataRestTemplateInterceptor;
- }
-
- @Override
- public Object postProcessBeforeInitialization(Object bean, String beanName) {
- return bean;
- }
-
- @Override
- public Object postProcessAfterInitialization(Object bean, String beanName) {
- if (bean instanceof RestTemplate) {
- RestTemplate restTemplate = (RestTemplate) bean;
- List interceptors = restTemplate.getInterceptors();
- // Avoid setting interceptor repeatedly.
- if (!interceptors.contains(encodeTransferMedataRestTemplateInterceptor)) {
- interceptors.add(this.encodeTransferMedataRestTemplateInterceptor);
- restTemplate.setInterceptors(interceptors);
- }
- }
- return bean;
- }
+ public SmartInitializingSingleton addEncodeTransferMedataInterceptorForRestTemplate(EncodeTransferMedataRestTemplateInterceptor interceptor) {
+ return () -> restTemplates.forEach(restTemplate -> {
+ List list = new ArrayList<>(restTemplate.getInterceptors());
+ list.add(interceptor);
+ restTemplate.setInterceptors(list);
+ });
}
}
}
diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolver.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolver.java
index b4303f6f1..f37ff1405 100644
--- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolver.java
+++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolver.java
@@ -25,6 +25,7 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest;
+import com.tencent.cloud.common.constant.MetadataConstant;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpHeaders;
@@ -38,9 +39,6 @@ import org.springframework.web.server.ServerWebExchange;
*/
public final class CustomTransitiveMetadataResolver {
- private static final String TRANSITIVE_HEADER_PREFIX = "X-SCT-Metadata-Transitive-";
- private static final int TRANSITIVE_HEADER_PREFIX_LENGTH = TRANSITIVE_HEADER_PREFIX.length();
-
private CustomTransitiveMetadataResolver() {
}
@@ -50,10 +48,20 @@ public final class CustomTransitiveMetadataResolver {
HttpHeaders headers = exchange.getRequest().getHeaders();
for (Map.Entry> entry : headers.entrySet()) {
String key = entry.getKey();
- if (StringUtils.isNotBlank(key)
- && StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX)
+ if (StringUtils.isBlank(key)) {
+ continue;
+ }
+ // resolve sct transitive header
+ if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX)
+ && !CollectionUtils.isEmpty(entry.getValue())) {
+ String sourceKey = StringUtils.substring(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX_LENGTH);
+ result.put(sourceKey, entry.getValue().get(0));
+ }
+
+ //resolve polaris transitive header
+ if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX)
&& !CollectionUtils.isEmpty(entry.getValue())) {
- String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH);
+ String sourceKey = StringUtils.substring(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH);
result.put(sourceKey, entry.getValue().get(0));
}
}
@@ -67,11 +75,21 @@ public final class CustomTransitiveMetadataResolver {
Enumeration headers = request.getHeaderNames();
while (headers.hasMoreElements()) {
String key = headers.nextElement();
+ if (StringUtils.isBlank(key)) {
+ continue;
+ }
+
+ // resolve sct transitive header
+ if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX)
+ && StringUtils.isNotBlank(request.getHeader(key))) {
+ String sourceKey = StringUtils.substring(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX_LENGTH);
+ result.put(sourceKey, request.getHeader(key));
+ }
- if (StringUtils.isNotBlank(key)
- && StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX)
+ // resolve polaris transitive header
+ if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX)
&& StringUtils.isNotBlank(request.getHeader(key))) {
- String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH);
+ String sourceKey = StringUtils.substring(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH);
result.put(sourceKey, request.getHeader(key));
}
}
diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilter.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilter.java
index e6c2946e8..f8274b506 100644
--- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilter.java
+++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilter.java
@@ -59,11 +59,10 @@ public class EncodeTransferMedataScgFilter implements GlobalFilter, Ordered {
// get metadata of current thread
MetadataContext metadataContext = exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT);
-
- // add new metadata and cover old
if (metadataContext == null) {
metadataContext = MetadataContextHolder.get();
}
+
Map customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE);
if (!CollectionUtils.isEmpty(customMetadata)) {
String metadataStr = JacksonUtils.serialize2Json(customMetadata);
diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java
index 1e5579f90..6e56bd3e1 100644
--- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java
+++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java
@@ -18,6 +18,12 @@
package com.tencent.cloud.metadata.config;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignInterceptor;
import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor;
import com.tencent.cloud.metadata.core.EncodeTransferMetadataZuulFilter;
@@ -26,7 +32,12 @@ import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.web.client.RestTemplate;
/**
* Test for {@link MetadataTransferAutoConfiguration}.
@@ -54,10 +65,6 @@ public class MetadataTransferAutoConfigurationTest {
MetadataTransferAutoConfiguration.MetadataTransferRestTemplateConfig.class);
Assertions.assertThat(context).hasSingleBean(
EncodeTransferMedataRestTemplateInterceptor.class);
- Assertions.assertThat(context).hasSingleBean(
- MetadataTransferAutoConfiguration
- .MetadataTransferRestTemplateConfig
- .EncodeTransferMetadataRestTemplatePostProcessor.class);
Assertions.assertThat(context).hasSingleBean(
MetadataTransferAutoConfiguration.MetadataTransferZuulFilterConfig.class);
Assertions.assertThat(context)
@@ -67,4 +74,44 @@ public class MetadataTransferAutoConfigurationTest {
Assertions.assertThat(context).hasSingleBean(GlobalFilter.class);
});
}
+
+
+ @Test
+ public void test2() {
+ this.applicationContextRunner
+ .withConfiguration(
+ AutoConfigurations.of(MetadataTransferAutoConfiguration.class, RestTemplateConfiguration.class))
+ .run(context -> {
+ Assertions.assertThat(context)
+ .hasSingleBean(EncodeTransferMedataFeignInterceptor.class);
+ EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor = context.getBean(EncodeTransferMedataRestTemplateInterceptor.class);
+ Map restTemplateMap = context.getBeansOfType(RestTemplate.class);
+ Assertions.assertThat(restTemplateMap.size()).isEqualTo(2);
+ for (String beanName : Arrays.asList("restTemplate", "loadBalancedRestTemplate")) {
+ RestTemplate restTemplate = restTemplateMap.get(beanName);
+ Assertions.assertThat(restTemplate).isNotNull();
+ List encodeTransferMedataFeignInterceptorList = restTemplate.getInterceptors()
+ .stream()
+ .filter(interceptor -> Objects.equals(interceptor, encodeTransferMedataRestTemplateInterceptor))
+ .collect(Collectors.toList());
+ //EncodeTransferMedataFeignInterceptor is not added repeatedly
+ Assertions.assertThat(encodeTransferMedataFeignInterceptorList.size()).isEqualTo(1);
+ }
+ });
+ }
+
+ @Configuration
+ static class RestTemplateConfiguration {
+
+ @Bean
+ public RestTemplate restTemplate() {
+ return new RestTemplate();
+ }
+
+ @LoadBalanced
+ @Bean
+ public RestTemplate loadBalancedRestTemplate() {
+ return new RestTemplate();
+ }
+ }
}
diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolverTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolverTest.java
index 9dc83bd51..a1b286456 100644
--- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolverTest.java
+++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolverTest.java
@@ -34,7 +34,7 @@ import org.springframework.mock.web.server.MockServerWebExchange;
public class CustomTransitiveMetadataResolverTest {
@Test
- public void test() {
+ public void testSCTTransitiveMetadata() {
MockServerHttpRequest.BaseBuilder> builder = MockServerHttpRequest.get("");
builder.header("X-SCT-Metadata-Transitive-a", "test");
MockServerWebExchange exchange = MockServerWebExchange.from(builder);
@@ -44,11 +44,30 @@ public class CustomTransitiveMetadataResolverTest {
}
@Test
- public void testServlet() {
+ public void testPolarisTransitiveMetadata() {
+ MockServerHttpRequest.BaseBuilder> builder = MockServerHttpRequest.get("");
+ builder.header("X-Polaris-Metadata-Transitive-a", "test");
+ MockServerWebExchange exchange = MockServerWebExchange.from(builder);
+ Map resolve = CustomTransitiveMetadataResolver.resolve(exchange);
+ Assertions.assertThat(resolve.size()).isEqualTo(1);
+ Assertions.assertThat(resolve.get("a")).isEqualTo("test");
+ }
+
+ @Test
+ public void testSCTServletTransitiveMetadata() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("X-SCT-Metadata-Transitive-a", "test");
Map resolve = CustomTransitiveMetadataResolver.resolve(request);
Assertions.assertThat(resolve.size()).isEqualTo(1);
Assertions.assertThat(resolve.get("a")).isEqualTo("test");
}
+
+ @Test
+ public void testPolarisServletTransitiveMetadata() {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addHeader("X-Polaris-Metadata-Transitive-a", "test");
+ Map resolve = CustomTransitiveMetadataResolver.resolve(request);
+ Assertions.assertThat(resolve.size()).isEqualTo(1);
+ Assertions.assertThat(resolve.get("a")).isEqualTo("test");
+ }
}
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..3976a7d01 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
@@ -20,13 +20,21 @@ package com.tencent.cloud.polaris.config;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceAutoRefresher;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
+import com.tencent.cloud.polaris.config.adapter.SmartConfigurationPropertiesRebinder;
import com.tencent.cloud.polaris.config.annotation.PolarisConfigAnnotationProcessor;
+import com.tencent.cloud.polaris.config.condition.ConditionalOnNonDefaultBehavior;
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.SpringValueRegistry;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.cloud.context.refresh.ContextRefresher;
+import org.springframework.boot.autoconfigure.condition.SearchStrategy;
+import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans;
+import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -44,9 +52,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 +67,30 @@ public class PolarisConfigAutoConfiguration {
public PolarisConfigChangeEventListener polarisConfigChangeEventListener() {
return new PolarisConfigChangeEventListener();
}
+
+ @Bean
+ public SpringValueRegistry springValueRegistry() {
+ return new SpringValueRegistry();
+ }
+
+ @Bean
+ public PlaceholderHelper placeholderHelper() {
+ return new PlaceholderHelper();
+ }
+
+ @Bean
+ public SpringValueProcessor springValueProcessor(PlaceholderHelper placeholderHelper, SpringValueRegistry springValueRegistry, PolarisConfigProperties polarisConfigProperties) {
+ return new SpringValueProcessor(placeholderHelper, springValueRegistry, polarisConfigProperties);
+ }
+
+ @Bean
+ @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);
+ }
+
}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java
index a67c8a89d..7c7afdf87 100644
--- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java
@@ -19,6 +19,8 @@ package com.tencent.cloud.polaris.config;
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.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
@@ -27,7 +29,11 @@ import com.tencent.polaris.client.api.SDKContext;
import com.tencent.polaris.configuration.api.core.ConfigFileService;
import com.tencent.polaris.configuration.factory.ConfigFileServiceFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.SearchStrategy;
+import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans;
+import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@@ -79,4 +85,14 @@ public class PolarisConfigBootstrapAutoConfiguration {
PolarisContextProperties polarisContextProperties) {
return new ConfigurationModifier(polarisConfigProperties, polarisContextProperties);
}
+
+ @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);
+ }
}
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..fd70a8fa9 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,22 +18,32 @@
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.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.refresh.ContextRefresher;
+import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
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;
/**
@@ -43,29 +53,40 @@ import org.springframework.util.CollectionUtils;
* @author lepdou 2022-03-28
*/
public class PolarisPropertySourceAutoRefresher
- implements ApplicationListener, ApplicationContextAware {
+ implements ApplicationListener, 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 ApplicationContext applicationContext;
+
+ private ConfigurableApplicationContext context;
+
+ private TypeConverter typeConverter;
+ private final SpringValueRegistry springValueRegistry;
+ private ConfigurableBeanFactory beanFactory;
+ private final PlaceholderHelper placeholderHelper;
+
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.context = (ConfigurableApplicationContext) applicationContext;
+ this.beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
+ this.typeConverter = this.beanFactory.getTypeConverter();
}
@Override
@@ -91,6 +112,7 @@ public class PolarisPropertySourceAutoRefresher
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 = {}",
@@ -115,11 +137,70 @@ public class PolarisPropertySourceAutoRefresher
source.remove(changedKey);
break;
}
- }
- // rebuild beans with @RefreshScope annotation
- contextRefresher.refresh();
+ Collection targetValues = springValueRegistry.get(beanFactory, changedKey);
+ if (targetValues == null || targetValues.isEmpty()) {
+ continue;
+ }
+ // update the value
+ for (SpringValue val : targetValues) {
+ updateSpringValue(val);
+ }
+
+ }
+ context.publishEvent(new EnvironmentChangeEvent(context, configKVFileChangeEvent.changedKeys()));
});
}
}
+
+ 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;
+ }
}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/SmartConfigurationPropertiesRebinder.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/SmartConfigurationPropertiesRebinder.java
new file mode 100644
index 000000000..bc275482c
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/SmartConfigurationPropertiesRebinder.java
@@ -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.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}.
+ *
+ * Spring team doesn't seem to support single {@link ConfigurationPropertiesBean} refresh.
+ *
+ * SmartConfigurationPropertiesRebinder can refresh specific
+ * {@link ConfigurationPropertiesBean} base on the change keys.
+ *
+ * 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.
+ *
+ * SmartConfigurationPropertiesRebinder
+ *
+ * @author weihubeats 2022-7-10
+ */
+public class SmartConfigurationPropertiesRebinder extends ConfigurationPropertiesRebinder {
+
+ private static final String BEANS = "beans";
+
+ private Map 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) 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 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);
+ }
+ }));
+ }
+
+}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigAnnotationProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigAnnotationProcessor.java
index 59bbb994c..52704cc9b 100644
--- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigAnnotationProcessor.java
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigAnnotationProcessor.java
@@ -40,7 +40,7 @@ import static com.tencent.cloud.polaris.config.listener.PolarisConfigListenerCon
/**
* {@link PolarisConfigAnnotationProcessor} implementation for spring .
- *
Refer to the Apollo project implementation:
+ *
This source file was reference from:
*
* ApolloAnnotationProcessor
* @author Palmer Xu 2022-06-07
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java
index a324c4d78..d5e291996 100644
--- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java
@@ -26,7 +26,7 @@ import java.lang.annotation.Target;
/**
* Configuring the change listener annotation.
- *
Refer to the Apollo project implementation:
+ *
This source file was reference from:
*
* ApolloAnnotationProcessor
* @author Palmer Xu 2022-05-31
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/ConditionalOnNonDefaultBehavior.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/ConditionalOnNonDefaultBehavior.java
new file mode 100644
index 000000000..d799ec28b
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/ConditionalOnNonDefaultBehavior.java
@@ -0,0 +1,40 @@
+/*
+ * 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 java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.context.annotation.Conditional;
+
+/**
+ * custom annotation.
+ *
+ * @author weihubeats 2022-7-13
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Documented
+@Conditional(NonDefaultBehaviorCondition.class)
+public @interface ConditionalOnNonDefaultBehavior {
+
+}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/NonDefaultBehaviorCondition.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/NonDefaultBehaviorCondition.java
new file mode 100644
index 000000000..98b14318e
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/NonDefaultBehaviorCondition.java
@@ -0,0 +1,57 @@
+/*
+ * 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.enums.RefreshBehavior;
+
+import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
+import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+
+/**
+ * Extend SpringBootCondition.
+ *
+ * @author weihubeats 2022-7-13
+ */
+public class NonDefaultBehaviorCondition extends SpringBootCondition {
+
+ /**
+ * refresh behavior config.
+ */
+ public static final String POLARIS_CONFIG_REFRESH_BEHAVIOR = "spring.cloud.polaris.config.refresh-behavior";
+
+ /**
+ * refresh behavior config default value.
+ */
+ private static final RefreshBehavior DEFAULT_REFRESH_BEHAVIOR = RefreshBehavior.ALL_BEANS;
+
+ @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) {
+ return ConditionOutcome.noMatch("no matched");
+ }
+ return ConditionOutcome.match("matched");
+ }
+
+}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/enums/RefreshBehavior.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/enums/RefreshBehavior.java
new file mode 100644
index 000000000..12abd79cb
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/enums/RefreshBehavior.java
@@ -0,0 +1,40 @@
+/*
+ * 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.enums;
+
+import org.springframework.boot.context.properties.ConfigurationPropertiesBean;
+
+/**
+ * Refresh behavior.
+ *
+ * @author weihubeats 2022-7-13
+ */
+public enum RefreshBehavior {
+
+ /**
+ * Refresh all {@link ConfigurationPropertiesBean}s.
+ */
+ ALL_BEANS,
+ /**
+ * Refresh specific {@link ConfigurationPropertiesBean} base on change key.
+ */
+ SPECIFIC_BEAN,
+
+}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigListenerContext.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigListenerContext.java
index 15a9a9336..6bd266bf8 100644
--- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigListenerContext.java
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigListenerContext.java
@@ -49,7 +49,7 @@ import static com.tencent.polaris.configuration.api.core.ChangeType.MODIFIED;
/**
* Polaris Config Listener Context Defined .
- *
Refer to the Apollo project implementation:
+ *
This source file was reference from:
*
* AbstractConfig
*
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/PolarisProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/PolarisProcessor.java
new file mode 100644
index 000000000..3058e5eb8
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/PolarisProcessor.java
@@ -0,0 +1,73 @@
+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;
+
+/**
+ * Get spring bean properties and methods.
+ *
+ * @author weihubeats 2022-7-10
+ */
+public abstract class PolarisProcessor 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, field -> res.add(field));
+ return res;
+ }
+
+ private List findAllMethod(Class clazz) {
+ final List res = new LinkedList<>();
+ ReflectionUtils.doWithMethods(clazz, method -> 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..d7d09912b
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java
@@ -0,0 +1,172 @@
+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;
+
+/**
+ * Spring value processor of field or method which has @Value and xml config placeholders.
+ *
+ *
+ * This source file was originally from:
+ *
+ * SpringValueProcessor
+ *
+ * @author weihubeats 2022-7-10
+ */
+public class SpringValueProcessor extends PolarisProcessor implements BeanFactoryPostProcessor, BeanFactoryAware {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SpringValueProcessor.class);
+
+ private final PolarisConfigProperties polarisConfigProperties;
+ private final PlaceholderHelper placeholderHelper;
+ private final SpringValueRegistry springValueRegistry;
+
+ private BeanFactory beanFactory;
+ private Multimap beanName2SpringValueDefinitions;
+
+ public SpringValueProcessor(PlaceholderHelper placeholderHelper,
+ SpringValueRegistry springValueRegistry,
+ PolarisConfigProperties polarisConfigProperties) {
+ this.placeholderHelper = placeholderHelper;
+ this.polarisConfigProperties = polarisConfigProperties;
+ this.springValueRegistry = springValueRegistry;
+ beanName2SpringValueDefinitions = LinkedListMultimap.create();
+ }
+
+ @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..2d7515661
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/PlaceholderHelper.java
@@ -0,0 +1,195 @@
+/*
+ * 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;
+
+/**
+ * Placeholder helper functions.
+ *
+ *
+ * This source file was originally from:
+ *
+ * PlaceholderHelper
+ *
+ * @author weihubeats 2022-7-10
+ */
+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));
+ }
+
+ /**
+ *
+ * @param propertyString propertyString
+ * @return
+ * Extract keys from placeholder, e.g.
+ *