From 0ff2b595a1a54041acbd011b9f216cdcd68bb13a Mon Sep 17 00:00:00 2001 From: jarvisxiong <691567780@qq.com> Date: Mon, 17 Jul 2023 11:11:02 +0800 Subject: [PATCH] feat:added automatic optimization for dynamic config refresh type. (#1059) --- CHANGELOG.md | 1 + .../PolarisConfigAutoConfiguration.java | 12 ++ ...sConfigRefreshScopeAnnotationDetector.java | 94 +++++++++++ ...arisConfigRefreshOptimizationListener.java | 126 ++++++++++++++ ...figRefreshScopeAnnotationDetectorTest.java | 101 ++++++++++++ ...hOptimizationListenerNotTriggeredTest.java | 154 +++++++++++++++++ ...reshOptimizationListenerTriggeredTest.java | 156 ++++++++++++++++++ 7 files changed, 644 insertions(+) create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigRefreshScopeAnnotationDetector.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListener.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisConfigRefreshScopeAnnotationDetectorTest.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListenerNotTriggeredTest.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/listener/PolarisConfigRefreshOptimizationListenerTriggeredTest.java 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 { + + } + } +}