From 3efd4a33a1be5425e96cc3052f141d3fd3fab903 Mon Sep 17 00:00:00 2001 From: nobodyiam Date: Fri, 23 Feb 2018 22:22:37 +0800 Subject: [PATCH] 1. Use Spring bean factory to resolve the placeholder values 2. Support XML config placeholders 3. Add more tests to cover all kinds of cases 4. Update apollo demo to adapt for auto update functionality --- .../apollo/internals/DefaultInjector.java | 4 + .../annotation/ApolloConfigRegistrar.java | 4 +- .../annotation/SpringValueProcessor.java | 394 +++++-- .../apollo/spring/auto/SpringFieldValue.java | 39 - .../apollo/spring/auto/SpringMethodValue.java | 45 - .../apollo/spring/auto/SpringValue.java | 57 - .../ApolloSpringApplicationRunListener.java | 7 +- .../spring/config/ConfigPropertySource.java | 7 +- .../config/ConfigPropertySourceFactory.java | 23 + .../ConfigPropertySourcesProcessor.java | 16 +- .../config/PropertySourcesProcessor.java | 18 +- .../spring/property/PlaceholderHelper.java | 127 +++ .../apollo/spring/property/SpringValue.java | 96 ++ .../property/SpringValueDefinition.java | 26 + .../SpringValueDefinitionProcessor.java | 96 ++ .../framework/apollo/util/ConfigUtil.java | 18 + .../com/ctrip/framework/apollo/AllTests.java | 25 +- .../framework/apollo/build/MockInjector.java | 1 - .../spring/AbstractSpringIntegrationTest.java | 79 +- .../JavaConfigPlaceholderAutoUpdateTest.java | 1007 +++++++++++++++++ .../spring/JavaConfigPlaceholderTest.java | 56 +- .../XmlConfigPlaceholderAutoUpdateTest.java | 606 ++++++++++ .../apollo/spring/auto/SpringValueTest.java | 83 -- .../config/ConfigPropertySourceTest.java | 98 ++ .../property/PlaceholderHelperTest.java | 62 + .../framework/apollo/util/ConfigUtilTest.java | 16 +- .../spring/XmlConfigPlaceholderTest10.xml | 21 + .../spring/XmlConfigPlaceholderTest7.xml | 13 + .../spring/XmlConfigPlaceholderTest8.xml | 13 + .../spring/XmlConfigPlaceholderTest9.xml | 16 + .../provider/DefaultApplicationProvider.java | 33 +- .../internals/provider/NullProvider.java | 5 - .../spi/provider/ApplicationProvider.java | 4 - .../DefaultApplicationProviderTest.java | 30 - .../test/resources/META-INF/app.properties | 2 +- .../META-INF/some-invalid-app.properties | 1 - .../apollo/demo/api/ApolloConfigDemo.java | 2 +- .../spring/common/bean/AnnotatedBean.java | 17 +- .../common/config/AnotherAppConfig.java | 2 +- .../common/refresh/ApolloRefreshConfig.java | 37 - .../javaConfigDemo/AnnotationApplication.java | 28 +- .../config/RefreshScopeConfig.java | 14 - .../SpringBootSampleApplication.java | 32 +- .../SpringBootApolloRefreshConfig.java | 23 +- .../spring/xmlConfigDemo/XmlApplication.java | 29 +- .../spring/xmlConfigDemo/bean/XmlBean.java | 5 + .../refresh/ManualRefreshUtil.java | 37 - .../src/main/resources/application.yml | 4 +- apollo-demo/src/main/resources/spring.xml | 10 +- 49 files changed, 2794 insertions(+), 594 deletions(-) delete mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/spring/auto/SpringFieldValue.java delete mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/spring/auto/SpringMethodValue.java delete mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/spring/auto/SpringValue.java create mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySourceFactory.java create mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/PlaceholderHelper.java create mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValue.java create mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValueDefinition.java create mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValueDefinitionProcessor.java create mode 100644 apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigPlaceholderAutoUpdateTest.java create mode 100644 apollo-client/src/test/java/com/ctrip/framework/apollo/spring/XmlConfigPlaceholderAutoUpdateTest.java delete mode 100644 apollo-client/src/test/java/com/ctrip/framework/apollo/spring/auto/SpringValueTest.java create mode 100644 apollo-client/src/test/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySourceTest.java create mode 100644 apollo-client/src/test/java/com/ctrip/framework/apollo/spring/property/PlaceholderHelperTest.java create mode 100644 apollo-client/src/test/resources/spring/XmlConfigPlaceholderTest10.xml create mode 100644 apollo-client/src/test/resources/spring/XmlConfigPlaceholderTest7.xml create mode 100644 apollo-client/src/test/resources/spring/XmlConfigPlaceholderTest8.xml create mode 100644 apollo-client/src/test/resources/spring/XmlConfigPlaceholderTest9.xml delete mode 100644 apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/common/refresh/ApolloRefreshConfig.java delete mode 100644 apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/javaConfigDemo/config/RefreshScopeConfig.java delete mode 100644 apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/xmlConfigDemo/refresh/ManualRefreshUtil.java 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 3e4b3aed498..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; @@ -31,6 +32,7 @@ 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, 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 index 966297d8ecf..ff0d54f0671 100644 --- 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 @@ -1,163 +1,317 @@ package com.ctrip.framework.apollo.spring.annotation; -import com.ctrip.framework.apollo.ConfigChangeListener; -import com.ctrip.framework.apollo.model.ConfigChange; -import com.ctrip.framework.apollo.model.ConfigChangeEvent; -import com.ctrip.framework.apollo.spring.auto.SpringFieldValue; -import com.ctrip.framework.apollo.spring.auto.SpringMethodValue; -import com.ctrip.framework.apollo.spring.auto.SpringValue; -import com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor; -import com.ctrip.framework.foundation.Foundation; -import com.ctrip.framework.foundation.spi.provider.ApplicationProvider; -import com.google.common.collect.LinkedListMultimap; -import com.google.common.collect.Multimap; +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 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 java.util.regex.Matcher; -import java.util.regex.Pattern; +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. + * 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 { - private Pattern pattern = Pattern.compile("\\$\\{([^:]*)\\}:?(.*)"); - private static Multimap monitor = LinkedListMultimap.create(); - private static ApplicationProvider applicationProvider = Foundation.app(); - private Environment environment; - private Logger logger = LoggerFactory.getLogger(SpringValueProcessor.class); - - public static Multimap monitor() { - return monitor; +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(); } + } - public static boolean enable(){ - return applicationProvider.isAutoUpdateEnable(); + @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 postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - boolean enabled = enable(); - if (enabled){ - Class clazz = bean.getClass(); - processFields(bean, findAllField(clazz)); - processMethods(bean, findAllMethod(clazz)); - } - 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); + } } + } - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - return bean; + private void processBeanPropertyValues(Object bean, String beanName) { + Collection propertySpringValues = beanName2SpringValueDefinitions + .get(beanName); + if (propertySpringValues == null || propertySpringValues.isEmpty()) { + return; } - private void processFields(Object bean, List declaredFields) { - for (Field field : declaredFields) { - // regist @Value on field - Value value = field.getAnnotation(Value.class); - if (value == null) { - continue; - } - Matcher matcher = pattern.matcher(value.value()); - if (matcher.matches()) { - String key = matcher.group(1); - monitor.put(key, SpringFieldValue.create(key,bean, field)); - logger.info("Listening apollo key = {}", key); - } + 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()); + } } - private void processMethods(final Object bean, List declaredMethods) { - for (final Method method : declaredMethods) { - //regist @Value on method - Value value = method.getAnnotation(Value.class); - if (value == null) { - continue; - } - Matcher matcher = pattern.matcher(value.value()); - if (matcher.matches()) { - String key = matcher.group(1); - monitor.put(key, SpringMethodValue.create(key,bean, method)); - logger.info("Listening apollo key = {}", key); - } + // 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); - @Override - public int getOrder() { - //make it as late as possible - return Ordered.LOWEST_PRECEDENCE; + 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); - 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; + 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()); } - 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; + 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)); + } - @Override - public void setEnvironment(Environment env) { - this.environment = env; - PropertySourcesProcessor.registerListener(new ConfigChangeListener() { - @Override - public void onChange(ConfigChangeEvent changeEvent) { - Set keys = changeEvent.changedKeys(); - if (CollectionUtils.isEmpty(keys)) { - return; - } - if (!SpringValueProcessor.enable()) { - return; - } - for (String k : keys) { - ConfigChange configChange = changeEvent.getChange(k); - if (!Objects.equals(environment.getProperty(k), configChange.getNewValue())) { - continue; - } - Collection targetValues = SpringValueProcessor.monitor().get(k); - if (targetValues == null || targetValues.isEmpty()) { - continue; - } - for (SpringValue val : targetValues) { - val.updateVal(environment.getProperty(k)); - } - } - } - }); + 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/auto/SpringFieldValue.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/auto/SpringFieldValue.java deleted file mode 100644 index c45b88f7563..00000000000 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/auto/SpringFieldValue.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.ctrip.framework.apollo.spring.auto; - -import java.lang.reflect.Field; - -/** - * Spring @Value field info - * @author github.com/zhegexiaohuozi seimimaster@gmail.com - * @since 2018/2/6. - */ -public class SpringFieldValue extends SpringValue { - private Field field; - - private SpringFieldValue(String key, Object ins, Field field) { - super(); - this.bean = ins; - this.className = ins.getClass().getName(); - this.fieldName = field.getName(); - this.field = field; - this.parser = findParser(field.getType()); - this.valKey = key; - } - - public static SpringFieldValue create(String key, Object ins, Field field) { - return new SpringFieldValue(key, ins, field); - } - - @Override - public void updateVal(String newVal) { - try { - boolean accessible = field.isAccessible(); - field.setAccessible(true); - field.set(bean, parseVal(newVal)); - field.setAccessible(accessible); - logger.info("auto update apollo changed value, key={}, newVal={} in {}.{}", valKey, newVal, className, fieldName); - } catch (Exception e) { - logger.error("update field {}.{} fail with new val={},key = {}, msg = {}", className, fieldName, newVal, valKey, e.getMessage()); - } - } -} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/auto/SpringMethodValue.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/auto/SpringMethodValue.java deleted file mode 100644 index 4cbd3686637..00000000000 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/auto/SpringMethodValue.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.ctrip.framework.apollo.spring.auto; - -import java.lang.reflect.Method; - -/** - * Spring @Value method info - * @author github.com/zhegexiaohuozi seimimaster@gmail.com - * @since 2018/2/6. - */ -public class SpringMethodValue extends SpringValue { - private Method method; - - private SpringMethodValue(String key, Object ins, Method method) { - this.bean = ins; - this.method = method; - this.className = ins.getClass().getName(); - this.fieldName = method.getName() + "(*)"; - Class[] paramTps = method.getParameterTypes(); - if (paramTps.length != 1) { - logger.error("invalid setter,can not update in {}.{}", className, fieldName); - return; - } - this.parser = findParser(paramTps[0]); - this.valKey = key; - } - - public static SpringMethodValue create(String key, Object ins, Method method) { - return new SpringMethodValue(key, ins, method); - } - - @Override - public void updateVal(String newVal) { - try { - Class[] paramTps = method.getParameterTypes(); - if (paramTps.length != 1) { - logger.error("invalid setter ,can not update key={} val={} in {}.{}", valKey, newVal, className, fieldName); - return; - } - method.invoke(bean, parseVal(newVal)); - logger.info("auto update apollo changed value, key={}, newVal={} in {}.{}", valKey, newVal, className, fieldName); - } catch (Exception e) { - logger.error("update field {}.{} fail with new val={},key = {}, msg = {}", className, fieldName, newVal, valKey, e.getMessage()); - } - } -} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/auto/SpringValue.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/auto/SpringValue.java deleted file mode 100644 index 2c001e0f860..00000000000 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/auto/SpringValue.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.ctrip.framework.apollo.spring.auto; - -import com.ctrip.framework.apollo.util.function.Functions; -import com.google.common.base.Function; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Date; - -/** - * Spring @Value field and method common info - * - * @author github.com/zhegexiaohuozi seimimaster@gmail.com - * @since 2017/12/20. - */ -public abstract class SpringValue { - protected Object bean; - String className; - String fieldName; - String valKey; - protected Function parser; - protected Logger logger = LoggerFactory.getLogger(getClass()); - - public abstract void updateVal(String newVal); - - Object parseVal(String newVal) { - if (parser == null) { - return newVal; - } - return parser.apply(newVal); - } - - Function findParser(Class targetType) { - Function res = null; - if (targetType.equals(String.class)) { - return null; - } else if (targetType.equals(int.class) || targetType.equals(Integer.class)) { - res = Functions.TO_INT_FUNCTION; - } else if (targetType.equals(long.class) || targetType.equals(Long.class)) { - res = Functions.TO_LONG_FUNCTION; - } else if (targetType.equals(boolean.class) || targetType.equals(Boolean.class)) { - res = Functions.TO_BOOLEAN_FUNCTION; - } else if (targetType.equals(Date.class)) { - res = Functions.TO_DATE_FUNCTION; - } else if (targetType.equals(short.class) || targetType.equals(Short.class)) { - res = Functions.TO_SHORT_FUNCTION; - } else if (targetType.equals(double.class) || targetType.equals(Double.class)) { - res = Functions.TO_DOUBLE_FUNCTION; - } else if (targetType.equals(float.class) || targetType.equals(Float.class)) { - res = Functions.TO_FLOAT_FUNCTION; - } else if (targetType.equals(byte.class) || targetType.equals(Byte.class)) { - res = Functions.TO_BYTE_FUNCTION; - } - return res; - } - -} 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 1f64ff373e2..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,6 +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; @@ -23,6 +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); + 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 9643d738c99..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,9 +1,7 @@ package com.ctrip.framework.apollo.spring.config; -import com.google.common.collect.HashMultimap; -import com.ctrip.framework.apollo.ConfigChangeListener; +import com.ctrip.framework.apollo.build.ApolloInjector; import com.google.common.collect.ImmutableSortedSet; -import com.google.common.collect.Lists; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; @@ -22,7 +20,6 @@ import java.util.Collection; import java.util.Iterator; -import java.util.List; /** * Apollo Property Sources processor for Spring Annotation Based Application.

@@ -35,21 +32,16 @@ * @author Jason Song(song_s@ctrip.com) */ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered { - private static final List ALL_CONFIG = Lists.newLinkedList(); 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) { return NAMESPACE_NAMES.putAll(order, namespaces); } - public static void registerListener(ConfigChangeListener configChangeListener){ - for(Config config:ALL_CONFIG){ - config.addChangeListener(configChangeListener); - } - } - @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { initializePropertySources(); @@ -70,8 +62,8 @@ protected void initializePropertySources() { int order = iterator.next(); for (String namespace : NAMESPACE_NAMES.get(order)) { Config config = ConfigService.getConfig(namespace); - ALL_CONFIG.add(config); - 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/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/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/auto/SpringValueTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/auto/SpringValueTest.java deleted file mode 100644 index 03c2e8de2be..00000000000 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/auto/SpringValueTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.ctrip.framework.apollo.spring.auto; - -import com.ctrip.framework.apollo.enums.PropertyChangeType; -import com.ctrip.framework.apollo.model.ConfigChange; -import com.ctrip.framework.apollo.util.function.Functions; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.Date; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * SpringValue Tester. - * - * @author github.com/zhegexiaohuozi seimimaster@gmail.com - * @version 1.0 - */ -public class SpringValueTest { - - private SpringValue defaultVal; - private ConfigChange testTarget; - //SpringValueProcessor.pattern - private Pattern pattern = Pattern.compile("\\$\\{([^:]*)\\}:?(.*)"); - - @Before - public void before() throws Exception { - Field field = ConfigChange.class.getDeclaredField("newValue"); - field.setAccessible(true); - testTarget = new ConfigChange("test","test","testO","testN", PropertyChangeType.MODIFIED); - defaultVal = SpringFieldValue.create("test",testTarget,field); - } - - /** - * Method: updateVal(String newVal) - */ - @Test - public void testUpdateVal() throws Exception { - defaultVal.updateVal("testUp"); - Assert.assertEquals("testUp",testTarget.getNewValue()); - } - - /** - * Method: findParser(Class targetType) - */ - @Test - public void testFindParser() throws Exception { - Method findParser = SpringValue.class.getDeclaredMethod("findParser",Class.class); - findParser.setAccessible(true); - - Assert.assertNull(findParser.invoke(defaultVal,String.class)); - Assert.assertEquals(Functions.TO_INT_FUNCTION,findParser.invoke(defaultVal,int.class)); - Assert.assertEquals(Functions.TO_INT_FUNCTION,findParser.invoke(defaultVal,Integer.class)); - Assert.assertEquals(Functions.TO_LONG_FUNCTION,findParser.invoke(defaultVal,long.class)); - Assert.assertEquals(Functions.TO_LONG_FUNCTION,findParser.invoke(defaultVal,Long.class)); - Assert.assertEquals(Functions.TO_DOUBLE_FUNCTION,findParser.invoke(defaultVal,double.class)); - Assert.assertEquals(Functions.TO_DOUBLE_FUNCTION,findParser.invoke(defaultVal,Double.class)); - Assert.assertEquals(Functions.TO_FLOAT_FUNCTION,findParser.invoke(defaultVal,float.class)); - Assert.assertEquals(Functions.TO_FLOAT_FUNCTION,findParser.invoke(defaultVal,Float.class)); - Assert.assertEquals(Functions.TO_BYTE_FUNCTION,findParser.invoke(defaultVal,byte.class)); - Assert.assertEquals(Functions.TO_BYTE_FUNCTION,findParser.invoke(defaultVal,Byte.class)); - Assert.assertEquals(Functions.TO_BOOLEAN_FUNCTION,findParser.invoke(defaultVal,boolean.class)); - Assert.assertEquals(Functions.TO_BOOLEAN_FUNCTION,findParser.invoke(defaultVal,Boolean.class)); - Assert.assertEquals(Functions.TO_SHORT_FUNCTION,findParser.invoke(defaultVal,short.class)); - Assert.assertEquals(Functions.TO_SHORT_FUNCTION,findParser.invoke(defaultVal,Short.class)); - Assert.assertEquals(Functions.TO_DATE_FUNCTION,findParser.invoke(defaultVal,Date.class)); - - } - - @Test - public void testPattern(){ - String valP = "${some.timeout:5000}"; - Matcher matcher = pattern.matcher(valP); - if (matcher.matches()) { - String key = matcher.group(1); - Assert.assertEquals("some.timeout",key); - } - } - -} 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/main/java/com/ctrip/framework/foundation/internals/provider/DefaultApplicationProvider.java b/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/DefaultApplicationProvider.java index 96239e78a9d..e4ffb820eb3 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/DefaultApplicationProvider.java +++ b/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/DefaultApplicationProvider.java @@ -19,7 +19,6 @@ public class DefaultApplicationProvider implements ApplicationProvider { private Properties m_appProperties = new Properties(); private String m_appId; - private boolean m_enableAutoUpdate; @Override public void initialize() { @@ -50,20 +49,11 @@ public void initialize(InputStream in) { } initAppId(); - initEnableAutoUpdate(); } catch (Throwable ex) { logger.error("Initialize DefaultApplicationProvider failed.", ex); } } - /** - * @return whether update the field or method which has '@Value' automatically - */ - @Override - public boolean isAutoUpdateEnable() { - return m_enableAutoUpdate; - } - @Override public String getAppId() { return m_appId; @@ -111,29 +101,8 @@ private void initAppId() { logger.warn("app.id is not available from System Property and {}. It is set to null", APP_PROPERTIES_CLASSPATH); } - private void initEnableAutoUpdate(){ - // 1. Get app.autoupdate.enabled from System Property - String enabeAutoUpdate = System.getProperty("app.autoupdate.enabled"); - if (!Utils.isBlank(enabeAutoUpdate)) { - m_enableAutoUpdate = Boolean.parseBoolean(enabeAutoUpdate.trim()); - logger.info("App update value automatically is {} by app.autoupdate property from System Property", m_enableAutoUpdate); - return; - } - - // 2. Try to get app.autoupdate.enabled from app.properties. - enabeAutoUpdate = m_appProperties.getProperty("app.autoupdate.enabled"); - if (!Utils.isBlank(enabeAutoUpdate)) { - m_enableAutoUpdate = Boolean.parseBoolean(enabeAutoUpdate.trim()); - logger.info("App update value automatically is {} by app.autoupdate property from {}", m_enableAutoUpdate, APP_PROPERTIES_CLASSPATH); - return; - } - - // default true, update field automatically - m_enableAutoUpdate = true; - } - @Override public String toString() { - return "appId [" + getAppId() + "],enableAutoUpdate["+isAutoUpdateEnable()+"] properties: " + m_appProperties + " (DefaultApplicationProvider)"; + return "appId [" + getAppId() + "] properties: " + m_appProperties + " (DefaultApplicationProvider)"; } } diff --git a/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/NullProvider.java b/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/NullProvider.java index 9ac8db6f585..b91f393b4fc 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/NullProvider.java +++ b/apollo-core/src/main/java/com/ctrip/framework/foundation/internals/provider/NullProvider.java @@ -58,11 +58,6 @@ public void initialize(InputStream in) { } - @Override - public boolean isAutoUpdateEnable() { - return false; - } - @Override public String getHostAddress() { return null; diff --git a/apollo-core/src/main/java/com/ctrip/framework/foundation/spi/provider/ApplicationProvider.java b/apollo-core/src/main/java/com/ctrip/framework/foundation/spi/provider/ApplicationProvider.java index 3a7c3b44347..cef1e5479ac 100644 --- a/apollo-core/src/main/java/com/ctrip/framework/foundation/spi/provider/ApplicationProvider.java +++ b/apollo-core/src/main/java/com/ctrip/framework/foundation/spi/provider/ApplicationProvider.java @@ -20,8 +20,4 @@ public interface ApplicationProvider extends Provider { * Initialize the application provider with the specified input stream */ public void initialize(InputStream in); - /** - * @return whether update the field or method which has '@Value' automatically - */ - public boolean isAutoUpdateEnable(); } diff --git a/apollo-core/src/test/java/com/ctrip/framework/apollo/foundation/internals/provider/DefaultApplicationProviderTest.java b/apollo-core/src/test/java/com/ctrip/framework/apollo/foundation/internals/provider/DefaultApplicationProviderTest.java index c5511ceefcc..0b858f76ab1 100644 --- a/apollo-core/src/test/java/com/ctrip/framework/apollo/foundation/internals/provider/DefaultApplicationProviderTest.java +++ b/apollo-core/src/test/java/com/ctrip/framework/apollo/foundation/internals/provider/DefaultApplicationProviderTest.java @@ -61,34 +61,4 @@ public void testLoadAppPropertiesFailed() throws Exception { assertEquals(null, defaultApplicationProvider.getAppId()); assertFalse(defaultApplicationProvider.isAppIdSet()); } - - @Test - public void testLoadAutoUpdateSwitchFromSystemProperty(){ - String notEnable = "false"; - System.setProperty("app.autoupdate.enabled", notEnable); - defaultApplicationProvider.initialize(); - System.clearProperty("app.autoupdate.enabled"); - - assertFalse(defaultApplicationProvider.isAutoUpdateEnable()); - } - - @Test - public void testLoadAutoUpdateSwitchFormConfigFile() throws Exception { - File baseDir = new File("src/test/resources/META-INF"); - File appProperties = new File(baseDir, "some-invalid-app.properties"); - - defaultApplicationProvider.initialize(new FileInputStream(appProperties)); - - assertFalse(defaultApplicationProvider.isAutoUpdateEnable()); - } - - @Test - public void testLoadAutoUpdateSwitchByDefault() throws Exception { - File baseDir = new File("src/test/resources/META-INF"); - File appProperties = new File(baseDir, "app.properties"); - - defaultApplicationProvider.initialize(new FileInputStream(appProperties)); - - assertTrue(defaultApplicationProvider.isAutoUpdateEnable()); - } } diff --git a/apollo-core/src/test/resources/META-INF/app.properties b/apollo-core/src/test/resources/META-INF/app.properties index 61336b2f864..00a4ef803a7 100644 --- a/apollo-core/src/test/resources/META-INF/app.properties +++ b/apollo-core/src/test/resources/META-INF/app.properties @@ -1 +1 @@ -app.id=110402 \ No newline at end of file +app.id=110402 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 eb05a91e463..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,2 +1 @@ appid=110402 -app.autoupdate.enabled=false \ No newline at end of file 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"> - + - - - - - - +