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..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 @@ -3,14 +3,30 @@ 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; +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; /** @@ -18,10 +34,42 @@ * * @author Jason Song(song_s@ctrip.com) */ -public class ApolloAnnotationProcessor extends ApolloProcessor { +public class ApolloAnnotationProcessor extends ApolloProcessor implements BeanFactoryAware, + EnvironmentAware { + + 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; + + /** + * resolve the expression. + */ + private ConfigurableBeanFactory configurableBeanFactory; + + private Environment environment; + + public ApolloAnnotationProcessor() { + 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) { + 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) { ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class); if (annotation == null) { return; @@ -30,15 +78,15 @@ 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.environment.resolveRequiredPlaceholders(namespace); + Config config = ConfigService.getConfig(resolvedNamespace); ReflectionUtils.makeAccessible(field); ReflectionUtils.setField(field, bean, config); } - @Override - protected void processMethod(final Object bean, String beanName, final Method method) { + private void processApolloConfigChangeListener(final Object bean, final Method method) { ApolloConfigChangeListener annotation = AnnotationUtils .findAnnotation(method, ApolloConfigChangeListener.class); if (annotation == null) { @@ -63,11 +111,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(namespace); + final String resolvedNamespace = this.environment.resolveRequiredPlaceholders(namespace); + Config config = ConfigService.getConfig(resolvedNamespace); if (interestedKeys == null && interestedKeyPrefixes == null) { config.addChangeListener(configChangeListener); @@ -76,4 +128,90 @@ 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; + } + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } } 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) 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..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,6 +18,12 @@ * //handle change event * } *
+ * //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 + * } + *
* //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) { 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..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 @@ -3,18 +3,26 @@ import com.ctrip.framework.apollo.spring.spi.ApolloConfigRegistrarHelper; import com.ctrip.framework.foundation.internals.ServiceBootstrap; 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 { +public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware { - 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 setEnvironment(Environment environment) { + this.helper.setEnvironment(environment); + } + } 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/annotation/EnableApolloConfig.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/EnableApolloConfig.java index 9572f970080..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 @@ -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,18 @@
  * }
  * 
* + *

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.
+ * // 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 {
+ *
+ * }
+ * 
+ * * @author Jason Song(song_s@ctrip.com) */ @Retention(RetentionPolicy.RUNTIME) 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"); 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..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 @@ -2,9 +2,10 @@ import com.ctrip.framework.apollo.core.spi.Ordered; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.EnvironmentAware; import org.springframework.core.type.AnnotationMetadata; -public interface ApolloConfigRegistrarHelper extends Ordered { +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 e25f4a88caf..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 @@ -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; @@ -14,17 +13,21 @@ 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 { + private Environment environment; + @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 @@ -40,12 +43,24 @@ 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) { + String[] resolvedNamespaces = new String[namespaces.length]; + for (int i = 0; i < namespaces.length; i++) { + // throw IllegalArgumentException if given text is null or if any placeholders are unresolvable + resolvedNamespaces[i] = this.environment.resolveRequiredPlaceholders(namespaces[i]); + } + return resolvedNamespaces; } @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } } 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); } 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..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 @@ -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; @@ -10,15 +11,19 @@ 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.Field; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +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; @@ -31,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) @@ -74,10 +81,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); @@ -90,7 +102,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); 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..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 @@ -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,13 +13,18 @@ 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.UUID; import java.util.concurrent.TimeUnit; +import org.junit.After; import org.junit.Test; import org.mockito.ArgumentCaptor; 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; @@ -26,15 +32,19 @@ 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.junit.Assert.assertSame; 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) @@ -43,6 +53,28 @@ 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 getBean(clazz, clazz); + } + + @Override + @After + public void tearDown() throws Exception { + // 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); + super.tearDown(); + } + @Test public void testApolloConfig() throws Exception { Config applicationConfig = mock(Config.class); @@ -91,6 +123,56 @@ public void testApolloConfigWithInheritance() throws Exception { assertEquals(applicationConfig, bean.getSomeConfig()); } + @Test + public void testEnableApolloConfigResolveExpressionSimple() { + 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); + mockConfig("xxx", xxxConfig); + + TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration configuration = + getSimpleBean(TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration.class); + + // check + assertEquals(someValue, configuration.getSomeKey()); + verify(xxxConfig, times(1)).getProperty(eq(someKey), anyString()); + } + + @Test + public void testEnableApolloConfigResolveExpressionFromSystemProperty() { + mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class)); + final String someKey = "someKey-2020-11-14-1750"; + final String someValue = UUID.randomUUID().toString(); + + final String resolvedNamespaceName = "yyy"; + System.setProperty(SystemPropertyKeyConstants.SIMPLE_NAMESPACE, 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()); + } + + @Test(expected = BeanCreationException.class) + public void testEnableApolloConfigUnresolvedValueInField() { + mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class)); + mockConfig("xxx", mock(Config.class)); + getSimpleBean(TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration.class); + } + + @Test(expected = IllegalArgumentException.class) + public void testEnableApolloConfigUnresolvable() { + getSimpleBean(TestEnableApolloConfigUnresolvableConfiguration.class); + } + @Test public void testApolloConfigChangeListener() throws Exception { Config applicationConfig = mock(Config.class); @@ -102,7 +184,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)); @@ -111,7 +193,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)); @@ -175,7 +257,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)); @@ -184,7 +266,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)); @@ -281,10 +363,279 @@ public void testApolloConfigChangeListenerWithYamlFile() throws Exception { assertEquals(anotherValue, yamlConfig.getProperty(someKey, null)); } - private T getBean(Class beanClass, Class... annotatedClasses) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(annotatedClasses); + @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)); + + // one invocation for spring value auto update and another for the @ApolloConfigChangeListener annotation + 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(SystemPropertyKeyConstants.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() { + 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() { + mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class)); + Config defaultConfig = mock(Config.class); + Config yamlConfig = mock(Config.class); + mockConfig("default-2020-11-14-1733", defaultConfig); + mockConfig(APPLICATION_YAML_NAMESPACE, yamlConfig); + TestApolloConfigResolveExpressionDefaultConfiguration configuration = getSimpleBean( + TestApolloConfigResolveExpressionDefaultConfiguration.class); + assertSame(defaultConfig, configuration.getDefaultConfig()); + assertSame(yamlConfig, configuration.getYamlConfig()); + } + + @Test + public void testApolloConfigResolveExpressionFromSystemProperty() { + mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class)); + final String namespaceName = "xxx6"; + final String yamlNamespaceName = "yyy8.yml"; + + 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); + mockConfig(yamlNamespaceName, yamlConfig); + TestApolloConfigResolveExpressionFromSystemPropertyConfiguration configuration = getSimpleBean( + TestApolloConfigResolveExpressionFromSystemPropertyConfiguration.class); + assertSame(config, configuration.getConfig()); + assertSame(yamlConfig, configuration.getYamlConfig()); + } + + @Test(expected = BeanCreationException.class) + public void testApolloConfigUnresolvedExpression() { + mockConfig(ConfigConsts.NAMESPACE_APPLICATION, mock(Config.class)); + getSimpleBean(TestApolloConfigUnresolvedExpressionConfiguration.class); + } + + @Test + public void testApolloConfigResolveExpressionFromApolloConfigNamespaceApplication() { + + final String namespaceName = "xxx6"; + final String yamlNamespaceName = "yyy8.yml"; + { + // hide variable scope + Properties properties = new Properties(); + 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); + final Config yamlConfig = mock(Config.class); + mockConfig(namespaceName, config); + mockConfig(yamlNamespaceName, yamlConfig); + TestApolloConfigResolveExpressionFromApolloConfigNamespaceApplication configuration = getSimpleBean( + TestApolloConfigResolveExpressionFromApolloConfigNamespaceApplication.class); + assertSame(config, configuration.getConfig()); + assertSame(yamlConfig, configuration.getYamlConfig()); + } + + private 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 { + + @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 getDefaultConfig() { + return defaultConfig; + } + + public Config getYamlConfig() { + return yamlConfig; + } + } + + @EnableApolloConfig + protected static class TestApolloConfigResolveExpressionFromSystemPropertyConfiguration { + + @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 TestApolloConfigUnresolvedExpressionConfiguration { + + @ApolloConfig(value = "${so.complex.to.resolve}") + private Config config; + } + + @EnableApolloConfig + protected static class TestApolloConfigResolveExpressionFromApolloConfigNamespaceApplication { + + @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; + } + } + + + @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}"}) + static class TestEnableApolloConfigResolveExpressionWithDefaultValueConfiguration { + + @Value("${someKey-2020-11-14-1750}") + private String someKey; + + public String getSomeKey() { + return this.someKey; + } + } + + @Configuration + @EnableApolloConfig(value = "${unresolvable.property}") + static class TestEnableApolloConfigUnresolvableConfiguration { - return context.getBean(beanClass); } @Configuration 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..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 @@ -6,30 +6,43 @@ 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.After; 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) */ 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"; 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 properties + System.clearProperty(SystemPropertyKeyConstants.XXX_FROM_SYSTEM_PROPERTY); + System.clearProperty(SystemPropertyKeyConstants.YYY_FROM_SYSTEM_PROPERTY); + } + @Test public void testPropertySourceWithNoNamespace() throws Exception { int someTimeout = 1000; 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 +63,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 +78,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 +119,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 +129,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); } @@ -133,7 +139,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.placeholder.with.default.value.xml", anotherTimeout, someBatch); + } + + @Test + public void testResolveNamespacesFromSystemProperty() throws Exception { + 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; + this.prepare(someTimeout, anotherTimeout, someBatch); + + check("spring/config.namespace.placeholder.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.placeholder.xml", anotherTimeout, someBatch); + } + + private static void check(String xmlLocation, int expectedTimeout, int expectedBatch) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlLocation); TestXmlBean bean = context.getBean(TestXmlBean.class); @@ -142,7 +181,14 @@ private void check(String xmlLocation, int expectedTimeout, int expectedBatch) { 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; private int batch; 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/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