diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultInjector.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultInjector.java index 46e935ff7f8..c157b414a7a 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultInjector.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/DefaultInjector.java @@ -7,6 +7,8 @@ import com.ctrip.framework.apollo.spi.DefaultConfigFactory; import com.ctrip.framework.apollo.spi.DefaultConfigFactoryManager; import com.ctrip.framework.apollo.spi.DefaultConfigRegistry; +import com.ctrip.framework.apollo.spring.config.ConfigPropertySourceFactory; +import com.ctrip.framework.apollo.spring.property.PlaceholderHelper; import com.ctrip.framework.apollo.tracer.Tracer; import com.ctrip.framework.apollo.util.ConfigUtil; import com.ctrip.framework.apollo.util.http.HttpUtil; @@ -60,6 +62,8 @@ protected void configure() { bind(HttpUtil.class).in(Singleton.class); bind(ConfigServiceLocator.class).in(Singleton.class); bind(RemoteConfigLongPollService.class).in(Singleton.class); + bind(PlaceholderHelper.class).in(Singleton.class); + bind(ConfigPropertySourceFactory.class).in(Singleton.class); } } } 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 03d55be90a4..af6ca19e2b8 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 @@ -1,5 +1,6 @@ package com.ctrip.framework.apollo.spring.annotation; +import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; @@ -30,5 +31,8 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(), ApolloAnnotationProcessor.class); + + BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class); + BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(), SpringValueDefinitionProcessor.class); } } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/SpringValueProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/SpringValueProcessor.java new file mode 100644 index 00000000000..ff0d54f0671 --- /dev/null +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/SpringValueProcessor.java @@ -0,0 +1,317 @@ +package com.ctrip.framework.apollo.spring.annotation; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanExpressionContext; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.Scope; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.annotation.Bean; +import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; +import org.springframework.core.env.Environment; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ReflectionUtils; + +import com.ctrip.framework.apollo.ConfigChangeListener; +import com.ctrip.framework.apollo.build.ApolloInjector; +import com.ctrip.framework.apollo.model.ConfigChange; +import com.ctrip.framework.apollo.model.ConfigChangeEvent; +import com.ctrip.framework.apollo.spring.config.ConfigPropertySource; +import com.ctrip.framework.apollo.spring.config.ConfigPropertySourceFactory; +import com.ctrip.framework.apollo.spring.property.PlaceholderHelper; +import com.ctrip.framework.apollo.spring.property.SpringValue; +import com.ctrip.framework.apollo.spring.property.SpringValueDefinition; +import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor; +import com.ctrip.framework.apollo.util.ConfigUtil; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Multimap; + +/** + * Spring value processor of field or method which has @Value and xml config placeholders. + * + * @author github.com/zhegexiaohuozi seimimaster@gmail.com + * @since 2017/12/20. + */ +public class SpringValueProcessor implements BeanPostProcessor, PriorityOrdered, EnvironmentAware, + BeanFactoryAware, BeanFactoryPostProcessor { + + private static final Logger logger = LoggerFactory.getLogger(SpringValueProcessor.class); + + private final Multimap monitor = LinkedListMultimap.create(); + private final ConfigUtil configUtil; + private final PlaceholderHelper placeholderHelper; + private final ConfigPropertySourceFactory configPropertySourceFactory; + private final boolean typeConverterHasConvertIfNecessaryWithFieldParameter; + + private Environment environment; + private ConfigurableBeanFactory beanFactory; + private TypeConverter typeConverter; + + private static Multimap beanName2SpringValueDefinitions = LinkedListMultimap.create(); + + public SpringValueProcessor() { + configUtil = ApolloInjector.getInstance(ConfigUtil.class); + placeholderHelper = ApolloInjector.getInstance(PlaceholderHelper.class); + configPropertySourceFactory = ApolloInjector.getInstance(ConfigPropertySourceFactory.class); + typeConverterHasConvertIfNecessaryWithFieldParameter = testTypeConverterHasConvertIfNecessaryWithFieldParameter(); + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = (ConfigurableBeanFactory) beanFactory; + this.typeConverter = this.beanFactory.getTypeConverter(); + } + + @Override + public void setEnvironment(Environment env) { + this.environment = env; + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) { + beanName2SpringValueDefinitions = SpringValueDefinitionProcessor.getBeanName2SpringValueDefinitions(); + registerConfigChangeListener(); + } + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) { + Class clazz = bean.getClass(); + processFields(bean, beanName, findAllField(clazz)); + processMethods(bean, beanName, findAllMethod(clazz)); + processBeanPropertyValues(bean, beanName); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + private void processFields(Object bean, String beanName, List declaredFields) { + for (Field field : declaredFields) { + // register @Value on field + Value value = field.getAnnotation(Value.class); + if (value == null) { + continue; + } + Set keys = placeholderHelper.extractPlaceholderKeys(value.value()); + + if (keys.isEmpty()) { + continue; + } + + for (String key : keys) { + SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field); + monitor.put(key, springValue); + logger.debug("Monitoring {}", springValue); + } + } + } + + private void processMethods(final Object bean, String beanName, List declaredMethods) { + for (final Method method : declaredMethods) { + //register @Value on method + Value value = method.getAnnotation(Value.class); + if (value == null) { + continue; + } + //skip Configuration bean methods + if (method.getAnnotation(Bean.class) != null) { + continue; + } + if (method.getParameterTypes().length != 1) { + logger.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters", + bean.getClass().getName(), method.getName(), method.getParameterTypes().length); + continue; + } + + Set keys = placeholderHelper.extractPlaceholderKeys(value.value()); + + if (keys.isEmpty()) { + continue; + } + + for (String key : keys) { + SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method); + monitor.put(key, springValue); + logger.debug("Monitoring {}", springValue); + } + } + } + + private void processBeanPropertyValues(Object bean, String beanName) { + Collection propertySpringValues = beanName2SpringValueDefinitions + .get(beanName); + if (propertySpringValues == null || propertySpringValues.isEmpty()) { + return; + } + + for (SpringValueDefinition definition : propertySpringValues) { + try { + PropertyDescriptor pd = BeanUtils + .getPropertyDescriptor(bean.getClass(), definition.getPropertyName()); + Method method = pd.getWriteMethod(); + if (method == null) { + continue; + } + SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(), + bean, beanName, method); + monitor.put(definition.getKey(), springValue); + logger.debug("Monitoring {}", springValue); + } catch (Throwable ex) { + logger.error("Failed to enable auto update feature for {}.{}", bean.getClass(), + definition.getPropertyName()); + } + } + + // clear + beanName2SpringValueDefinitions.removeAll(beanName); + } + + private List findAllField(Class clazz) { + final List res = new LinkedList<>(); + ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() { + @Override + public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { + res.add(field); + } + }); + return res; + } + + private List findAllMethod(Class clazz) { + final List res = new LinkedList<>(); + ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() { + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + res.add(method); + } + }); + return res; + } + + private void registerConfigChangeListener() { + ConfigChangeListener changeListener = new ConfigChangeListener() { + @Override + public void onChange(ConfigChangeEvent changeEvent) { + Set keys = changeEvent.changedKeys(); + if (CollectionUtils.isEmpty(keys)) { + return; + } + for (String key : keys) { + // 1. check whether the changed key is relevant + Collection targetValues = monitor.get(key); + if (targetValues == null || targetValues.isEmpty()) { + continue; + } + + // 2. check whether the value is really changed or not (since spring property sources have hierarchies) + ConfigChange configChange = changeEvent.getChange(key); + if (!Objects.equals(environment.getProperty(key), configChange.getNewValue())) { + continue; + } + + // 3. update the value + for (SpringValue val : targetValues) { + updateSpringValue(val); + } + } + } + }; + + List configPropertySources = configPropertySourceFactory.getAllConfigPropertySources(); + + for (ConfigPropertySource configPropertySource : configPropertySources) { + configPropertySource.addChangeListener(changeListener); + } + } + + private void updateSpringValue(SpringValue springValue) { + try { + Object value = resolvePropertyValue(springValue); + springValue.update(value); + + logger.debug("Auto update apollo changed value successfully, new value: {}, {}", value, + springValue.toString()); + } catch (Throwable ex) { + logger.error("Auto update apollo changed value failed, {}", springValue.toString(), ex); + } + } + + /** + * Logic transplanted from DefaultListableBeanFactory + * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency(org.springframework.beans.factory.config.DependencyDescriptor, java.lang.String, java.util.Set, org.springframework.beans.TypeConverter) + */ + private Object resolvePropertyValue(SpringValue springValue) { + String strVal = beanFactory.resolveEmbeddedValue(springValue.getPlaceholder()); + Object value; + + BeanDefinition bd = (beanFactory.containsBean(springValue.getBeanName()) ? beanFactory + .getMergedBeanDefinition(springValue.getBeanName()) : null); + value = evaluateBeanDefinitionString(strVal, bd); + + if (springValue.isField()) { + // org.springframework.beans.TypeConverter#convertIfNecessary(java.lang.Object, java.lang.Class, java.lang.reflect.Field) is available from Spring 3.2.0+ + if (typeConverterHasConvertIfNecessaryWithFieldParameter) { + value = this.typeConverter + .convertIfNecessary(value, springValue.getTargetType(), springValue.getField()); + } else { + value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType()); + } + } else { + value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), + springValue.getMethodParameter()); + } + + return value; + } + + private Object evaluateBeanDefinitionString(String value, BeanDefinition beanDefinition) { + if (beanFactory.getBeanExpressionResolver() == null) { + return value; + } + Scope scope = (beanDefinition != null ? beanFactory.getRegisteredScope(beanDefinition.getScope()) : null); + return beanFactory.getBeanExpressionResolver().evaluate(value, new BeanExpressionContext(beanFactory, scope)); + } + + private boolean testTypeConverterHasConvertIfNecessaryWithFieldParameter() { + try { + TypeConverter.class.getMethod("convertIfNecessary", Object.class, Class.class, Field.class); + } catch (Throwable ex) { + return false; + } + + return true; + } + + @Override + public int getOrder() { + //make it as late as possible + return Ordered.LOWEST_PRECEDENCE; + } +} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloSpringApplicationRunListener.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloSpringApplicationRunListener.java index 2da8bd2366e..2e4198033ce 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloSpringApplicationRunListener.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/boot/ApolloSpringApplicationRunListener.java @@ -2,7 +2,9 @@ import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.ConfigService; +import com.ctrip.framework.apollo.build.ApolloInjector; import com.ctrip.framework.apollo.core.ConfigConsts; +import com.ctrip.framework.apollo.spring.config.ConfigPropertySourceFactory; import com.ctrip.framework.apollo.spring.config.PropertySourcesConstants; import com.ctrip.framework.apollo.spring.config.ConfigPropertySource; import com.google.common.base.Splitter; @@ -39,6 +41,9 @@ public class ApolloSpringApplicationRunListener implements SpringApplicationRunL private static final Logger logger = LoggerFactory.getLogger(ApolloSpringApplicationRunListener.class); private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults(); + private final ConfigPropertySourceFactory configPropertySourceFactory = ApolloInjector + .getInstance(ConfigPropertySourceFactory.class); + public ApolloSpringApplicationRunListener(SpringApplication application, String[] args) { //ignore } @@ -74,7 +79,7 @@ public void contextPrepared(ConfigurableApplicationContext context) { for (String namespace : namespaceList) { Config config = ConfigService.getConfig(namespace); - composite.addPropertySource(new ConfigPropertySource(namespace, config)); + composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config)); } environment.getPropertySources().addFirst(composite); diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySource.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySource.java index 9a53fd654db..d81a608e569 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySource.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySource.java @@ -1,5 +1,6 @@ package com.ctrip.framework.apollo.spring.config; +import com.ctrip.framework.apollo.ConfigChangeListener; import java.util.Set; import org.springframework.core.env.EnumerablePropertySource; @@ -14,7 +15,7 @@ public class ConfigPropertySource extends EnumerablePropertySource { private static final String[] EMPTY_ARRAY = new String[0]; - public ConfigPropertySource(String name, Config source) { + ConfigPropertySource(String name, Config source) { super(name, source); } @@ -31,4 +32,8 @@ public String[] getPropertyNames() { public Object getProperty(String name) { return this.source.getProperty(name, null); } + + public void addChangeListener(ConfigChangeListener listener) { + this.source.addChangeListener(listener); + } } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySourceFactory.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySourceFactory.java new file mode 100644 index 00000000000..2fccc6c93c5 --- /dev/null +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySourceFactory.java @@ -0,0 +1,23 @@ +package com.ctrip.framework.apollo.spring.config; + +import java.util.List; + +import com.ctrip.framework.apollo.Config; +import com.google.common.collect.Lists; + +public class ConfigPropertySourceFactory { + + private final List configPropertySources = Lists.newLinkedList(); + + public ConfigPropertySource getConfigPropertySource(String name, Config source) { + ConfigPropertySource configPropertySource = new ConfigPropertySource(name, source); + + configPropertySources.add(configPropertySource); + + return configPropertySource; + } + + public List getAllConfigPropertySources() { + return Lists.newLinkedList(configPropertySources); + } +} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySourcesProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySourcesProcessor.java index 1cc082be9fa..f6666fb9441 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySourcesProcessor.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySourcesProcessor.java @@ -1,5 +1,7 @@ package com.ctrip.framework.apollo.spring.config; +import com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor; +import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; @@ -22,5 +24,19 @@ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) t PropertySourcesPlaceholderConfigurer.class); BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(), ApolloAnnotationProcessor.class); + BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class); + + processSpringValueDefinition(registry); + } + + /** + * For Spring 3.x versions, the BeanDefinitionRegistryPostProcessor would not be + * instantiated if it is added in postProcessBeanDefinitionRegistry phase, so we have to manually + * call the postProcessBeanDefinitionRegistry method of SpringValueDefinitionProcessor here... + */ + private void processSpringValueDefinition(BeanDefinitionRegistry registry) { + SpringValueDefinitionProcessor springValueDefinitionProcessor = new SpringValueDefinitionProcessor(); + + springValueDefinitionProcessor.postProcessBeanDefinitionRegistry(registry); } } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/PropertySourcesProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/PropertySourcesProcessor.java index 6e1ed5ce79c..d87094c01b7 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/PropertySourcesProcessor.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/PropertySourcesProcessor.java @@ -1,5 +1,6 @@ package com.ctrip.framework.apollo.spring.config; +import com.ctrip.framework.apollo.build.ApolloInjector; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; @@ -33,6 +34,8 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered { private static final Multimap NAMESPACE_NAMES = LinkedHashMultimap.create(); + private final ConfigPropertySourceFactory configPropertySourceFactory = ApolloInjector + .getInstance(ConfigPropertySourceFactory.class); private ConfigurableEnvironment environment; public static boolean addNamespaces(Collection namespaces, int order) { @@ -60,7 +63,7 @@ protected void initializePropertySources() { for (String namespace : NAMESPACE_NAMES.get(order)) { Config config = ConfigService.getConfig(namespace); - composite.addPropertySource(new ConfigPropertySource(namespace, config)); + composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config)); } } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/PlaceholderHelper.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/PlaceholderHelper.java new file mode 100644 index 00000000000..7ab9f222b45 --- /dev/null +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/PlaceholderHelper.java @@ -0,0 +1,127 @@ +package com.ctrip.framework.apollo.spring.property; + +import com.google.common.base.Strings; +import com.google.common.collect.Sets; +import java.util.Set; +import java.util.Stack; +import org.springframework.util.StringUtils; + +/** + * Extract keys from placeholder, e.g. + *
    + *
  • ${some.key} => "some.key"
  • + *
  • ${some.key:${some.other.key:100}} => "some.key", "some.other.key"
  • + *
  • ${${some.key}} => "some.key"
  • + *
  • ${${some.key:other.key}} => "some.key"
  • + *
  • ${${some.key}:${another.key}} => "some.key", "another.key"
  • + *
  • #{new java.text.SimpleDateFormat('${some.key}').parse('${another.key}')} => "some.key", "another.key"
  • + *
+ */ +public class PlaceholderHelper { + + private static final String PLACEHOLDER_PREFIX = "${"; + private static final String PLACEHOLDER_SUFFIX = "}"; + private static final String VALUE_SEPARATOR = ":"; + private static final String SIMPLE_PLACEHOLDER_PREFIX = "{"; + private static final String EXPRESSION_PREFIX = "#{"; + private static final String EXPRESSION_SUFFIX = "}"; + + public Set extractPlaceholderKeys(String propertyString) { + Set placeholderKeys = Sets.newHashSet(); + + if (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString)) { + return placeholderKeys; + } + + Stack stack = new Stack<>(); + stack.push(propertyString); + + while (!stack.isEmpty()) { + String strVal = stack.pop(); + int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX); + if (startIndex == -1) { + placeholderKeys.add(strVal); + continue; + } + int endIndex = findPlaceholderEndIndex(strVal, startIndex); + if (endIndex == -1) { + // invalid placeholder? + continue; + } + + String placeholderCandidate = strVal.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex); + + // ${some.key:other.key} + if (placeholderCandidate.startsWith(PLACEHOLDER_PREFIX)) { + stack.push(placeholderCandidate); + } else { + // some.key:${some.other.key:100} + int separatorIndex = placeholderCandidate.indexOf(VALUE_SEPARATOR); + + if (separatorIndex == -1) { + stack.push(placeholderCandidate); + } else { + stack.push(placeholderCandidate.substring(0, separatorIndex)); + String defaultValuePart = + normalizeToPlaceholder(placeholderCandidate.substring(separatorIndex + VALUE_SEPARATOR.length())); + if (!Strings.isNullOrEmpty(defaultValuePart)) { + stack.push(defaultValuePart); + } + } + } + + // has remaining part, e.g. ${a}.${b} + if (endIndex + PLACEHOLDER_SUFFIX.length() < strVal.length() - 1) { + String remainingPart = normalizeToPlaceholder(strVal.substring(endIndex + PLACEHOLDER_SUFFIX.length())); + if (!Strings.isNullOrEmpty(remainingPart)) { + stack.push(remainingPart); + } + } + } + + return placeholderKeys; + } + + private boolean isNormalizedPlaceholder(String propertyString) { + return propertyString.startsWith(PLACEHOLDER_PREFIX) && propertyString.endsWith(PLACEHOLDER_SUFFIX); + } + + private boolean isExpressionWithPlaceholder(String propertyString) { + return propertyString.startsWith(EXPRESSION_PREFIX) && propertyString.endsWith(EXPRESSION_SUFFIX) + && propertyString.contains(PLACEHOLDER_PREFIX); + } + + private String normalizeToPlaceholder(String strVal) { + int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX); + if (startIndex == -1) { + return null; + } + int endIndex = strVal.lastIndexOf(PLACEHOLDER_SUFFIX); + if (endIndex == -1) { + return null; + } + + return strVal.substring(startIndex, endIndex + PLACEHOLDER_SUFFIX.length()); + } + + private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { + int index = startIndex + PLACEHOLDER_PREFIX.length(); + int withinNestedPlaceholder = 0; + while (index < buf.length()) { + if (StringUtils.substringMatch(buf, index, PLACEHOLDER_SUFFIX)) { + if (withinNestedPlaceholder > 0) { + withinNestedPlaceholder--; + index = index + PLACEHOLDER_SUFFIX.length(); + } else { + return index; + } + } else if (StringUtils.substringMatch(buf, index, SIMPLE_PLACEHOLDER_PREFIX)) { + withinNestedPlaceholder++; + index = index + SIMPLE_PLACEHOLDER_PREFIX.length(); + } else { + index++; + } + } + return -1; + } +} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValue.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValue.java new file mode 100644 index 00000000000..956e993e8fd --- /dev/null +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValue.java @@ -0,0 +1,96 @@ +package com.ctrip.framework.apollo.spring.property; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import org.springframework.core.MethodParameter; + +/** + * Spring @Value method info + * + * @author github.com/zhegexiaohuozi seimimaster@gmail.com + * @since 2018/2/6. + */ +public class SpringValue { + + private MethodParameter methodParameter; + private Field field; + private Object bean; + private String beanName; + private String key; + private String placeholder; + private Class targetType; + + public SpringValue(String key, String placeholder, Object bean, String beanName, Field field) { + this.bean = bean; + this.beanName = beanName; + this.field = field; + this.key = key; + this.placeholder = placeholder; + this.targetType = field.getType(); + } + + public SpringValue(String key, String placeholder, Object bean, String beanName, Method method) { + this.bean = bean; + this.beanName = beanName; + this.methodParameter = new MethodParameter(method, 0); + this.key = key; + this.placeholder = placeholder; + Class[] paramTps = method.getParameterTypes(); + this.targetType = paramTps[0]; + } + + public void update(Object newVal) throws IllegalAccessException, InvocationTargetException { + if (isField()) { + injectField(newVal); + } else { + injectMethod(newVal); + } + } + + private void injectField(Object newVal) throws IllegalAccessException { + boolean accessible = field.isAccessible(); + field.setAccessible(true); + field.set(bean, newVal); + field.setAccessible(accessible); + } + + private void injectMethod(Object newVal) + throws InvocationTargetException, IllegalAccessException { + methodParameter.getMethod().invoke(bean, newVal); + } + + public String getBeanName() { + return beanName; + } + + public Class getTargetType() { + return targetType; + } + + public String getPlaceholder() { + return this.placeholder; + } + + public MethodParameter getMethodParameter() { + return methodParameter; + } + + public boolean isField() { + return this.field != null; + } + + public Field getField() { + return field; + } + + @Override + public String toString() { + if (isField()) { + return String + .format("key: %s, beanName: %s, field: %s.%s", key, beanName, bean.getClass().getName(), field.getName()); + } + return String.format("key: %s, beanName: %s, method: %s.%s", key, beanName, bean.getClass().getName(), + methodParameter.getMethod().getName()); + } +} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValueDefinition.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValueDefinition.java new file mode 100644 index 00000000000..d8b55977f55 --- /dev/null +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValueDefinition.java @@ -0,0 +1,26 @@ +package com.ctrip.framework.apollo.spring.property; + +public class SpringValueDefinition { + + private final String key; + private final String placeholder; + private final String propertyName; + + public SpringValueDefinition(String key, String placeholder, String propertyName) { + this.key = key; + this.placeholder = placeholder; + this.propertyName = propertyName; + } + + public String getKey() { + return key; + } + + public String getPlaceholder() { + return placeholder; + } + + public String getPropertyName() { + return propertyName; + } +} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValueDefinitionProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValueDefinitionProcessor.java new file mode 100644 index 00000000000..4d44b266cef --- /dev/null +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValueDefinitionProcessor.java @@ -0,0 +1,96 @@ +package com.ctrip.framework.apollo.spring.property; + +import java.util.List; +import java.util.Set; + +import java.util.concurrent.atomic.AtomicBoolean; +import org.springframework.beans.BeansException; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.PropertyValue; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; + +import com.ctrip.framework.apollo.build.ApolloInjector; +import com.ctrip.framework.apollo.util.ConfigUtil; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Multimap; + +/** + * To process xml config placeholders, e.g. + * + *
+ *  <bean class="com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean">
+ *    <property name="timeout" value="${timeout:200}"/>
+ *    <property name="batch" value="${batch:100}"/>
+ *  </bean>
+ * 
+ */ +public class SpringValueDefinitionProcessor implements BeanDefinitionRegistryPostProcessor { + private static final Multimap beanName2SpringValueDefinitions = + LinkedListMultimap.create(); + private static final AtomicBoolean initialized = new AtomicBoolean(false); + + private final ConfigUtil configUtil; + private final PlaceholderHelper placeholderHelper; + + public SpringValueDefinitionProcessor() { + configUtil = ApolloInjector.getInstance(ConfigUtil.class); + placeholderHelper = ApolloInjector.getInstance(PlaceholderHelper.class); + } + + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) { + processPropertyValues(registry); + } + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + + } + + public static Multimap getBeanName2SpringValueDefinitions() { + return beanName2SpringValueDefinitions; + } + + private void processPropertyValues(BeanDefinitionRegistry beanRegistry) { + if (!initialized.compareAndSet(false, true)) { + // already initialized + return; + } + + String[] beanNames = beanRegistry.getBeanDefinitionNames(); + for (String beanName : beanNames) { + BeanDefinition beanDefinition = beanRegistry.getBeanDefinition(beanName); + MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues(); + List propertyValues = mutablePropertyValues.getPropertyValueList(); + for (PropertyValue propertyValue : propertyValues) { + Object value = propertyValue.getValue(); + if (!(value instanceof TypedStringValue)) { + continue; + } + String placeholder = ((TypedStringValue) value).getValue(); + Set keys = placeholderHelper.extractPlaceholderKeys(placeholder); + + if (keys.isEmpty()) { + continue; + } + + for (String key : keys) { + beanName2SpringValueDefinitions.put(beanName, + new SpringValueDefinition(key, placeholder, propertyValue.getName())); + } + } + } + } + + //only for test + private static void reset() { + initialized.set(false); + beanName2SpringValueDefinitions.clear(); + } +} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java index 2c3414f81ca..4c9c06f43a2 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/ConfigUtil.java @@ -33,6 +33,7 @@ public class ConfigUtil { private long configCacheExpireTime = 1;//1 minute private TimeUnit configCacheExpireTimeUnit = TimeUnit.MINUTES;//1 minute private long longPollingInitialDelayInMills = 2000;//2 seconds + private boolean autoUpdateInjectedSpringProperties = true; public ConfigUtil() { initRefreshInterval(); @@ -42,6 +43,7 @@ public ConfigUtil() { initQPS(); initMaxConfigCacheSize(); initLongPollingInitialDelayInMills(); + initAutoUpdateInjectedSpringProperties(); } /** @@ -263,4 +265,20 @@ private void initLongPollingInitialDelayInMills() { public long getLongPollingInitialDelayInMills() { return longPollingInitialDelayInMills; } + + private void initAutoUpdateInjectedSpringProperties() { + // 1. Get from System Property + String enableAutoUpdate = System.getProperty("apollo.autoUpdateInjectedSpringProperties"); + if (Strings.isNullOrEmpty(enableAutoUpdate)) { + // 2. Get from app.properties + enableAutoUpdate = Foundation.app().getProperty("apollo.autoUpdateInjectedSpringProperties", null); + } + if (!Strings.isNullOrEmpty(enableAutoUpdate)) { + autoUpdateInjectedSpringProperties = Boolean.parseBoolean(enableAutoUpdate.trim()); + } + } + + public boolean isAutoUpdateInjectedSpringPropertiesEnabled() { + return autoUpdateInjectedSpringProperties; + } } diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/AllTests.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/AllTests.java index 23926cb907f..4ec05b3d6f1 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/AllTests.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/AllTests.java @@ -1,11 +1,5 @@ package com.ctrip.framework.apollo; - -import com.ctrip.framework.apollo.spring.BootstrapConfigTest; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; - import com.ctrip.framework.apollo.integration.ConfigIntegrationTest; import com.ctrip.framework.apollo.internals.DefaultConfigManagerTest; import com.ctrip.framework.apollo.internals.DefaultConfigTest; @@ -19,24 +13,35 @@ import com.ctrip.framework.apollo.spi.DefaultConfigFactoryManagerTest; import com.ctrip.framework.apollo.spi.DefaultConfigFactoryTest; import com.ctrip.framework.apollo.spi.DefaultConfigRegistryTest; +import com.ctrip.framework.apollo.spring.BootstrapConfigTest; import com.ctrip.framework.apollo.spring.JavaConfigAnnotationTest; +import com.ctrip.framework.apollo.spring.JavaConfigPlaceholderAutoUpdateTest; import com.ctrip.framework.apollo.spring.JavaConfigPlaceholderTest; import com.ctrip.framework.apollo.spring.XMLConfigAnnotationTest; +import com.ctrip.framework.apollo.spring.XmlConfigPlaceholderAutoUpdateTest; import com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest; +import com.ctrip.framework.apollo.spring.config.ConfigPropertySourceTest; +import com.ctrip.framework.apollo.spring.property.PlaceholderHelperTest; import com.ctrip.framework.apollo.util.ConfigUtilTest; import com.ctrip.framework.apollo.util.ExceptionUtilTest; import com.ctrip.framework.apollo.util.parser.DateParserTest; import com.ctrip.framework.apollo.util.parser.DurationParserTest; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) @SuiteClasses({ ConfigServiceTest.class, DefaultConfigRegistryTest.class, DefaultConfigFactoryManagerTest.class, DefaultConfigManagerTest.class, DefaultConfigTest.class, LocalFileConfigRepositoryTest.class, RemoteConfigRepositoryTest.class, SimpleConfigTest.class, DefaultConfigFactoryTest.class, - ConfigIntegrationTest.class, ExceptionUtilTest.class, XmlConfigFileTest.class, PropertiesConfigFileTest.class, - RemoteConfigLongPollServiceTest.class, DateParserTest.class, DurationParserTest.class, JsonConfigFileTest.class, - XmlConfigPlaceholderTest.class, JavaConfigPlaceholderTest.class, XMLConfigAnnotationTest.class, - JavaConfigAnnotationTest.class, ConfigUtilTest.class, BootstrapConfigTest.class + ConfigIntegrationTest.class, ExceptionUtilTest.class, XmlConfigFileTest.class, + PropertiesConfigFileTest.class, RemoteConfigLongPollServiceTest.class, DateParserTest.class, + DurationParserTest.class, JsonConfigFileTest.class, XmlConfigPlaceholderTest.class, + JavaConfigPlaceholderTest.class, XMLConfigAnnotationTest.class, JavaConfigAnnotationTest.class, + ConfigUtilTest.class, BootstrapConfigTest.class, JavaConfigPlaceholderAutoUpdateTest.class, + XmlConfigPlaceholderAutoUpdateTest.class, ConfigPropertySourceTest.class, + PlaceholderHelperTest.class }) public class AllTests { diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/build/MockInjector.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/build/MockInjector.java index e410ebe9726..145402c874a 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/build/MockInjector.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/build/MockInjector.java @@ -59,6 +59,5 @@ public static void setDelegate(Injector delegateInjector) { public static void reset() { classMap.clear(); classTable.clear(); - delegate = null; } } 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 5bbd899307e..2325d505724 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,8 +1,19 @@ package com.ctrip.framework.apollo.spring; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.ctrip.framework.apollo.core.ConfigConsts; +import com.ctrip.framework.apollo.internals.ConfigRepository; +import com.ctrip.framework.apollo.internals.SimpleConfig; +import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor; +import com.ctrip.framework.apollo.util.ConfigUtil; import java.lang.reflect.Method; +import java.util.Calendar; +import java.util.Date; import java.util.Map; +import java.util.Properties; import org.junit.After; import org.junit.Before; import org.springframework.util.ReflectionUtils; @@ -22,12 +33,15 @@ public abstract class AbstractSpringIntegrationTest { private static final Map CONFIG_REGISTRY = Maps.newHashMap(); private static Method PROPERTY_SOURCES_PROCESSOR_CLEAR; + private static Method SPRING_VALUE_DEFINITION_PROCESS_CLEAR; private static Method CONFIG_SERVICE_RESET; static { try { PROPERTY_SOURCES_PROCESSOR_CLEAR = PropertySourcesProcessor.class.getDeclaredMethod("reset"); ReflectionUtils.makeAccessible(PROPERTY_SOURCES_PROCESSOR_CLEAR); + SPRING_VALUE_DEFINITION_PROCESS_CLEAR = SpringValueDefinitionProcessor.class.getDeclaredMethod("reset"); + ReflectionUtils.makeAccessible(SPRING_VALUE_DEFINITION_PROCESS_CLEAR); CONFIG_SERVICE_RESET = ConfigService.class.getDeclaredMethod("reset"); ReflectionUtils.makeAccessible(CONFIG_SERVICE_RESET); } catch (NoSuchMethodException e) { @@ -45,6 +59,53 @@ public void tearDown() throws Exception { doTearDown(); } + protected SimpleConfig prepareConfig(String namespaceName, Properties properties) { + ConfigRepository configRepository = mock(ConfigRepository.class); + + when(configRepository.getConfig()).thenReturn(properties); + + SimpleConfig config = new SimpleConfig(ConfigConsts.NAMESPACE_APPLICATION, configRepository); + + mockConfig(namespaceName, config); + + return config; + } + + protected Properties assembleProperties(String key, String value) { + Properties properties = new Properties(); + properties.setProperty(key, value); + + return properties; + } + + protected Properties assembleProperties(String key, String value, String key2, String value2) { + Properties properties = new Properties(); + properties.setProperty(key, value); + properties.setProperty(key2, value2); + + return properties; + } + + protected Properties assembleProperties(String key, String value, String key2, String value2, + String key3, String value3) { + + Properties properties = new Properties(); + properties.setProperty(key, value); + properties.setProperty(key2, value2); + properties.setProperty(key3, value3); + + return properties; + } + + protected Date assembleDate(int year, int month, int day, int hour, int minute, int second, int millisecond) { + Calendar date = Calendar.getInstance(); + date.set(year, month - 1, day, hour, minute, second); //Month in Calendar is 0 based + date.set(Calendar.MILLISECOND, millisecond); + + return date.getTime(); + } + + protected static void mockConfig(String namespace, Config config) { CONFIG_REGISTRY.put(namespace, config); } @@ -52,6 +113,8 @@ protected static void mockConfig(String namespace, Config config) { protected static void doSetUp() { //as PropertySourcesProcessor has some static states, so we must manually clear its state ReflectionUtils.invokeMethod(PROPERTY_SOURCES_PROCESSOR_CLEAR, null); + //as SpringValueDefinitionProcessor has some static states, so we must manually clear its state + ReflectionUtils.invokeMethod(SPRING_VALUE_DEFINITION_PROCESS_CLEAR, null); //as ConfigService is singleton, so we must manually clear its container ReflectionUtils.invokeMethod(CONFIG_SERVICE_RESET, null); MockInjector.reset(); @@ -62,7 +125,7 @@ protected static void doTearDown() { CONFIG_REGISTRY.clear(); } - public static class MockConfigManager implements ConfigManager { + private static class MockConfigManager implements ConfigManager { @Override public Config getConfig(String namespace) { @@ -74,4 +137,18 @@ public ConfigFile getConfigFile(String namespace, ConfigFileFormat configFileFor return null; } } + + protected static class MockConfigUtil extends ConfigUtil { + + private boolean isAutoUpdateInjectedSpringProperties; + + public void setAutoUpdateInjectedSpringProperties(boolean autoUpdateInjectedSpringProperties) { + isAutoUpdateInjectedSpringProperties = autoUpdateInjectedSpringProperties; + } + + @Override + public boolean isAutoUpdateInjectedSpringPropertiesEnabled() { + return isAutoUpdateInjectedSpringProperties; + } + } } 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 98b2b8a312c..5f05a2663a7 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 @@ -88,7 +88,8 @@ public Object answer(InvocationOnMock invocation) throws Throwable { TestApolloConfigChangeListenerBean1 bean = getBean(TestApolloConfigChangeListenerBean1.class, AppConfig3.class); - assertEquals(3, applicationListeners.size()); + //PropertySourcesProcessor add listeners to listen config changed of all namespace + assertEquals(4, applicationListeners.size()); assertEquals(1, fxApolloListeners.size()); for (ConfigChangeListener listener : applicationListeners) { diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigPlaceholderAutoUpdateTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigPlaceholderAutoUpdateTest.java new file mode 100644 index 00000000000..f5cc4c823de --- /dev/null +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigPlaceholderAutoUpdateTest.java @@ -0,0 +1,1007 @@ +package com.ctrip.framework.apollo.spring; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.ImportResource; +import org.springframework.stereotype.Component; + +import com.ctrip.framework.apollo.build.MockInjector; +import com.ctrip.framework.apollo.core.ConfigConsts; +import com.ctrip.framework.apollo.internals.SimpleConfig; +import com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest.TestXmlBean; +import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; +import com.ctrip.framework.apollo.util.ConfigUtil; +import com.google.common.primitives.Ints; + +public class JavaConfigPlaceholderAutoUpdateTest 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"; + private static final String SOME_KEY_PROPERTY = "someKey"; + private static final String ANOTHER_KEY_PROPERTY = "anotherKey"; + + @Test + public void testAutoUpdateWithOneNamespace() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + int newBatch = 2001; + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), BATCH_PROPERTY, + String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig1.class); + + TestJavaConfigBean bean = context.getBean(TestJavaConfigBean.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newProperties = + assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout), BATCH_PROPERTY, String.valueOf(newBatch)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(newTimeout, bean.getTimeout()); + assertEquals(newBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithValueAndXmlProperty() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + int newBatch = 2001; + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), BATCH_PROPERTY, + String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig8.class); + + TestJavaConfigBean javaConfigBean = context.getBean(TestJavaConfigBean.class); + TestXmlBean xmlBean = context.getBean(TestXmlBean.class); + + assertEquals(initialTimeout, javaConfigBean.getTimeout()); + assertEquals(initialBatch, javaConfigBean.getBatch()); + assertEquals(initialTimeout, xmlBean.getTimeout()); + assertEquals(initialBatch, xmlBean.getBatch()); + + Properties newProperties = + assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout), BATCH_PROPERTY, String.valueOf(newBatch)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(newTimeout, javaConfigBean.getTimeout()); + assertEquals(newBatch, javaConfigBean.getBatch()); + assertEquals(newTimeout, xmlBean.getTimeout()); + assertEquals(newBatch, xmlBean.getBatch()); + } + + @Test + public void testAutoUpdateDisabled() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + int newBatch = 2001; + + MockConfigUtil mockConfigUtil = new MockConfigUtil(); + mockConfigUtil.setAutoUpdateInjectedSpringProperties(false); + + MockInjector.setInstance(ConfigUtil.class, mockConfigUtil); + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), BATCH_PROPERTY, + String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig1.class); + + TestJavaConfigBean bean = context.getBean(TestJavaConfigBean.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newProperties = + assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout), BATCH_PROPERTY, String.valueOf(newBatch)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithMultipleNamespaces() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + int newBatch = 2001; + + Properties applicationProperties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout)); + Properties fxApolloProperties = assembleProperties(BATCH_PROPERTY, String.valueOf(initialBatch)); + + SimpleConfig applicationConfig = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationProperties); + SimpleConfig fxApolloConfig = prepareConfig(FX_APOLLO_NAMESPACE, fxApolloProperties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig2.class); + + TestJavaConfigBean bean = context.getBean(TestJavaConfigBean.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newApplicationProperties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout)); + + applicationConfig.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newApplicationProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(newTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newFxApolloProperties = assembleProperties(BATCH_PROPERTY, String.valueOf(newBatch)); + + fxApolloConfig.onRepositoryChange(FX_APOLLO_NAMESPACE, newFxApolloProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(newTimeout, bean.getTimeout()); + assertEquals(newBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithMultipleNamespacesWithSameProperties() throws Exception { + int someTimeout = 1000; + int someBatch = 2000; + int anotherBatch = 3000; + int someNewTimeout = 1001; + int someNewBatch = 2001; + + Properties applicationProperties = assembleProperties(BATCH_PROPERTY, String.valueOf(someBatch)); + Properties fxApolloProperties = + assembleProperties(TIMEOUT_PROPERTY, String.valueOf(someTimeout), BATCH_PROPERTY, String.valueOf(anotherBatch)); + + SimpleConfig applicationConfig = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationProperties); + SimpleConfig fxApolloConfig = prepareConfig(FX_APOLLO_NAMESPACE, fxApolloProperties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig2.class); + + TestJavaConfigBean bean = context.getBean(TestJavaConfigBean.class); + + assertEquals(someTimeout, bean.getTimeout()); + assertEquals(someBatch, bean.getBatch()); + + Properties newFxApolloProperties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(someNewTimeout), + BATCH_PROPERTY, String.valueOf(someNewBatch)); + + fxApolloConfig.onRepositoryChange(FX_APOLLO_NAMESPACE, newFxApolloProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(someNewTimeout, bean.getTimeout()); + assertEquals(someBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithNewProperties() throws Exception { + int initialTimeout = 1000; + int newTimeout = 1001; + int newBatch = 2001; + + Properties applicationProperties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout)); + + SimpleConfig applicationConfig = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationProperties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig1.class); + + TestJavaConfigBean bean = context.getBean(TestJavaConfigBean.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(DEFAULT_BATCH, bean.getBatch()); + + Properties newApplicationProperties = + assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout), BATCH_PROPERTY, String.valueOf(newBatch)); + + applicationConfig.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newApplicationProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(newTimeout, bean.getTimeout()); + assertEquals(newBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithIrrelevantProperties() throws Exception { + int initialTimeout = 1000; + + String someIrrelevantKey = "someIrrelevantKey"; + String someIrrelevantValue = "someIrrelevantValue"; + + String anotherIrrelevantKey = "anotherIrrelevantKey"; + String anotherIrrelevantValue = "anotherIrrelevantValue"; + + Properties applicationProperties = + assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), someIrrelevantKey, someIrrelevantValue); + + SimpleConfig applicationConfig = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, applicationProperties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig1.class); + + TestJavaConfigBean bean = context.getBean(TestJavaConfigBean.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(DEFAULT_BATCH, bean.getBatch()); + + Properties newApplicationProperties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), + anotherIrrelevantKey, String.valueOf(anotherIrrelevantValue)); + + applicationConfig.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newApplicationProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(DEFAULT_BATCH, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithDeletedProperties() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), BATCH_PROPERTY, + String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig1.class); + + TestJavaConfigBean bean = context.getBean(TestJavaConfigBean.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newProperties = new Properties(); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(DEFAULT_TIMEOUT, bean.getTimeout()); + assertEquals(DEFAULT_BATCH, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithDeletedPropertiesWithNoDefaultValue() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), BATCH_PROPERTY, + String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig6.class); + + TestJavaConfigBean5 bean = context.getBean(TestJavaConfigBean5.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newProperties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(newTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithTypeMismatch() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + String newBatch = "newBatch"; + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), BATCH_PROPERTY, + String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig1.class); + + TestJavaConfigBean bean = context.getBean(TestJavaConfigBean.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newProperties = + assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout), BATCH_PROPERTY, newBatch); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(newTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithValueInjectedAsParameter() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + int newBatch = 2001; + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), BATCH_PROPERTY, + String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig3.class); + + TestJavaConfigBean2 bean = context.getBean(TestJavaConfigBean2.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newProperties = + assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout), BATCH_PROPERTY, String.valueOf(newBatch)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + // Does not support this scenario + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + } + + @Test + public void testApplicationPropertySourceWithValueInjectedInConfiguration() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + int newBatch = 2001; + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), BATCH_PROPERTY, + String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig7.class); + + TestJavaConfigBean2 bean = context.getBean(TestJavaConfigBean2.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newProperties = + assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout), BATCH_PROPERTY, String.valueOf(newBatch)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + // Does not support this scenario + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithValueInjectedAsConstructorArgs() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + int newBatch = 2001; + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), BATCH_PROPERTY, + String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig4.class); + + TestJavaConfigBean3 bean = context.getBean(TestJavaConfigBean3.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newProperties = + assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout), BATCH_PROPERTY, String.valueOf(newBatch)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + // Does not support this scenario + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithInvalidSetter() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + int newBatch = 2001; + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), BATCH_PROPERTY, + String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig5.class); + + TestJavaConfigBean4 bean = context.getBean(TestJavaConfigBean4.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newProperties = + assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout), BATCH_PROPERTY, String.valueOf(newBatch)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + // Does not support this scenario + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithNestedProperty() throws Exception { + String someKeyValue = "someKeyValue"; + String anotherKeyValue = "anotherKeyValue"; + String newKeyValue = "newKeyValue"; + int someValue = 1234; + int someNewValue = 2345; + + Properties properties = assembleProperties(SOME_KEY_PROPERTY, someKeyValue, ANOTHER_KEY_PROPERTY, anotherKeyValue, + String.format("%s.%s", someKeyValue, anotherKeyValue), String.valueOf(someValue)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(NestedPropertyConfig1.class); + + TestNestedPropertyBean bean = context.getBean(TestNestedPropertyBean.class); + + assertEquals(someValue, bean.getNestedProperty()); + + Properties newProperties = assembleProperties(SOME_KEY_PROPERTY, newKeyValue, ANOTHER_KEY_PROPERTY, anotherKeyValue, + String.format("%s.%s", newKeyValue, anotherKeyValue), String.valueOf(someNewValue)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(someNewValue, bean.getNestedProperty()); + } + + @Test + public void testAutoUpdateWithNotSupportedNestedProperty() throws Exception { + String someKeyValue = "someKeyValue"; + String anotherKeyValue = "anotherKeyValue"; + int someValue = 1234; + int someNewValue = 2345; + + Properties properties = assembleProperties(SOME_KEY_PROPERTY, someKeyValue, ANOTHER_KEY_PROPERTY, anotherKeyValue, + String.format("%s.%s", someKeyValue, anotherKeyValue), String.valueOf(someValue)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(NestedPropertyConfig1.class); + + TestNestedPropertyBean bean = context.getBean(TestNestedPropertyBean.class); + + assertEquals(someValue, bean.getNestedProperty()); + + Properties newProperties = assembleProperties(SOME_KEY_PROPERTY, someKeyValue, ANOTHER_KEY_PROPERTY, + anotherKeyValue, String.format("%s.%s", someKeyValue, anotherKeyValue), String.valueOf(someNewValue)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + // Does not support this scenario + assertEquals(someValue, bean.getNestedProperty()); + } + + @Test + public void testAutoUpdateWithNestedPropertyWithDefaultValue() throws Exception { + String someKeyValue = "someKeyValue"; + String someNewKeyValue = "someNewKeyValue"; + int someValue = 1234; + int someNewValue = 2345; + + Properties properties = + assembleProperties(SOME_KEY_PROPERTY, someKeyValue, ANOTHER_KEY_PROPERTY, String.valueOf(someValue)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(NestedPropertyConfig2.class); + + TestNestedPropertyBeanWithDefaultValue bean = context.getBean(TestNestedPropertyBeanWithDefaultValue.class); + + assertEquals(someValue, bean.getNestedProperty()); + + Properties newProperties = assembleProperties(SOME_KEY_PROPERTY, someNewKeyValue, ANOTHER_KEY_PROPERTY, + String.valueOf(someValue), someNewKeyValue, String.valueOf(someNewValue)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(someNewValue, bean.getNestedProperty()); + } + + @Test + public void testAutoUpdateWithMultipleNestedProperty() throws Exception { + String someKeyValue = "someKeyValue"; + String someNewKeyValue = "someNewKeyValue"; + String anotherKeyValue = "anotherKeyValue"; + String someNestedKey = "someNestedKey"; + String someNestedPlaceholder = String.format("${%s}", someNestedKey); + String anotherNestedKey = "anotherNestedKey"; + String anotherNestedPlaceholder = String.format("${%s}", anotherNestedKey); + int someValue = 1234; + int someNewValue = 2345; + + Properties properties = assembleProperties(SOME_KEY_PROPERTY, someKeyValue, ANOTHER_KEY_PROPERTY, anotherKeyValue, + someKeyValue, someNestedPlaceholder); + + properties.setProperty(someNestedKey, String.valueOf(someValue)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(NestedPropertyConfig2.class); + + TestNestedPropertyBeanWithDefaultValue bean = context.getBean(TestNestedPropertyBeanWithDefaultValue.class); + + assertEquals(someValue, bean.getNestedProperty()); + + Properties newProperties = assembleProperties(SOME_KEY_PROPERTY, someNewKeyValue, ANOTHER_KEY_PROPERTY, + anotherKeyValue, someNewKeyValue, anotherNestedPlaceholder); + + newProperties.setProperty(anotherNestedKey, String.valueOf(someNewValue)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(someNewValue, bean.getNestedProperty()); + } + + @Test + public void testAutoUpdateWithAllKindsOfDataTypes() throws Exception { + int someInt = 1000; + int someNewInt = 1001; + int[] someIntArray = {1, 2, 3, 4}; + int[] someNewIntArray = {5, 6, 7, 8}; + long someLong = 2000L; + long someNewLong = 2001L; + short someShort = 3000; + short someNewShort = 3001; + float someFloat = 1.2F; + float someNewFloat = 2.2F; + double someDouble = 3.10D; + double someNewDouble = 4.10D; + byte someByte = 123; + byte someNewByte = 124; + boolean someBoolean = true; + boolean someNewBoolean = !someBoolean; + String someString = "someString"; + String someNewString = "someNewString"; + + String someDateFormat = "yyyy-MM-dd HH:mm:ss.SSS"; + Date someDate = assembleDate(2018, 2, 23, 20, 1, 2, 123); + Date someNewDate = assembleDate(2018, 2, 23, 21, 2, 3, 345); + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(someDateFormat, Locale.US); + + Properties properties = new Properties(); + properties.setProperty("intProperty", String.valueOf(someInt)); + properties.setProperty("intArrayProperty", Ints.join(", ", someIntArray)); + properties.setProperty("longProperty", String.valueOf(someLong)); + properties.setProperty("shortProperty", String.valueOf(someShort)); + properties.setProperty("floatProperty", String.valueOf(someFloat)); + properties.setProperty("doubleProperty", String.valueOf(someDouble)); + properties.setProperty("byteProperty", String.valueOf(someByte)); + properties.setProperty("booleanProperty", String.valueOf(someBoolean)); + properties.setProperty("stringProperty", String.valueOf(someString)); + properties.setProperty("dateFormat", String.valueOf(someDateFormat)); + properties.setProperty("dateProperty", simpleDateFormat.format(someDate)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig9.class); + + TestAllKindsOfDataTypesBean bean = context.getBean(TestAllKindsOfDataTypesBean.class); + + assertEquals(someInt, bean.getIntProperty()); + assertArrayEquals(someIntArray, bean.getIntArrayProperty()); + assertEquals(someLong, bean.getLongProperty()); + assertEquals(someShort, bean.getShortProperty()); + assertEquals(someFloat, bean.getFloatProperty(), 0.001F); + assertEquals(someDouble, bean.getDoubleProperty(), 0.001D); + assertEquals(someByte, bean.getByteProperty()); + assertEquals(someBoolean, bean.getBooleanProperty()); + assertEquals(someString, bean.getStringProperty()); + assertEquals(someDate, bean.getDateProperty()); + + Properties newProperties = new Properties(); + newProperties.setProperty("intProperty", String.valueOf(someNewInt)); + newProperties.setProperty("intArrayProperty", Ints.join(", ", someNewIntArray)); + newProperties.setProperty("longProperty", String.valueOf(someNewLong)); + newProperties.setProperty("shortProperty", String.valueOf(someNewShort)); + newProperties.setProperty("floatProperty", String.valueOf(someNewFloat)); + newProperties.setProperty("doubleProperty", String.valueOf(someNewDouble)); + newProperties.setProperty("byteProperty", String.valueOf(someNewByte)); + newProperties.setProperty("booleanProperty", String.valueOf(someNewBoolean)); + newProperties.setProperty("stringProperty", String.valueOf(someNewString)); + newProperties.setProperty("dateFormat", String.valueOf(someDateFormat)); + newProperties.setProperty("dateProperty", simpleDateFormat.format(someNewDate)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(someNewInt, bean.getIntProperty()); + assertArrayEquals(someNewIntArray, bean.getIntArrayProperty()); + assertEquals(someNewLong, bean.getLongProperty()); + assertEquals(someNewShort, bean.getShortProperty()); + assertEquals(someNewFloat, bean.getFloatProperty(), 0.001F); + assertEquals(someNewDouble, bean.getDoubleProperty(), 0.001D); + assertEquals(someNewByte, bean.getByteProperty()); + assertEquals(someNewBoolean, bean.getBooleanProperty()); + assertEquals(someNewString, bean.getStringProperty()); + assertEquals(someNewDate, bean.getDateProperty()); + } + + @Configuration + @EnableApolloConfig + static class AppConfig1 { + @Bean + TestJavaConfigBean testJavaConfigBean() { + return new TestJavaConfigBean(); + } + } + + @Configuration + @EnableApolloConfig({"application", "FX.apollo"}) + static class AppConfig2 { + @Bean + TestJavaConfigBean testJavaConfigBean() { + return new TestJavaConfigBean(); + } + } + + @Configuration + @EnableApolloConfig + static class AppConfig3 { + /** + * This case won't get auto updated + */ + @Bean + TestJavaConfigBean2 testJavaConfigBean2(@Value("${timeout:100}") int timeout, @Value("${batch:200}") int batch) { + TestJavaConfigBean2 bean = new TestJavaConfigBean2(); + + bean.setTimeout(timeout); + bean.setBatch(batch); + + return bean; + } + } + + @Configuration + @ComponentScan(includeFilters = {@Filter(type = FilterType.ANNOTATION, value = {Component.class})}, + excludeFilters = {@Filter(type = FilterType.ANNOTATION, value = {Configuration.class})}) + @EnableApolloConfig + static class AppConfig4 { + } + + @Configuration + @EnableApolloConfig + static class AppConfig5 { + @Bean + TestJavaConfigBean4 testJavaConfigBean() { + return new TestJavaConfigBean4(); + } + } + + @Configuration + @EnableApolloConfig + static class AppConfig6 { + @Bean + TestJavaConfigBean5 testJavaConfigBean() { + return new TestJavaConfigBean5(); + } + } + + @Configuration + @EnableApolloConfig + static class AppConfig7 { + + @Value("${batch}") + private int batch; + + @Bean + @Value("${timeout}") + TestJavaConfigBean2 testJavaConfigBean2(int timeout) { + TestJavaConfigBean2 bean = new TestJavaConfigBean2(); + + bean.setTimeout(timeout); + bean.setBatch(batch); + + return bean; + } + } + + @Configuration + @EnableApolloConfig + @ImportResource("spring/XmlConfigPlaceholderTest1.xml") + static class AppConfig8 { + @Bean + TestJavaConfigBean testJavaConfigBean() { + return new TestJavaConfigBean(); + } + } + + @Configuration + @EnableApolloConfig + static class AppConfig9 { + @Bean + TestAllKindsOfDataTypesBean testAllKindsOfDataTypesBean() { + return new TestAllKindsOfDataTypesBean(); + } + } + + @Configuration + @EnableApolloConfig + static class NestedPropertyConfig1 { + @Bean + TestNestedPropertyBean testNestedPropertyBean() { + return new TestNestedPropertyBean(); + } + } + + @Configuration + @EnableApolloConfig + static class NestedPropertyConfig2 { + @Bean + TestNestedPropertyBeanWithDefaultValue testNestedPropertyBean() { + return new TestNestedPropertyBeanWithDefaultValue(); + } + } + + static class TestJavaConfigBean { + + @Value("${timeout:100}") + private int timeout; + private int batch; + + @Value("${batch:200}") + public void setBatch(int batch) { + this.batch = batch; + } + + public int getTimeout() { + return timeout; + } + + public int getBatch() { + return batch; + } + } + + static class TestJavaConfigBean2 { + private int timeout; + private int batch; + + public int getTimeout() { + return timeout; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + public int getBatch() { + return batch; + } + + public void setBatch(int batch) { + this.batch = batch; + } + } + + /** + * This case won't get auto updated + */ + @Component + static class TestJavaConfigBean3 { + private final int timeout; + private final int batch; + + @Autowired + public TestJavaConfigBean3(@Value("${timeout:100}") int timeout, @Value("${batch:200}") int batch) { + this.timeout = timeout; + this.batch = batch; + } + + public int getTimeout() { + return timeout; + } + + public int getBatch() { + return batch; + } + } + + /** + * This case won't get auto updated + */ + static class TestJavaConfigBean4 { + + private int timeout; + private int batch; + + @Value("${batch:200}") + public void setValues(int batch, @Value("${timeout:100}") int timeout) { + this.batch = batch; + this.timeout = timeout; + } + + public int getTimeout() { + return timeout; + } + + public int getBatch() { + return batch; + } + } + + static class TestJavaConfigBean5 { + + @Value("${timeout}") + private int timeout; + private int batch; + + @Value("${batch}") + public void setBatch(int batch) { + this.batch = batch; + } + + public int getTimeout() { + return timeout; + } + + public int getBatch() { + return batch; + } + } + + static class TestNestedPropertyBean { + + @Value("${${someKey}.${anotherKey}}") + private int nestedProperty; + + public int getNestedProperty() { + return nestedProperty; + } + } + + static class TestNestedPropertyBeanWithDefaultValue { + + @Value("${${someKey}:${anotherKey}}") + private int nestedProperty; + + public int getNestedProperty() { + return nestedProperty; + } + } + + static class TestAllKindsOfDataTypesBean { + + @Value("${intProperty}") + private int intProperty; + + @Value("${intArrayProperty}") + private int[] intArrayProperty; + + @Value("${longProperty}") + private long longProperty; + + @Value("${shortProperty}") + private short shortProperty; + + @Value("${floatProperty}") + private float floatProperty; + + @Value("${doubleProperty}") + private double doubleProperty; + + @Value("${byteProperty}") + private byte byteProperty; + + @Value("${booleanProperty}") + private boolean booleanProperty; + + @Value("${stringProperty}") + private String stringProperty; + + @Value("#{new java.text.SimpleDateFormat('${dateFormat}').parse('${dateProperty}')}") + private Date dateProperty; + + public int getIntProperty() { + return intProperty; + } + + public int[] getIntArrayProperty() { + return intArrayProperty; + } + + public long getLongProperty() { + return longProperty; + } + + public short getShortProperty() { + return shortProperty; + } + + public float getFloatProperty() { + return floatProperty; + } + + public double getDoubleProperty() { + return doubleProperty; + } + + public byte getByteProperty() { + return byteProperty; + } + + public boolean getBooleanProperty() { + return booleanProperty; + } + + public String getStringProperty() { + return stringProperty; + } + + public Date getDateProperty() { + return dateProperty; + } + } +} diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigPlaceholderTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigPlaceholderTest.java index bcf19343172..495238d9ac8 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigPlaceholderTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigPlaceholderTest.java @@ -7,15 +7,19 @@ import static org.mockito.Mockito.when; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Configuration; -import org.springframework.stereotype.Component; import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; +import org.springframework.context.annotation.FilterType; +import org.springframework.stereotype.Component; /** * @author Jason Song(song_s@ctrip.com) @@ -153,6 +157,25 @@ public void testApplicationPropertySourceWithValueInjectedAsParameter() throws E assertEquals(someBatch, bean.getBatch()); } + @Test + public void testApplicationPropertySourceWithValueInjectedAsConstructorArgs() 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(BATCH_PROPERTY), anyString())).thenReturn(String.valueOf(someBatch)); + + mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config); + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig7.class); + + TestJavaConfigBean3 bean = context.getBean(TestJavaConfigBean3.class); + + assertEquals(someTimeout, bean.getTimeout()); + assertEquals(someBatch, bean.getBatch()); + } + @Test public void testNestedProperty() throws Exception { String a = "a"; @@ -323,6 +346,14 @@ TestJavaConfigBean testJavaConfigBean() { } } + @Configuration + @ComponentScan( + includeFilters = {@Filter(type = FilterType.ANNOTATION, value = {Component.class})}, + excludeFilters = {@Filter(type = FilterType.ANNOTATION, value = {Configuration.class})}) + @EnableApolloConfig + static class AppConfig7 { + } + @Configuration @EnableApolloConfig static class NestedPropertyConfig1 { @@ -332,8 +363,6 @@ TestNestedPropertyBean testNestedPropertyBean() { } } - - @Component static class TestJavaConfigBean { @Value("${timeout:100}") private int timeout; @@ -374,6 +403,27 @@ public void setBatch(int batch) { } } + @Component + static class TestJavaConfigBean3 { + private final int timeout; + private final int batch; + + @Autowired + public TestJavaConfigBean3(@Value("${timeout:100}") int timeout, + @Value("${batch:200}") int batch) { + this.timeout = timeout; + this.batch = batch; + } + + public int getTimeout() { + return timeout; + } + + public int getBatch() { + return batch; + } + } + static class TestNestedPropertyBean { @Value("${${a}.${b}:${c:100}}") diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XMLConfigAnnotationTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XMLConfigAnnotationTest.java index 0d5476646da..a58c0a8de35 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XMLConfigAnnotationTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XMLConfigAnnotationTest.java @@ -86,7 +86,8 @@ public Object answer(InvocationOnMock invocation) throws Throwable { TestApolloConfigChangeListenerBean1 bean = getBean("spring/XmlConfigAnnotationTest3.xml", TestApolloConfigChangeListenerBean1.class); - assertEquals(3, applicationListeners.size()); + //PropertySourcesProcessor add listeners to listen config changed of all namespace + assertEquals(4, applicationListeners.size()); assertEquals(1, fxApolloListeners.size()); for (ConfigChangeListener listener : applicationListeners) { diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderAutoUpdateTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderAutoUpdateTest.java new file mode 100644 index 00000000000..161be3e592d --- /dev/null +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderAutoUpdateTest.java @@ -0,0 +1,606 @@ +package com.ctrip.framework.apollo.spring; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import com.ctrip.framework.apollo.build.MockInjector; +import com.ctrip.framework.apollo.core.ConfigConsts; +import com.ctrip.framework.apollo.internals.SimpleConfig; +import com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest.TestXmlBean; +import com.ctrip.framework.apollo.util.ConfigUtil; +import com.google.common.primitives.Ints; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Properties; +import java.util.concurrent.TimeUnit; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class XmlConfigPlaceholderAutoUpdateTest 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"; + + @Test + public void testAutoUpdateWithOneNamespace() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + int newBatch = 2001; + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), + BATCH_PROPERTY, String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/XmlConfigPlaceholderTest1.xml"); + + TestXmlBean bean = context.getBean(TestXmlBean.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newProperties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout), + BATCH_PROPERTY, String.valueOf(newBatch)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(newTimeout, bean.getTimeout()); + assertEquals(newBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateDisabled() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + int newBatch = 2001; + + MockConfigUtil mockConfigUtil = new MockConfigUtil(); + mockConfigUtil.setAutoUpdateInjectedSpringProperties(false); + + MockInjector.setInstance(ConfigUtil.class, mockConfigUtil); + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), + BATCH_PROPERTY, String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/XmlConfigPlaceholderTest1.xml"); + + TestXmlBean bean = context.getBean(TestXmlBean.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newProperties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout), + BATCH_PROPERTY, String.valueOf(newBatch)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithMultipleNamespaces() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + int newBatch = 2001; + + Properties applicationProperties = assembleProperties(TIMEOUT_PROPERTY, + String.valueOf(initialTimeout)); + Properties fxApolloProperties = assembleProperties(BATCH_PROPERTY, + String.valueOf(initialBatch)); + + SimpleConfig applicationConfig = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, + applicationProperties); + SimpleConfig fxApolloConfig = prepareConfig(FX_APOLLO_NAMESPACE, fxApolloProperties); + + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/XmlConfigPlaceholderTest3.xml"); + + TestXmlBean bean = context.getBean(TestXmlBean.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newApplicationProperties = assembleProperties(TIMEOUT_PROPERTY, + String.valueOf(newTimeout)); + + applicationConfig + .onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newApplicationProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(newTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newFxApolloProperties = assembleProperties(BATCH_PROPERTY, String.valueOf(newBatch)); + + fxApolloConfig.onRepositoryChange(FX_APOLLO_NAMESPACE, newFxApolloProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(newTimeout, bean.getTimeout()); + assertEquals(newBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithMultipleNamespacesWithSameProperties() throws Exception { + int someTimeout = 1000; + int someBatch = 2000; + int anotherBatch = 3000; + int someNewTimeout = 1001; + int someNewBatch = 2001; + + Properties applicationProperties = assembleProperties(BATCH_PROPERTY, + String.valueOf(someBatch)); + Properties fxApolloProperties = assembleProperties(TIMEOUT_PROPERTY, + String.valueOf(someTimeout), BATCH_PROPERTY, String.valueOf(anotherBatch)); + + SimpleConfig applicationConfig = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, + applicationProperties); + SimpleConfig fxApolloConfig = prepareConfig(FX_APOLLO_NAMESPACE, fxApolloProperties); + + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/XmlConfigPlaceholderTest3.xml"); + + TestXmlBean bean = context.getBean(TestXmlBean.class); + + assertEquals(someTimeout, bean.getTimeout()); + assertEquals(someBatch, bean.getBatch()); + + Properties newFxApolloProperties = assembleProperties(TIMEOUT_PROPERTY, + String.valueOf(someNewTimeout), BATCH_PROPERTY, String.valueOf(someNewBatch)); + + fxApolloConfig.onRepositoryChange(FX_APOLLO_NAMESPACE, newFxApolloProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(someNewTimeout, bean.getTimeout()); + assertEquals(someBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithNewProperties() throws Exception { + int initialTimeout = 1000; + int newTimeout = 1001; + int newBatch = 2001; + + Properties applicationProperties = assembleProperties(TIMEOUT_PROPERTY, + String.valueOf(initialTimeout)); + + SimpleConfig applicationConfig = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, + applicationProperties); + + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/XmlConfigPlaceholderTest1.xml"); + + TestXmlBean bean = context.getBean(TestXmlBean.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(DEFAULT_BATCH, bean.getBatch()); + + Properties newApplicationProperties = assembleProperties(TIMEOUT_PROPERTY, + String.valueOf(newTimeout), BATCH_PROPERTY, String.valueOf(newBatch)); + + applicationConfig + .onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newApplicationProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(newTimeout, bean.getTimeout()); + assertEquals(newBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithIrrelevantProperties() throws Exception { + int initialTimeout = 1000; + + String someIrrelevantKey = "someIrrelevantKey"; + String someIrrelevantValue = "someIrrelevantValue"; + + String anotherIrrelevantKey = "anotherIrrelevantKey"; + String anotherIrrelevantValue = "anotherIrrelevantValue"; + + Properties applicationProperties = assembleProperties(TIMEOUT_PROPERTY, + String.valueOf(initialTimeout), someIrrelevantKey, someIrrelevantValue); + + SimpleConfig applicationConfig = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, + applicationProperties); + + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/XmlConfigPlaceholderTest1.xml"); + + TestXmlBean bean = context.getBean(TestXmlBean.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(DEFAULT_BATCH, bean.getBatch()); + + Properties newApplicationProperties = assembleProperties(TIMEOUT_PROPERTY, + String.valueOf(initialTimeout), anotherIrrelevantKey, String.valueOf(anotherIrrelevantValue)); + + applicationConfig + .onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newApplicationProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(DEFAULT_BATCH, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithDeletedProperties() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), + BATCH_PROPERTY, String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/XmlConfigPlaceholderTest1.xml"); + + TestXmlBean bean = context.getBean(TestXmlBean.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newProperties = new Properties(); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(DEFAULT_TIMEOUT, bean.getTimeout()); + assertEquals(DEFAULT_BATCH, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithDeletedPropertiesWithNoDefaultValue() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), + BATCH_PROPERTY, String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/XmlConfigPlaceholderTest7.xml"); + + TestXmlBean bean = context.getBean(TestXmlBean.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newProperties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(newTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithTypeMismatch() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + String newBatch = "newBatch"; + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), + BATCH_PROPERTY, String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/XmlConfigPlaceholderTest1.xml"); + + TestXmlBean bean = context.getBean(TestXmlBean.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newProperties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout), + BATCH_PROPERTY, newBatch); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(newTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithValueInjectedAsConstructorArgs() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + int newBatch = 2001; + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), + BATCH_PROPERTY, String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/XmlConfigPlaceholderTest8.xml"); + + TestXmlBeanWithConstructorArgs bean = context.getBean(TestXmlBeanWithConstructorArgs.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newProperties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout), + BATCH_PROPERTY, String.valueOf(newBatch)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + // Does not support this scenario + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithValueAndProperty() throws Exception { + int initialTimeout = 1000; + int initialBatch = 2000; + int newTimeout = 1001; + int newBatch = 2001; + + Properties properties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(initialTimeout), + BATCH_PROPERTY, String.valueOf(initialBatch)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/XmlConfigPlaceholderTest9.xml"); + + TestXmlBeanWithInjectedValue bean = context.getBean(TestXmlBeanWithInjectedValue.class); + + assertEquals(initialTimeout, bean.getTimeout()); + assertEquals(initialBatch, bean.getBatch()); + + Properties newProperties = assembleProperties(TIMEOUT_PROPERTY, String.valueOf(newTimeout), + BATCH_PROPERTY, String.valueOf(newBatch)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(newTimeout, bean.getTimeout()); + assertEquals(newBatch, bean.getBatch()); + } + + @Test + public void testAutoUpdateWithAllKindsOfDataTypes() throws Exception { + int someInt = 1000; + int someNewInt = 1001; + int[] someIntArray = {1, 2, 3, 4}; + int[] someNewIntArray = {5, 6, 7, 8}; + long someLong = 2000L; + long someNewLong = 2001L; + short someShort = 3000; + short someNewShort = 3001; + float someFloat = 1.2F; + float someNewFloat = 2.2F; + double someDouble = 3.10D; + double someNewDouble = 4.10D; + byte someByte = 123; + byte someNewByte = 124; + boolean someBoolean = true; + boolean someNewBoolean = !someBoolean; + String someString = "someString"; + String someNewString = "someNewString"; + + String someDateFormat = "yyyy-MM-dd HH:mm:ss.SSS"; + Date someDate = assembleDate(2018, 2, 23, 20, 1, 2, 123); + Date someNewDate = assembleDate(2018, 2, 23, 21, 2, 3, 345); + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(someDateFormat, Locale.US); + + Properties properties = new Properties(); + properties.setProperty("intProperty", String.valueOf(someInt)); + properties.setProperty("intArrayProperty", Ints.join(", ", someIntArray)); + properties.setProperty("longProperty", String.valueOf(someLong)); + properties.setProperty("shortProperty", String.valueOf(someShort)); + properties.setProperty("floatProperty", String.valueOf(someFloat)); + properties.setProperty("doubleProperty", String.valueOf(someDouble)); + properties.setProperty("byteProperty", String.valueOf(someByte)); + properties.setProperty("booleanProperty", String.valueOf(someBoolean)); + properties.setProperty("stringProperty", String.valueOf(someString)); + properties.setProperty("dateFormat", String.valueOf(someDateFormat)); + properties.setProperty("dateProperty", simpleDateFormat.format(someDate)); + + SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/XmlConfigPlaceholderTest10.xml"); + + TestAllKindsOfDataTypesBean bean = context.getBean(TestAllKindsOfDataTypesBean.class); + + assertEquals(someInt, bean.getIntProperty()); + assertArrayEquals(someIntArray, bean.getIntArrayProperty()); + assertEquals(someLong, bean.getLongProperty()); + assertEquals(someShort, bean.getShortProperty()); + assertEquals(someFloat, bean.getFloatProperty(), 0.001F); + assertEquals(someDouble, bean.getDoubleProperty(), 0.001D); + assertEquals(someByte, bean.getByteProperty()); + assertEquals(someBoolean, bean.getBooleanProperty()); + assertEquals(someString, bean.getStringProperty()); + assertEquals(someDate, bean.getDateProperty()); + + Properties newProperties = new Properties(); + newProperties.setProperty("intProperty", String.valueOf(someNewInt)); + newProperties.setProperty("intArrayProperty", Ints.join(", ", someNewIntArray)); + newProperties.setProperty("longProperty", String.valueOf(someNewLong)); + newProperties.setProperty("shortProperty", String.valueOf(someNewShort)); + newProperties.setProperty("floatProperty", String.valueOf(someNewFloat)); + newProperties.setProperty("doubleProperty", String.valueOf(someNewDouble)); + newProperties.setProperty("byteProperty", String.valueOf(someNewByte)); + newProperties.setProperty("booleanProperty", String.valueOf(someNewBoolean)); + newProperties.setProperty("stringProperty", String.valueOf(someNewString)); + newProperties.setProperty("dateFormat", String.valueOf(someDateFormat)); + newProperties.setProperty("dateProperty", simpleDateFormat.format(someNewDate)); + + config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); + + TimeUnit.MILLISECONDS.sleep(50); + + assertEquals(someNewInt, bean.getIntProperty()); + assertArrayEquals(someNewIntArray, bean.getIntArrayProperty()); + assertEquals(someNewLong, bean.getLongProperty()); + assertEquals(someNewShort, bean.getShortProperty()); + assertEquals(someNewFloat, bean.getFloatProperty(), 0.001F); + assertEquals(someNewDouble, bean.getDoubleProperty(), 0.001D); + assertEquals(someNewByte, bean.getByteProperty()); + assertEquals(someNewBoolean, bean.getBooleanProperty()); + assertEquals(someNewString, bean.getStringProperty()); + assertEquals(someNewDate, bean.getDateProperty()); + } + + public static class TestXmlBeanWithConstructorArgs { + private final int timeout; + private final int batch; + + public TestXmlBeanWithConstructorArgs(int timeout, int batch) { + this.timeout = timeout; + this.batch = batch; + } + + public int getTimeout() { + return timeout; + } + + public int getBatch() { + return batch; + } + } + + public static class TestXmlBeanWithInjectedValue { + @Value("${timeout}") + private int timeout; + private int batch; + + public void setBatch(int batch) { + this.batch = batch; + } + + public int getTimeout() { + return timeout; + } + + public int getBatch() { + return batch; + } + } + + static class TestAllKindsOfDataTypesBean { + + private int intProperty; + + private int[] intArrayProperty; + + private long longProperty; + + private short shortProperty; + + private float floatProperty; + + private double doubleProperty; + + private byte byteProperty; + + private boolean booleanProperty; + + private String stringProperty; + + private Date dateProperty; + + public void setDateProperty(Date dateProperty) { + this.dateProperty = dateProperty; + } + + public void setIntProperty(int intProperty) { + this.intProperty = intProperty; + } + + public void setIntArrayProperty(int[] intArrayProperty) { + this.intArrayProperty = intArrayProperty; + } + + public void setLongProperty(long longProperty) { + this.longProperty = longProperty; + } + + public void setShortProperty(short shortProperty) { + this.shortProperty = shortProperty; + } + + public void setFloatProperty(float floatProperty) { + this.floatProperty = floatProperty; + } + + public void setDoubleProperty(double doubleProperty) { + this.doubleProperty = doubleProperty; + } + + public void setByteProperty(byte byteProperty) { + this.byteProperty = byteProperty; + } + + public void setBooleanProperty(boolean booleanProperty) { + this.booleanProperty = booleanProperty; + } + + public void setStringProperty(String stringProperty) { + this.stringProperty = stringProperty; + } + + public int getIntProperty() { + return intProperty; + } + + public int[] getIntArrayProperty() { + return intArrayProperty; + } + + public long getLongProperty() { + return longProperty; + } + + public short getShortProperty() { + return shortProperty; + } + + public float getFloatProperty() { + return floatProperty; + } + + public double getDoubleProperty() { + return doubleProperty; + } + + public byte getByteProperty() { + return byteProperty; + } + + public boolean getBooleanProperty() { + return booleanProperty; + } + + public String getStringProperty() { + return stringProperty; + } + + public Date getDateProperty() { + return dateProperty; + } + } +} diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySourceTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySourceTest.java new file mode 100644 index 00000000000..730e6c571bd --- /dev/null +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySourceTest.java @@ -0,0 +1,98 @@ +package com.ctrip.framework.apollo.spring.config; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.ctrip.framework.apollo.Config; +import com.ctrip.framework.apollo.ConfigChangeListener; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import java.util.List; +import java.util.Set; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +@RunWith(MockitoJUnitRunner.class) +public class ConfigPropertySourceTest { + + private ConfigPropertySource configPropertySource; + + @Mock + private Config someConfig; + + @Before + public void setUp() throws Exception { + String someName = "someName"; + configPropertySource = new ConfigPropertySource(someName, someConfig); + } + + @Test + public void testGetPropertyNames() throws Exception { + String somePropertyName = "somePropertyName"; + String anotherPropertyName = "anotherPropertyName"; + Set somePropertyNames = Sets.newHashSet(somePropertyName, anotherPropertyName); + + when(someConfig.getPropertyNames()).thenReturn(somePropertyNames); + + String[] result = configPropertySource.getPropertyNames(); + + verify(someConfig, times(1)).getPropertyNames(); + + assertArrayEquals(somePropertyNames.toArray(), result); + } + + @Test + public void testGetEmptyPropertyNames() throws Exception { + when(someConfig.getPropertyNames()).thenReturn(Sets.newHashSet()); + + assertEquals(0, configPropertySource.getPropertyNames().length); + } + + @Test + public void testGetProperty() throws Exception { + String somePropertyName = "somePropertyName"; + + String someValue = "someValue"; + + when(someConfig.getProperty(somePropertyName, null)).thenReturn(someValue); + + assertEquals(someValue, configPropertySource.getProperty(somePropertyName)); + + verify(someConfig, times(1)).getProperty(somePropertyName, null); + } + + @Test + public void testAddChangeListener() throws Exception { + ConfigChangeListener someListener = mock(ConfigChangeListener.class); + ConfigChangeListener anotherListener = mock(ConfigChangeListener.class); + + final List listeners = Lists.newArrayList(); + + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + listeners.add(invocation.getArgumentAt(0, ConfigChangeListener.class)); + + return Void.class; + } + }).when(someConfig).addChangeListener(any(ConfigChangeListener.class)); + + configPropertySource.addChangeListener(someListener); + configPropertySource.addChangeListener(anotherListener); + + assertEquals(2, listeners.size()); + assertTrue(listeners.containsAll(Lists.newArrayList(someListener, anotherListener))); + } +} diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/property/PlaceholderHelperTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/property/PlaceholderHelperTest.java new file mode 100644 index 00000000000..8046ccb513f --- /dev/null +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/property/PlaceholderHelperTest.java @@ -0,0 +1,62 @@ +package com.ctrip.framework.apollo.spring.property; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.Sets; +import org.junit.Before; +import org.junit.Test; + +public class PlaceholderHelperTest { + + private PlaceholderHelper placeholderHelper; + + @Before + public void setUp() throws Exception { + placeholderHelper = new PlaceholderHelper(); + } + + @Test + public void testExtractPlaceholderKeys() throws Exception { + check("${some.key}", "some.key"); + check("${some.key:100}", "some.key"); + check("${some.key:${some.other.key}}", "some.key", "some.other.key"); + check("${some.key:${some.other.key:100}}", "some.key", "some.other.key"); + } + + @Test + public void testExtractNestedPlaceholderKeys() throws Exception { + check("${${some.key}}", "some.key"); + check("${${some.key:other.key}}", "some.key"); + check("${${some.key}:100}", "some.key"); + check("${${some.key}:${another.key}}", "some.key", "another.key"); + } + + @Test + public void testExtractComplexNestedPlaceholderKeys() throws Exception { + check("${${a}1${b}:3.${c:${d:100}}}", "a", "b", "c", "d"); + check("${1${a}2${b}3:4.${c:5${d:100}6}7}", "a", "b", "c", "d"); + } + + @Test + public void testExtractPlaceholderKeysFromExpression() throws Exception { + check("#{new java.text.SimpleDateFormat('${some.key}').parse('${another.key}')}", "some.key", "another.key"); + check("#{new java.text.SimpleDateFormat('${some.key:abc}').parse('${another.key:100}')}", "some.key", "another.key"); + check("#{new java.text.SimpleDateFormat('${some.key:${some.other.key}}').parse('${another.key}')}", "some.key", "another.key", "some.other.key"); + check("#{new java.text.SimpleDateFormat('${some.key:${some.other.key:abc}}').parse('${another.key}')}", "some.key", "another.key", "some.other.key"); + check("#{new java.text.SimpleDateFormat('${${some.key}}').parse('${${another.key:other.key}}')}", "some.key", "another.key"); + + assertTrue(placeholderHelper.extractPlaceholderKeys("#{systemProperties[some.key] ?: 123}").isEmpty()); + assertTrue(placeholderHelper.extractPlaceholderKeys("#{ T(java.lang.Math).random() * 100.0 }").isEmpty()); + } + + @Test + public void testExtractInvalidPlaceholderKeys() throws Exception { + assertTrue(placeholderHelper.extractPlaceholderKeys("some.key").isEmpty()); + assertTrue(placeholderHelper.extractPlaceholderKeys("some.key:100").isEmpty()); + } + + private void check(String propertyString, String... expectedPlaceholders) { + assertEquals(Sets.newHashSet(expectedPlaceholders), placeholderHelper.extractPlaceholderKeys(propertyString)); + } +} diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java index ab9dac9c6cd..5c569e413a7 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/util/ConfigUtilTest.java @@ -3,7 +3,6 @@ import com.ctrip.framework.apollo.core.ConfigConsts; import org.junit.After; -import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; @@ -22,6 +21,7 @@ public void tearDown() throws Exception { System.clearProperty("apollo.longPollQPS"); System.clearProperty("apollo.configCacheSize"); System.clearProperty("apollo.longPollingInitialDelayInMills"); + System.clearProperty("apollo.autoUpdateInjectedSpringProperties"); } @Test @@ -173,4 +173,16 @@ public void testCustomizeInvalidLongPollingInitialDelayInMills() throws Exceptio assertTrue(configUtil.getLongPollingInitialDelayInMills() > 0); } -} \ No newline at end of file + + @Test + public void testCustomizeAutoUpdateInjectedSpringProperties() throws Exception { + boolean someAutoUpdateInjectedSpringProperties = false; + System.setProperty("apollo.autoUpdateInjectedSpringProperties", + String.valueOf(someAutoUpdateInjectedSpringProperties)); + + ConfigUtil configUtil = new ConfigUtil(); + + assertEquals(someAutoUpdateInjectedSpringProperties, + configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()); + } +} diff --git a/apollo-client/src/test/resources/spring/XmlConfigPlaceholderTest10.xml b/apollo-client/src/test/resources/spring/XmlConfigPlaceholderTest10.xml new file mode 100644 index 00000000000..043e7e13940 --- /dev/null +++ b/apollo-client/src/test/resources/spring/XmlConfigPlaceholderTest10.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + diff --git a/apollo-client/src/test/resources/spring/XmlConfigPlaceholderTest7.xml b/apollo-client/src/test/resources/spring/XmlConfigPlaceholderTest7.xml new file mode 100644 index 00000000000..be78a6cec2e --- /dev/null +++ b/apollo-client/src/test/resources/spring/XmlConfigPlaceholderTest7.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/apollo-client/src/test/resources/spring/XmlConfigPlaceholderTest8.xml b/apollo-client/src/test/resources/spring/XmlConfigPlaceholderTest8.xml new file mode 100644 index 00000000000..e66cc5bd1c5 --- /dev/null +++ b/apollo-client/src/test/resources/spring/XmlConfigPlaceholderTest8.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/apollo-client/src/test/resources/spring/XmlConfigPlaceholderTest9.xml b/apollo-client/src/test/resources/spring/XmlConfigPlaceholderTest9.xml new file mode 100644 index 00000000000..8c210a6d73e --- /dev/null +++ b/apollo-client/src/test/resources/spring/XmlConfigPlaceholderTest9.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/apollo-core/src/test/resources/META-INF/some-invalid-app.properties b/apollo-core/src/test/resources/META-INF/some-invalid-app.properties index 1bd0bb9b3a8..d40fe68755a 100644 --- a/apollo-core/src/test/resources/META-INF/some-invalid-app.properties +++ b/apollo-core/src/test/resources/META-INF/some-invalid-app.properties @@ -1 +1 @@ -appid=110402 \ No newline at end of file +appid=110402 diff --git a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/api/ApolloConfigDemo.java b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/api/ApolloConfigDemo.java index 62f39f0bb43..033fc129ee0 100644 --- a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/api/ApolloConfigDemo.java +++ b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/api/ApolloConfigDemo.java @@ -46,7 +46,7 @@ public void onChange(ConfigChangeEvent changeEvent) { }; config = ConfigService.getAppConfig(); config.addChangeListener(changeListener); - publicConfig = ConfigService.getConfig("FX.apollo"); + publicConfig = ConfigService.getConfig("TEST1.apollo"); publicConfig.addChangeListener(changeListener); applicationConfigFile = ConfigService.getConfigFile("application", ConfigFileFormat.Properties); xmlConfigFile = ConfigService.getConfigFile("datasources", ConfigFileFormat.XML); diff --git a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/common/bean/AnnotatedBean.java b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/common/bean/AnnotatedBean.java index 3d21e5cd8d1..6b7bf6d4400 100644 --- a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/common/bean/AnnotatedBean.java +++ b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/common/bean/AnnotatedBean.java @@ -3,34 +3,29 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; -import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; - /** * @author Jason Song(song_s@ctrip.com) */ -@RefreshScope @Component("annotatedBean") public class AnnotatedBean { private static final Logger logger = LoggerFactory.getLogger(AnnotatedBean.class); - @Value("${timeout:200}") private int timeout; private int batch; - @PostConstruct - void initialize() { - logger.info("timeout is initialized as {}", timeout); - logger.info("batch is initialized as {}", batch); - } - @Value("${batch:100}") public void setBatch(int batch) { + logger.info("updating batch, old value: {}, new value: {}", this.batch, batch); this.batch = batch; } + @Value("${timeout:200}") + public void setTimeout(int timeout) { + logger.info("updating timeout, old value: {}, new value: {}", this.timeout, timeout); + this.timeout = timeout; + } @Override public String toString() { diff --git a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/common/config/AnotherAppConfig.java b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/common/config/AnotherAppConfig.java index ef3b6ba0a84..03905cbcc2e 100644 --- a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/common/config/AnotherAppConfig.java +++ b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/common/config/AnotherAppConfig.java @@ -8,6 +8,6 @@ * @author Jason Song(song_s@ctrip.com) */ @Configuration -@EnableApolloConfig(value = "FX.apollo", order = 11) +@EnableApolloConfig(value = "TEST1.apollo", order = 11) public class AnotherAppConfig { } diff --git a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/common/refresh/ApolloRefreshConfig.java b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/common/refresh/ApolloRefreshConfig.java deleted file mode 100644 index 73b8399ac62..00000000000 --- a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/common/refresh/ApolloRefreshConfig.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.ctrip.framework.apollo.demo.spring.common.refresh; - -import com.ctrip.framework.apollo.demo.spring.common.bean.AnnotatedBean; -import com.ctrip.framework.apollo.model.ConfigChangeEvent; -import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.context.scope.refresh.RefreshScope; -import org.springframework.stereotype.Component; - -/** - * To refresh the config bean when config is changed - * - * @author Jason Song(song_s@ctrip.com) - */ -@Component -public class ApolloRefreshConfig { - private static final Logger logger = LoggerFactory.getLogger(ApolloRefreshConfig.class); - - @Autowired - private RefreshScope refreshScope; - - @Autowired - private AnnotatedBean annotatedBean; - - @ApolloConfigChangeListener({"application", "FX.apollo"}) - private void onChange(ConfigChangeEvent changeEvent) { - if (changeEvent.isChanged("timeout") || changeEvent.isChanged("batch")) { - logger.info("before refresh {}", annotatedBean.toString()); - //could also call refreshScope.refreshAll(); - refreshScope.refresh("annotatedBean"); - logger.info("after refresh {}", annotatedBean.toString()); - } - } -} diff --git a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/javaConfigDemo/AnnotationApplication.java b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/javaConfigDemo/AnnotationApplication.java index a957777c924..23d5e519935 100644 --- a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/javaConfigDemo/AnnotationApplication.java +++ b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/javaConfigDemo/AnnotationApplication.java @@ -1,5 +1,12 @@ package com.ctrip.framework.apollo.demo.spring.javaConfigDemo; +import com.ctrip.framework.apollo.demo.spring.common.bean.AnnotatedBean; +import com.google.common.base.Charsets; +import com.google.common.base.Strings; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import java.util.Scanner; @@ -8,14 +15,19 @@ * @author Jason Song(song_s@ctrip.com) */ public class AnnotationApplication { - public static void main(String[] args) { - new AnnotationConfigApplicationContext("com.ctrip.framework.apollo.demo.spring.common", - "com.ctrip.framework.apollo.demo.spring.javaConfigDemo"); - onKeyExit(); - } + public static void main(String[] args) throws IOException { + ApplicationContext context = new AnnotationConfigApplicationContext("com.ctrip.framework.apollo.demo.spring.common"); + AnnotatedBean annotatedBean = context.getBean(AnnotatedBean.class); + + System.out.println("AnnotationApplication Demo. Input any key except quit to print the values. Input quit to exit."); + while (true) { + System.out.print("> "); + String input = new BufferedReader(new InputStreamReader(System.in, Charsets.UTF_8)).readLine(); + if (!Strings.isNullOrEmpty(input) && input.trim().equalsIgnoreCase("quit")) { + System.exit(0); + } - private static void onKeyExit() { - System.out.println("Press Enter to exit..."); - new Scanner(System.in).nextLine(); + System.out.println(annotatedBean.toString()); + } } } diff --git a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/javaConfigDemo/config/RefreshScopeConfig.java b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/javaConfigDemo/config/RefreshScopeConfig.java deleted file mode 100644 index ddbb8a07ab1..00000000000 --- a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/javaConfigDemo/config/RefreshScopeConfig.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.ctrip.framework.apollo.demo.spring.javaConfigDemo.config; - -import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -/** - * to support RefreshScope - * @author Jason Song(song_s@ctrip.com) - */ -@Configuration -@Import(RefreshAutoConfiguration.class) -public class RefreshScopeConfig { -} diff --git a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/springBootDemo/SpringBootSampleApplication.java b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/springBootDemo/SpringBootSampleApplication.java index facff1de59d..c63f2f87a12 100644 --- a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/springBootDemo/SpringBootSampleApplication.java +++ b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/springBootDemo/SpringBootSampleApplication.java @@ -1,9 +1,17 @@ package com.ctrip.framework.apollo.demo.spring.springBootDemo; +import com.ctrip.framework.apollo.demo.spring.springBootDemo.config.SampleRedisConfig; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ApplicationContext; -import java.util.Scanner; +import com.ctrip.framework.apollo.demo.spring.common.bean.AnnotatedBean; +import com.google.common.base.Charsets; +import com.google.common.base.Strings; /** * @author Jason Song(song_s@ctrip.com) @@ -13,13 +21,21 @@ }) public class SpringBootSampleApplication { - public static void main(String[] args) { - new SpringApplicationBuilder(SpringBootSampleApplication.class).run(args); - onKeyExit(); - } + public static void main(String[] args) throws IOException { + ApplicationContext context = new SpringApplicationBuilder(SpringBootSampleApplication.class).run(args); + AnnotatedBean annotatedBean = context.getBean(AnnotatedBean.class); + SampleRedisConfig redisConfig = context.getBean(SampleRedisConfig.class); + + System.out.println("SpringBootSampleApplication Demo. Input any key except quit to print the values. Input quit to exit."); + while (true) { + System.out.print("> "); + String input = new BufferedReader(new InputStreamReader(System.in, Charsets.UTF_8)).readLine(); + if (!Strings.isNullOrEmpty(input) && input.trim().equalsIgnoreCase("quit")) { + System.exit(0); + } - private static void onKeyExit() { - System.out.println("Press Enter to exit..."); - new Scanner(System.in).nextLine(); + System.out.println(annotatedBean.toString()); + System.out.println(redisConfig.toString()); + } } } diff --git a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/springBootDemo/refresh/SpringBootApolloRefreshConfig.java b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/springBootDemo/refresh/SpringBootApolloRefreshConfig.java index 4f006717500..de7c9ecf6c8 100644 --- a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/springBootDemo/refresh/SpringBootApolloRefreshConfig.java +++ b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/springBootDemo/refresh/SpringBootApolloRefreshConfig.java @@ -1,10 +1,5 @@ package com.ctrip.framework.apollo.demo.spring.springBootDemo.refresh; -import com.ctrip.framework.apollo.demo.spring.common.refresh.ApolloRefreshConfig; -import com.ctrip.framework.apollo.demo.spring.springBootDemo.config.SampleRedisConfig; -import com.ctrip.framework.apollo.model.ConfigChangeEvent; -import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -12,6 +7,10 @@ import org.springframework.cloud.context.scope.refresh.RefreshScope; import org.springframework.stereotype.Component; +import com.ctrip.framework.apollo.demo.spring.springBootDemo.config.SampleRedisConfig; +import com.ctrip.framework.apollo.model.ConfigChangeEvent; +import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; + /** * @author Jason Song(song_s@ctrip.com) */ @@ -20,9 +19,6 @@ public class SpringBootApolloRefreshConfig { private static final Logger logger = LoggerFactory.getLogger(SpringBootApolloRefreshConfig.class); - @Autowired - private ApolloRefreshConfig apolloRefreshConfig; - @Autowired private SampleRedisConfig sampleRedisConfig; @@ -31,6 +27,17 @@ public class SpringBootApolloRefreshConfig { @ApolloConfigChangeListener public void onChange(ConfigChangeEvent changeEvent) { + boolean redisCacheKeysChanged = false; + for (String changedKey : changeEvent.changedKeys()) { + if (changedKey.startsWith("redis.cache")) { + redisCacheKeysChanged = true; + break; + } + } + if (!redisCacheKeysChanged) { + return; + } + logger.info("before refresh {}", sampleRedisConfig.toString()); refreshScope.refresh("sampleRedisConfig"); logger.info("after refresh {}", sampleRedisConfig.toString()); diff --git a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/xmlConfigDemo/XmlApplication.java b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/xmlConfigDemo/XmlApplication.java index e3526755a1f..88196899b41 100644 --- a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/xmlConfigDemo/XmlApplication.java +++ b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/xmlConfigDemo/XmlApplication.java @@ -1,20 +1,33 @@ package com.ctrip.framework.apollo.demo.spring.xmlConfigDemo; +import com.google.common.base.Strings; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; -import java.util.Scanner; +import com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean; +import com.google.common.base.Charsets; /** * @author Jason Song(song_s@ctrip.com) */ public class XmlApplication { - public static void main(String[] args) { - new ClassPathXmlApplicationContext("spring.xml"); - onKeyExit(); - } + public static void main(String[] args) throws IOException { + ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); + XmlBean xmlBean = context.getBean(XmlBean.class); + + System.out.println("XmlApplication Demo. Input any key except quit to print the values. Input quit to exit."); + while (true) { + System.out.print("> "); + String input = new BufferedReader(new InputStreamReader(System.in, Charsets.UTF_8)).readLine(); + if (!Strings.isNullOrEmpty(input) && input.trim().equalsIgnoreCase("quit")) { + System.exit(0); + } - private static void onKeyExit() { - System.out.println("Press Enter to exit..."); - new Scanner(System.in).nextLine(); + System.out.println(xmlBean.toString()); + } } } diff --git a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/xmlConfigDemo/bean/XmlBean.java b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/xmlConfigDemo/bean/XmlBean.java index 3769e7a35b9..1da38458976 100644 --- a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/xmlConfigDemo/bean/XmlBean.java +++ b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/xmlConfigDemo/bean/XmlBean.java @@ -29,4 +29,9 @@ public int getTimeout() { public int getBatch() { return batch; } + + @Override + public String toString() { + return String.format("[XmlBean] timeout: %d, batch: %d", timeout, batch); + } } diff --git a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/xmlConfigDemo/refresh/ManualRefreshUtil.java b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/xmlConfigDemo/refresh/ManualRefreshUtil.java deleted file mode 100644 index 03ae4ef26d5..00000000000 --- a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/xmlConfigDemo/refresh/ManualRefreshUtil.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.refresh; - -import com.ctrip.framework.apollo.Config; -import com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean; -import com.ctrip.framework.apollo.model.ConfigChangeEvent; -import com.ctrip.framework.apollo.spring.annotation.ApolloConfig; -import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * @author Jason Song(song_s@ctrip.com) - */ -public class ManualRefreshUtil { - private static final Logger logger = LoggerFactory.getLogger(ManualRefreshUtil.class); - - @ApolloConfig - private Config config; - - @Autowired - private XmlBean xmlBean; - - @ApolloConfigChangeListener - private void onChange(ConfigChangeEvent changeEvent) { - if (changeEvent.isChanged("timeout")) { - logger.info("Manually refreshing xmlBean.timeout"); - xmlBean.setTimeout(config.getIntProperty("timeout", xmlBean.getTimeout())); - } - - if (changeEvent.isChanged("batch")) { - logger.info("Manually refreshing xmlBean.batch"); - xmlBean.setBatch(config.getIntProperty("batch", xmlBean.getBatch())); - } - } -} diff --git a/apollo-demo/src/main/resources/application.yml b/apollo-demo/src/main/resources/application.yml index 80857e796a1..e41a188aa05 100644 --- a/apollo-demo/src/main/resources/application.yml +++ b/apollo-demo/src/main/resources/application.yml @@ -1,5 +1,5 @@ apollo: bootstrap: enabled: true - # will inject 'application' and 'FX.apollo' namespaces in bootstrap phase - namespaces: application,FX.apollo + # will inject 'application' and 'TEST1.apollo' namespaces in bootstrap phase + namespaces: application,TEST1.apollo diff --git a/apollo-demo/src/main/resources/spring.xml b/apollo-demo/src/main/resources/spring.xml index ffe65f43253..ff7cb5d5824 100644 --- a/apollo-demo/src/main/resources/spring.xml +++ b/apollo-demo/src/main/resources/spring.xml @@ -7,18 +7,12 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd"> - + - - - - - - +