From b801014b890365e6346f201f42c7426f6b68144d Mon Sep 17 00:00:00 2001 From: anilople Date: Fri, 23 Oct 2020 22:40:19 +0800 Subject: [PATCH 01/42] feat(apollo-client): use ConfigurableBeanFactory to resolve embedded value in ApolloConfigChangeListener --- .../annotation/ApolloAnnotationProcessor.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java index 9c38d14c8b0..3c8503b855c 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java @@ -10,6 +10,10 @@ import java.util.Set; import com.google.common.collect.Sets; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.ReflectionUtils; @@ -18,7 +22,12 @@ * * @author Jason Song(song_s@ctrip.com) */ -public class ApolloAnnotationProcessor extends ApolloProcessor { +public class ApolloAnnotationProcessor extends ApolloProcessor implements BeanFactoryAware { + + /** + * resolve the expression. + */ + private ConfigurableBeanFactory configurableBeanFactory; @Override protected void processField(Object bean, String beanName, Field field) { @@ -39,6 +48,10 @@ protected void processField(Object bean, String beanName, Field field) { @Override protected void processMethod(final Object bean, String beanName, final Method method) { + this.processApolloConfigChangeListener(bean, method); + } + + private void processApolloConfigChangeListener(final Object bean, final Method method) { ApolloConfigChangeListener annotation = AnnotationUtils .findAnnotation(method, ApolloConfigChangeListener.class); if (annotation == null) { @@ -67,7 +80,7 @@ public void onChange(ConfigChangeEvent changeEvent) { Set interestedKeyPrefixes = annotatedInterestedKeyPrefixes.length > 0 ? Sets.newHashSet(annotatedInterestedKeyPrefixes) : null; for (String namespace : namespaces) { - Config config = ConfigService.getConfig(namespace); + Config config = ConfigService.getConfig(this.configurableBeanFactory.resolveEmbeddedValue(namespace)); if (interestedKeys == null && interestedKeyPrefixes == null) { config.addChangeListener(configChangeListener); @@ -76,4 +89,9 @@ public void onChange(ConfigChangeEvent changeEvent) { } } } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.configurableBeanFactory = (ConfigurableBeanFactory) beanFactory; + } } From 65135395a72eab87cb0b5144ad49b47296951f46 Mon Sep 17 00:00:00 2001 From: anilople Date: Fri, 23 Oct 2020 22:40:54 +0800 Subject: [PATCH 02/42] test(apollo-client): resolve embedded value in ApolloConfigChangeListener --- ...otationApolloConfigChangeListenerTest.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationApolloConfigChangeListenerTest.java diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationApolloConfigChangeListenerTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationApolloConfigChangeListenerTest.java new file mode 100644 index 00000000000..2c6b0f682c8 --- /dev/null +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationApolloConfigChangeListenerTest.java @@ -0,0 +1,92 @@ +package com.ctrip.framework.apollo.spring; + +import static org.mockito.Mockito.mock; + +import com.ctrip.framework.apollo.Config; +import com.ctrip.framework.apollo.core.ConfigConsts; +import com.ctrip.framework.apollo.model.ConfigChangeEvent; +import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; +import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; +import java.util.Properties; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; + +public class JavaConfigAnnotationApolloConfigChangeListenerTest extends + AbstractSpringIntegrationTest { + + @Test + public void testResolveExpressionSimple() { + Config applicationConfig = mock(Config.class); + mockConfig("application", applicationConfig); + new AnnotationConfigApplicationContext(TestResolveExpressionSimpleConfiguration.class); + } + + /** + * resolve namespace's name from system property. + */ + @Test + public void testResolveExpressionFromSystemProperty() { + Config applicationConfig = mock(Config.class); + mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig); + + final String namespaceName = "magicRedis"; + System.setProperty("redis.namespace", namespaceName); + Config redisConfig = mock(Config.class); + mockConfig(namespaceName, redisConfig); + new AnnotationConfigApplicationContext( + TestResolveExpressionFromSystemPropertyConfiguration.class); + } + + /** + * resolve namespace from config. ${mysql.namespace} will be resolved by config from namespace + * application. + */ + @Test + public void testResolveExpressionFromApplicationNamespace() { + Config applicationConfig = mock(Config.class); + mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig); + + final String namespaceKey = "mysql.namespace"; + final String namespaceName = "magicMysqlNamespaceApplication"; + + Properties properties = new Properties(); + properties.setProperty(namespaceKey, namespaceName); + this.prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + Config mysqlConfig = mock(Config.class); + mockConfig(namespaceName, mysqlConfig); + + new AnnotationConfigApplicationContext( + TestResolveExpressionFromApplicationNamespaceConfiguration.class); + } + + @Configuration + @EnableApolloConfig + static class TestResolveExpressionSimpleConfiguration { + + @ApolloConfigChangeListener("${simple.application:application}") + private void onChange(ConfigChangeEvent event) { + } + } + + @Configuration + @EnableApolloConfig + static class TestResolveExpressionFromSystemPropertyConfiguration { + + @ApolloConfigChangeListener(value = {ConfigConsts.NAMESPACE_APPLICATION, + "${redis.namespace}"}) + private void onChange(ConfigChangeEvent event) { + } + } + + @Configuration + @EnableApolloConfig + static class TestResolveExpressionFromApplicationNamespaceConfiguration { + + @ApolloConfigChangeListener(value = {ConfigConsts.NAMESPACE_APPLICATION, + "${mysql.namespace}"}) + private void onChange(ConfigChangeEvent event) { + } + } +} From 7823bda4845c23998c995cf2802de9d2c87eea6a Mon Sep 17 00:00:00 2001 From: anilople Date: Sat, 24 Oct 2020 09:59:56 +0800 Subject: [PATCH 03/42] docs(apollo-client): ApolloConfigChangeListener's embedded value usage --- .../spring/annotation/ApolloConfigChangeListener.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigChangeListener.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigChangeListener.java index 4ba569ddbea..331294991b2 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigChangeListener.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigChangeListener.java @@ -18,6 +18,12 @@ * //handle change event * } *
+ * //Listener on namespace of "xxx" by default, or the namespace which value of key "redis.namespace" you specify. + * @ApolloConfigChangeListener({"${redis.namespace:xxx}"}) + * private void onChange(ConfigChangeEvent changeEvent) { + * //handle change event + * } + *
* //Listener on namespaces of "someNamespace" and "anotherNamespace", will only be notified when "someKey" or "anotherKey" is changed * @ApolloConfigChangeListener(value = {"someNamespace","anotherNamespace"}, interestedKeys = {"someKey", "anotherKey"}) * private void onChange(ConfigChangeEvent changeEvent) { From d5407cdc4cf7f2613cf5d8026c0b3aba4bb04b17 Mon Sep 17 00:00:00 2001 From: anilople Date: Sun, 1 Nov 2020 23:04:49 +0800 Subject: [PATCH 04/42] feat(apollo-client): resolve embedded value in '@ApolloConfig' --- .../apollo/spring/annotation/ApolloAnnotationProcessor.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java index 3c8503b855c..5212c854c08 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java @@ -39,8 +39,9 @@ protected void processField(Object bean, String beanName, Field field) { Preconditions.checkArgument(Config.class.isAssignableFrom(field.getType()), "Invalid type: %s for field: %s, should be Config", field.getType(), field); - String namespace = annotation.value(); - Config config = ConfigService.getConfig(namespace); + final String namespace = annotation.value(); + final String resolvedNamespace = this.configurableBeanFactory.resolveEmbeddedValue(namespace); + Config config = ConfigService.getConfig(resolvedNamespace); ReflectionUtils.makeAccessible(field); ReflectionUtils.setField(field, bean, config); From 7bb01478894e68cefe3da80a433dbb3ca50d0f68 Mon Sep 17 00:00:00 2001 From: anilople Date: Sun, 1 Nov 2020 23:15:17 +0800 Subject: [PATCH 05/42] feat(apollo-client): resolve embedded value in '@EnableApolloConfig' --- .../DefaultApolloConfigRegistrarHelper.java | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultApolloConfigRegistrarHelper.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultApolloConfigRegistrarHelper.java index e25f4a88caf..38c3a2c573c 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultApolloConfigRegistrarHelper.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultApolloConfigRegistrarHelper.java @@ -11,20 +11,31 @@ import com.google.common.collect.Lists; import java.util.HashMap; import java.util.Map; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; -public class DefaultApolloConfigRegistrarHelper implements ApolloConfigRegistrarHelper { +public class DefaultApolloConfigRegistrarHelper implements ApolloConfigRegistrarHelper, + BeanFactoryAware { + + /** + * resolve the expression. + */ + private ConfigurableBeanFactory configurableBeanFactory; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes attributes = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(EnableApolloConfig.class.getName())); - String[] namespaces = attributes.getStringArray("value"); - int order = attributes.getNumber("order"); - PropertySourcesProcessor.addNamespaces(Lists.newArrayList(namespaces), order); + final String[] namespaces = attributes.getStringArray("value"); + final int order = attributes.getNumber("order"); + final String[] resolvedNamespaces = this.resolveNamespaces(namespaces); + PropertySourcesProcessor.addNamespaces(Lists.newArrayList(resolvedNamespaces), order); Map propertySourcesPlaceholderPropertyValues = new HashMap<>(); // to make sure the default PropertySourcesPlaceholderConfigurer's priority is higher than PropertyPlaceholderConfigurer @@ -44,8 +55,22 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B ApolloJsonValueProcessor.class); } + private String[] resolveNamespaces(String[] namespaces) { + String[] resolvedNamespaces = new String[namespaces.length]; + for (int i = 0; i < namespaces.length; i++) { + resolvedNamespaces[i] = this.configurableBeanFactory.resolveEmbeddedValue(namespaces[i]); + } + return resolvedNamespaces; + } + @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.configurableBeanFactory = (ConfigurableBeanFactory) beanFactory; + } + } From b096d8f732af8e6271cd1ad92d0f6222e9553e2a Mon Sep 17 00:00:00 2001 From: anilople Date: Mon, 2 Nov 2020 21:37:17 +0800 Subject: [PATCH 06/42] fix(apollo-client): implement BeanFactoryAware in ApolloConfigRegistrar --- .../spring/annotation/ApolloConfigRegistrar.java | 13 +++++++++++-- .../spring/spi/ApolloConfigRegistrarHelper.java | 3 ++- .../spi/DefaultApolloConfigRegistrarHelper.java | 4 +--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigRegistrar.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigRegistrar.java index 562866d7aa5..dfdd58f29b7 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigRegistrar.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigRegistrar.java @@ -2,6 +2,9 @@ import com.ctrip.framework.apollo.spring.spi.ApolloConfigRegistrarHelper; import com.ctrip.framework.foundation.internals.ServiceBootstrap; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; @@ -9,12 +12,18 @@ /** * @author Jason Song(song_s@ctrip.com) */ -public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar { +public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { - private ApolloConfigRegistrarHelper helper = ServiceBootstrap.loadPrimary(ApolloConfigRegistrarHelper.class); + private final ApolloConfigRegistrarHelper helper = ServiceBootstrap.loadPrimary(ApolloConfigRegistrarHelper.class); @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { helper.registerBeanDefinitions(importingClassMetadata, registry); } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.helper.setBeanFactory(beanFactory); + } + } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/ApolloConfigRegistrarHelper.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/ApolloConfigRegistrarHelper.java index 12442019355..521e6b5e944 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/ApolloConfigRegistrarHelper.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/ApolloConfigRegistrarHelper.java @@ -1,10 +1,11 @@ package com.ctrip.framework.apollo.spring.spi; import com.ctrip.framework.apollo.core.spi.Ordered; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.core.type.AnnotationMetadata; -public interface ApolloConfigRegistrarHelper extends Ordered { +public interface ApolloConfigRegistrarHelper extends Ordered, BeanFactoryAware { void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry); } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultApolloConfigRegistrarHelper.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultApolloConfigRegistrarHelper.java index 38c3a2c573c..0c89bd42716 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultApolloConfigRegistrarHelper.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultApolloConfigRegistrarHelper.java @@ -13,15 +13,13 @@ import java.util.Map; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; -public class DefaultApolloConfigRegistrarHelper implements ApolloConfigRegistrarHelper, - BeanFactoryAware { +public class DefaultApolloConfigRegistrarHelper implements ApolloConfigRegistrarHelper { /** * resolve the expression. From d5670b53afcdcd63820b581d61a288f7906f3941 Mon Sep 17 00:00:00 2001 From: anilople Date: Tue, 3 Nov 2020 21:44:00 +0800 Subject: [PATCH 07/42] refactor(apollo-client): process annotation ApolloConfig --- .../annotation/ApolloAnnotationProcessor.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java index 5212c854c08..d60f271bbe6 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java @@ -31,6 +31,15 @@ public class ApolloAnnotationProcessor extends ApolloProcessor implements BeanFa @Override protected void processField(Object bean, String beanName, Field field) { + this.processApolloConfig(bean, field); + } + + @Override + protected void processMethod(final Object bean, String beanName, final Method method) { + this.processApolloConfigChangeListener(bean, method); + } + + private void processApolloConfig(Object bean, Field field) { ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class); if (annotation == null) { return; @@ -47,11 +56,6 @@ protected void processField(Object bean, String beanName, Field field) { ReflectionUtils.setField(field, bean, config); } - @Override - protected void processMethod(final Object bean, String beanName, final Method method) { - this.processApolloConfigChangeListener(bean, method); - } - private void processApolloConfigChangeListener(final Object bean, final Method method) { ApolloConfigChangeListener annotation = AnnotationUtils .findAnnotation(method, ApolloConfigChangeListener.class); From ac672e6b5b8110d680fb96a9dfb1c3cfc6cc3687 Mon Sep 17 00:00:00 2001 From: anilople Date: Tue, 3 Nov 2020 23:06:47 +0800 Subject: [PATCH 08/42] refactor(apollo-client): move '@ApolloJsonValue' processor --- .../annotation/ApolloAnnotationProcessor.java | 111 +++++++++++++++- .../annotation/ApolloJsonValueProcessor.java | 124 ------------------ .../DefaultApolloConfigRegistrarHelper.java | 3 - ...tConfigPropertySourcesProcessorHelper.java | 3 - 4 files changed, 108 insertions(+), 133 deletions(-) delete mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloJsonValueProcessor.java diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java index d60f271bbe6..b7536d536db 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java @@ -3,13 +3,23 @@ import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.ConfigChangeListener; import com.ctrip.framework.apollo.ConfigService; +import com.ctrip.framework.apollo.build.ApolloInjector; import com.ctrip.framework.apollo.model.ConfigChangeEvent; +import com.ctrip.framework.apollo.spring.property.PlaceholderHelper; +import com.ctrip.framework.apollo.spring.property.SpringValue; +import com.ctrip.framework.apollo.spring.property.SpringValueRegistry; +import com.ctrip.framework.apollo.spring.util.SpringInjector; +import com.ctrip.framework.apollo.util.ConfigUtil; import com.google.common.base.Preconditions; +import com.google.gson.Gson; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.util.Set; import com.google.common.collect.Sets; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; @@ -24,6 +34,19 @@ */ public class ApolloAnnotationProcessor extends ApolloProcessor implements BeanFactoryAware { + private static final Logger logger = LoggerFactory.getLogger(ApolloAnnotationProcessor.class); + private static final Gson GSON = new Gson(); + + private final ConfigUtil configUtil; + private final PlaceholderHelper placeholderHelper; + private final SpringValueRegistry springValueRegistry; + + public ApolloAnnotationProcessor() { + configUtil = ApolloInjector.getInstance(ConfigUtil.class); + placeholderHelper = SpringInjector.getInstance(PlaceholderHelper.class); + springValueRegistry = SpringInjector.getInstance(SpringValueRegistry.class); + } + /** * resolve the expression. */ @@ -32,11 +55,13 @@ public class ApolloAnnotationProcessor extends ApolloProcessor implements BeanFa @Override protected void processField(Object bean, String beanName, Field field) { this.processApolloConfig(bean, field); + this.processApolloJsonValue(bean, beanName, field); } @Override protected void processMethod(final Object bean, String beanName, final Method method) { this.processApolloConfigChangeListener(bean, method); + this.processApolloJsonValue(bean, beanName, method); } private void processApolloConfig(Object bean, Field field) { @@ -81,11 +106,15 @@ public void onChange(ConfigChangeEvent changeEvent) { } }; - Set interestedKeys = annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null; - Set interestedKeyPrefixes = annotatedInterestedKeyPrefixes.length > 0 ? Sets.newHashSet(annotatedInterestedKeyPrefixes) : null; + Set interestedKeys = + annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null; + Set interestedKeyPrefixes = + annotatedInterestedKeyPrefixes.length > 0 ? Sets.newHashSet(annotatedInterestedKeyPrefixes) + : null; for (String namespace : namespaces) { - Config config = ConfigService.getConfig(this.configurableBeanFactory.resolveEmbeddedValue(namespace)); + Config config = ConfigService + .getConfig(this.configurableBeanFactory.resolveEmbeddedValue(namespace)); if (interestedKeys == null && interestedKeyPrefixes == null) { config.addChangeListener(configChangeListener); @@ -95,6 +124,82 @@ public void onChange(ConfigChangeEvent changeEvent) { } } + + private void processApolloJsonValue(Object bean, String beanName, Field field) { + ApolloJsonValue apolloJsonValue = AnnotationUtils.getAnnotation(field, ApolloJsonValue.class); + if (apolloJsonValue == null) { + return; + } + String placeholder = apolloJsonValue.value(); + Object propertyValue = placeholderHelper + .resolvePropertyValue(this.configurableBeanFactory, beanName, placeholder); + + // propertyValue will never be null, as @ApolloJsonValue will not allow that + if (!(propertyValue instanceof String)) { + return; + } + + boolean accessible = field.isAccessible(); + field.setAccessible(true); + ReflectionUtils + .setField(field, bean, parseJsonValue((String) propertyValue, field.getGenericType())); + field.setAccessible(accessible); + + if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) { + Set keys = placeholderHelper.extractPlaceholderKeys(placeholder); + for (String key : keys) { + SpringValue springValue = new SpringValue(key, placeholder, bean, beanName, field, true); + springValueRegistry.register(this.configurableBeanFactory, key, springValue); + logger.debug("Monitoring {}", springValue); + } + } + } + + private void processApolloJsonValue(Object bean, String beanName, Method method) { + ApolloJsonValue apolloJsonValue = AnnotationUtils.getAnnotation(method, ApolloJsonValue.class); + if (apolloJsonValue == null) { + return; + } + String placeHolder = apolloJsonValue.value(); + + Object propertyValue = placeholderHelper + .resolvePropertyValue(this.configurableBeanFactory, beanName, placeHolder); + + // propertyValue will never be null, as @ApolloJsonValue will not allow that + if (!(propertyValue instanceof String)) { + return; + } + + Type[] types = method.getGenericParameterTypes(); + Preconditions.checkArgument(types.length == 1, + "Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters", + bean.getClass().getName(), method.getName(), method.getParameterTypes().length); + + boolean accessible = method.isAccessible(); + method.setAccessible(true); + ReflectionUtils.invokeMethod(method, bean, parseJsonValue((String) propertyValue, types[0])); + method.setAccessible(accessible); + + if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) { + Set keys = placeholderHelper.extractPlaceholderKeys(placeHolder); + for (String key : keys) { + SpringValue springValue = new SpringValue(key, apolloJsonValue.value(), bean, beanName, + method, true); + springValueRegistry.register(this.configurableBeanFactory, key, springValue); + logger.debug("Monitoring {}", springValue); + } + } + } + + private Object parseJsonValue(String json, Type targetType) { + try { + return GSON.fromJson(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.configurableBeanFactory = (ConfigurableBeanFactory) beanFactory; diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloJsonValueProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloJsonValueProcessor.java deleted file mode 100644 index 0c98bc451be..00000000000 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloJsonValueProcessor.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.ctrip.framework.apollo.spring.annotation; - -import com.ctrip.framework.apollo.build.ApolloInjector; -import com.ctrip.framework.apollo.spring.property.PlaceholderHelper; -import com.ctrip.framework.apollo.spring.property.SpringValue; -import com.ctrip.framework.apollo.spring.property.SpringValueRegistry; -import com.ctrip.framework.apollo.spring.util.SpringInjector; -import com.ctrip.framework.apollo.util.ConfigUtil; -import com.google.common.base.Preconditions; -import com.google.gson.Gson; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.Set; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.util.ReflectionUtils; - -/** - * Create by zhangzheng on 2018/2/6 - */ -public class ApolloJsonValueProcessor extends ApolloProcessor implements BeanFactoryAware { - - private static final Logger logger = LoggerFactory.getLogger(ApolloJsonValueProcessor.class); - private static final Gson GSON = new Gson(); - - private final ConfigUtil configUtil; - private final PlaceholderHelper placeholderHelper; - private final SpringValueRegistry springValueRegistry; - private ConfigurableBeanFactory beanFactory; - - public ApolloJsonValueProcessor() { - configUtil = ApolloInjector.getInstance(ConfigUtil.class); - placeholderHelper = SpringInjector.getInstance(PlaceholderHelper.class); - springValueRegistry = SpringInjector.getInstance(SpringValueRegistry.class); - } - - @Override - protected void processField(Object bean, String beanName, Field field) { - ApolloJsonValue apolloJsonValue = AnnotationUtils.getAnnotation(field, ApolloJsonValue.class); - if (apolloJsonValue == null) { - return; - } - String placeholder = apolloJsonValue.value(); - Object propertyValue = placeholderHelper - .resolvePropertyValue(beanFactory, beanName, placeholder); - - // propertyValue will never be null, as @ApolloJsonValue will not allow that - if (!(propertyValue instanceof String)) { - return; - } - - boolean accessible = field.isAccessible(); - field.setAccessible(true); - ReflectionUtils - .setField(field, bean, parseJsonValue((String)propertyValue, field.getGenericType())); - field.setAccessible(accessible); - - if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) { - Set keys = placeholderHelper.extractPlaceholderKeys(placeholder); - for (String key : keys) { - SpringValue springValue = new SpringValue(key, placeholder, bean, beanName, field, true); - springValueRegistry.register(beanFactory, key, springValue); - logger.debug("Monitoring {}", springValue); - } - } - } - - @Override - protected void processMethod(Object bean, String beanName, Method method) { - ApolloJsonValue apolloJsonValue = AnnotationUtils.getAnnotation(method, ApolloJsonValue.class); - if (apolloJsonValue == null) { - return; - } - String placeHolder = apolloJsonValue.value(); - - Object propertyValue = placeholderHelper - .resolvePropertyValue(beanFactory, beanName, placeHolder); - - // propertyValue will never be null, as @ApolloJsonValue will not allow that - if (!(propertyValue instanceof String)) { - return; - } - - Type[] types = method.getGenericParameterTypes(); - Preconditions.checkArgument(types.length == 1, - "Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters", - bean.getClass().getName(), method.getName(), method.getParameterTypes().length); - - boolean accessible = method.isAccessible(); - method.setAccessible(true); - ReflectionUtils.invokeMethod(method, bean, parseJsonValue((String)propertyValue, types[0])); - method.setAccessible(accessible); - - if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) { - Set keys = placeholderHelper.extractPlaceholderKeys(placeHolder); - for (String key : keys) { - SpringValue springValue = new SpringValue(key, apolloJsonValue.value(), bean, beanName, - method, true); - springValueRegistry.register(beanFactory, key, springValue); - logger.debug("Monitoring {}", springValue); - } - } - } - - private Object parseJsonValue(String json, Type targetType) { - try { - return GSON.fromJson(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/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultApolloConfigRegistrarHelper.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultApolloConfigRegistrarHelper.java index 0c89bd42716..e98710f71db 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultApolloConfigRegistrarHelper.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultApolloConfigRegistrarHelper.java @@ -2,7 +2,6 @@ import com.ctrip.framework.apollo.core.spi.Ordered; import com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor; -import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValueProcessor; import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; import com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor; import com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor; @@ -49,8 +48,6 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B SpringValueProcessor.class); BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(), SpringValueDefinitionProcessor.class); - BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(), - ApolloJsonValueProcessor.class); } private String[] resolveNamespaces(String[] namespaces) { diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultConfigPropertySourcesProcessorHelper.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultConfigPropertySourcesProcessorHelper.java index 5e95c99e880..d4d0ff3ea05 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultConfigPropertySourcesProcessorHelper.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultConfigPropertySourcesProcessorHelper.java @@ -2,7 +2,6 @@ import com.ctrip.framework.apollo.core.spi.Ordered; import com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor; -import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValueProcessor; import com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor; import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor; import com.ctrip.framework.apollo.spring.util.BeanRegistrationUtil; @@ -26,8 +25,6 @@ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) t ApolloAnnotationProcessor.class); BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class); - BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(), - ApolloJsonValueProcessor.class); processSpringValueDefinition(registry); } From 2e1616cc4b85da2077ead73ca8ba7362593ddb2b Mon Sep 17 00:00:00 2001 From: anilople Date: Tue, 3 Nov 2020 23:15:25 +0800 Subject: [PATCH 09/42] refactor(apollo-client): move field --- .../spring/annotation/ApolloAnnotationProcessor.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java index b7536d536db..0bbf51cfa76 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java @@ -41,17 +41,17 @@ public class ApolloAnnotationProcessor extends ApolloProcessor implements BeanFa private final PlaceholderHelper placeholderHelper; private final SpringValueRegistry springValueRegistry; + /** + * resolve the expression. + */ + private ConfigurableBeanFactory configurableBeanFactory; + public ApolloAnnotationProcessor() { configUtil = ApolloInjector.getInstance(ConfigUtil.class); placeholderHelper = SpringInjector.getInstance(PlaceholderHelper.class); springValueRegistry = SpringInjector.getInstance(SpringValueRegistry.class); } - /** - * resolve the expression. - */ - private ConfigurableBeanFactory configurableBeanFactory; - @Override protected void processField(Object bean, String beanName, Field field) { this.processApolloConfig(bean, field); From 66389c7b61e7c304ed7580b2259c34dadcdb242b Mon Sep 17 00:00:00 2001 From: anilople Date: Wed, 4 Nov 2020 21:55:14 +0800 Subject: [PATCH 10/42] test(apollo-client): embedded value in '@EnableApolloConfig' cannot be resolved --- .../spring/annotation/ApolloConfigTest.java | 32 +++++++++++++++++++ .../annotation/EnableApolloConfigTest.java | 30 +++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigTest.java create mode 100644 apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfigTest.java diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigTest.java new file mode 100644 index 00000000000..9f30bfbed22 --- /dev/null +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigTest.java @@ -0,0 +1,32 @@ +package com.ctrip.framework.apollo.spring.annotation; + +import static org.mockito.Mockito.mock; + +import com.ctrip.framework.apollo.Config; +import com.ctrip.framework.apollo.core.ConfigConsts; +import com.ctrip.framework.apollo.spring.AbstractSpringIntegrationTest; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; + +public class ApolloConfigTest extends AbstractSpringIntegrationTest { + + @Test + public void testResolveExpressionSimple() { + mockConfig("application", mock(Config.class)); + mockConfig("xxx", mock(Config.class)); + mockConfig("yyy", mock(Config.class)); + new AnnotationConfigApplicationContext(TestResolveExpressionSimpleConfiguration.class); + } + + @Configuration + @EnableApolloConfig(value = {"xxx", "yyy"}) + protected static class TestResolveExpressionSimpleConfiguration { + + @ApolloConfig(value = "${simple.namespace:xxx}") + private Config xxx; + + @ApolloConfig(value = "${simple.namespace:yyy}") + private Config yyy; + } +} diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfigTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfigTest.java new file mode 100644 index 00000000000..642fd02fec3 --- /dev/null +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfigTest.java @@ -0,0 +1,30 @@ +package com.ctrip.framework.apollo.spring.annotation; + +import static org.mockito.Mockito.mock; + +import com.ctrip.framework.apollo.Config; +import com.ctrip.framework.apollo.core.ConfigConsts; +import com.ctrip.framework.apollo.spring.AbstractSpringIntegrationTest; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; + +public class EnableApolloConfigTest extends AbstractSpringIntegrationTest { + + @Test + public void testResolveExpressionSimple() { + mockConfig("application", mock(Config.class)); + mockConfig("xxx", mock(Config.class)); + new AnnotationConfigApplicationContext(TestResolveExpressionSimpleConfiguration.class); + } + + // TODO, cannot resolve "${simple.namespace:xxx}" in @EnableApolloConfig + // because have no embeddedValueResolvers in ConfigurableBeanFactory when use ImportBeanDefinitionRegistrar + @Configuration + @EnableApolloConfig(value = {ConfigConsts.NAMESPACE_APPLICATION, "${simple.namespace:xxx}"}) + static class TestResolveExpressionSimpleConfiguration { + + } + + +} From ee934e3c1ae3b3484de4259551a10d7176ea1f65 Mon Sep 17 00:00:00 2001 From: anilople Date: Fri, 6 Nov 2020 21:43:10 +0800 Subject: [PATCH 11/42] feat(apollo-client): use Environment to resolve value in '@EnableApolloConfig' instead of ConfigurableBeanFactory --- .../annotation/ApolloConfigRegistrar.java | 11 +++++------ .../spring/spi/ApolloConfigRegistrarHelper.java | 4 ++-- .../spi/DefaultApolloConfigRegistrarHelper.java | 17 ++++++----------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigRegistrar.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigRegistrar.java index dfdd58f29b7..9f5c7ac35b8 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigRegistrar.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigRegistrar.java @@ -2,17 +2,16 @@ import com.ctrip.framework.apollo.spring.spi.ApolloConfigRegistrarHelper; import com.ctrip.framework.foundation.internals.ServiceBootstrap; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotationMetadata; /** * @author Jason Song(song_s@ctrip.com) */ -public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware { +public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware { private final ApolloConfigRegistrarHelper helper = ServiceBootstrap.loadPrimary(ApolloConfigRegistrarHelper.class); @@ -22,8 +21,8 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B } @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.helper.setBeanFactory(beanFactory); + public void setEnvironment(Environment environment) { + this.helper.setEnvironment(environment); } } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/ApolloConfigRegistrarHelper.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/ApolloConfigRegistrarHelper.java index 521e6b5e944..3aa454e05b0 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/ApolloConfigRegistrarHelper.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/ApolloConfigRegistrarHelper.java @@ -1,11 +1,11 @@ package com.ctrip.framework.apollo.spring.spi; import com.ctrip.framework.apollo.core.spi.Ordered; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.EnvironmentAware; import org.springframework.core.type.AnnotationMetadata; -public interface ApolloConfigRegistrarHelper extends Ordered, BeanFactoryAware { +public interface ApolloConfigRegistrarHelper extends Ordered, EnvironmentAware { void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry); } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultApolloConfigRegistrarHelper.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultApolloConfigRegistrarHelper.java index e98710f71db..3692b1e7ab7 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultApolloConfigRegistrarHelper.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/spi/DefaultApolloConfigRegistrarHelper.java @@ -10,20 +10,15 @@ import com.google.common.collect.Lists; import java.util.HashMap; import java.util.Map; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotationMetadata; public class DefaultApolloConfigRegistrarHelper implements ApolloConfigRegistrarHelper { - /** - * resolve the expression. - */ - private ConfigurableBeanFactory configurableBeanFactory; + private Environment environment; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { @@ -53,7 +48,8 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B private String[] resolveNamespaces(String[] namespaces) { String[] resolvedNamespaces = new String[namespaces.length]; for (int i = 0; i < namespaces.length; i++) { - resolvedNamespaces[i] = this.configurableBeanFactory.resolveEmbeddedValue(namespaces[i]); + // throw IllegalArgumentException if given text is null or if any placeholders are unresolvable + resolvedNamespaces[i] = this.environment.resolveRequiredPlaceholders(namespaces[i]); } return resolvedNamespaces; } @@ -64,8 +60,7 @@ public int getOrder() { } @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.configurableBeanFactory = (ConfigurableBeanFactory) beanFactory; + public void setEnvironment(Environment environment) { + this.environment = environment; } - } From cc1bbf7de43709bedc3f7f7c41ce8ae82b6f828e Mon Sep 17 00:00:00 2001 From: anilople Date: Fri, 6 Nov 2020 21:43:35 +0800 Subject: [PATCH 12/42] test(apollo-client): '@EnableApolloConfig' resolving testing --- .../annotation/EnableApolloConfigTest.java | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfigTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfigTest.java index 642fd02fec3..c94a1bd6753 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfigTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfigTest.java @@ -15,16 +15,42 @@ public class EnableApolloConfigTest extends AbstractSpringIntegrationTest { public void testResolveExpressionSimple() { mockConfig("application", mock(Config.class)); mockConfig("xxx", mock(Config.class)); - new AnnotationConfigApplicationContext(TestResolveExpressionSimpleConfiguration.class); + new AnnotationConfigApplicationContext( + TestResolveExpressionWithDefaultValueConfiguration.class); + } + + @Test + public void testResolveExpressionFromSystemProperty() { + mockConfig("application", mock(Config.class)); + + final String resolvedNamespaceName = "yyy"; + System.setProperty("from.system.property", resolvedNamespaceName); + mockConfig(resolvedNamespaceName, mock(Config.class)); + new AnnotationConfigApplicationContext( + TestResolveExpressionFromSystemPropertyConfiguration.class); + } + + @Test(expected = IllegalArgumentException.class) + public void testUnresolvable() { + new AnnotationConfigApplicationContext(TestUnresolvableConfiguration.class); } - // TODO, cannot resolve "${simple.namespace:xxx}" in @EnableApolloConfig - // because have no embeddedValueResolvers in ConfigurableBeanFactory when use ImportBeanDefinitionRegistrar @Configuration @EnableApolloConfig(value = {ConfigConsts.NAMESPACE_APPLICATION, "${simple.namespace:xxx}"}) - static class TestResolveExpressionSimpleConfiguration { + static class TestResolveExpressionWithDefaultValueConfiguration { } + @Configuration + @EnableApolloConfig(value = {ConfigConsts.NAMESPACE_APPLICATION, "${from.system.property}"}) + static class TestResolveExpressionFromSystemPropertyConfiguration { + + } + + @Configuration + @EnableApolloConfig(value = "${unresolvable.property}") + static class TestUnresolvableConfiguration { + + } } From bd3c5de61ca2033979cb674c518d93e309dd34d1 Mon Sep 17 00:00:00 2001 From: wxq Date: Sun, 8 Nov 2020 15:06:08 +0800 Subject: [PATCH 13/42] docs(apollo-client): ApolloConfigChangeListener usage Co-authored-by: Jason Song --- .../apollo/spring/annotation/ApolloConfigChangeListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigChangeListener.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigChangeListener.java index 331294991b2..76d470b473b 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigChangeListener.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigChangeListener.java @@ -18,7 +18,7 @@ * //handle change event * } *
- * //Listener on namespace of "xxx" by default, or the namespace which value of key "redis.namespace" you specify. + * //The namespace could also be specified as a placeholder, e.g. ${redis.namespace:xxx}, which will use the value of the key "redis.namespace" or "xxx" if this key is not configured. * @ApolloConfigChangeListener({"${redis.namespace:xxx}"}) * private void onChange(ConfigChangeEvent changeEvent) { * //handle change event From 4170a992b662a28ebaee814fac7edad94724df29 Mon Sep 17 00:00:00 2001 From: anilople Date: Sun, 8 Nov 2020 15:19:24 +0800 Subject: [PATCH 14/42] docs(apollo-client): '@ApolloConfig' usage. --- .../framework/apollo/spring/annotation/ApolloConfig.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfig.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfig.java index 731bd6f5839..e7d21371ea1 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfig.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfig.java @@ -18,6 +18,15 @@ * private Config config; * * + *

Usage example with placeholder:

+ *
+ * // The namespace could also be specified as a placeholder, e.g. ${redis.namespace:xxx},
+ * // which will use the value of the key "redis.namespace" or "xxx" if this key is not configured.
+ * @ApolloConfig("${redis.namespace:xxx}")
+ * private Config config;
+ * 
+ * + * * @author Jason Song(song_s@ctrip.com) */ @Retention(RetentionPolicy.RUNTIME) From 4c0e7ded17ae3a475f6073e7cacd3fe0cf0e596a Mon Sep 17 00:00:00 2001 From: anilople Date: Sun, 8 Nov 2020 15:19:40 +0800 Subject: [PATCH 15/42] docs(apollo-client): '@EnableApolloConfig' usage. --- .../spring/annotation/EnableApolloConfig.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfig.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfig.java index 9572f970080..ae5781ed28d 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfig.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfig.java @@ -14,7 +14,7 @@ /** * Use this annotation to register Apollo property sources when using Java Config. * - *

Configuration example:

+ *

Configuration example with multiple namespaces:

*
  * @Configuration
  * @EnableApolloConfig({"someNamespace","anotherNamespace"})
@@ -23,6 +23,17 @@
  * }
  * 
* + *

Configuration example with placeholder:

+ *
+ * // The namespace could also be specified as a placeholder, e.g. ${redis.namespace:xxx},
+ * // which will use the value of the key "redis.namespace" or "xxx" if this key is not configured.
+ * @Configuration
+ * @EnableApolloConfig({"${redis.namespace:xxx}"})
+ * public class AppConfig {
+ *
+ * }
+ * 
+ * * @author Jason Song(song_s@ctrip.com) */ @Retention(RetentionPolicy.RUNTIME) From 44d96bbc5b9f7467e16805af1abc7f3b183ec285 Mon Sep 17 00:00:00 2001 From: anilople Date: Tue, 10 Nov 2020 22:47:40 +0800 Subject: [PATCH 16/42] feat(apollo-client): use Environment to resolve placeholder --- .../annotation/ApolloAnnotationProcessor.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java index 0bbf51cfa76..3d8b575f243 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java @@ -24,7 +24,9 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.EnvironmentAware; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.env.Environment; import org.springframework.util.ReflectionUtils; /** @@ -32,7 +34,8 @@ * * @author Jason Song(song_s@ctrip.com) */ -public class ApolloAnnotationProcessor extends ApolloProcessor implements BeanFactoryAware { +public class ApolloAnnotationProcessor extends ApolloProcessor implements BeanFactoryAware, + EnvironmentAware { private static final Logger logger = LoggerFactory.getLogger(ApolloAnnotationProcessor.class); private static final Gson GSON = new Gson(); @@ -46,6 +49,8 @@ public class ApolloAnnotationProcessor extends ApolloProcessor implements BeanFa */ private ConfigurableBeanFactory configurableBeanFactory; + private Environment environment; + public ApolloAnnotationProcessor() { configUtil = ApolloInjector.getInstance(ConfigUtil.class); placeholderHelper = SpringInjector.getInstance(PlaceholderHelper.class); @@ -74,7 +79,7 @@ private void processApolloConfig(Object bean, Field field) { "Invalid type: %s for field: %s, should be Config", field.getType(), field); final String namespace = annotation.value(); - final String resolvedNamespace = this.configurableBeanFactory.resolveEmbeddedValue(namespace); + final String resolvedNamespace = this.environment.resolveRequiredPlaceholders(namespace); Config config = ConfigService.getConfig(resolvedNamespace); ReflectionUtils.makeAccessible(field); @@ -113,8 +118,8 @@ public void onChange(ConfigChangeEvent changeEvent) { : null; for (String namespace : namespaces) { - Config config = ConfigService - .getConfig(this.configurableBeanFactory.resolveEmbeddedValue(namespace)); + final String resolvedNamespace = this.environment.resolveRequiredPlaceholders(namespace); + Config config = ConfigService.getConfig(resolvedNamespace); if (interestedKeys == null && interestedKeyPrefixes == null) { config.addChangeListener(configChangeListener); @@ -204,4 +209,9 @@ private Object parseJsonValue(String json, Type targetType) { public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.configurableBeanFactory = (ConfigurableBeanFactory) beanFactory; } + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } } From a8b029a9c28454e67a9e7761587a7d214db25d2c Mon Sep 17 00:00:00 2001 From: anilople Date: Wed, 11 Nov 2020 19:18:51 +0800 Subject: [PATCH 17/42] test(apollo-client): placeholder in '@ApolloConfig' --- .../spring/JavaConfigAnnotationTest.java | 126 ++++++++++++++++++ .../spring/annotation/ApolloConfigTest.java | 32 ----- 2 files changed, 126 insertions(+), 32 deletions(-) delete mode 100644 apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigTest.java diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java index 5dfa270eeab..5ab5f9e298e 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java @@ -13,6 +13,7 @@ import com.google.common.collect.Sets; import com.google.common.util.concurrent.SettableFuture; import java.util.Collections; +import java.util.Properties; import java.util.concurrent.TimeUnit; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -43,6 +44,10 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest { private static final String FX_APOLLO_NAMESPACE = "FX.apollo"; private static final String APPLICATION_YAML_NAMESPACE = "application.yaml"; + private static T getSimpleBean(Class clazz) { + return new AnnotationConfigApplicationContext(clazz).getBean(clazz); + } + @Test public void testApolloConfig() throws Exception { Config applicationConfig = mock(Config.class); @@ -281,6 +286,127 @@ public void testApolloConfigChangeListenerWithYamlFile() throws Exception { assertEquals(anotherValue, yamlConfig.getProperty(someKey, null)); } + + @Test + public void testResolveExpressionDefault() { + mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class)); + Config xxx = mock(Config.class); + Config yamlConfig = mock(Config.class); + mockConfig("xxx", xxx); + mockConfig(APPLICATION_YAML_NAMESPACE, yamlConfig); + TestResolveExpressionDefaultConfiguration configuration = getSimpleBean( + TestResolveExpressionDefaultConfiguration.class); + assertEquals(xxx, configuration.getXxx()); + assertEquals(yamlConfig, configuration.getYamlConfig()); + } + + @Test + public void testResolveExpressionFromSystemProperty() { + mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class)); + final String namespaceName = "xxx6"; + final String yamlNamespaceName = "yyy8.yml"; + + System.setProperty("from.system.namespace", namespaceName); + System.setProperty("from.system.yaml.namespace", yamlNamespaceName); + Config config = mock(Config.class); + Config yamlConfig = mock(Config.class); + mockConfig(namespaceName, config); + mockConfig(yamlNamespaceName, yamlConfig); + TestResolveExpressionFromSystemPropertyConfiguration configuration = getSimpleBean( + TestResolveExpressionFromSystemPropertyConfiguration.class); + assertEquals(config, configuration.getConfig()); + assertEquals(yamlConfig, configuration.getYamlConfig()); + } + + @Test(expected = BeanCreationException.class) + public void testUnresolvedExpression() { + mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class)); + getSimpleBean(TestUnresolvedExpressionConfiguration.class); + } + + @Test + public void testResolveExpressionFromApolloConfigNamespaceApplication() { + + final String namespaceName = "xxx6"; + final String yamlNamespaceName = "yyy8.yml"; + { + // hide variable scope + Properties properties = new Properties(); + properties.setProperty("from.namespace.application.key", namespaceName); + properties.setProperty("from.namespace.application.key.yaml", yamlNamespaceName); + this.prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + } + final Config config = mock(Config.class); + final Config yamlConfig = mock(Config.class); + mockConfig(namespaceName, config); + mockConfig(yamlNamespaceName, yamlConfig); + TestResolveExpressionFromApolloConfigNamespaceApplication configuration = getSimpleBean( + TestResolveExpressionFromApolloConfigNamespaceApplication.class); + assertEquals(config, configuration.getConfig()); + assertEquals(yamlConfig, configuration.getYamlConfig()); + } + + @EnableApolloConfig + protected static class TestResolveExpressionDefaultConfiguration { + + @ApolloConfig(value = "${simple.namespace:xxx}") + private Config xxx; + + @ApolloConfig(value = "${simple.yaml.namespace:" + APPLICATION_YAML_NAMESPACE + "}") + private Config yamlConfig; + + public Config getXxx() { + return xxx; + } + + public Config getYamlConfig() { + return yamlConfig; + } + } + + @EnableApolloConfig + protected static class TestResolveExpressionFromSystemPropertyConfiguration { + + @ApolloConfig(value = "${from.system.namespace}") + private Config config; + + @ApolloConfig(value = "${from.system.yaml.namespace}") + private Config yamlConfig; + + public Config getConfig() { + return config; + } + + public Config getYamlConfig() { + return yamlConfig; + } + } + + @EnableApolloConfig + protected static class TestUnresolvedExpressionConfiguration { + + @ApolloConfig(value = "${so.complex.to.resolve}") + private Config config; + } + + @EnableApolloConfig + protected static class TestResolveExpressionFromApolloConfigNamespaceApplication { + + @ApolloConfig(value = "${from.namespace.application.key}") + private Config config; + + @ApolloConfig(value = "${from.namespace.application.key.yaml}") + private Config yamlConfig; + + public Config getConfig() { + return config; + } + + public Config getYamlConfig() { + return yamlConfig; + } + } + private T getBean(Class beanClass, Class... annotatedClasses) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(annotatedClasses); diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigTest.java deleted file mode 100644 index 9f30bfbed22..00000000000 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.ctrip.framework.apollo.spring.annotation; - -import static org.mockito.Mockito.mock; - -import com.ctrip.framework.apollo.Config; -import com.ctrip.framework.apollo.core.ConfigConsts; -import com.ctrip.framework.apollo.spring.AbstractSpringIntegrationTest; -import org.junit.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Configuration; - -public class ApolloConfigTest extends AbstractSpringIntegrationTest { - - @Test - public void testResolveExpressionSimple() { - mockConfig("application", mock(Config.class)); - mockConfig("xxx", mock(Config.class)); - mockConfig("yyy", mock(Config.class)); - new AnnotationConfigApplicationContext(TestResolveExpressionSimpleConfiguration.class); - } - - @Configuration - @EnableApolloConfig(value = {"xxx", "yyy"}) - protected static class TestResolveExpressionSimpleConfiguration { - - @ApolloConfig(value = "${simple.namespace:xxx}") - private Config xxx; - - @ApolloConfig(value = "${simple.namespace:yyy}") - private Config yyy; - } -} From 2f8ba8c88736fc81a18133838887b40471cc9db4 Mon Sep 17 00:00:00 2001 From: anilople Date: Wed, 11 Nov 2020 19:32:41 +0800 Subject: [PATCH 18/42] test(apollo-client): resolve placeholder in '@EnableApolloConfig' --- .../spring/JavaConfigAnnotationTest.java | 69 +++++++++++++++++-- .../annotation/EnableApolloConfigTest.java | 56 --------------- 2 files changed, 65 insertions(+), 60 deletions(-) delete mode 100644 apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfigTest.java diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java index 5ab5f9e298e..ca8e54c3a38 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java @@ -44,8 +44,13 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest { private static final String FX_APOLLO_NAMESPACE = "FX.apollo"; private static final String APPLICATION_YAML_NAMESPACE = "application.yaml"; + private static T getBean(Class beanClass, Class... annotatedClasses) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(annotatedClasses); + return context.getBean(beanClass); + } + private static T getSimpleBean(Class clazz) { - return new AnnotationConfigApplicationContext(clazz).getBean(clazz); + return getBean(clazz, clazz); } @Test @@ -96,6 +101,44 @@ public void testApolloConfigWithInheritance() throws Exception { assertEquals(applicationConfig, bean.getSomeConfig()); } + + @Test + public void testEnableApolloConfigResolveExpressionSimple() { + mockConfig("application", mock(Config.class)); + mockConfig("xxx", mock(Config.class)); + getSimpleBean(TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration.class); + } + + @Test + public void testEnableApolloConfigResolveExpressionFromSystemProperty() { + mockConfig("application", mock(Config.class)); + + final String resolvedNamespaceName = "yyy"; + System.setProperty("from.system.property", resolvedNamespaceName); + mockConfig(resolvedNamespaceName, mock(Config.class)); + getSimpleBean(TestEnableApolloConfigResolveExpressionFromSystemPropertyConfiguration.class); + } + + @Test(expected = IllegalArgumentException.class) + public void testEnableApolloConfigUnresolvable() { + getSimpleBean(TestEnableApolloConfigUnresolvableConfiguration.class); + } + + /** + * Could not resolve placeholder. + * Unfortunately, when use {@link EnableApolloConfig}, + * only key in {@link System#getenv(String)} or {@link System#getProperty(String)} can be resolved. + */ + @Test(expected = IllegalArgumentException.class) + public void testEnableApolloConfigResolveFromNamespaceApplication() { + { + Properties properties = new Properties(); + properties.setProperty("from.namespace.application.key", "abc"); + this.prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + } + getSimpleBean(TestEnableApolloConfigResolveFromNamespaceApplicationConfiguration.class); + } + @Test public void testApolloConfigChangeListener() throws Exception { Config applicationConfig = mock(Config.class); @@ -407,10 +450,28 @@ public Config getYamlConfig() { } } - private T getBean(Class beanClass, Class... annotatedClasses) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(annotatedClasses); + @Configuration + @EnableApolloConfig(value = {ConfigConsts.NAMESPACE_APPLICATION, "${simple.namespace:xxx}"}) + protected static class TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration { + + } + + @Configuration + @EnableApolloConfig(value = {ConfigConsts.NAMESPACE_APPLICATION, "${from.system.property}"}) + static class TestEnableApolloConfigResolveExpressionFromSystemPropertyConfiguration { + + } + + @Configuration + @EnableApolloConfig(value = "${unresolvable.property}") + static class TestEnableApolloConfigUnresolvableConfiguration { + + } + + @Configuration + @EnableApolloConfig(value = "${from.namespace.application.key}") + static class TestEnableApolloConfigResolveFromNamespaceApplicationConfiguration { - return context.getBean(beanClass); } @Configuration diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfigTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfigTest.java deleted file mode 100644 index c94a1bd6753..00000000000 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfigTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.ctrip.framework.apollo.spring.annotation; - -import static org.mockito.Mockito.mock; - -import com.ctrip.framework.apollo.Config; -import com.ctrip.framework.apollo.core.ConfigConsts; -import com.ctrip.framework.apollo.spring.AbstractSpringIntegrationTest; -import org.junit.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Configuration; - -public class EnableApolloConfigTest extends AbstractSpringIntegrationTest { - - @Test - public void testResolveExpressionSimple() { - mockConfig("application", mock(Config.class)); - mockConfig("xxx", mock(Config.class)); - new AnnotationConfigApplicationContext( - TestResolveExpressionWithDefaultValueConfiguration.class); - } - - @Test - public void testResolveExpressionFromSystemProperty() { - mockConfig("application", mock(Config.class)); - - final String resolvedNamespaceName = "yyy"; - System.setProperty("from.system.property", resolvedNamespaceName); - mockConfig(resolvedNamespaceName, mock(Config.class)); - new AnnotationConfigApplicationContext( - TestResolveExpressionFromSystemPropertyConfiguration.class); - } - - @Test(expected = IllegalArgumentException.class) - public void testUnresolvable() { - new AnnotationConfigApplicationContext(TestUnresolvableConfiguration.class); - } - - @Configuration - @EnableApolloConfig(value = {ConfigConsts.NAMESPACE_APPLICATION, "${simple.namespace:xxx}"}) - static class TestResolveExpressionWithDefaultValueConfiguration { - - } - - @Configuration - @EnableApolloConfig(value = {ConfigConsts.NAMESPACE_APPLICATION, "${from.system.property}"}) - static class TestResolveExpressionFromSystemPropertyConfiguration { - - } - - @Configuration - @EnableApolloConfig(value = "${unresolvable.property}") - static class TestUnresolvableConfiguration { - - } - -} From cab3dfe826dc94dac1a349f134c63ea0608ebed5 Mon Sep 17 00:00:00 2001 From: anilople Date: Wed, 11 Nov 2020 19:34:12 +0800 Subject: [PATCH 19/42] test(apollo-client): rename --- .../spring/JavaConfigAnnotationTest.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java index ca8e54c3a38..5f775ab024e 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java @@ -331,20 +331,20 @@ public void testApolloConfigChangeListenerWithYamlFile() throws Exception { @Test - public void testResolveExpressionDefault() { + public void testApolloConfigResolveExpressionDefault() { mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class)); Config xxx = mock(Config.class); Config yamlConfig = mock(Config.class); mockConfig("xxx", xxx); mockConfig(APPLICATION_YAML_NAMESPACE, yamlConfig); - TestResolveExpressionDefaultConfiguration configuration = getSimpleBean( - TestResolveExpressionDefaultConfiguration.class); + TestApolloConfigResolveExpressionDefaultConfiguration configuration = getSimpleBean( + TestApolloConfigResolveExpressionDefaultConfiguration.class); assertEquals(xxx, configuration.getXxx()); assertEquals(yamlConfig, configuration.getYamlConfig()); } @Test - public void testResolveExpressionFromSystemProperty() { + public void testApolloConfigResolveExpressionFromSystemProperty() { mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class)); final String namespaceName = "xxx6"; final String yamlNamespaceName = "yyy8.yml"; @@ -355,20 +355,20 @@ public void testResolveExpressionFromSystemProperty() { Config yamlConfig = mock(Config.class); mockConfig(namespaceName, config); mockConfig(yamlNamespaceName, yamlConfig); - TestResolveExpressionFromSystemPropertyConfiguration configuration = getSimpleBean( - TestResolveExpressionFromSystemPropertyConfiguration.class); + TestApolloConfigResolveExpressionFromSystemPropertyConfiguration configuration = getSimpleBean( + TestApolloConfigResolveExpressionFromSystemPropertyConfiguration.class); assertEquals(config, configuration.getConfig()); assertEquals(yamlConfig, configuration.getYamlConfig()); } @Test(expected = BeanCreationException.class) - public void testUnresolvedExpression() { + public void testApolloConfigUnresolvedExpression() { mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class)); - getSimpleBean(TestUnresolvedExpressionConfiguration.class); + getSimpleBean(TestApolloConfigUnresolvedExpressionConfiguration.class); } @Test - public void testResolveExpressionFromApolloConfigNamespaceApplication() { + public void testApolloConfigResolveExpressionFromApolloConfigNamespaceApplication() { final String namespaceName = "xxx6"; final String yamlNamespaceName = "yyy8.yml"; @@ -383,14 +383,14 @@ public void testResolveExpressionFromApolloConfigNamespaceApplication() { final Config yamlConfig = mock(Config.class); mockConfig(namespaceName, config); mockConfig(yamlNamespaceName, yamlConfig); - TestResolveExpressionFromApolloConfigNamespaceApplication configuration = getSimpleBean( - TestResolveExpressionFromApolloConfigNamespaceApplication.class); + TestApolloConfigResolveExpressionFromApolloConfigNamespaceApplication configuration = getSimpleBean( + TestApolloConfigResolveExpressionFromApolloConfigNamespaceApplication.class); assertEquals(config, configuration.getConfig()); assertEquals(yamlConfig, configuration.getYamlConfig()); } @EnableApolloConfig - protected static class TestResolveExpressionDefaultConfiguration { + protected static class TestApolloConfigResolveExpressionDefaultConfiguration { @ApolloConfig(value = "${simple.namespace:xxx}") private Config xxx; @@ -408,7 +408,7 @@ public Config getYamlConfig() { } @EnableApolloConfig - protected static class TestResolveExpressionFromSystemPropertyConfiguration { + protected static class TestApolloConfigResolveExpressionFromSystemPropertyConfiguration { @ApolloConfig(value = "${from.system.namespace}") private Config config; @@ -426,14 +426,14 @@ public Config getYamlConfig() { } @EnableApolloConfig - protected static class TestUnresolvedExpressionConfiguration { + protected static class TestApolloConfigUnresolvedExpressionConfiguration { @ApolloConfig(value = "${so.complex.to.resolve}") private Config config; } @EnableApolloConfig - protected static class TestResolveExpressionFromApolloConfigNamespaceApplication { + protected static class TestApolloConfigResolveExpressionFromApolloConfigNamespaceApplication { @ApolloConfig(value = "${from.namespace.application.key}") private Config config; From 936784ad0ffdcff24dfeea96ba056cdbef590c2c Mon Sep 17 00:00:00 2001 From: anilople Date: Wed, 11 Nov 2020 21:21:42 +0800 Subject: [PATCH 20/42] docs(apollo-client): '@EnableApolloConfig' usage --- .../framework/apollo/spring/annotation/EnableApolloConfig.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfig.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfig.java index ae5781ed28d..26277b47823 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfig.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfig.java @@ -27,6 +27,8 @@ *
  * // The namespace could also be specified as a placeholder, e.g. ${redis.namespace:xxx},
  * // which will use the value of the key "redis.namespace" or "xxx" if this key is not configured.
+ * // Be careful that just key in {@link System#getProperty(String)} or {@link System#getenv(String)} can be resolved.
+ * // You can't put the value in remote(apollo) and resolve it!
  * @Configuration
  * @EnableApolloConfig({"${redis.namespace:xxx}"})
  * public class AppConfig {

From 60408cb63d384fc413e4f0f40f6691e9c4e52893 Mon Sep 17 00:00:00 2001
From: anilople 
Date: Wed, 11 Nov 2020 21:22:40 +0800
Subject: [PATCH 21/42] test(apollo-client): spy the return of
 prepareYamlConfigFile

---
 .../apollo/spring/AbstractSpringIntegrationTest.java          | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/AbstractSpringIntegrationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/AbstractSpringIntegrationTest.java
index bed7e5bc85c..70d02377ac9 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/AbstractSpringIntegrationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/AbstractSpringIntegrationTest.java
@@ -1,6 +1,7 @@
 package com.ctrip.framework.apollo.spring;
 
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import com.ctrip.framework.apollo.core.ConfigConsts;
@@ -90,7 +91,8 @@ protected static YamlConfigFile prepareYamlConfigFile(String namespaceNameWithFo
 
     when(configRepository.getConfig()).thenReturn(properties);
 
-    YamlConfigFile configFile = new YamlConfigFile(namespaceNameWithFormat, configRepository);
+    // spy it for testing after
+    YamlConfigFile configFile = spy(new YamlConfigFile(namespaceNameWithFormat, configRepository));
 
     mockConfigFile(namespaceNameWithFormat, configFile);
 

From b80b639874c3b656fc20b359e2aba80ab3fd92eb Mon Sep 17 00:00:00 2001
From: anilople 
Date: Wed, 11 Nov 2020 21:42:52 +0800
Subject: [PATCH 22/42] test(apollo-client): refactor the way to read file
 under src/test/resources/

---
 .../spring/AbstractSpringIntegrationTest.java | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/AbstractSpringIntegrationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/AbstractSpringIntegrationTest.java
index 70d02377ac9..e3f8a2083b8 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/AbstractSpringIntegrationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/AbstractSpringIntegrationTest.java
@@ -11,15 +11,17 @@
 import com.ctrip.framework.apollo.internals.YamlConfigFile;
 import com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor;
 import com.ctrip.framework.apollo.util.ConfigUtil;
-import com.google.common.base.Charsets;
-import com.google.common.io.Files;
-import java.io.File;
+import com.google.common.io.CharStreams;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.Map;
 
+import java.util.Objects;
 import java.util.Properties;
 import org.junit.After;
 import org.junit.Before;
@@ -75,10 +77,15 @@ protected SimpleConfig prepareConfig(String namespaceName, Properties properties
     return config;
   }
 
-  protected static Properties readYamlContentAsConfigFileProperties(String caseName) throws IOException {
-    File file = new File("src/test/resources/spring/yaml/" + caseName);
+  protected static Properties readYamlContentAsConfigFileProperties(String caseName)
+      throws IOException {
+    final String filePath = "spring/yaml/" + caseName;
+    ClassLoader classLoader = AbstractSpringIntegrationTest.class.getClassLoader();
 
-    String yamlContent = Files.toString(file, Charsets.UTF_8);
+    InputStream inputStream = classLoader.getResourceAsStream(filePath);
+    Objects.requireNonNull(inputStream, filePath + " may be not exist under src/test/resources/");
+    String yamlContent = CharStreams
+        .toString(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
 
     Properties properties = new Properties();
     properties.setProperty(ConfigConsts.CONFIG_FILE_CONTENT_KEY, yamlContent);

From b847d86692d08aa8dff1a72044028c8803c41644 Mon Sep 17 00:00:00 2001
From: anilople 
Date: Wed, 11 Nov 2020 21:43:23 +0800
Subject: [PATCH 23/42] test(apollo-client): resolve placeholder in
 '@ApolloConfigChangeListener'

---
 ...otationApolloConfigChangeListenerTest.java |  92 -----------
 .../spring/JavaConfigAnnotationTest.java      | 144 +++++++++++++++++-
 .../spring/yaml/resolve.from.self.yml         |   2 +
 3 files changed, 140 insertions(+), 98 deletions(-)
 delete mode 100644 apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationApolloConfigChangeListenerTest.java
 create mode 100644 apollo-client/src/test/resources/spring/yaml/resolve.from.self.yml

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationApolloConfigChangeListenerTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationApolloConfigChangeListenerTest.java
deleted file mode 100644
index 2c6b0f682c8..00000000000
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationApolloConfigChangeListenerTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package com.ctrip.framework.apollo.spring;
-
-import static org.mockito.Mockito.mock;
-
-import com.ctrip.framework.apollo.Config;
-import com.ctrip.framework.apollo.core.ConfigConsts;
-import com.ctrip.framework.apollo.model.ConfigChangeEvent;
-import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
-import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
-import java.util.Properties;
-import org.junit.Test;
-import org.springframework.context.annotation.AnnotationConfigApplicationContext;
-import org.springframework.context.annotation.Configuration;
-
-public class JavaConfigAnnotationApolloConfigChangeListenerTest extends
-    AbstractSpringIntegrationTest {
-
-  @Test
-  public void testResolveExpressionSimple() {
-    Config applicationConfig = mock(Config.class);
-    mockConfig("application", applicationConfig);
-    new AnnotationConfigApplicationContext(TestResolveExpressionSimpleConfiguration.class);
-  }
-
-  /**
-   * resolve namespace's name from system property.
-   */
-  @Test
-  public void testResolveExpressionFromSystemProperty() {
-    Config applicationConfig = mock(Config.class);
-    mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
-
-    final String namespaceName = "magicRedis";
-    System.setProperty("redis.namespace", namespaceName);
-    Config redisConfig = mock(Config.class);
-    mockConfig(namespaceName, redisConfig);
-    new AnnotationConfigApplicationContext(
-        TestResolveExpressionFromSystemPropertyConfiguration.class);
-  }
-
-  /**
-   * resolve namespace from config. ${mysql.namespace} will be resolved by config from namespace
-   * application.
-   */
-  @Test
-  public void testResolveExpressionFromApplicationNamespace() {
-    Config applicationConfig = mock(Config.class);
-    mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
-
-    final String namespaceKey = "mysql.namespace";
-    final String namespaceName = "magicMysqlNamespaceApplication";
-
-    Properties properties = new Properties();
-    properties.setProperty(namespaceKey, namespaceName);
-    this.prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties);
-
-    Config mysqlConfig = mock(Config.class);
-    mockConfig(namespaceName, mysqlConfig);
-
-    new AnnotationConfigApplicationContext(
-        TestResolveExpressionFromApplicationNamespaceConfiguration.class);
-  }
-
-  @Configuration
-  @EnableApolloConfig
-  static class TestResolveExpressionSimpleConfiguration {
-
-    @ApolloConfigChangeListener("${simple.application:application}")
-    private void onChange(ConfigChangeEvent event) {
-    }
-  }
-
-  @Configuration
-  @EnableApolloConfig
-  static class TestResolveExpressionFromSystemPropertyConfiguration {
-
-    @ApolloConfigChangeListener(value = {ConfigConsts.NAMESPACE_APPLICATION,
-        "${redis.namespace}"})
-    private void onChange(ConfigChangeEvent event) {
-    }
-  }
-
-  @Configuration
-  @EnableApolloConfig
-  static class TestResolveExpressionFromApplicationNamespaceConfiguration {
-
-    @ApolloConfigChangeListener(value = {ConfigConsts.NAMESPACE_APPLICATION,
-        "${mysql.namespace}"})
-    private void onChange(ConfigChangeEvent event) {
-    }
-  }
-}
diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
index 5f775ab024e..3c027a689c9 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
@@ -2,6 +2,7 @@
 
 import com.ctrip.framework.apollo.Config;
 import com.ctrip.framework.apollo.ConfigChangeListener;
+import com.ctrip.framework.apollo.ConfigFileChangeListener;
 import com.ctrip.framework.apollo.core.ConfigConsts;
 import com.ctrip.framework.apollo.internals.YamlConfigFile;
 import com.ctrip.framework.apollo.model.ConfigChange;
@@ -12,11 +13,13 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.SettableFuture;
+import java.io.IOException;
 import java.util.Collections;
 import java.util.Properties;
 import java.util.concurrent.TimeUnit;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 import org.springframework.beans.factory.BeanCreationException;
@@ -34,6 +37,7 @@
 import static org.mockito.Matchers.anySetOf;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -104,14 +108,14 @@ public void testApolloConfigWithInheritance() throws Exception {
 
   @Test
   public void testEnableApolloConfigResolveExpressionSimple() {
-    mockConfig("application", mock(Config.class));
+    mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class));
     mockConfig("xxx", mock(Config.class));
     getSimpleBean(TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration.class);
   }
 
   @Test
   public void testEnableApolloConfigResolveExpressionFromSystemProperty() {
-    mockConfig("application", mock(Config.class));
+    mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class));
 
     final String resolvedNamespaceName = "yyy";
     System.setProperty("from.system.property", resolvedNamespaceName);
@@ -150,7 +154,7 @@ public void testApolloConfigChangeListener() throws Exception {
     final List applicationListeners = Lists.newArrayList();
     final List fxApolloListeners = Lists.newArrayList();
 
-    doAnswer(new Answer() {
+    doAnswer(new Answer() {
       @Override
       public Object answer(InvocationOnMock invocation) throws Throwable {
         applicationListeners.add(invocation.getArgumentAt(0, ConfigChangeListener.class));
@@ -159,7 +163,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
       }
     }).when(applicationConfig).addChangeListener(any(ConfigChangeListener.class));
 
-    doAnswer(new Answer() {
+    doAnswer(new Answer() {
       @Override
       public Object answer(InvocationOnMock invocation) throws Throwable {
         fxApolloListeners.add(invocation.getArgumentAt(0, ConfigChangeListener.class));
@@ -223,7 +227,7 @@ public void testApolloConfigChangeListenerWithInheritance() throws Exception {
     final List applicationListeners = Lists.newArrayList();
     final List fxApolloListeners = Lists.newArrayList();
 
-    doAnswer(new Answer() {
+    doAnswer(new Answer() {
       @Override
       public Object answer(InvocationOnMock invocation) throws Throwable {
         applicationListeners.add(invocation.getArgumentAt(0, ConfigChangeListener.class));
@@ -232,7 +236,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
       }
     }).when(applicationConfig).addChangeListener(any(ConfigChangeListener.class));
 
-    doAnswer(new Answer() {
+    doAnswer(new Answer() {
       @Override
       public Object answer(InvocationOnMock invocation) throws Throwable {
         fxApolloListeners.add(invocation.getArgumentAt(0, ConfigChangeListener.class));
@@ -329,6 +333,84 @@ public void testApolloConfigChangeListenerWithYamlFile() throws Exception {
     assertEquals(anotherValue, yamlConfig.getProperty(someKey, null));
   }
 
+  @Test
+  public void testApolloConfigChangeListenerResolveExpressionSimple() {
+    // for ignore, no listener use it
+    Config ignoreConfig = mock(Config.class);
+    mockConfig("ignore.for.listener", ignoreConfig);
+
+    Config applicationConfig = mock(Config.class);
+    mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
+
+    getSimpleBean(TestApolloConfigChangeListenerResolveExpressionSimpleConfiguration.class);
+
+    // no using
+    verify(ignoreConfig, never()).addChangeListener(any(ConfigChangeListener.class));
+
+    // strange, it invoke 2 times
+    verify(applicationConfig, times(2)).addChangeListener(any(ConfigChangeListener.class));
+  }
+
+  /**
+   * resolve namespace's name from system property.
+   */
+  @Test
+  public void testApolloConfigChangeListenerResolveExpressionFromSystemProperty() {
+    Config applicationConfig = mock(Config.class);
+    mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
+
+    final String namespaceName = "magicRedis";
+    System.setProperty("redis.namespace", namespaceName);
+    Config redisConfig = mock(Config.class);
+    mockConfig(namespaceName, redisConfig);
+    getSimpleBean(TestApolloConfigChangeListenerResolveExpressionFromSystemPropertyConfiguration.class);
+
+    // if config was used, it must be invoked on method addChangeListener 1 time
+    verify(redisConfig, times(1)).addChangeListener(any(ConfigChangeListener.class));
+  }
+
+  /**
+   * resolve namespace from config. ${mysql.namespace} will be resolved by config from namespace
+   * application.
+   */
+  @Test
+  public void testApolloConfigChangeListenerResolveExpressionFromApplicationNamespace() {
+    Config applicationConfig = mock(Config.class);
+    mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
+
+    final String namespaceKey = "mysql.namespace";
+    final String namespaceName = "magicMysqlNamespaceApplication";
+
+    Properties properties = new Properties();
+    properties.setProperty(namespaceKey, namespaceName);
+    this.prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties);
+
+    Config mysqlConfig = mock(Config.class);
+    mockConfig(namespaceName, mysqlConfig);
+
+    getSimpleBean(
+        TestApolloConfigChangeListenerResolveExpressionFromApplicationNamespaceConfiguration.class);
+
+    // if config was used, it must be invoked on method addChangeListener 1 time
+    verify(mysqlConfig, times(1)).addChangeListener(any(ConfigChangeListener.class));
+  }
+
+  @Test(expected = BeanCreationException.class)
+  public void testApolloConfigChangeListenerUnresolvedPlaceholder() {
+    Config applicationConfig = mock(Config.class);
+    mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
+    getSimpleBean(TestApolloConfigChangeListenerUnresolvedPlaceholderConfiguration.class);
+  }
+
+  @Test
+  public void testApolloConfigChangeListenerResolveExpressionFromSelfYaml() throws IOException {
+    mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class));
+
+    final String resolvedValue = "resolve.from.self.yml";
+    YamlConfigFile yamlConfigFile = prepareYamlConfigFile(resolvedValue, readYamlContentAsConfigFileProperties(resolvedValue));
+    getSimpleBean(TestApolloConfigChangeListenerResolveExpressionFromSelfYamlConfiguration.class);
+    verify(yamlConfigFile, times(1)).addChangeListener(any(ConfigFileChangeListener.class));
+  }
 
   @Test
   public void testApolloConfigResolveExpressionDefault() {
@@ -450,6 +532,56 @@ public Config getYamlConfig() {
     }
   }
 
+
+  @Configuration
+  @EnableApolloConfig
+  static class TestApolloConfigChangeListenerResolveExpressionSimpleConfiguration {
+
+    @ApolloConfigChangeListener("${simple.application:application}")
+    private void onChange(ConfigChangeEvent event) {
+    }
+  }
+
+  @Configuration
+  @EnableApolloConfig
+  static class TestApolloConfigChangeListenerResolveExpressionFromSystemPropertyConfiguration {
+
+    @ApolloConfigChangeListener("${redis.namespace}")
+    private void onChange(ConfigChangeEvent event) {
+    }
+  }
+
+  @Configuration
+  @EnableApolloConfig
+  static class TestApolloConfigChangeListenerResolveExpressionFromApplicationNamespaceConfiguration {
+
+    @ApolloConfigChangeListener(value = {ConfigConsts.NAMESPACE_APPLICATION,
+        "${mysql.namespace}"})
+    private void onChange(ConfigChangeEvent event) {
+    }
+  }
+
+  @Configuration
+  @EnableApolloConfig
+  static class TestApolloConfigChangeListenerUnresolvedPlaceholderConfiguration {
+    @ApolloConfigChangeListener(value = {ConfigConsts.NAMESPACE_APPLICATION,
+        "${i.can.not.be.resolved}"})
+    private void onChange(ConfigChangeEvent event) {
+    }
+  }
+
+  @Configuration
+  @EnableApolloConfig("resolve.from.self.yml")
+  static class TestApolloConfigChangeListenerResolveExpressionFromSelfYamlConfiguration {
+
+    /**
+     * value in file src/test/resources/spring/yaml/resolve.from.self.yml
+     */
+    @ApolloConfigChangeListener("${i.can.resolve.from.self}")
+    private void onChange(ConfigChangeEvent event) {
+    }
+  }
+
   @Configuration
   @EnableApolloConfig(value = {ConfigConsts.NAMESPACE_APPLICATION, "${simple.namespace:xxx}"})
   protected static class TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration {
diff --git a/apollo-client/src/test/resources/spring/yaml/resolve.from.self.yml b/apollo-client/src/test/resources/spring/yaml/resolve.from.self.yml
new file mode 100644
index 00000000000..d289afa9cc2
--- /dev/null
+++ b/apollo-client/src/test/resources/spring/yaml/resolve.from.self.yml
@@ -0,0 +1,2 @@
+# value must as same as file name
+i.can.resolve.from.self: resolve.from.self.yml
\ No newline at end of file

From 0d5020fc584d8c20b704c4cb63863b41b1a54f3f Mon Sep 17 00:00:00 2001
From: anilople 
Date: Wed, 11 Nov 2020 23:09:15 +0800
Subject: [PATCH 24/42] test(apollo-client): refactor XmlConfigPlaceholderTest

---
 .../spring/XmlConfigPlaceholderTest.java      | 58 +++++++++----------
 1 file changed, 27 insertions(+), 31 deletions(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java
index a424fdd1bf0..9430d3be608 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java
@@ -17,6 +17,7 @@
  * @author Jason Song(song_s@ctrip.com)
  */
 public class XmlConfigPlaceholderTest extends AbstractSpringIntegrationTest {
+
   private static final String TIMEOUT_PROPERTY = "timeout";
   private static final int DEFAULT_TIMEOUT = 100;
   private static final String BATCH_PROPERTY = "batch";
@@ -29,7 +30,8 @@ public void testPropertySourceWithNoNamespace() throws Exception {
     int someBatch = 2000;
 
     Config config = mock(Config.class);
-    when(config.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout));
+    when(config.getProperty(eq(TIMEOUT_PROPERTY), anyString()))
+        .thenReturn(String.valueOf(someTimeout));
     when(config.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch));
 
     mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config);
@@ -50,7 +52,8 @@ public void testApplicationPropertySource() throws Exception {
     int someBatch = 2000;
 
     Config config = mock(Config.class);
-    when(config.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout));
+    when(config.getProperty(eq(TIMEOUT_PROPERTY), anyString()))
+        .thenReturn(String.valueOf(someTimeout));
     when(config.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch));
 
     mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config);
@@ -64,30 +67,38 @@ public void testMultiplePropertySources() throws Exception {
     int someBatch = 2000;
 
     Config application = mock(Config.class);
-    when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout));
+    when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString()))
+        .thenReturn(String.valueOf(someTimeout));
     mockConfig(ConfigConsts.NAMESPACE_APPLICATION, application);
 
     Config fxApollo = mock(Config.class);
-    when(application.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch));
+    when(application.getProperty(eq(BATCH_PROPERTY), anyString()))
+        .thenReturn(String.valueOf(someBatch));
     mockConfig(FX_APOLLO_NAMESPACE, fxApollo);
 
     check("spring/XmlConfigPlaceholderTest3.xml", someTimeout, someBatch);
   }
 
-  @Test
-  public void testMultiplePropertySourcesWithSameProperties() throws Exception {
-    int someTimeout = 1000;
-    int anotherTimeout = someTimeout + 1;
-    int someBatch = 2000;
-
+  private void prepare(int someTimeout, int anotherTimeout, int someBatch) {
     Config application = mock(Config.class);
-    when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout));
-    when(application.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch));
+    when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString()))
+        .thenReturn(String.valueOf(someTimeout));
+    when(application.getProperty(eq(BATCH_PROPERTY), anyString()))
+        .thenReturn(String.valueOf(someBatch));
     mockConfig(ConfigConsts.NAMESPACE_APPLICATION, application);
 
     Config fxApollo = mock(Config.class);
-    when(fxApollo.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(anotherTimeout));
+    when(fxApollo.getProperty(eq(TIMEOUT_PROPERTY), anyString()))
+        .thenReturn(String.valueOf(anotherTimeout));
     mockConfig(FX_APOLLO_NAMESPACE, fxApollo);
+  }
+
+  @Test
+  public void testMultiplePropertySourcesWithSameProperties() throws Exception {
+    int someTimeout = 1000;
+    int anotherTimeout = someTimeout + 1;
+    int someBatch = 2000;
+    this.prepare(someTimeout, anotherTimeout, someBatch);
 
     check("spring/XmlConfigPlaceholderTest3.xml", someTimeout, someBatch);
   }
@@ -97,15 +108,7 @@ public void testMultiplePropertySourcesWithSameProperties2() throws Exception {
     int someTimeout = 1000;
     int anotherTimeout = someTimeout + 1;
     int someBatch = 2000;
-
-    Config application = mock(Config.class);
-    when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout));
-    when(application.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch));
-    mockConfig(ConfigConsts.NAMESPACE_APPLICATION, application);
-
-    Config fxApollo = mock(Config.class);
-    when(fxApollo.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(anotherTimeout));
-    mockConfig(FX_APOLLO_NAMESPACE, fxApollo);
+    this.prepare(someTimeout, anotherTimeout, someBatch);
 
     check("spring/XmlConfigPlaceholderTest6.xml", anotherTimeout, someBatch);
   }
@@ -115,15 +118,7 @@ public void testMultiplePropertySourcesWithSamePropertiesWithWeight() throws Exc
     int someTimeout = 1000;
     int anotherTimeout = someTimeout + 1;
     int someBatch = 2000;
-
-    Config application = mock(Config.class);
-    when(application.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(someTimeout));
-    when(application.getProperty(eq(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch));
-    mockConfig(ConfigConsts.NAMESPACE_APPLICATION, application);
-
-    Config fxApollo = mock(Config.class);
-    when(fxApollo.getProperty(eq(TIMEOUT_PROPERTY), anyString())).thenReturn(String.valueOf(anotherTimeout));
-    mockConfig(FX_APOLLO_NAMESPACE, fxApollo);
+    this.prepare(someTimeout, anotherTimeout, someBatch);
 
     check("spring/XmlConfigPlaceholderTest4.xml", anotherTimeout, someBatch);
   }
@@ -143,6 +138,7 @@ private void check(String xmlLocation, int expectedTimeout, int expectedBatch) {
   }
 
   public static class TestXmlBean {
+
     private int timeout;
     private int batch;
 

From 7a6e3c227ad437b7d5cde600d29878759c695b56 Mon Sep 17 00:00:00 2001
From: anilople 
Date: Wed, 11 Nov 2020 23:26:23 +0800
Subject: [PATCH 25/42] feat(apollo-client): resolve placeholder in xml config
 #3367

---
 .../spring/config/NamespaceHandler.java       | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/NamespaceHandler.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/NamespaceHandler.java
index 31dcf0f1506..b68a4aa5b19 100644
--- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/NamespaceHandler.java
+++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/NamespaceHandler.java
@@ -4,6 +4,7 @@
 import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
 import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
 import org.springframework.core.Ordered;
+import org.springframework.util.SystemPropertyUtils;
 import org.w3c.dom.Element;
 
 import com.ctrip.framework.apollo.core.ConfigConsts;
@@ -14,7 +15,9 @@
  * @author Jason Song(song_s@ctrip.com)
  */
 public class NamespaceHandler extends NamespaceHandlerSupport {
-  private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
+
+  private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings()
+      .trimResults();
 
   @Override
   public void init() {
@@ -22,6 +25,7 @@ public void init() {
   }
 
   static class BeanParser extends AbstractSingleBeanDefinitionParser {
+
     @Override
     protected Class getBeanClass(Element element) {
       return ConfigPropertySourcesProcessor.class;
@@ -32,13 +36,18 @@ protected boolean shouldGenerateId() {
       return true;
     }
 
-    @Override
-    protected void doParse(Element element, BeanDefinitionBuilder builder) {
+    private String resolveNamespaces(Element element) {
       String namespaces = element.getAttribute("namespaces");
-      //default to application
       if (Strings.isNullOrEmpty(namespaces)) {
-        namespaces = ConfigConsts.NAMESPACE_APPLICATION;
+        //default to application
+        return ConfigConsts.NAMESPACE_APPLICATION;
       }
+      return SystemPropertyUtils.resolvePlaceholders(namespaces);
+    }
+
+    @Override
+    protected void doParse(Element element, BeanDefinitionBuilder builder) {
+      String namespaces = this.resolveNamespaces(element);
 
       int order = Ordered.LOWEST_PRECEDENCE;
       String orderAttribute = element.getAttribute("order");

From 9480b2101459eb293af706ff4cfcddf452e4741e Mon Sep 17 00:00:00 2001
From: anilople 
Date: Wed, 11 Nov 2020 23:26:48 +0800
Subject: [PATCH 26/42] test(apollo-client): resolve placeholder in xml config
 #3367

---
 .../spring/XmlConfigPlaceholderTest.java      | 41 +++++++++++++++++--
 ...namespace.resolve.from.system.property.xml | 14 +++++++
 .../config.namespace.resolve.to.default.xml   | 14 +++++++
 .../spring/config.namespace.unresolved.xml    | 14 +++++++
 4 files changed, 79 insertions(+), 4 deletions(-)
 create mode 100644 apollo-client/src/test/resources/spring/config.namespace.resolve.from.system.property.xml
 create mode 100644 apollo-client/src/test/resources/spring/config.namespace.resolve.to.default.xml
 create mode 100644 apollo-client/src/test/resources/spring/config.namespace.unresolved.xml

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java
index 9430d3be608..378bdad562e 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java
@@ -6,13 +6,13 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import com.ctrip.framework.apollo.Config;
+import com.ctrip.framework.apollo.core.ConfigConsts;
 import org.junit.Test;
+import org.springframework.beans.FatalBeanException;
 import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 
-import com.ctrip.framework.apollo.Config;
-import com.ctrip.framework.apollo.core.ConfigConsts;
-
 /**
  * @author Jason Song(song_s@ctrip.com)
  */
@@ -128,7 +128,40 @@ public void testWithInvalidWeight() throws Exception {
     check("spring/XmlConfigPlaceholderTest5.xml", DEFAULT_TIMEOUT, DEFAULT_BATCH);
   }
 
-  private void check(String xmlLocation, int expectedTimeout, int expectedBatch) {
+
+  @Test
+  public void testResolveNamespacesWithDefaultValue() throws Exception {
+    int someTimeout = 1000;
+    int anotherTimeout = someTimeout + 1;
+    int someBatch = 2000;
+    this.prepare(someTimeout, anotherTimeout, someBatch);
+
+    check("spring/config.namespace.resolve.to.default.xml", anotherTimeout, someBatch);
+  }
+
+  @Test
+  public void testResolveNamespacesFromSystemProperty() throws Exception {
+    System.setProperty("xxx.from.system.property", ConfigConsts.NAMESPACE_APPLICATION);
+    System.setProperty("yyy.from.system.property", "FX.apollo");
+    int someTimeout = 1000;
+    int anotherTimeout = someTimeout + 1;
+    int someBatch = 2000;
+    this.prepare(someTimeout, anotherTimeout, someBatch);
+
+    check("spring/config.namespace.resolve.from.system.property.xml", anotherTimeout, someBatch);
+  }
+
+  @Test(expected = FatalBeanException.class)
+  public void testUnresolvedNamespaces() {
+    int someTimeout = 1000;
+    int anotherTimeout = someTimeout + 1;
+    int someBatch = 2000;
+    this.prepare(someTimeout, anotherTimeout, someBatch);
+
+    check("spring/config.namespace.unresolved.xml", anotherTimeout, someBatch);
+  }
+
+  private static void check(String xmlLocation, int expectedTimeout, int expectedBatch) {
     ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlLocation);
 
     TestXmlBean bean = context.getBean(TestXmlBean.class);
diff --git a/apollo-client/src/test/resources/spring/config.namespace.resolve.from.system.property.xml b/apollo-client/src/test/resources/spring/config.namespace.resolve.from.system.property.xml
new file mode 100644
index 00000000000..93ac9f66e33
--- /dev/null
+++ b/apollo-client/src/test/resources/spring/config.namespace.resolve.from.system.property.xml
@@ -0,0 +1,14 @@
+
+
+    
+    
+
+    
+        
+        
+    
+
diff --git a/apollo-client/src/test/resources/spring/config.namespace.resolve.to.default.xml b/apollo-client/src/test/resources/spring/config.namespace.resolve.to.default.xml
new file mode 100644
index 00000000000..748129d4d86
--- /dev/null
+++ b/apollo-client/src/test/resources/spring/config.namespace.resolve.to.default.xml
@@ -0,0 +1,14 @@
+
+
+    
+    
+
+    
+        
+        
+    
+
diff --git a/apollo-client/src/test/resources/spring/config.namespace.unresolved.xml b/apollo-client/src/test/resources/spring/config.namespace.unresolved.xml
new file mode 100644
index 00000000000..0ec1c764872
--- /dev/null
+++ b/apollo-client/src/test/resources/spring/config.namespace.unresolved.xml
@@ -0,0 +1,14 @@
+
+
+    
+    
+
+    
+        
+        
+    
+

From e23b8525046e1c30efa280a79918a0657258ec88 Mon Sep 17 00:00:00 2001
From: anilople 
Date: Sat, 14 Nov 2020 16:06:32 +0800
Subject: [PATCH 27/42] docs(apollo-client): '@EnableApolloConfig' placeholder
 could not be configured in Apollo as Apollo is not activated

---
 .../framework/apollo/spring/annotation/EnableApolloConfig.java | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfig.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfig.java
index 26277b47823..65bbdeb2712 100644
--- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfig.java
+++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfig.java
@@ -27,8 +27,7 @@
  * 
  * // The namespace could also be specified as a placeholder, e.g. ${redis.namespace:xxx},
  * // which will use the value of the key "redis.namespace" or "xxx" if this key is not configured.
- * // Be careful that just key in {@link System#getProperty(String)} or {@link System#getenv(String)} can be resolved.
- * // You can't put the value in remote(apollo) and resolve it!
+ * // Please note that this placeholder could not be configured in Apollo as Apollo is not activated during this phase.
  * @Configuration
  * @EnableApolloConfig({"${redis.namespace:xxx}"})
  * public class AppConfig {

From 9c5f8d95594e20b30849d8585b4863ce8d25b290 Mon Sep 17 00:00:00 2001
From: anilople 
Date: Sat, 14 Nov 2020 16:18:50 +0800
Subject: [PATCH 28/42] test(apollo-client): '@EnableApolloConfig' mock value
 to field for check

---
 .../spring/JavaConfigAnnotationTest.java      | 27 ++++++++++++++++---
 1 file changed, 23 insertions(+), 4 deletions(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
index 3c027a689c9..9d2c03ba4c6 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
@@ -23,6 +23,7 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -35,11 +36,14 @@
 import static org.junit.Assert.assertFalse;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anySetOf;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 /**
  * @author Jason Song(song_s@ctrip.com)
@@ -105,12 +109,21 @@ public void testApolloConfigWithInheritance() throws Exception {
     assertEquals(applicationConfig, bean.getSomeConfig());
   }
 
-
   @Test
   public void testEnableApolloConfigResolveExpressionSimple() {
+    String someKey = "someKey";
+    String someValue = "someValue";
     mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class));
-    mockConfig("xxx", mock(Config.class));
-    getSimpleBean(TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration.class);
+    Config xxxConfig = mock(Config.class);
+    when(xxxConfig.getProperty(eq(someKey), anyString())).thenReturn(someValue);
+    mockConfig("xxx", xxxConfig);
+
+    TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration configuration =
+        getSimpleBean(TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration.class);
+
+    // check
+    assertEquals(someValue, configuration.getSomeKey());
+    verify(xxxConfig, times(1)).getProperty(eq(someKey), anyString());
   }
 
   @Test
@@ -584,8 +597,14 @@ private void onChange(ConfigChangeEvent event) {
 
   @Configuration
   @EnableApolloConfig(value = {ConfigConsts.NAMESPACE_APPLICATION, "${simple.namespace:xxx}"})
-  protected static class TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration {
+  static class TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration {
+
+    @Value("${someKey}")
+    private String someKey;
 
+    public String getSomeKey() {
+      return this.someKey;
+    }
   }
 
   @Configuration

From cd0491bd8594f02b2b5010fe6368694d05c21133 Mon Sep 17 00:00:00 2001
From: anilople 
Date: Sat, 14 Nov 2020 16:32:15 +0800
Subject: [PATCH 29/42] test(apollo-client): '@EnableApolloConfig' resolve
 placeholder from system property

---
 .../spring/JavaConfigAnnotationTest.java      | 28 +++++++++++++------
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
index 9d2c03ba4c6..efbeb3a2514 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
@@ -129,11 +129,27 @@ public void testEnableApolloConfigResolveExpressionSimple() {
   @Test
   public void testEnableApolloConfigResolveExpressionFromSystemProperty() {
     mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class));
+    final String someKey = "someKey";
+    final String someValue = "someValue";
 
+    final String propertyKey = "simple.namespace";
     final String resolvedNamespaceName = "yyy";
-    System.setProperty("from.system.property", resolvedNamespaceName);
-    mockConfig(resolvedNamespaceName, mock(Config.class));
-    getSimpleBean(TestEnableApolloConfigResolveExpressionFromSystemPropertyConfiguration.class);
+    // set property to system, remember to clear it after the testing
+    System.setProperty(propertyKey, resolvedNamespaceName);
+
+    Config yyyConfig = mock(Config.class);
+    when(yyyConfig.getProperty(eq(someKey), anyString())).thenReturn(someValue);
+    mockConfig(resolvedNamespaceName, yyyConfig);
+
+    TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration configuration =
+        getSimpleBean(TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration.class);
+
+    // check
+    assertEquals(someValue, configuration.getSomeKey());
+    verify(yyyConfig, times(1)).getProperty(eq(someKey), anyString());
+
+    // remember to clear property after the testing
+    System.clearProperty(propertyKey);
   }
 
   @Test(expected = IllegalArgumentException.class)
@@ -607,12 +623,6 @@ public String getSomeKey() {
     }
   }
 
-  @Configuration
-  @EnableApolloConfig(value = {ConfigConsts.NAMESPACE_APPLICATION, "${from.system.property}"})
-  static class TestEnableApolloConfigResolveExpressionFromSystemPropertyConfiguration {
-
-  }
-
   @Configuration
   @EnableApolloConfig(value = "${unresolvable.property}")
   static class TestEnableApolloConfigUnresolvableConfiguration {

From 138a50eeea5f2e06af06cbcda0b300e7a9cd4a6c Mon Sep 17 00:00:00 2001
From: anilople 
Date: Sat, 14 Nov 2020 16:53:03 +0800
Subject: [PATCH 30/42] test(apollo-client): reset system propery after each
 test case

---
 .../spring/JavaConfigAnnotationTest.java      | 35 +++++++++++++++----
 1 file changed, 29 insertions(+), 6 deletions(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
index efbeb3a2514..ab3c70f44d9 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
@@ -15,11 +15,12 @@
 import com.google.common.util.concurrent.SettableFuture;
 import java.io.IOException;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Properties;
 import java.util.concurrent.TimeUnit;
+import org.junit.After;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 import org.springframework.beans.factory.BeanCreationException;
@@ -31,7 +32,6 @@
 import java.util.List;
 import java.util.Set;
 
-import static java.util.Arrays.asList;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.mockito.Matchers.any;
@@ -52,6 +52,18 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest {
   private static final String FX_APOLLO_NAMESPACE = "FX.apollo";
   private static final String APPLICATION_YAML_NAMESPACE = "application.yaml";
 
+  /**
+   * for reset system property after test case finished.
+   *
+   * @see #after()
+   */
+  private static final Set originSystemPropertiesKeys;
+
+  static {
+    originSystemPropertiesKeys = Collections
+        .unmodifiableSet(System.getProperties().stringPropertyNames());
+  }
+
   private static  T getBean(Class beanClass, Class... annotatedClasses) {
     AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(annotatedClasses);
     return context.getBean(beanClass);
@@ -61,6 +73,21 @@ private static  T getSimpleBean(Class clazz) {
     return getBean(clazz, clazz);
   }
 
+  @After
+  public void after() {
+    // reset system property
+    Set keysNeedToClear = new HashSet<>();
+    for (String key : System.getProperties().stringPropertyNames()) {
+      if (!originSystemPropertiesKeys.contains(key)) {
+        keysNeedToClear.add(key);
+      }
+    }
+    // clear them
+    for (String key : keysNeedToClear) {
+      System.clearProperty(key);
+    }
+  }
+
   @Test
   public void testApolloConfig() throws Exception {
     Config applicationConfig = mock(Config.class);
@@ -134,7 +161,6 @@ public void testEnableApolloConfigResolveExpressionFromSystemProperty() {
 
     final String propertyKey = "simple.namespace";
     final String resolvedNamespaceName = "yyy";
-    // set property to system, remember to clear it after the testing
     System.setProperty(propertyKey, resolvedNamespaceName);
 
     Config yyyConfig = mock(Config.class);
@@ -147,9 +173,6 @@ public void testEnableApolloConfigResolveExpressionFromSystemProperty() {
     // check
     assertEquals(someValue, configuration.getSomeKey());
     verify(yyyConfig, times(1)).getProperty(eq(someKey), anyString());
-
-    // remember to clear property after the testing
-    System.clearProperty(propertyKey);
   }
 
   @Test(expected = IllegalArgumentException.class)

From 280ca0fbce51115e52da35885f861f0f2216ba90 Mon Sep 17 00:00:00 2001
From: anilople 
Date: Sat, 14 Nov 2020 17:12:53 +0800
Subject: [PATCH 31/42] test(apollo-client): change the way to clear system
 propery.

Sometime test failure in DefaultConfigTest#testGetIntPropertyMultipleTimesWithShortExpireTime, because system property is global
---
 .../spring/JavaConfigAnnotationTest.java      | 61 +++++++++----------
 1 file changed, 30 insertions(+), 31 deletions(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
index ab3c70f44d9..7f13ce7677b 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
@@ -52,18 +52,6 @@ public class JavaConfigAnnotationTest extends AbstractSpringIntegrationTest {
   private static final String FX_APOLLO_NAMESPACE = "FX.apollo";
   private static final String APPLICATION_YAML_NAMESPACE = "application.yaml";
 
-  /**
-   * for reset system property after test case finished.
-   *
-   * @see #after()
-   */
-  private static final Set originSystemPropertiesKeys;
-
-  static {
-    originSystemPropertiesKeys = Collections
-        .unmodifiableSet(System.getProperties().stringPropertyNames());
-  }
-
   private static  T getBean(Class beanClass, Class... annotatedClasses) {
     AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(annotatedClasses);
     return context.getBean(beanClass);
@@ -73,21 +61,6 @@ private static  T getSimpleBean(Class clazz) {
     return getBean(clazz, clazz);
   }
 
-  @After
-  public void after() {
-    // reset system property
-    Set keysNeedToClear = new HashSet<>();
-    for (String key : System.getProperties().stringPropertyNames()) {
-      if (!originSystemPropertiesKeys.contains(key)) {
-        keysNeedToClear.add(key);
-      }
-    }
-    // clear them
-    for (String key : keysNeedToClear) {
-      System.clearProperty(key);
-    }
-  }
-
   @Test
   public void testApolloConfig() throws Exception {
     Config applicationConfig = mock(Config.class);
@@ -173,6 +146,23 @@ public void testEnableApolloConfigResolveExpressionFromSystemProperty() {
     // check
     assertEquals(someValue, configuration.getSomeKey());
     verify(yyyConfig, times(1)).getProperty(eq(someKey), anyString());
+
+    System.clearProperty(propertyKey);
+  }
+
+  @Test(expected = BeanCreationException.class)
+  public void testEnableApolloConfigUnresolvedValueInField() {
+    mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class));
+
+    final String resolvedNamespaceName = "yyy";
+    final String SystemPropertyKey = "simple.namespace";
+    System.setProperty(SystemPropertyKey, resolvedNamespaceName);
+    Config yyyConfig = mock(Config.class);
+    mockConfig(resolvedNamespaceName, yyyConfig);
+
+    getSimpleBean(TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration.class);
+
+    System.clearProperty(SystemPropertyKey);
   }
 
   @Test(expected = IllegalArgumentException.class)
@@ -412,13 +402,17 @@ public void testApolloConfigChangeListenerResolveExpressionFromSystemProperty()
     mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
 
     final String namespaceName = "magicRedis";
-    System.setProperty("redis.namespace", namespaceName);
+    final String systemPropertyKey = "redis.namespace";
+    System.setProperty(systemPropertyKey, namespaceName);
     Config redisConfig = mock(Config.class);
     mockConfig(namespaceName, redisConfig);
-    getSimpleBean(TestApolloConfigChangeListenerResolveExpressionFromSystemPropertyConfiguration.class);
+    getSimpleBean(
+        TestApolloConfigChangeListenerResolveExpressionFromSystemPropertyConfiguration.class);
 
     // if config was used, it must be invoked on method addChangeListener 1 time
     verify(redisConfig, times(1)).addChangeListener(any(ConfigChangeListener.class));
+
+    System.clearProperty(systemPropertyKey);
   }
 
   /**
@@ -483,8 +477,10 @@ public void testApolloConfigResolveExpressionFromSystemProperty() {
     final String namespaceName = "xxx6";
     final String yamlNamespaceName = "yyy8.yml";
 
-    System.setProperty("from.system.namespace", namespaceName);
-    System.setProperty("from.system.yaml.namespace", yamlNamespaceName);
+    final String systemPropertyKey = "from.system.namespace";
+    final String systemPropertyKeyYaml = "from.system.yaml.namespace";
+    System.setProperty(systemPropertyKey, namespaceName);
+    System.setProperty(systemPropertyKeyYaml, yamlNamespaceName);
     Config config = mock(Config.class);
     Config yamlConfig = mock(Config.class);
     mockConfig(namespaceName, config);
@@ -493,6 +489,9 @@ public void testApolloConfigResolveExpressionFromSystemProperty() {
         TestApolloConfigResolveExpressionFromSystemPropertyConfiguration.class);
     assertEquals(config, configuration.getConfig());
     assertEquals(yamlConfig, configuration.getYamlConfig());
+
+    System.clearProperty(systemPropertyKey);
+    System.clearProperty(systemPropertyKeyYaml);
   }
 
   @Test(expected = BeanCreationException.class)

From a550b346bcf13c2ee8d8d913cd5adb5b62ca3829 Mon Sep 17 00:00:00 2001
From: anilople 
Date: Sat, 14 Nov 2020 17:47:41 +0800
Subject: [PATCH 32/42] test(apollo-client): fix concurrent problem

---
 .../spring/JavaConfigAnnotationTest.java      | 26 ++++++-------------
 1 file changed, 8 insertions(+), 18 deletions(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
index 7f13ce7677b..bca18843b76 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
@@ -15,10 +15,8 @@
 import com.google.common.util.concurrent.SettableFuture;
 import java.io.IOException;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.Properties;
 import java.util.concurrent.TimeUnit;
-import org.junit.After;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.invocation.InvocationOnMock;
@@ -153,16 +151,8 @@ public void testEnableApolloConfigResolveExpressionFromSystemProperty() {
   @Test(expected = BeanCreationException.class)
   public void testEnableApolloConfigUnresolvedValueInField() {
     mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class));
-
-    final String resolvedNamespaceName = "yyy";
-    final String SystemPropertyKey = "simple.namespace";
-    System.setProperty(SystemPropertyKey, resolvedNamespaceName);
-    Config yyyConfig = mock(Config.class);
-    mockConfig(resolvedNamespaceName, yyyConfig);
-
+    mockConfig("xxx", mock(Config.class));
     getSimpleBean(TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration.class);
-
-    System.clearProperty(SystemPropertyKey);
   }
 
   @Test(expected = IllegalArgumentException.class)
@@ -461,13 +451,13 @@ public void testApolloConfigChangeListenerResolveExpressionFromSelfYaml() throws
   @Test
   public void testApolloConfigResolveExpressionDefault() {
     mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class));
-    Config xxx = mock(Config.class);
+    Config defaultConfig = mock(Config.class);
     Config yamlConfig = mock(Config.class);
-    mockConfig("xxx", xxx);
+    mockConfig("default-2020-11-14-1733", defaultConfig);
     mockConfig(APPLICATION_YAML_NAMESPACE, yamlConfig);
     TestApolloConfigResolveExpressionDefaultConfiguration configuration = getSimpleBean(
         TestApolloConfigResolveExpressionDefaultConfiguration.class);
-    assertEquals(xxx, configuration.getXxx());
+    assertEquals(defaultConfig, configuration.getDefaultConfig());
     assertEquals(yamlConfig, configuration.getYamlConfig());
   }
 
@@ -525,14 +515,14 @@ public void testApolloConfigResolveExpressionFromApolloConfigNamespaceApplicatio
   @EnableApolloConfig
   protected static class TestApolloConfigResolveExpressionDefaultConfiguration {
 
-    @ApolloConfig(value = "${simple.namespace:xxx}")
-    private Config xxx;
+    @ApolloConfig(value = "${simple.namespace:default-2020-11-14-1733}")
+    private Config defaultConfig;
 
     @ApolloConfig(value = "${simple.yaml.namespace:" + APPLICATION_YAML_NAMESPACE + "}")
     private Config yamlConfig;
 
-    public Config getXxx() {
-      return xxx;
+    public Config getDefaultConfig() {
+      return defaultConfig;
     }
 
     public Config getYamlConfig() {

From 1f3e6f4ce33680676f73ee02c29f3d26d6530d93 Mon Sep 17 00:00:00 2001
From: anilople 
Date: Sat, 14 Nov 2020 17:55:51 +0800
Subject: [PATCH 33/42] test(apollo-client): change "someKey" to forbid
 confliction

---
 .../apollo/spring/JavaConfigAnnotationTest.java       | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
index bca18843b76..65445c7ae77 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
@@ -16,6 +16,7 @@
 import java.io.IOException;
 import java.util.Collections;
 import java.util.Properties;
+import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -109,8 +110,8 @@ public void testApolloConfigWithInheritance() throws Exception {
 
   @Test
   public void testEnableApolloConfigResolveExpressionSimple() {
-    String someKey = "someKey";
-    String someValue = "someValue";
+    String someKey = "someKey-2020-11-14-1750";
+    String someValue = UUID.randomUUID().toString();
     mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class));
     Config xxxConfig = mock(Config.class);
     when(xxxConfig.getProperty(eq(someKey), anyString())).thenReturn(someValue);
@@ -127,8 +128,8 @@ public void testEnableApolloConfigResolveExpressionSimple() {
   @Test
   public void testEnableApolloConfigResolveExpressionFromSystemProperty() {
     mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class));
-    final String someKey = "someKey";
-    final String someValue = "someValue";
+    final String someKey = "someKey-2020-11-14-1750";
+    final String someValue = UUID.randomUUID().toString();
 
     final String propertyKey = "simple.namespace";
     final String resolvedNamespaceName = "yyy";
@@ -627,7 +628,7 @@ private void onChange(ConfigChangeEvent event) {
   @EnableApolloConfig(value = {ConfigConsts.NAMESPACE_APPLICATION, "${simple.namespace:xxx}"})
   static class TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration {
 
-    @Value("${someKey}")
+    @Value("${someKey-2020-11-14-1750}")
     private String someKey;
 
     public String getSomeKey() {

From 2ff60d588a924e95480acba10ca9b08437632bc6 Mon Sep 17 00:00:00 2001
From: anilople 
Date: Sun, 22 Nov 2020 16:04:33 +0800
Subject: [PATCH 34/42] test(apollo-client): clear custom system property after
 each test case

---
 .../spring/AbstractSpringIntegrationTest.java | 31 +++++++++++++
 .../spring/JavaConfigAnnotationTest.java      | 43 +++++++++++--------
 2 files changed, 57 insertions(+), 17 deletions(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/AbstractSpringIntegrationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/AbstractSpringIntegrationTest.java
index e3f8a2083b8..985f3cf9ad9 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/AbstractSpringIntegrationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/AbstractSpringIntegrationTest.java
@@ -15,7 +15,9 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.nio.charset.StandardCharsets;
 import java.util.Calendar;
 import java.util.Date;
@@ -34,6 +36,8 @@
 import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
 import com.ctrip.framework.apollo.internals.ConfigManager;
 import com.google.common.collect.Maps;
+import org.springframework.util.ReflectionUtils.FieldCallback;
+import org.springframework.util.ReflectionUtils.FieldFilter;
 
 /**
  * @author Jason Song(song_s@ctrip.com)
@@ -149,6 +153,33 @@ protected static void mockConfigFile(String namespaceNameWithFormat, ConfigFile
     CONFIG_FILE_REGISTRY.put(namespaceNameWithFormat, configFile);
   }
 
+  /**
+   * Clear the system property after each test case. The key is declared in a class's field, and it
+   * should be a static string.
+   */
+  protected static void clearSystemPropertiesDefineWithStaticStringField(Class clazz) {
+    // filter static string
+    FieldFilter isStaticString = new FieldFilter() {
+      @Override
+      public boolean matches(Field field) {
+        int modifiers = field.getModifiers();
+        return Modifier.isStatic(modifiers) && field.getType().isAssignableFrom(String.class);
+      }
+    };
+
+    // clear system property by field's value
+    FieldCallback clearSystemProperty = new FieldCallback() {
+      @Override
+      public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
+        ReflectionUtils.makeAccessible(field);
+        String systemPropertyKey = (String) field.get(null);
+        System.clearProperty(systemPropertyKey);
+      }
+    };
+
+    ReflectionUtils.doWithFields(clazz, clearSystemProperty, isStaticString);
+  }
+
   protected static void doSetUp() {
     //as ConfigService is singleton, so we must manually clear its container
     ReflectionUtils.invokeMethod(CONFIG_SERVICE_RESET, null);
diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
index 65445c7ae77..4bc7c68247d 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
@@ -18,6 +18,7 @@
 import java.util.Properties;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
+import org.junit.After;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.invocation.InvocationOnMock;
@@ -60,6 +61,15 @@ private static  T getSimpleBean(Class clazz) {
     return getBean(clazz, clazz);
   }
 
+  /**
+   * forbidden to override the method {@link super#tearDown()}.
+   */
+  @After
+  public void javaConfigAnnotationTestTearDown() {
+    // clear the system property
+    clearSystemPropertiesDefineWithStaticStringField(SystemPropertyKeyConstants.class);
+  }
+
   @Test
   public void testApolloConfig() throws Exception {
     Config applicationConfig = mock(Config.class);
@@ -131,9 +141,8 @@ public void testEnableApolloConfigResolveExpressionFromSystemProperty() {
     final String someKey = "someKey-2020-11-14-1750";
     final String someValue = UUID.randomUUID().toString();
 
-    final String propertyKey = "simple.namespace";
     final String resolvedNamespaceName = "yyy";
-    System.setProperty(propertyKey, resolvedNamespaceName);
+    System.setProperty(SystemPropertyKeyConstants.SIMPLE_NAMESPACE, resolvedNamespaceName);
 
     Config yyyConfig = mock(Config.class);
     when(yyyConfig.getProperty(eq(someKey), anyString())).thenReturn(someValue);
@@ -145,8 +154,6 @@ public void testEnableApolloConfigResolveExpressionFromSystemProperty() {
     // check
     assertEquals(someValue, configuration.getSomeKey());
     verify(yyyConfig, times(1)).getProperty(eq(someKey), anyString());
-
-    System.clearProperty(propertyKey);
   }
 
   @Test(expected = BeanCreationException.class)
@@ -393,8 +400,7 @@ public void testApolloConfigChangeListenerResolveExpressionFromSystemProperty()
     mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
 
     final String namespaceName = "magicRedis";
-    final String systemPropertyKey = "redis.namespace";
-    System.setProperty(systemPropertyKey, namespaceName);
+    System.setProperty(SystemPropertyKeyConstants.REDIS_NAMESPACE, namespaceName);
     Config redisConfig = mock(Config.class);
     mockConfig(namespaceName, redisConfig);
     getSimpleBean(
@@ -402,8 +408,6 @@ public void testApolloConfigChangeListenerResolveExpressionFromSystemProperty()
 
     // if config was used, it must be invoked on method addChangeListener 1 time
     verify(redisConfig, times(1)).addChangeListener(any(ConfigChangeListener.class));
-
-    System.clearProperty(systemPropertyKey);
   }
 
   /**
@@ -468,10 +472,8 @@ public void testApolloConfigResolveExpressionFromSystemProperty() {
     final String namespaceName = "xxx6";
     final String yamlNamespaceName = "yyy8.yml";
 
-    final String systemPropertyKey = "from.system.namespace";
-    final String systemPropertyKeyYaml = "from.system.yaml.namespace";
-    System.setProperty(systemPropertyKey, namespaceName);
-    System.setProperty(systemPropertyKeyYaml, yamlNamespaceName);
+    System.setProperty(SystemPropertyKeyConstants.FROM_SYSTEM_NAMESPACE, namespaceName);
+    System.setProperty(SystemPropertyKeyConstants.FROM_SYSTEM_YAML_NAMESPACE, yamlNamespaceName);
     Config config = mock(Config.class);
     Config yamlConfig = mock(Config.class);
     mockConfig(namespaceName, config);
@@ -480,9 +482,6 @@ public void testApolloConfigResolveExpressionFromSystemProperty() {
         TestApolloConfigResolveExpressionFromSystemPropertyConfiguration.class);
     assertEquals(config, configuration.getConfig());
     assertEquals(yamlConfig, configuration.getYamlConfig());
-
-    System.clearProperty(systemPropertyKey);
-    System.clearProperty(systemPropertyKeyYaml);
   }
 
   @Test(expected = BeanCreationException.class)
@@ -499,8 +498,8 @@ public void testApolloConfigResolveExpressionFromApolloConfigNamespaceApplicatio
     {
       // hide variable scope
       Properties properties = new Properties();
-      properties.setProperty("from.namespace.application.key", namespaceName);
-      properties.setProperty("from.namespace.application.key.yaml", yamlNamespaceName);
+      properties.setProperty(SystemPropertyKeyConstants.FROM_NAMESPACE_APPLICATION_KEY, namespaceName);
+      properties.setProperty(SystemPropertyKeyConstants.FROM_NAMESPACE_APPLICATION_KEY_YAML, yamlNamespaceName);
       this.prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties);
     }
     final Config config = mock(Config.class);
@@ -513,6 +512,16 @@ public void testApolloConfigResolveExpressionFromApolloConfigNamespaceApplicatio
     assertEquals(yamlConfig, configuration.getYamlConfig());
   }
 
+  protected static class SystemPropertyKeyConstants {
+
+    static final String SIMPLE_NAMESPACE = "simple.namespace";
+    static final String REDIS_NAMESPACE = "redis.namespace";
+    static final String FROM_SYSTEM_NAMESPACE = "from.system.namespace";
+    static final String FROM_SYSTEM_YAML_NAMESPACE = "from.system.yaml.namespace";
+    static final String FROM_NAMESPACE_APPLICATION_KEY = "from.namespace.application.key";
+    static final String FROM_NAMESPACE_APPLICATION_KEY_YAML = "from.namespace.application.key.yaml";
+  }
+
   @EnableApolloConfig
   protected static class TestApolloConfigResolveExpressionDefaultConfiguration {
 

From 363c7277f27bf38deae30c3a69b4654006f1734c Mon Sep 17 00:00:00 2001
From: anilople 
Date: Sun, 22 Nov 2020 16:07:15 +0800
Subject: [PATCH 35/42] test(apollo-client): delete unnecessary test case.

---
 .../spring/JavaConfigAnnotationTest.java      | 21 -------------------
 1 file changed, 21 deletions(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
index 4bc7c68247d..3466596d439 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
@@ -168,21 +168,6 @@ public void testEnableApolloConfigUnresolvable() {
     getSimpleBean(TestEnableApolloConfigUnresolvableConfiguration.class);
   }
 
-  /**
-   * Could not resolve placeholder.
-   * Unfortunately, when use {@link EnableApolloConfig},
-   * only key in {@link System#getenv(String)} or {@link System#getProperty(String)} can be resolved.
-   */
-  @Test(expected = IllegalArgumentException.class)
-  public void testEnableApolloConfigResolveFromNamespaceApplication() {
-    {
-      Properties properties = new Properties();
-      properties.setProperty("from.namespace.application.key", "abc");
-      this.prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties);
-    }
-    getSimpleBean(TestEnableApolloConfigResolveFromNamespaceApplicationConfiguration.class);
-  }
-
   @Test
   public void testApolloConfigChangeListener() throws Exception {
     Config applicationConfig = mock(Config.class);
@@ -651,12 +636,6 @@ static class TestEnableApolloConfigUnresolvableConfiguration {
 
   }
 
-  @Configuration
-  @EnableApolloConfig(value = "${from.namespace.application.key}")
-  static class TestEnableApolloConfigResolveFromNamespaceApplicationConfiguration {
-
-  }
-
   @Configuration
   @EnableApolloConfig
   static class AppConfig1 {

From 593ce02f27eca7c2ea87fd01daa4ddedb4be1550 Mon Sep 17 00:00:00 2001
From: anilople 
Date: Sun, 22 Nov 2020 16:10:37 +0800
Subject: [PATCH 36/42] test(apollo-client): delete redundant code in test case
 testApolloConfigChangeListenerResolveExpressionFromApplicationNamespace.

---
 .../framework/apollo/spring/JavaConfigAnnotationTest.java      | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
index 3466596d439..5b586857a7e 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
@@ -401,9 +401,6 @@ public void testApolloConfigChangeListenerResolveExpressionFromSystemProperty()
    */
   @Test
   public void testApolloConfigChangeListenerResolveExpressionFromApplicationNamespace() {
-    Config applicationConfig = mock(Config.class);
-    mockConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationConfig);
-
     final String namespaceKey = "mysql.namespace";
     final String namespaceName = "magicMysqlNamespaceApplication";
 

From 28e34e69b3425e9b5c06e8844a11a509fa25e90c Mon Sep 17 00:00:00 2001
From: anilople 
Date: Sun, 22 Nov 2020 16:14:06 +0800
Subject: [PATCH 37/42] test(apollo-client): change assertEquals to assertSame
 to check mocked config

---
 .../apollo/spring/JavaConfigAnnotationTest.java     | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
index 5b586857a7e..a3f350a7e13 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
@@ -34,6 +34,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anySetOf;
 import static org.mockito.Matchers.anyString;
@@ -444,8 +445,8 @@ public void testApolloConfigResolveExpressionDefault() {
     mockConfig(APPLICATION_YAML_NAMESPACE, yamlConfig);
     TestApolloConfigResolveExpressionDefaultConfiguration configuration = getSimpleBean(
         TestApolloConfigResolveExpressionDefaultConfiguration.class);
-    assertEquals(defaultConfig, configuration.getDefaultConfig());
-    assertEquals(yamlConfig, configuration.getYamlConfig());
+    assertSame(defaultConfig, configuration.getDefaultConfig());
+    assertSame(yamlConfig, configuration.getYamlConfig());
   }
 
   @Test
@@ -462,8 +463,8 @@ public void testApolloConfigResolveExpressionFromSystemProperty() {
     mockConfig(yamlNamespaceName, yamlConfig);
     TestApolloConfigResolveExpressionFromSystemPropertyConfiguration configuration = getSimpleBean(
         TestApolloConfigResolveExpressionFromSystemPropertyConfiguration.class);
-    assertEquals(config, configuration.getConfig());
-    assertEquals(yamlConfig, configuration.getYamlConfig());
+    assertSame(config, configuration.getConfig());
+    assertSame(yamlConfig, configuration.getYamlConfig());
   }
 
   @Test(expected = BeanCreationException.class)
@@ -490,8 +491,8 @@ public void testApolloConfigResolveExpressionFromApolloConfigNamespaceApplicatio
     mockConfig(yamlNamespaceName, yamlConfig);
     TestApolloConfigResolveExpressionFromApolloConfigNamespaceApplication configuration = getSimpleBean(
         TestApolloConfigResolveExpressionFromApolloConfigNamespaceApplication.class);
-    assertEquals(config, configuration.getConfig());
-    assertEquals(yamlConfig, configuration.getYamlConfig());
+    assertSame(config, configuration.getConfig());
+    assertSame(yamlConfig, configuration.getYamlConfig());
   }
 
   protected static class SystemPropertyKeyConstants {

From 27c043d257e94d902c39d0eb6dd3b5e16562ce83 Mon Sep 17 00:00:00 2001
From: anilople 
Date: Sun, 22 Nov 2020 16:27:18 +0800
Subject: [PATCH 38/42] test(apollo-client): clear system property after test

---
 .../apollo/spring/JavaConfigAnnotationTest.java |  2 +-
 .../apollo/spring/XmlConfigPlaceholderTest.java | 17 +++++++++++++++--
 2 files changed, 16 insertions(+), 3 deletions(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
index a3f350a7e13..8349e2280fd 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
@@ -495,7 +495,7 @@ public void testApolloConfigResolveExpressionFromApolloConfigNamespaceApplicatio
     assertSame(yamlConfig, configuration.getYamlConfig());
   }
 
-  protected static class SystemPropertyKeyConstants {
+  private static class SystemPropertyKeyConstants {
 
     static final String SIMPLE_NAMESPACE = "simple.namespace";
     static final String REDIS_NAMESPACE = "redis.namespace";
diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java
index 378bdad562e..2889e121164 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java
@@ -8,6 +8,7 @@
 
 import com.ctrip.framework.apollo.Config;
 import com.ctrip.framework.apollo.core.ConfigConsts;
+import org.junit.After;
 import org.junit.Test;
 import org.springframework.beans.FatalBeanException;
 import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException;
@@ -24,6 +25,12 @@ public class XmlConfigPlaceholderTest extends AbstractSpringIntegrationTest {
   private static final int DEFAULT_BATCH = 200;
   private static final String FX_APOLLO_NAMESPACE = "FX.apollo";
 
+  @After
+  public void XmlConfigPlaceholderTestTearDown() {
+    // clear the system property
+    clearSystemPropertiesDefineWithStaticStringField(SystemPropertyKeyConstants.class);
+  }
+
   @Test
   public void testPropertySourceWithNoNamespace() throws Exception {
     int someTimeout = 1000;
@@ -141,8 +148,8 @@ public void testResolveNamespacesWithDefaultValue() throws Exception {
 
   @Test
   public void testResolveNamespacesFromSystemProperty() throws Exception {
-    System.setProperty("xxx.from.system.property", ConfigConsts.NAMESPACE_APPLICATION);
-    System.setProperty("yyy.from.system.property", "FX.apollo");
+    System.setProperty(SystemPropertyKeyConstants.XXX_FROM_SYSTEM_PROPERTY, ConfigConsts.NAMESPACE_APPLICATION);
+    System.setProperty(SystemPropertyKeyConstants.YYY_FROM_SYSTEM_PROPERTY, "FX.apollo");
     int someTimeout = 1000;
     int anotherTimeout = someTimeout + 1;
     int someBatch = 2000;
@@ -170,6 +177,12 @@ private static void check(String xmlLocation, int expectedTimeout, int expectedB
     assertEquals(expectedBatch, bean.getBatch());
   }
 
+  private static class SystemPropertyKeyConstants {
+
+    static final String XXX_FROM_SYSTEM_PROPERTY = "xxx.from.system.property";
+    static final String YYY_FROM_SYSTEM_PROPERTY = "yyy.from.system.property";
+  }
+
   public static class TestXmlBean {
 
     private int timeout;

From b5eb6f0b3f77b2b62441b2f77b7fad8873704f88 Mon Sep 17 00:00:00 2001
From: anilople 
Date: Sun, 22 Nov 2020 16:34:42 +0800
Subject: [PATCH 39/42] test(apollo-client): fix 'there is no value for
 xxx.from.system.property'. Merge similar xml file.

---
 .../apollo/spring/XmlConfigPlaceholderTest.java    |  6 +++---
 ...ig.namespace.placeholder.with.default.value.xml | 14 ++++++++++++++
 .../spring/config.namespace.placeholder.xml        | 14 ++++++++++++++
 ...nfig.namespace.resolve.from.system.property.xml | 14 --------------
 .../spring/config.namespace.resolve.to.default.xml | 14 --------------
 .../spring/config.namespace.unresolved.xml         | 14 --------------
 6 files changed, 31 insertions(+), 45 deletions(-)
 create mode 100644 apollo-client/src/test/resources/spring/config.namespace.placeholder.with.default.value.xml
 create mode 100644 apollo-client/src/test/resources/spring/config.namespace.placeholder.xml
 delete mode 100644 apollo-client/src/test/resources/spring/config.namespace.resolve.from.system.property.xml
 delete mode 100644 apollo-client/src/test/resources/spring/config.namespace.resolve.to.default.xml
 delete mode 100644 apollo-client/src/test/resources/spring/config.namespace.unresolved.xml

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java
index 2889e121164..bdf0fd14439 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java
@@ -143,7 +143,7 @@ public void testResolveNamespacesWithDefaultValue() throws Exception {
     int someBatch = 2000;
     this.prepare(someTimeout, anotherTimeout, someBatch);
 
-    check("spring/config.namespace.resolve.to.default.xml", anotherTimeout, someBatch);
+    check("spring/config.namespace.placeholder.with.default.value.xml", anotherTimeout, someBatch);
   }
 
   @Test
@@ -155,7 +155,7 @@ public void testResolveNamespacesFromSystemProperty() throws Exception {
     int someBatch = 2000;
     this.prepare(someTimeout, anotherTimeout, someBatch);
 
-    check("spring/config.namespace.resolve.from.system.property.xml", anotherTimeout, someBatch);
+    check("spring/config.namespace.placeholder.xml", anotherTimeout, someBatch);
   }
 
   @Test(expected = FatalBeanException.class)
@@ -165,7 +165,7 @@ public void testUnresolvedNamespaces() {
     int someBatch = 2000;
     this.prepare(someTimeout, anotherTimeout, someBatch);
 
-    check("spring/config.namespace.unresolved.xml", anotherTimeout, someBatch);
+    check("spring/config.namespace.placeholder.xml", anotherTimeout, someBatch);
   }
 
   private static void check(String xmlLocation, int expectedTimeout, int expectedBatch) {
diff --git a/apollo-client/src/test/resources/spring/config.namespace.placeholder.with.default.value.xml b/apollo-client/src/test/resources/spring/config.namespace.placeholder.with.default.value.xml
new file mode 100644
index 00000000000..3b5a9c1acfd
--- /dev/null
+++ b/apollo-client/src/test/resources/spring/config.namespace.placeholder.with.default.value.xml
@@ -0,0 +1,14 @@
+
+
+  
+  
+
+  
+    
+    
+  
+
diff --git a/apollo-client/src/test/resources/spring/config.namespace.placeholder.xml b/apollo-client/src/test/resources/spring/config.namespace.placeholder.xml
new file mode 100644
index 00000000000..4c658bd668e
--- /dev/null
+++ b/apollo-client/src/test/resources/spring/config.namespace.placeholder.xml
@@ -0,0 +1,14 @@
+
+
+  
+  
+
+  
+    
+    
+  
+
diff --git a/apollo-client/src/test/resources/spring/config.namespace.resolve.from.system.property.xml b/apollo-client/src/test/resources/spring/config.namespace.resolve.from.system.property.xml
deleted file mode 100644
index 93ac9f66e33..00000000000
--- a/apollo-client/src/test/resources/spring/config.namespace.resolve.from.system.property.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-    
-    
-
-    
-        
-        
-    
-
diff --git a/apollo-client/src/test/resources/spring/config.namespace.resolve.to.default.xml b/apollo-client/src/test/resources/spring/config.namespace.resolve.to.default.xml
deleted file mode 100644
index 748129d4d86..00000000000
--- a/apollo-client/src/test/resources/spring/config.namespace.resolve.to.default.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-    
-    
-
-    
-        
-        
-    
-
diff --git a/apollo-client/src/test/resources/spring/config.namespace.unresolved.xml b/apollo-client/src/test/resources/spring/config.namespace.unresolved.xml
deleted file mode 100644
index 0ec1c764872..00000000000
--- a/apollo-client/src/test/resources/spring/config.namespace.unresolved.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-    
-    
-
-    
-        
-        
-    
-

From f18b4f8d37a9d1d6e6e240804770c598efeb7345 Mon Sep 17 00:00:00 2001
From: anilople 
Date: Sun, 22 Nov 2020 18:00:34 +0800
Subject: [PATCH 40/42] test(apollo-client): clear system property manually

---
 .../spring/AbstractSpringIntegrationTest.java | 27 -------------------
 .../spring/JavaConfigAnnotationTest.java      |  9 +++++--
 .../spring/XmlConfigPlaceholderTest.java      |  8 ++++--
 3 files changed, 13 insertions(+), 31 deletions(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/AbstractSpringIntegrationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/AbstractSpringIntegrationTest.java
index 985f3cf9ad9..8e5b43450b7 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/AbstractSpringIntegrationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/AbstractSpringIntegrationTest.java
@@ -153,33 +153,6 @@ protected static void mockConfigFile(String namespaceNameWithFormat, ConfigFile
     CONFIG_FILE_REGISTRY.put(namespaceNameWithFormat, configFile);
   }
 
-  /**
-   * Clear the system property after each test case. The key is declared in a class's field, and it
-   * should be a static string.
-   */
-  protected static void clearSystemPropertiesDefineWithStaticStringField(Class clazz) {
-    // filter static string
-    FieldFilter isStaticString = new FieldFilter() {
-      @Override
-      public boolean matches(Field field) {
-        int modifiers = field.getModifiers();
-        return Modifier.isStatic(modifiers) && field.getType().isAssignableFrom(String.class);
-      }
-    };
-
-    // clear system property by field's value
-    FieldCallback clearSystemProperty = new FieldCallback() {
-      @Override
-      public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
-        ReflectionUtils.makeAccessible(field);
-        String systemPropertyKey = (String) field.get(null);
-        System.clearProperty(systemPropertyKey);
-      }
-    };
-
-    ReflectionUtils.doWithFields(clazz, clearSystemProperty, isStaticString);
-  }
-
   protected static void doSetUp() {
     //as ConfigService is singleton, so we must manually clear its container
     ReflectionUtils.invokeMethod(CONFIG_SERVICE_RESET, null);
diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
index 8349e2280fd..95353412d00 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
@@ -67,8 +67,13 @@ private static  T getSimpleBean(Class clazz) {
    */
   @After
   public void javaConfigAnnotationTestTearDown() {
-    // clear the system property
-    clearSystemPropertiesDefineWithStaticStringField(SystemPropertyKeyConstants.class);
+    // clear the system properties
+    System.clearProperty(SystemPropertyKeyConstants.SIMPLE_NAMESPACE);
+    System.clearProperty(SystemPropertyKeyConstants.REDIS_NAMESPACE);
+    System.clearProperty(SystemPropertyKeyConstants.FROM_SYSTEM_NAMESPACE);
+    System.clearProperty(SystemPropertyKeyConstants.FROM_SYSTEM_YAML_NAMESPACE);
+    System.clearProperty(SystemPropertyKeyConstants.FROM_NAMESPACE_APPLICATION_KEY);
+    System.clearProperty(SystemPropertyKeyConstants.FROM_NAMESPACE_APPLICATION_KEY_YAML);
   }
 
   @Test
diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java
index bdf0fd14439..3b7d64dc2ed 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderTest.java
@@ -25,10 +25,14 @@ public class XmlConfigPlaceholderTest extends AbstractSpringIntegrationTest {
   private static final int DEFAULT_BATCH = 200;
   private static final String FX_APOLLO_NAMESPACE = "FX.apollo";
 
+  /**
+   * forbidden to override the method {@link super#tearDown()}.
+   */
   @After
   public void XmlConfigPlaceholderTestTearDown() {
-    // clear the system property
-    clearSystemPropertiesDefineWithStaticStringField(SystemPropertyKeyConstants.class);
+    // clear the system properties
+    System.clearProperty(SystemPropertyKeyConstants.XXX_FROM_SYSTEM_PROPERTY);
+    System.clearProperty(SystemPropertyKeyConstants.YYY_FROM_SYSTEM_PROPERTY);
   }
 
   @Test

From c4f0665601a50b3b6226a5b65833fd1bca26f034 Mon Sep 17 00:00:00 2001
From: Jason Song 
Date: Sun, 22 Nov 2020 19:23:03 +0800
Subject: [PATCH 41/42] Update JavaConfigAnnotationTest.java

---
 .../framework/apollo/spring/JavaConfigAnnotationTest.java  | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
index 95353412d00..63eed65cbf4 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
@@ -62,11 +62,9 @@ private static  T getSimpleBean(Class clazz) {
     return getBean(clazz, clazz);
   }
 
-  /**
-   * forbidden to override the method {@link super#tearDown()}.
-   */
+  @Override
   @After
-  public void javaConfigAnnotationTestTearDown() {
+  public void tearDown() throws Exception {
     // clear the system properties
     System.clearProperty(SystemPropertyKeyConstants.SIMPLE_NAMESPACE);
     System.clearProperty(SystemPropertyKeyConstants.REDIS_NAMESPACE);
@@ -74,6 +72,7 @@ public void javaConfigAnnotationTestTearDown() {
     System.clearProperty(SystemPropertyKeyConstants.FROM_SYSTEM_YAML_NAMESPACE);
     System.clearProperty(SystemPropertyKeyConstants.FROM_NAMESPACE_APPLICATION_KEY);
     System.clearProperty(SystemPropertyKeyConstants.FROM_NAMESPACE_APPLICATION_KEY_YAML);
+    super.tearDown();
   }
 
   @Test

From 811f42a129b27ecbd06bb9d42b6b63677a82f19a Mon Sep 17 00:00:00 2001
From: Jason Song 
Date: Sun, 22 Nov 2020 19:25:36 +0800
Subject: [PATCH 42/42] Update JavaConfigAnnotationTest.java

---
 .../ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
index 63eed65cbf4..d83e2cb12fe 100644
--- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
+++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigAnnotationTest.java
@@ -377,7 +377,7 @@ public void testApolloConfigChangeListenerResolveExpressionSimple() {
     // no using
     verify(ignoreConfig, never()).addChangeListener(any(ConfigChangeListener.class));
 
-    // strange, it invoke 2 times
+    // one invocation for spring value auto update and another for the @ApolloConfigChangeListener annotation
     verify(applicationConfig, times(2)).addChangeListener(any(ConfigChangeListener.class));
   }