diff --git a/CHANGELOG.md b/CHANGELOG.md
index ae4ccb387..8c47f725e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,3 +18,4 @@
- [fix:update guava version.](https://github.com/Tencent/spring-cloud-tencent/pull/1042)
- [fix:fix circuit breaker bean load order bug when using Nacos discovery.](https://github.com/Tencent/spring-cloud-tencent/pull/1049)
- [refactor:refactor Polaris registration.](https://github.com/Tencent/spring-cloud-tencent/pull/1056)
+- [feat:added automatic optimization for dynamic config refresh type.](https://github.com/Tencent/spring-cloud-tencent/pull/1059)
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 f336be29c..be91d2287 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,6 +20,7 @@ package com.tencent.cloud.polaris.config;
import com.tencent.cloud.polaris.config.adapter.AffectedConfigurationPropertiesRebinder;
import com.tencent.cloud.polaris.config.adapter.PolarisConfigPropertyRefresher;
+import com.tencent.cloud.polaris.config.adapter.PolarisConfigRefreshScopeAnnotationDetector;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
import com.tencent.cloud.polaris.config.adapter.PolarisRefreshAffectedContextRefresher;
import com.tencent.cloud.polaris.config.adapter.PolarisRefreshEntireContextRefresher;
@@ -27,6 +28,7 @@ import com.tencent.cloud.polaris.config.annotation.PolarisConfigAnnotationProces
import com.tencent.cloud.polaris.config.condition.ConditionalOnReflectRefreshType;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.listener.PolarisConfigChangeEventListener;
+import com.tencent.cloud.polaris.config.listener.PolarisConfigRefreshOptimizationListener;
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;
@@ -102,5 +104,15 @@ public class PolarisConfigAutoConfiguration {
return new PolarisRefreshAffectedContextRefresher(polarisConfigProperties, polarisPropertySourceManager,
springValueRegistry, placeholderHelper);
}
+
+ @Bean
+ public PolarisConfigRefreshScopeAnnotationDetector polarisConfigRefreshScopeAnnotationDetector() {
+ return new PolarisConfigRefreshScopeAnnotationDetector();
+ }
+
+ @Bean
+ public PolarisConfigRefreshOptimizationListener polarisConfigRefreshOptimizationListener() {
+ return new PolarisConfigRefreshOptimizationListener();
+ }
}
}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigRefreshScopeAnnotationDetector.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigRefreshScopeAnnotationDetector.java
new file mode 100644
index 000000000..5c26342ce
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigRefreshScopeAnnotationDetector.java
@@ -0,0 +1,94 @@
+/*
+ * 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.annotation.Annotation;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.core.Ordered;
+import org.springframework.core.PriorityOrdered;
+import org.springframework.lang.NonNull;
+
+/**
+ * Mainly used to detect whether the annotation class {@link org.springframework.cloud.context.config.annotation.RefreshScope}
+ * exists, and whether the user has configured beans using this annotation in their business system.
+ * If the annotation {@code @RefreshScope} exists and is used, the auto-optimization will be triggered
+ * in listener {@link com.tencent.cloud.polaris.config.listener.PolarisConfigRefreshOptimizationListener}.
+ *
+ *
This bean will only be created and initialized when the config refresh type is {@code RefreshType.REFLECT}.
+ *
+ * @author jarvisxiong
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class PolarisConfigRefreshScopeAnnotationDetector implements BeanPostProcessor, InitializingBean, PriorityOrdered {
+
+ private final AtomicBoolean isRefreshScopeAnnotationUsed = new AtomicBoolean(false);
+
+ private Class refreshScopeAnnotationClass;
+
+ private String annotatedRefreshScopeBeanName;
+
+ @Override
+ public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName)
+ throws BeansException {
+ return bean;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName)
+ throws BeansException {
+ if (isRefreshScopeAnnotationUsed() || refreshScopeAnnotationClass == null) {
+ return bean;
+ }
+ Annotation[] refreshScopeAnnotations = bean.getClass().getAnnotationsByType(refreshScopeAnnotationClass);
+ if (refreshScopeAnnotations.length > 0) {
+ if (isRefreshScopeAnnotationUsed.compareAndSet(false, true)) {
+ annotatedRefreshScopeBeanName = beanName;
+ }
+ }
+ return bean;
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+ try {
+ refreshScopeAnnotationClass = Class.forName(
+ "org.springframework.cloud.context.config.annotation.RefreshScope",
+ false,
+ getClass().getClassLoader());
+ }
+ catch (ClassNotFoundException ignored) {
+ }
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.LOWEST_PRECEDENCE;
+ }
+
+ public boolean isRefreshScopeAnnotationUsed() {
+ return isRefreshScopeAnnotationUsed.get();
+ }
+
+ public String getAnnotatedRefreshScopeBeanName() {
+ return annotatedRefreshScopeBeanName;
+ }
+}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListener.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListener.java
new file mode 100644
index 000000000..0c93fd8ff
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListener.java
@@ -0,0 +1,126 @@
+/*
+ * Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
+ *
+ * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ */
+
+package com.tencent.cloud.polaris.config.listener;
+
+import java.util.Collections;
+
+import com.tencent.cloud.polaris.config.adapter.PolarisConfigRefreshScopeAnnotationDetector;
+import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
+import com.tencent.cloud.polaris.config.adapter.PolarisRefreshEntireContextRefresher;
+import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
+import com.tencent.cloud.polaris.config.enums.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.beans.factory.config.ConstructorArgumentValues;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.cloud.context.refresh.ContextRefresher;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.core.env.MapPropertySource;
+import org.springframework.core.env.MutablePropertySources;
+import org.springframework.lang.NonNull;
+
+import static com.tencent.cloud.polaris.config.condition.ReflectRefreshTypeCondition.POLARIS_CONFIG_REFRESH_TYPE;
+
+/**
+ * When {@link com.tencent.cloud.polaris.config.adapter.PolarisConfigRefreshScopeAnnotationDetector} detects that
+ * the annotation {@code @RefreshScope} exists and is used, but the config refresh type
+ * {@code spring.cloud.polaris.config.refresh-type} is still {@code RefreshType.REFLECT}, then the framework will
+ * automatically switch the config refresh type to {@code RefreshType.REFRESH_CONTEXT}.
+ *
+ *
The purpose of this optimization is to omit additional configuration, and facilitate for users to use the
+ * dynamic configuration refresh strategy of Spring Cloud Context.
+ *
+ * @author jarvisxiong
+ */
+public class PolarisConfigRefreshOptimizationListener implements ApplicationListener {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(PolarisConfigRefreshOptimizationListener.class);
+
+ private static final String CONFIG_REFRESH_TYPE_PROPERTY = "configRefreshTypeProperty";
+
+ private static final String REFLECT_REBINDER_BEAN_NAME = "affectedConfigurationPropertiesRebinder";
+
+ private static final String REFLECT_REFRESHER_BEAN_NAME = "polarisReflectPropertySourceAutoRefresher";
+
+ private static final String REFRESH_CONTEXT_REFRESHER_BEAN_NAME = "polarisRefreshContextPropertySourceAutoRefresher";
+
+ @Override
+ public void onApplicationEvent(@NonNull ContextRefreshedEvent event) {
+ ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) event.getApplicationContext();
+ PolarisConfigRefreshScopeAnnotationDetector detector = applicationContext
+ .getBean(PolarisConfigRefreshScopeAnnotationDetector.class);
+ boolean isRefreshScopeAnnotationUsed = detector.isRefreshScopeAnnotationUsed();
+ String annotatedRefreshScopeBeanName = detector.getAnnotatedRefreshScopeBeanName();
+ // a bean is using @RefreshScope, but the config refresh type is still [reflect], switch automatically
+ if (isRefreshScopeAnnotationUsed) {
+ LOGGER.warn("Detected that the bean [{}] is using @RefreshScope annotation, but the config refresh type is still [reflect]. "
+ + "[SCT] will automatically switch to [refresh_context].", annotatedRefreshScopeBeanName);
+ switchConfigRefreshTypeProperty(applicationContext);
+ modifyPolarisConfigPropertiesBean(applicationContext);
+ // remove related bean of type [reflect]
+ removeRelatedBeansOfReflect(applicationContext);
+ // register a new refresher bean of type [refresh_context]
+ registerRefresherBeanOfRefreshContext(applicationContext);
+ // add the new refresher to context as a listener
+ addRefresherBeanAsListener(applicationContext);
+ }
+ }
+
+ private void switchConfigRefreshTypeProperty(ConfigurableApplicationContext applicationContext) {
+ MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
+ propertySources.addFirst(new MapPropertySource(CONFIG_REFRESH_TYPE_PROPERTY,
+ Collections.singletonMap(POLARIS_CONFIG_REFRESH_TYPE, RefreshType.REFRESH_CONTEXT)));
+ }
+
+ private void modifyPolarisConfigPropertiesBean(ConfigurableApplicationContext applicationContext) {
+ PolarisConfigProperties polarisConfigProperties = applicationContext.getBean(PolarisConfigProperties.class);
+ polarisConfigProperties.setRefreshType(RefreshType.REFRESH_CONTEXT);
+ }
+
+ private void removeRelatedBeansOfReflect(ConfigurableApplicationContext applicationContext) {
+ DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
+ beanFactory.removeBeanDefinition(REFLECT_REFRESHER_BEAN_NAME);
+ beanFactory.removeBeanDefinition(REFLECT_REBINDER_BEAN_NAME);
+ }
+
+ private void registerRefresherBeanOfRefreshContext(ConfigurableApplicationContext applicationContext) {
+ DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getBeanFactory();
+ AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
+ beanDefinition.setBeanClass(PolarisRefreshEntireContextRefresher.class);
+ PolarisConfigProperties polarisConfigProperties = beanFactory.getBean(PolarisConfigProperties.class);
+ PolarisPropertySourceManager polarisPropertySourceManager = beanFactory.getBean(PolarisPropertySourceManager.class);
+ ContextRefresher contextRefresher = beanFactory.getBean(ContextRefresher.class);
+ ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues();
+ constructorArgumentValues.addIndexedArgumentValue(0, polarisConfigProperties);
+ constructorArgumentValues.addIndexedArgumentValue(1, polarisPropertySourceManager);
+ constructorArgumentValues.addIndexedArgumentValue(2, contextRefresher);
+ beanFactory.registerBeanDefinition(REFRESH_CONTEXT_REFRESHER_BEAN_NAME, beanDefinition);
+ }
+
+ private void addRefresherBeanAsListener(ConfigurableApplicationContext applicationContext) {
+ PolarisRefreshEntireContextRefresher refresher = (PolarisRefreshEntireContextRefresher) applicationContext
+ .getBean(REFRESH_CONTEXT_REFRESHER_BEAN_NAME);
+ applicationContext.addApplicationListener(refresher);
+ }
+}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigRefreshScopeAnnotationDetectorTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigRefreshScopeAnnotationDetectorTest.java
new file mode 100644
index 000000000..31e3c1d57
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigRefreshScopeAnnotationDetectorTest.java
@@ -0,0 +1,101 @@
+/*
+ * Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
+ *
+ * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ */
+
+package com.tencent.cloud.polaris.config.adapter;
+
+import com.tencent.cloud.polaris.config.PolarisConfigAutoConfiguration;
+import com.tencent.cloud.polaris.config.PolarisConfigBootstrapAutoConfiguration;
+import org.assertj.core.api.InstanceOfAssertFactories;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration;
+import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+
+import static org.assertj.core.api.Assertions.as;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * test for {@link PolarisConfigRefreshScopeAnnotationDetector}.
+ */
+@SuppressWarnings("rawtypes")
+public class PolarisConfigRefreshScopeAnnotationDetectorTest {
+
+ private static Class refreshScopeAnnotationClass = null;
+
+ static {
+ try {
+ refreshScopeAnnotationClass = Class.forName(
+ "org.springframework.cloud.context.config.annotation.RefreshScope",
+ false,
+ PolarisConfigRefreshScopeAnnotationDetectorTest.class.getClassLoader());
+ }
+ catch (ClassNotFoundException ignored) {
+ }
+ }
+
+ @Test
+ public void testUseRefreshScope() {
+ ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(PolarisConfigBootstrapAutoConfiguration.class))
+ .withConfiguration(AutoConfigurations.of(PolarisConfigAutoConfiguration.class))
+ .withConfiguration(AutoConfigurations.of(RefreshAutoConfiguration.class))
+ .withConfiguration(AutoConfigurations.of(ConfigurationPropertiesRebinderAutoConfiguration.class))
+ .withBean("testBeanWithRefreshScope", TestBeanWithRefreshScope.class)
+ .withPropertyValues("spring.application.name=" + "polarisConfigRefreshScopeAnnotationDetectorTest")
+ .withPropertyValues("server.port=" + 8080)
+ .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081")
+ .withPropertyValues("spring.cloud.polaris.config.connect-remote-server=false");
+ contextRunner.run(context -> {
+ assertThat(context).hasSingleBean(PolarisConfigRefreshScopeAnnotationDetector.class);
+ PolarisConfigRefreshScopeAnnotationDetector detector = context.getBean(PolarisConfigRefreshScopeAnnotationDetector.class);
+ assertThat(detector.isRefreshScopeAnnotationUsed()).isTrue();
+ assertThat(detector.getAnnotatedRefreshScopeBeanName()).isEqualTo("scopedTarget.testBeanWithRefreshScope");
+ assertThat(detector).extracting("refreshScopeAnnotationClass", as(InstanceOfAssertFactories.type(Class.class)))
+ .isEqualTo(refreshScopeAnnotationClass);
+ });
+ }
+
+ @Test
+ public void testNotUseRefreshScope() {
+ ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(PolarisConfigBootstrapAutoConfiguration.class))
+ .withConfiguration(AutoConfigurations.of(PolarisConfigAutoConfiguration.class))
+ .withConfiguration(AutoConfigurations.of(RefreshAutoConfiguration.class))
+ .withConfiguration(AutoConfigurations.of(ConfigurationPropertiesRebinderAutoConfiguration.class))
+ .withPropertyValues("spring.application.name=" + "polarisConfigRefreshScopeAnnotationDetectorTest")
+ .withPropertyValues("server.port=" + 8080)
+ .withPropertyValues("spring.cloud.polaris.address=grpc://127.0.0.1:10081")
+ .withPropertyValues("spring.cloud.polaris.config.connect-remote-server=false");
+ contextRunner.run(context -> {
+ assertThat(context).hasSingleBean(PolarisConfigRefreshScopeAnnotationDetector.class);
+ PolarisConfigRefreshScopeAnnotationDetector detector = context.getBean(PolarisConfigRefreshScopeAnnotationDetector.class);
+ assertThat(detector.isRefreshScopeAnnotationUsed()).isFalse();
+ assertThat(detector.getAnnotatedRefreshScopeBeanName()).isNull();
+ assertThat(detector).extracting("refreshScopeAnnotationClass", as(InstanceOfAssertFactories.type(Class.class)))
+ .isEqualTo(refreshScopeAnnotationClass);
+ });
+ }
+
+ @RefreshScope
+ protected static class TestBeanWithRefreshScope {
+
+ }
+}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListenerNotTriggeredTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListenerNotTriggeredTest.java
new file mode 100644
index 000000000..2a0444a01
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListenerNotTriggeredTest.java
@@ -0,0 +1,154 @@
+/*
+ * Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
+ *
+ * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ */
+
+package com.tencent.cloud.polaris.config.listener;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.collect.Lists;
+import com.tencent.cloud.polaris.config.adapter.MockedConfigKVFile;
+import com.tencent.cloud.polaris.config.adapter.PolarisPropertySource;
+import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
+import com.tencent.cloud.polaris.config.adapter.PolarisRefreshAffectedContextRefresher;
+import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
+import com.tencent.cloud.polaris.config.enums.RefreshType;
+import com.tencent.polaris.configuration.api.core.ChangeType;
+import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeEvent;
+import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.context.refresh.ContextRefresher;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.context.support.AbstractApplicationContext;
+import org.springframework.stereotype.Component;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static com.tencent.cloud.polaris.config.condition.ReflectRefreshTypeCondition.POLARIS_CONFIG_REFRESH_TYPE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT;
+
+/**
+ * test for {@link PolarisConfigRefreshOptimizationListener}.
+ */
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(webEnvironment = DEFINED_PORT, classes = PolarisConfigRefreshOptimizationListenerNotTriggeredTest.TestApplication.class,
+ properties = {
+ "server.port=8081",
+ "spring.cloud.polaris.address=grpc://127.0.0.1:10081",
+ "spring.cloud.polaris.config.connect-remote-server=false",
+ "spring.cloud.polaris.config.refresh-type=reflect",
+ "spring.config.location = classpath:application-test.yml"
+ })
+public class PolarisConfigRefreshOptimizationListenerNotTriggeredTest {
+
+ private static final String REFLECT_REFRESHER_BEAN_NAME = "polarisReflectPropertySourceAutoRefresher";
+
+ private static final String TEST_NAMESPACE = "testNamespace";
+
+ private static final String TEST_SERVICE_NAME = "testServiceName";
+
+ private static final String TEST_FILE_NAME = "application.properties";
+
+ @Autowired
+ private ConfigurableApplicationContext context;
+
+ @Test
+ public void testNotSwitchConfigRefreshType() {
+ RefreshType actualRefreshType = context.getEnvironment()
+ .getProperty(POLARIS_CONFIG_REFRESH_TYPE, RefreshType.class);
+ assertThat(actualRefreshType).isEqualTo(RefreshType.REFLECT);
+ PolarisConfigProperties polarisConfigProperties = context.getBean(PolarisConfigProperties.class);
+ assertThat(polarisConfigProperties.getRefreshType()).isEqualTo(RefreshType.REFLECT);
+ assertThat(context.containsBean(REFLECT_REFRESHER_BEAN_NAME)).isTrue();
+ PolarisRefreshAffectedContextRefresher refresher = context
+ .getBean(REFLECT_REFRESHER_BEAN_NAME, PolarisRefreshAffectedContextRefresher.class);
+ assertThat(((AbstractApplicationContext) context).getApplicationListeners().contains(refresher)).isTrue();
+ }
+
+ @Test
+ public void testConfigFileChanged() {
+ Map content = new HashMap<>();
+ content.put("k1", "v1");
+ content.put("k2", "v2");
+ content.put("k3", "v3");
+ MockedConfigKVFile file = new MockedConfigKVFile(content);
+
+ PolarisPropertySource polarisPropertySource = new PolarisPropertySource(TEST_NAMESPACE, TEST_SERVICE_NAME, TEST_FILE_NAME,
+ file, content);
+ PolarisPropertySourceManager manager = context.getBean(PolarisPropertySourceManager.class);
+ when(manager.getAllPropertySources()).thenReturn(Lists.newArrayList(polarisPropertySource));
+
+ PolarisRefreshAffectedContextRefresher refresher = context.getBean(PolarisRefreshAffectedContextRefresher.class);
+ PolarisRefreshAffectedContextRefresher spyRefresher = Mockito.spy(refresher);
+
+ spyRefresher.onApplicationEvent(null);
+
+ ConfigPropertyChangeInfo changeInfo = new ConfigPropertyChangeInfo("k1", "v1", "v11", ChangeType.MODIFIED);
+ ConfigPropertyChangeInfo changeInfo2 = new ConfigPropertyChangeInfo("k4", null, "v4", ChangeType.ADDED);
+ ConfigPropertyChangeInfo changeInfo3 = new ConfigPropertyChangeInfo("k2", "v2", null, ChangeType.DELETED);
+ Map changeInfos = new HashMap<>();
+ changeInfos.put("k1", changeInfo);
+ changeInfos.put("k2", changeInfo3);
+ changeInfos.put("k4", changeInfo2);
+ ConfigKVFileChangeEvent event = new ConfigKVFileChangeEvent(changeInfos);
+ file.fireChangeListener(event);
+
+ ContextRefresher mockContextRefresher = context.getBean(ContextRefresher.class);
+ when(mockContextRefresher.refresh()).thenReturn(event.changedKeys());
+
+ Mockito.verify(spyRefresher, Mockito.times(1))
+ .refreshSpringValue("k1");
+ Mockito.verify(spyRefresher, Mockito.times(1))
+ .refreshSpringValue("k2");
+ Mockito.verify(spyRefresher, Mockito.times(1))
+ .refreshSpringValue("k4");
+ Mockito.verify(spyRefresher, Mockito.times(1))
+ .refreshConfigurationProperties(event.changedKeys());
+ }
+
+ @SpringBootApplication
+ protected static class TestApplication {
+
+ @Primary
+ @Bean
+ public PolarisPropertySourceManager polarisPropertySourceManager() {
+ return mock(PolarisPropertySourceManager.class);
+ }
+
+ @Primary
+ @Bean
+ public ContextRefresher contextRefresher() {
+ return mock(ContextRefresher.class);
+ }
+
+ @Component
+ protected static class TestBeanWithoutRefreshScope {
+
+ }
+ }
+}
diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListenerTriggeredTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListenerTriggeredTest.java
new file mode 100644
index 000000000..e62937326
--- /dev/null
+++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListenerTriggeredTest.java
@@ -0,0 +1,156 @@
+/*
+ * Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
+ *
+ * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ */
+
+package com.tencent.cloud.polaris.config.listener;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.collect.Lists;
+import com.tencent.cloud.polaris.config.adapter.MockedConfigKVFile;
+import com.tencent.cloud.polaris.config.adapter.PolarisPropertySource;
+import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
+import com.tencent.cloud.polaris.config.adapter.PolarisRefreshEntireContextRefresher;
+import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
+import com.tencent.cloud.polaris.config.enums.RefreshType;
+import com.tencent.polaris.configuration.api.core.ChangeType;
+import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeEvent;
+import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.cloud.context.refresh.ContextRefresher;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.context.support.AbstractApplicationContext;
+import org.springframework.stereotype.Component;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static com.tencent.cloud.polaris.config.condition.ReflectRefreshTypeCondition.POLARIS_CONFIG_REFRESH_TYPE;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT;
+
+/**
+ * test for {@link PolarisConfigRefreshOptimizationListener}.
+ */
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(webEnvironment = DEFINED_PORT, classes = PolarisConfigRefreshOptimizationListenerTriggeredTest.TestApplication.class,
+ properties = {
+ "server.port=8081",
+ "spring.cloud.polaris.address=grpc://127.0.0.1:10081",
+ "spring.cloud.polaris.config.connect-remote-server=false",
+ "spring.cloud.polaris.config.refresh-type=reflect",
+ "spring.config.location = classpath:application-test.yml"
+ })
+public class PolarisConfigRefreshOptimizationListenerTriggeredTest {
+
+ private static final String REFRESH_CONTEXT_REFRESHER_BEAN_NAME = "polarisRefreshContextPropertySourceAutoRefresher";
+
+ private static final String TEST_NAMESPACE = "testNamespace";
+
+ private static final String TEST_SERVICE_NAME = "testServiceName";
+
+ private static final String TEST_FILE_NAME = "application.properties";
+
+ @Autowired
+ private ConfigurableApplicationContext context;
+
+ @Test
+ public void testSwitchConfigRefreshType() {
+ RefreshType actualRefreshType = context.getEnvironment()
+ .getProperty(POLARIS_CONFIG_REFRESH_TYPE, RefreshType.class);
+ assertThat(actualRefreshType).isEqualTo(RefreshType.REFRESH_CONTEXT);
+ PolarisConfigProperties polarisConfigProperties = context.getBean(PolarisConfigProperties.class);
+ assertThat(polarisConfigProperties.getRefreshType()).isEqualTo(RefreshType.REFRESH_CONTEXT);
+ assertThat(context.containsBean(REFRESH_CONTEXT_REFRESHER_BEAN_NAME)).isTrue();
+ PolarisRefreshEntireContextRefresher refresher = context
+ .getBean(REFRESH_CONTEXT_REFRESHER_BEAN_NAME, PolarisRefreshEntireContextRefresher.class);
+ assertThat(((AbstractApplicationContext) context).getApplicationListeners().contains(refresher)).isTrue();
+ }
+
+ @Test
+ public void testConfigFileChanged() {
+ Map content = new HashMap<>();
+ content.put("k1", "v1");
+ content.put("k2", "v2");
+ content.put("k3", "v3");
+ MockedConfigKVFile file = new MockedConfigKVFile(content);
+
+ PolarisPropertySource polarisPropertySource = new PolarisPropertySource(TEST_NAMESPACE, TEST_SERVICE_NAME, TEST_FILE_NAME,
+ file, content);
+ PolarisPropertySourceManager manager = context.getBean(PolarisPropertySourceManager.class);
+ when(manager.getAllPropertySources()).thenReturn(Lists.newArrayList(polarisPropertySource));
+
+ PolarisRefreshEntireContextRefresher refresher = context.getBean(PolarisRefreshEntireContextRefresher.class);
+ PolarisRefreshEntireContextRefresher spyRefresher = Mockito.spy(refresher);
+
+ spyRefresher.onApplicationEvent(null);
+
+ ConfigPropertyChangeInfo changeInfo = new ConfigPropertyChangeInfo("k1", "v1", "v11", ChangeType.MODIFIED);
+ ConfigPropertyChangeInfo changeInfo2 = new ConfigPropertyChangeInfo("k4", null, "v4", ChangeType.ADDED);
+ ConfigPropertyChangeInfo changeInfo3 = new ConfigPropertyChangeInfo("k2", "v2", null, ChangeType.DELETED);
+ Map changeInfos = new HashMap<>();
+ changeInfos.put("k1", changeInfo);
+ changeInfos.put("k2", changeInfo3);
+ changeInfos.put("k4", changeInfo2);
+ ConfigKVFileChangeEvent event = new ConfigKVFileChangeEvent(changeInfos);
+ file.fireChangeListener(event);
+
+ ContextRefresher mockContextRefresher = context.getBean(ContextRefresher.class);
+ when(mockContextRefresher.refresh()).thenReturn(event.changedKeys());
+
+ Mockito.verify(spyRefresher, Mockito.times(1))
+ .refreshSpringValue("k1");
+ Mockito.verify(spyRefresher, Mockito.times(1))
+ .refreshSpringValue("k2");
+ Mockito.verify(spyRefresher, Mockito.times(1))
+ .refreshSpringValue("k4");
+ Mockito.verify(spyRefresher, Mockito.times(1))
+ .refreshConfigurationProperties(event.changedKeys());
+ }
+
+ @SpringBootApplication
+ protected static class TestApplication {
+
+ @Primary
+ @Bean
+ public PolarisPropertySourceManager polarisPropertySourceManager() {
+ return mock(PolarisPropertySourceManager.class);
+ }
+
+ @Primary
+ @Bean
+ public ContextRefresher contextRefresher() {
+ return mock(ContextRefresher.class);
+ }
+
+ @Component
+ @RefreshScope
+ protected static class TestBeanWithRefreshScope {
+
+ }
+ }
+}