Commit 3efd4a33 authored by nobodyiam's avatar nobodyiam

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
parent 21a8d3ba
...@@ -7,6 +7,8 @@ import com.ctrip.framework.apollo.spi.ConfigRegistry; ...@@ -7,6 +7,8 @@ import com.ctrip.framework.apollo.spi.ConfigRegistry;
import com.ctrip.framework.apollo.spi.DefaultConfigFactory; import com.ctrip.framework.apollo.spi.DefaultConfigFactory;
import com.ctrip.framework.apollo.spi.DefaultConfigFactoryManager; import com.ctrip.framework.apollo.spi.DefaultConfigFactoryManager;
import com.ctrip.framework.apollo.spi.DefaultConfigRegistry; 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.tracer.Tracer;
import com.ctrip.framework.apollo.util.ConfigUtil; import com.ctrip.framework.apollo.util.ConfigUtil;
import com.ctrip.framework.apollo.util.http.HttpUtil; import com.ctrip.framework.apollo.util.http.HttpUtil;
...@@ -60,6 +62,8 @@ public class DefaultInjector implements Injector { ...@@ -60,6 +62,8 @@ public class DefaultInjector implements Injector {
bind(HttpUtil.class).in(Singleton.class); bind(HttpUtil.class).in(Singleton.class);
bind(ConfigServiceLocator.class).in(Singleton.class); bind(ConfigServiceLocator.class).in(Singleton.class);
bind(RemoteConfigLongPollService.class).in(Singleton.class); bind(RemoteConfigLongPollService.class).in(Singleton.class);
bind(PlaceholderHelper.class).in(Singleton.class);
bind(ConfigPropertySourceFactory.class).in(Singleton.class);
} }
} }
} }
package com.ctrip.framework.apollo.spring.annotation; 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.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
...@@ -31,6 +32,7 @@ public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar { ...@@ -31,6 +32,7 @@ public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar {
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(), BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
ApolloAnnotationProcessor.class); 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);
} }
} }
package com.ctrip.framework.apollo.spring.annotation; package com.ctrip.framework.apollo.spring.annotation;
import com.ctrip.framework.apollo.ConfigChangeListener; import java.beans.PropertyDescriptor;
import com.ctrip.framework.apollo.model.ConfigChange; import java.lang.reflect.Field;
import com.ctrip.framework.apollo.model.ConfigChangeEvent; import java.lang.reflect.Method;
import com.ctrip.framework.apollo.spring.auto.SpringFieldValue; import java.util.Collection;
import com.ctrip.framework.apollo.spring.auto.SpringMethodValue; import java.util.LinkedList;
import com.ctrip.framework.apollo.spring.auto.SpringValue; import java.util.List;
import com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor; import java.util.Objects;
import com.ctrip.framework.foundation.Foundation; import java.util.Set;
import com.ctrip.framework.foundation.spi.provider.ApplicationProvider;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException; 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.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.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.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered; import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field; import com.ctrip.framework.apollo.ConfigChangeListener;
import java.lang.reflect.Method; import com.ctrip.framework.apollo.build.ApolloInjector;
import java.util.Collection; import com.ctrip.framework.apollo.model.ConfigChange;
import java.util.LinkedList; import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import java.util.List; import com.ctrip.framework.apollo.spring.config.ConfigPropertySource;
import java.util.Objects; import com.ctrip.framework.apollo.spring.config.ConfigPropertySourceFactory;
import java.util.Set; import com.ctrip.framework.apollo.spring.property.PlaceholderHelper;
import java.util.regex.Matcher; import com.ctrip.framework.apollo.spring.property.SpringValue;
import java.util.regex.Pattern; 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 * @author github.com/zhegexiaohuozi seimimaster@gmail.com
* @since 2017/12/20. * @since 2017/12/20.
*/ */
public class SpringValueProcessor implements BeanPostProcessor, PriorityOrdered, EnvironmentAware { public class SpringValueProcessor implements BeanPostProcessor, PriorityOrdered, EnvironmentAware,
private Pattern pattern = Pattern.compile("\\$\\{([^:]*)\\}:?(.*)"); BeanFactoryAware, BeanFactoryPostProcessor {
private static Multimap<String, SpringValue> monitor = LinkedListMultimap.create();
private static ApplicationProvider applicationProvider = Foundation.app(); private static final Logger logger = LoggerFactory.getLogger(SpringValueProcessor.class);
private final Multimap<String, SpringValue> monitor = LinkedListMultimap.create();
private final ConfigUtil configUtil;
private final PlaceholderHelper placeholderHelper;
private final ConfigPropertySourceFactory configPropertySourceFactory;
private final boolean typeConverterHasConvertIfNecessaryWithFieldParameter;
private Environment environment; private Environment environment;
private Logger logger = LoggerFactory.getLogger(SpringValueProcessor.class); private ConfigurableBeanFactory beanFactory;
private TypeConverter typeConverter;
public static Multimap<String, SpringValue> monitor() { private static Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions = LinkedListMultimap.create();
return monitor;
public SpringValueProcessor() {
configUtil = ApolloInjector.getInstance(ConfigUtil.class);
placeholderHelper = ApolloInjector.getInstance(PlaceholderHelper.class);
configPropertySourceFactory = ApolloInjector.getInstance(ConfigPropertySourceFactory.class);
typeConverterHasConvertIfNecessaryWithFieldParameter = testTypeConverterHasConvertIfNecessaryWithFieldParameter();
} }
public static boolean enable(){ @Override
return applicationProvider.isAutoUpdateEnable(); public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
this.typeConverter = this.beanFactory.getTypeConverter();
}
@Override
public void setEnvironment(Environment env) {
this.environment = env;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
beanName2SpringValueDefinitions = SpringValueDefinitionProcessor.getBeanName2SpringValueDefinitions();
registerConfigChangeListener();
}
} }
@Override @Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { public Object postProcessBeforeInitialization(Object bean, String beanName)
boolean enabled = enable(); throws BeansException {
if (enabled){ if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
Class clazz = bean.getClass(); Class clazz = bean.getClass();
processFields(bean, findAllField(clazz)); processFields(bean, beanName, findAllField(clazz));
processMethods(bean, findAllMethod(clazz)); processMethods(bean, beanName, findAllMethod(clazz));
processBeanPropertyValues(bean, beanName);
} }
return bean; return bean;
} }
...@@ -70,44 +112,86 @@ public class SpringValueProcessor implements BeanPostProcessor, PriorityOrdered, ...@@ -70,44 +112,86 @@ public class SpringValueProcessor implements BeanPostProcessor, PriorityOrdered,
return bean; return bean;
} }
private void processFields(Object bean, List<Field> declaredFields) { private void processFields(Object bean, String beanName, List<Field> declaredFields) {
for (Field field : declaredFields) { for (Field field : declaredFields) {
// regist @Value on field // register @Value on field
Value value = field.getAnnotation(Value.class); Value value = field.getAnnotation(Value.class);
if (value == null) { if (value == null) {
continue; continue;
} }
Matcher matcher = pattern.matcher(value.value()); Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
if (matcher.matches()) {
String key = matcher.group(1); if (keys.isEmpty()) {
monitor.put(key, SpringFieldValue.create(key,bean, field)); continue;
logger.info("Listening apollo key = {}", key); }
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, List<Method> declaredMethods) { private void processMethods(final Object bean, String beanName, List<Method> declaredMethods) {
for (final Method method : declaredMethods) { for (final Method method : declaredMethods) {
//regist @Value on method //register @Value on method
Value value = method.getAnnotation(Value.class); Value value = method.getAnnotation(Value.class);
if (value == null) { if (value == null) {
continue; continue;
} }
Matcher matcher = pattern.matcher(value.value()); //skip Configuration bean methods
if (matcher.matches()) { if (method.getAnnotation(Bean.class) != null) {
String key = matcher.group(1); continue;
monitor.put(key, SpringMethodValue.create(key,bean, method));
logger.info("Listening apollo key = {}", key);
} }
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<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
if (keys.isEmpty()) {
continue;
} }
@Override for (String key : keys) {
public int getOrder() { SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method);
//make it as late as possible monitor.put(key, springValue);
return Ordered.LOWEST_PRECEDENCE; logger.debug("Monitoring {}", springValue);
}
}
} }
private void processBeanPropertyValues(Object bean, String beanName) {
Collection<SpringValueDefinition> propertySpringValues = beanName2SpringValueDefinitions
.get(beanName);
if (propertySpringValues == null || propertySpringValues.isEmpty()) {
return;
}
for (SpringValueDefinition definition : propertySpringValues) {
try {
PropertyDescriptor pd = BeanUtils
.getPropertyDescriptor(bean.getClass(), definition.getPropertyName());
Method method = pd.getWriteMethod();
if (method == null) {
continue;
}
SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(),
bean, beanName, method);
monitor.put(definition.getKey(), springValue);
logger.debug("Monitoring {}", springValue);
} catch (Throwable ex) {
logger.error("Failed to enable auto update feature for {}.{}", bean.getClass(),
definition.getPropertyName());
}
}
// clear
beanName2SpringValueDefinitions.removeAll(beanName);
}
private List<Field> findAllField(Class clazz) { private List<Field> findAllField(Class clazz) {
final List<Field> res = new LinkedList<>(); final List<Field> res = new LinkedList<>();
...@@ -131,33 +215,103 @@ public class SpringValueProcessor implements BeanPostProcessor, PriorityOrdered, ...@@ -131,33 +215,103 @@ public class SpringValueProcessor implements BeanPostProcessor, PriorityOrdered,
return res; return res;
} }
@Override private void registerConfigChangeListener() {
public void setEnvironment(Environment env) { ConfigChangeListener changeListener = new ConfigChangeListener() {
this.environment = env;
PropertySourcesProcessor.registerListener(new ConfigChangeListener() {
@Override @Override
public void onChange(ConfigChangeEvent changeEvent) { public void onChange(ConfigChangeEvent changeEvent) {
Set<String> keys = changeEvent.changedKeys(); Set<String> keys = changeEvent.changedKeys();
if (CollectionUtils.isEmpty(keys)) { if (CollectionUtils.isEmpty(keys)) {
return; return;
} }
if (!SpringValueProcessor.enable()) { for (String key : keys) {
return; // 1. check whether the changed key is relevant
} Collection<SpringValue> targetValues = monitor.get(key);
for (String k : keys) { if (targetValues == null || targetValues.isEmpty()) {
ConfigChange configChange = changeEvent.getChange(k);
if (!Objects.equals(environment.getProperty(k), configChange.getNewValue())) {
continue; continue;
} }
Collection<SpringValue> targetValues = SpringValueProcessor.monitor().get(k);
if (targetValues == null || targetValues.isEmpty()) { // 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; continue;
} }
// 3. update the value
for (SpringValue val : targetValues) { for (SpringValue val : targetValues) {
val.updateVal(environment.getProperty(k)); updateSpringValue(val);
} }
} }
} }
}); };
List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources();
for (ConfigPropertySource configPropertySource : configPropertySources) {
configPropertySource.addChangeListener(changeListener);
}
}
private void updateSpringValue(SpringValue springValue) {
try {
Object value = resolvePropertyValue(springValue);
springValue.update(value);
logger.debug("Auto update apollo changed value successfully, new value: {}, {}", value,
springValue.toString());
} catch (Throwable ex) {
logger.error("Auto update apollo changed value failed, {}", springValue.toString(), ex);
}
}
/**
* Logic transplanted from DefaultListableBeanFactory
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency(org.springframework.beans.factory.config.DependencyDescriptor, java.lang.String, java.util.Set, org.springframework.beans.TypeConverter)
*/
private Object resolvePropertyValue(SpringValue springValue) {
String strVal = beanFactory.resolveEmbeddedValue(springValue.getPlaceholder());
Object value;
BeanDefinition bd = (beanFactory.containsBean(springValue.getBeanName()) ? beanFactory
.getMergedBeanDefinition(springValue.getBeanName()) : null);
value = evaluateBeanDefinitionString(strVal, bd);
if (springValue.isField()) {
// org.springframework.beans.TypeConverter#convertIfNecessary(java.lang.Object, java.lang.Class, java.lang.reflect.Field) is available from Spring 3.2.0+
if (typeConverterHasConvertIfNecessaryWithFieldParameter) {
value = this.typeConverter
.convertIfNecessary(value, springValue.getTargetType(), springValue.getField());
} else {
value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType());
}
} else {
value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType(),
springValue.getMethodParameter());
}
return value;
}
private Object evaluateBeanDefinitionString(String value, BeanDefinition beanDefinition) {
if (beanFactory.getBeanExpressionResolver() == null) {
return value;
}
Scope scope = (beanDefinition != null ? beanFactory.getRegisteredScope(beanDefinition.getScope()) : null);
return beanFactory.getBeanExpressionResolver().evaluate(value, new BeanExpressionContext(beanFactory, scope));
}
private boolean testTypeConverterHasConvertIfNecessaryWithFieldParameter() {
try {
TypeConverter.class.getMethod("convertIfNecessary", Object.class, Class.class, Field.class);
} catch (Throwable ex) {
return false;
}
return true;
}
@Override
public int getOrder() {
//make it as late as possible
return Ordered.LOWEST_PRECEDENCE;
} }
} }
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());
}
}
}
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());
}
}
}
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<String, ?> 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<String, ?> findParser(Class<?> targetType) {
Function<String, ?> 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;
}
}
...@@ -2,7 +2,9 @@ package com.ctrip.framework.apollo.spring.boot; ...@@ -2,7 +2,9 @@ package com.ctrip.framework.apollo.spring.boot;
import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService; 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.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.PropertySourcesConstants;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySource; import com.ctrip.framework.apollo.spring.config.ConfigPropertySource;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
...@@ -39,6 +41,9 @@ public class ApolloSpringApplicationRunListener implements SpringApplicationRunL ...@@ -39,6 +41,9 @@ public class ApolloSpringApplicationRunListener implements SpringApplicationRunL
private static final Logger logger = LoggerFactory.getLogger(ApolloSpringApplicationRunListener.class); private static final Logger logger = LoggerFactory.getLogger(ApolloSpringApplicationRunListener.class);
private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults(); private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();
private final ConfigPropertySourceFactory configPropertySourceFactory = ApolloInjector
.getInstance(ConfigPropertySourceFactory.class);
public ApolloSpringApplicationRunListener(SpringApplication application, String[] args) { public ApolloSpringApplicationRunListener(SpringApplication application, String[] args) {
//ignore //ignore
} }
...@@ -74,7 +79,7 @@ public class ApolloSpringApplicationRunListener implements SpringApplicationRunL ...@@ -74,7 +79,7 @@ public class ApolloSpringApplicationRunListener implements SpringApplicationRunL
for (String namespace : namespaceList) { for (String namespace : namespaceList) {
Config config = ConfigService.getConfig(namespace); Config config = ConfigService.getConfig(namespace);
composite.addPropertySource(new ConfigPropertySource(namespace, config)); composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
} }
environment.getPropertySources().addFirst(composite); environment.getPropertySources().addFirst(composite);
......
package com.ctrip.framework.apollo.spring.config; package com.ctrip.framework.apollo.spring.config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import java.util.Set; import java.util.Set;
import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.EnumerablePropertySource;
...@@ -14,7 +15,7 @@ import com.ctrip.framework.apollo.Config; ...@@ -14,7 +15,7 @@ import com.ctrip.framework.apollo.Config;
public class ConfigPropertySource extends EnumerablePropertySource<Config> { public class ConfigPropertySource extends EnumerablePropertySource<Config> {
private static final String[] EMPTY_ARRAY = new String[0]; private static final String[] EMPTY_ARRAY = new String[0];
public ConfigPropertySource(String name, Config source) { ConfigPropertySource(String name, Config source) {
super(name, source); super(name, source);
} }
...@@ -31,4 +32,8 @@ public class ConfigPropertySource extends EnumerablePropertySource<Config> { ...@@ -31,4 +32,8 @@ public class ConfigPropertySource extends EnumerablePropertySource<Config> {
public Object getProperty(String name) { public Object getProperty(String name) {
return this.source.getProperty(name, null); return this.source.getProperty(name, null);
} }
public void addChangeListener(ConfigChangeListener listener) {
this.source.addChangeListener(listener);
}
} }
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<ConfigPropertySource> configPropertySources = Lists.newLinkedList();
public ConfigPropertySource getConfigPropertySource(String name, Config source) {
ConfigPropertySource configPropertySource = new ConfigPropertySource(name, source);
configPropertySources.add(configPropertySource);
return configPropertySource;
}
public List<ConfigPropertySource> getAllConfigPropertySources() {
return Lists.newLinkedList(configPropertySources);
}
}
package com.ctrip.framework.apollo.spring.config; package com.ctrip.framework.apollo.spring.config;
import com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor; 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.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
...@@ -23,6 +24,19 @@ public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor ...@@ -23,6 +24,19 @@ public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor
PropertySourcesPlaceholderConfigurer.class); PropertySourcesPlaceholderConfigurer.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(), BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
ApolloAnnotationProcessor.class); 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);
} }
} }
package com.ctrip.framework.apollo.spring.config; package com.ctrip.framework.apollo.spring.config;
import com.google.common.collect.HashMultimap; import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Lists;
import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
...@@ -22,7 +20,6 @@ import org.springframework.core.env.Environment; ...@@ -22,7 +20,6 @@ import org.springframework.core.env.Environment;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
/** /**
* Apollo Property Sources processor for Spring Annotation Based Application. <br /> <br /> * Apollo Property Sources processor for Spring Annotation Based Application. <br /> <br />
...@@ -35,21 +32,16 @@ import java.util.List; ...@@ -35,21 +32,16 @@ import java.util.List;
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered { public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {
private static final List<Config> ALL_CONFIG = Lists.newLinkedList();
private static final Multimap<Integer, String> NAMESPACE_NAMES = LinkedHashMultimap.create(); private static final Multimap<Integer, String> NAMESPACE_NAMES = LinkedHashMultimap.create();
private final ConfigPropertySourceFactory configPropertySourceFactory = ApolloInjector
.getInstance(ConfigPropertySourceFactory.class);
private ConfigurableEnvironment environment; private ConfigurableEnvironment environment;
public static boolean addNamespaces(Collection<String> namespaces, int order) { public static boolean addNamespaces(Collection<String> namespaces, int order) {
return NAMESPACE_NAMES.putAll(order, namespaces); return NAMESPACE_NAMES.putAll(order, namespaces);
} }
public static void registerListener(ConfigChangeListener configChangeListener){
for(Config config:ALL_CONFIG){
config.addChangeListener(configChangeListener);
}
}
@Override @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
initializePropertySources(); initializePropertySources();
...@@ -70,8 +62,8 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir ...@@ -70,8 +62,8 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir
int order = iterator.next(); int order = iterator.next();
for (String namespace : NAMESPACE_NAMES.get(order)) { for (String namespace : NAMESPACE_NAMES.get(order)) {
Config config = ConfigService.getConfig(namespace); Config config = ConfigService.getConfig(namespace);
ALL_CONFIG.add(config);
composite.addPropertySource(new ConfigPropertySource(namespace, config)); composite.addPropertySource(configPropertySourceFactory.getConfigPropertySource(namespace, config));
} }
} }
......
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.
* <ul>
* <li>${some.key} => "some.key"</li>
* <li>${some.key:${some.other.key:100}} => "some.key", "some.other.key"</li>
* <li>${${some.key}} => "some.key"</li>
* <li>${${some.key:other.key}} => "some.key"</li>
* <li>${${some.key}:${another.key}} => "some.key", "another.key"</li>
* <li>#{new java.text.SimpleDateFormat('${some.key}').parse('${another.key}')} => "some.key", "another.key"</li>
* </ul>
*/
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<String> extractPlaceholderKeys(String propertyString) {
Set<String> placeholderKeys = Sets.newHashSet();
if (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString)) {
return placeholderKeys;
}
Stack<String> 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;
}
}
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());
}
}
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;
}
}
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.
*
* <pre>
* &lt;bean class=&quot;com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean&quot;&gt;
* &lt;property name=&quot;timeout&quot; value=&quot;${timeout:200}&quot;/&gt;
* &lt;property name=&quot;batch&quot; value=&quot;${batch:100}&quot;/&gt;
* &lt;/bean&gt;
* </pre>
*/
public class SpringValueDefinitionProcessor implements BeanDefinitionRegistryPostProcessor {
private static final Multimap<String, SpringValueDefinition> 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<String, SpringValueDefinition> 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<PropertyValue> propertyValues = mutablePropertyValues.getPropertyValueList();
for (PropertyValue propertyValue : propertyValues) {
Object value = propertyValue.getValue();
if (!(value instanceof TypedStringValue)) {
continue;
}
String placeholder = ((TypedStringValue) value).getValue();
Set<String> 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();
}
}
...@@ -33,6 +33,7 @@ public class ConfigUtil { ...@@ -33,6 +33,7 @@ public class ConfigUtil {
private long configCacheExpireTime = 1;//1 minute private long configCacheExpireTime = 1;//1 minute
private TimeUnit configCacheExpireTimeUnit = TimeUnit.MINUTES;//1 minute private TimeUnit configCacheExpireTimeUnit = TimeUnit.MINUTES;//1 minute
private long longPollingInitialDelayInMills = 2000;//2 seconds private long longPollingInitialDelayInMills = 2000;//2 seconds
private boolean autoUpdateInjectedSpringProperties = true;
public ConfigUtil() { public ConfigUtil() {
initRefreshInterval(); initRefreshInterval();
...@@ -42,6 +43,7 @@ public class ConfigUtil { ...@@ -42,6 +43,7 @@ public class ConfigUtil {
initQPS(); initQPS();
initMaxConfigCacheSize(); initMaxConfigCacheSize();
initLongPollingInitialDelayInMills(); initLongPollingInitialDelayInMills();
initAutoUpdateInjectedSpringProperties();
} }
/** /**
...@@ -263,4 +265,20 @@ public class ConfigUtil { ...@@ -263,4 +265,20 @@ public class ConfigUtil {
public long getLongPollingInitialDelayInMills() { public long getLongPollingInitialDelayInMills() {
return longPollingInitialDelayInMills; 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;
}
} }
package com.ctrip.framework.apollo; 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.integration.ConfigIntegrationTest;
import com.ctrip.framework.apollo.internals.DefaultConfigManagerTest; import com.ctrip.framework.apollo.internals.DefaultConfigManagerTest;
import com.ctrip.framework.apollo.internals.DefaultConfigTest; import com.ctrip.framework.apollo.internals.DefaultConfigTest;
...@@ -19,24 +13,35 @@ import com.ctrip.framework.apollo.internals.XmlConfigFileTest; ...@@ -19,24 +13,35 @@ import com.ctrip.framework.apollo.internals.XmlConfigFileTest;
import com.ctrip.framework.apollo.spi.DefaultConfigFactoryManagerTest; import com.ctrip.framework.apollo.spi.DefaultConfigFactoryManagerTest;
import com.ctrip.framework.apollo.spi.DefaultConfigFactoryTest; import com.ctrip.framework.apollo.spi.DefaultConfigFactoryTest;
import com.ctrip.framework.apollo.spi.DefaultConfigRegistryTest; 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.JavaConfigAnnotationTest;
import com.ctrip.framework.apollo.spring.JavaConfigPlaceholderAutoUpdateTest;
import com.ctrip.framework.apollo.spring.JavaConfigPlaceholderTest; import com.ctrip.framework.apollo.spring.JavaConfigPlaceholderTest;
import com.ctrip.framework.apollo.spring.XMLConfigAnnotationTest; 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.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.ConfigUtilTest;
import com.ctrip.framework.apollo.util.ExceptionUtilTest; import com.ctrip.framework.apollo.util.ExceptionUtilTest;
import com.ctrip.framework.apollo.util.parser.DateParserTest; import com.ctrip.framework.apollo.util.parser.DateParserTest;
import com.ctrip.framework.apollo.util.parser.DurationParserTest; 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) @RunWith(Suite.class)
@SuiteClasses({ @SuiteClasses({
ConfigServiceTest.class, DefaultConfigRegistryTest.class, DefaultConfigFactoryManagerTest.class, ConfigServiceTest.class, DefaultConfigRegistryTest.class, DefaultConfigFactoryManagerTest.class,
DefaultConfigManagerTest.class, DefaultConfigTest.class, LocalFileConfigRepositoryTest.class, DefaultConfigManagerTest.class, DefaultConfigTest.class, LocalFileConfigRepositoryTest.class,
RemoteConfigRepositoryTest.class, SimpleConfigTest.class, DefaultConfigFactoryTest.class, RemoteConfigRepositoryTest.class, SimpleConfigTest.class, DefaultConfigFactoryTest.class,
ConfigIntegrationTest.class, ExceptionUtilTest.class, XmlConfigFileTest.class, PropertiesConfigFileTest.class, ConfigIntegrationTest.class, ExceptionUtilTest.class, XmlConfigFileTest.class,
RemoteConfigLongPollServiceTest.class, DateParserTest.class, DurationParserTest.class, JsonConfigFileTest.class, PropertiesConfigFileTest.class, RemoteConfigLongPollServiceTest.class, DateParserTest.class,
XmlConfigPlaceholderTest.class, JavaConfigPlaceholderTest.class, XMLConfigAnnotationTest.class, DurationParserTest.class, JsonConfigFileTest.class, XmlConfigPlaceholderTest.class,
JavaConfigAnnotationTest.class, ConfigUtilTest.class, BootstrapConfigTest.class JavaConfigPlaceholderTest.class, XMLConfigAnnotationTest.class, JavaConfigAnnotationTest.class,
ConfigUtilTest.class, BootstrapConfigTest.class, JavaConfigPlaceholderAutoUpdateTest.class,
XmlConfigPlaceholderAutoUpdateTest.class, ConfigPropertySourceTest.class,
PlaceholderHelperTest.class
}) })
public class AllTests { public class AllTests {
......
...@@ -59,6 +59,5 @@ public class MockInjector implements Injector { ...@@ -59,6 +59,5 @@ public class MockInjector implements Injector {
public static void reset() { public static void reset() {
classMap.clear(); classMap.clear();
classTable.clear(); classTable.clear();
delegate = null;
} }
} }
package com.ctrip.framework.apollo.spring; 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.lang.reflect.Method;
import java.util.Calendar;
import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.Properties;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
...@@ -22,12 +33,15 @@ import com.google.common.collect.Maps; ...@@ -22,12 +33,15 @@ import com.google.common.collect.Maps;
public abstract class AbstractSpringIntegrationTest { public abstract class AbstractSpringIntegrationTest {
private static final Map<String, Config> CONFIG_REGISTRY = Maps.newHashMap(); private static final Map<String, Config> CONFIG_REGISTRY = Maps.newHashMap();
private static Method PROPERTY_SOURCES_PROCESSOR_CLEAR; private static Method PROPERTY_SOURCES_PROCESSOR_CLEAR;
private static Method SPRING_VALUE_DEFINITION_PROCESS_CLEAR;
private static Method CONFIG_SERVICE_RESET; private static Method CONFIG_SERVICE_RESET;
static { static {
try { try {
PROPERTY_SOURCES_PROCESSOR_CLEAR = PropertySourcesProcessor.class.getDeclaredMethod("reset"); PROPERTY_SOURCES_PROCESSOR_CLEAR = PropertySourcesProcessor.class.getDeclaredMethod("reset");
ReflectionUtils.makeAccessible(PROPERTY_SOURCES_PROCESSOR_CLEAR); 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"); CONFIG_SERVICE_RESET = ConfigService.class.getDeclaredMethod("reset");
ReflectionUtils.makeAccessible(CONFIG_SERVICE_RESET); ReflectionUtils.makeAccessible(CONFIG_SERVICE_RESET);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
...@@ -45,6 +59,53 @@ public abstract class AbstractSpringIntegrationTest { ...@@ -45,6 +59,53 @@ public abstract class AbstractSpringIntegrationTest {
doTearDown(); 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) { protected static void mockConfig(String namespace, Config config) {
CONFIG_REGISTRY.put(namespace, config); CONFIG_REGISTRY.put(namespace, config);
} }
...@@ -52,6 +113,8 @@ public abstract class AbstractSpringIntegrationTest { ...@@ -52,6 +113,8 @@ public abstract class AbstractSpringIntegrationTest {
protected static void doSetUp() { protected static void doSetUp() {
//as PropertySourcesProcessor has some static states, so we must manually clear its state //as PropertySourcesProcessor has some static states, so we must manually clear its state
ReflectionUtils.invokeMethod(PROPERTY_SOURCES_PROCESSOR_CLEAR, null); 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 //as ConfigService is singleton, so we must manually clear its container
ReflectionUtils.invokeMethod(CONFIG_SERVICE_RESET, null); ReflectionUtils.invokeMethod(CONFIG_SERVICE_RESET, null);
MockInjector.reset(); MockInjector.reset();
...@@ -62,7 +125,7 @@ public abstract class AbstractSpringIntegrationTest { ...@@ -62,7 +125,7 @@ public abstract class AbstractSpringIntegrationTest {
CONFIG_REGISTRY.clear(); CONFIG_REGISTRY.clear();
} }
public static class MockConfigManager implements ConfigManager { private static class MockConfigManager implements ConfigManager {
@Override @Override
public Config getConfig(String namespace) { public Config getConfig(String namespace) {
...@@ -74,4 +137,18 @@ public abstract class AbstractSpringIntegrationTest { ...@@ -74,4 +137,18 @@ public abstract class AbstractSpringIntegrationTest {
return null; return null;
} }
} }
protected static class MockConfigUtil extends ConfigUtil {
private boolean isAutoUpdateInjectedSpringProperties;
public void setAutoUpdateInjectedSpringProperties(boolean autoUpdateInjectedSpringProperties) {
isAutoUpdateInjectedSpringProperties = autoUpdateInjectedSpringProperties;
}
@Override
public boolean isAutoUpdateInjectedSpringPropertiesEnabled() {
return isAutoUpdateInjectedSpringProperties;
}
}
} }
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;
}
}
}
...@@ -7,15 +7,19 @@ import static org.mockito.Mockito.mock; ...@@ -7,15 +7,19 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; 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.Configuration;
import org.springframework.stereotype.Component;
import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; 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) * @author Jason Song(song_s@ctrip.com)
...@@ -153,6 +157,25 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -153,6 +157,25 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
assertEquals(someBatch, bean.getBatch()); 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 @Test
public void testNestedProperty() throws Exception { public void testNestedProperty() throws Exception {
String a = "a"; String a = "a";
...@@ -323,6 +346,14 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -323,6 +346,14 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
} }
} }
@Configuration
@ComponentScan(
includeFilters = {@Filter(type = FilterType.ANNOTATION, value = {Component.class})},
excludeFilters = {@Filter(type = FilterType.ANNOTATION, value = {Configuration.class})})
@EnableApolloConfig
static class AppConfig7 {
}
@Configuration @Configuration
@EnableApolloConfig @EnableApolloConfig
static class NestedPropertyConfig1 { static class NestedPropertyConfig1 {
...@@ -332,8 +363,6 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -332,8 +363,6 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
} }
} }
@Component
static class TestJavaConfigBean { static class TestJavaConfigBean {
@Value("${timeout:100}") @Value("${timeout:100}")
private int timeout; private int timeout;
...@@ -374,6 +403,27 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -374,6 +403,27 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
} }
} }
@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 { static class TestNestedPropertyBean {
@Value("${${a}.${b}:${c:100}}") @Value("${${a}.${b}:${c:100}}")
......
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;
}
}
}
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);
}
}
}
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<String> 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.<String>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<ConfigChangeListener> 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)));
}
}
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));
}
}
...@@ -3,7 +3,6 @@ package com.ctrip.framework.apollo.util; ...@@ -3,7 +3,6 @@ package com.ctrip.framework.apollo.util;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import org.junit.After; import org.junit.After;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.Assert.*;
...@@ -22,6 +21,7 @@ public class ConfigUtilTest { ...@@ -22,6 +21,7 @@ public class ConfigUtilTest {
System.clearProperty("apollo.longPollQPS"); System.clearProperty("apollo.longPollQPS");
System.clearProperty("apollo.configCacheSize"); System.clearProperty("apollo.configCacheSize");
System.clearProperty("apollo.longPollingInitialDelayInMills"); System.clearProperty("apollo.longPollingInitialDelayInMills");
System.clearProperty("apollo.autoUpdateInjectedSpringProperties");
} }
@Test @Test
...@@ -173,4 +173,16 @@ public class ConfigUtilTest { ...@@ -173,4 +173,16 @@ public class ConfigUtilTest {
assertTrue(configUtil.getLongPollingInitialDelayInMills() > 0); assertTrue(configUtil.getLongPollingInitialDelayInMills() > 0);
} }
@Test
public void testCustomizeAutoUpdateInjectedSpringProperties() throws Exception {
boolean someAutoUpdateInjectedSpringProperties = false;
System.setProperty("apollo.autoUpdateInjectedSpringProperties",
String.valueOf(someAutoUpdateInjectedSpringProperties));
ConfigUtil configUtil = new ConfigUtil();
assertEquals(someAutoUpdateInjectedSpringProperties,
configUtil.isAutoUpdateInjectedSpringPropertiesEnabled());
}
} }
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:apollo="http://www.ctrip.com/schema/apollo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd">
<apollo:config />
<bean class="com.ctrip.framework.apollo.spring.XmlConfigPlaceholderAutoUpdateTest.TestAllKindsOfDataTypesBean">
<property name="intProperty" value="${intProperty}"/>
<property name="intArrayProperty" value="${intArrayProperty}"/>
<property name="longProperty" value="${longProperty}"/>
<property name="shortProperty" value="${shortProperty}"/>
<property name="floatProperty" value="${floatProperty}"/>
<property name="doubleProperty" value="${doubleProperty}"/>
<property name="byteProperty" value="${byteProperty}"/>
<property name="booleanProperty" value="${booleanProperty}"/>
<property name="stringProperty" value="${stringProperty}"/>
<property name="dateProperty" value="#{new java.text.SimpleDateFormat('${dateFormat}').parse('${dateProperty}')}"/>
</bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:apollo="http://www.ctrip.com/schema/apollo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd">
<apollo:config />
<bean class="com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest.TestXmlBean">
<property name="timeout" value="${timeout}"/>
<property name="batch" value="${batch}"/>
</bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:apollo="http://www.ctrip.com/schema/apollo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd">
<apollo:config />
<bean class="com.ctrip.framework.apollo.spring.XmlConfigPlaceholderAutoUpdateTest.TestXmlBeanWithConstructorArgs">
<constructor-arg index="0" value="${timeout}"/>
<constructor-arg index="1" value="${batch}"/>
</bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:apollo="http://www.ctrip.com/schema/apollo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
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">
<context:annotation-config />
<apollo:config/>
<bean class="com.ctrip.framework.apollo.spring.XmlConfigPlaceholderAutoUpdateTest.TestXmlBeanWithInjectedValue">
<property name="batch" value="${batch}"/>
</bean>
</beans>
...@@ -19,7 +19,6 @@ public class DefaultApplicationProvider implements ApplicationProvider { ...@@ -19,7 +19,6 @@ public class DefaultApplicationProvider implements ApplicationProvider {
private Properties m_appProperties = new Properties(); private Properties m_appProperties = new Properties();
private String m_appId; private String m_appId;
private boolean m_enableAutoUpdate;
@Override @Override
public void initialize() { public void initialize() {
...@@ -50,20 +49,11 @@ public class DefaultApplicationProvider implements ApplicationProvider { ...@@ -50,20 +49,11 @@ public class DefaultApplicationProvider implements ApplicationProvider {
} }
initAppId(); initAppId();
initEnableAutoUpdate();
} catch (Throwable ex) { } catch (Throwable ex) {
logger.error("Initialize DefaultApplicationProvider failed.", 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 @Override
public String getAppId() { public String getAppId() {
return m_appId; return m_appId;
...@@ -111,29 +101,8 @@ public class DefaultApplicationProvider implements ApplicationProvider { ...@@ -111,29 +101,8 @@ public class DefaultApplicationProvider implements ApplicationProvider {
logger.warn("app.id is not available from System Property and {}. It is set to null", APP_PROPERTIES_CLASSPATH); 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 @Override
public String toString() { public String toString() {
return "appId [" + getAppId() + "],enableAutoUpdate["+isAutoUpdateEnable()+"] properties: " + m_appProperties + " (DefaultApplicationProvider)"; return "appId [" + getAppId() + "] properties: " + m_appProperties + " (DefaultApplicationProvider)";
} }
} }
...@@ -58,11 +58,6 @@ public class NullProvider implements ApplicationProvider, NetworkProvider, Serve ...@@ -58,11 +58,6 @@ public class NullProvider implements ApplicationProvider, NetworkProvider, Serve
} }
@Override
public boolean isAutoUpdateEnable() {
return false;
}
@Override @Override
public String getHostAddress() { public String getHostAddress() {
return null; return null;
......
...@@ -20,8 +20,4 @@ public interface ApplicationProvider extends Provider { ...@@ -20,8 +20,4 @@ public interface ApplicationProvider extends Provider {
* Initialize the application provider with the specified input stream * Initialize the application provider with the specified input stream
*/ */
public void initialize(InputStream in); public void initialize(InputStream in);
/**
* @return whether update the field or method which has '@Value' automatically
*/
public boolean isAutoUpdateEnable();
} }
...@@ -61,34 +61,4 @@ public class DefaultApplicationProviderTest { ...@@ -61,34 +61,4 @@ public class DefaultApplicationProviderTest {
assertEquals(null, defaultApplicationProvider.getAppId()); assertEquals(null, defaultApplicationProvider.getAppId());
assertFalse(defaultApplicationProvider.isAppIdSet()); 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());
}
} }
appid=110402 appid=110402
app.autoupdate.enabled=false
\ No newline at end of file
...@@ -46,7 +46,7 @@ public class ApolloConfigDemo { ...@@ -46,7 +46,7 @@ public class ApolloConfigDemo {
}; };
config = ConfigService.getAppConfig(); config = ConfigService.getAppConfig();
config.addChangeListener(changeListener); config.addChangeListener(changeListener);
publicConfig = ConfigService.getConfig("FX.apollo"); publicConfig = ConfigService.getConfig("TEST1.apollo");
publicConfig.addChangeListener(changeListener); publicConfig.addChangeListener(changeListener);
applicationConfigFile = ConfigService.getConfigFile("application", ConfigFileFormat.Properties); applicationConfigFile = ConfigService.getConfigFile("application", ConfigFileFormat.Properties);
xmlConfigFile = ConfigService.getConfigFile("datasources", ConfigFileFormat.XML); xmlConfigFile = ConfigService.getConfigFile("datasources", ConfigFileFormat.XML);
......
...@@ -3,34 +3,29 @@ package com.ctrip.framework.apollo.demo.spring.common.bean; ...@@ -3,34 +3,29 @@ package com.ctrip.framework.apollo.demo.spring.common.bean;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
@RefreshScope
@Component("annotatedBean") @Component("annotatedBean")
public class AnnotatedBean { public class AnnotatedBean {
private static final Logger logger = LoggerFactory.getLogger(AnnotatedBean.class); private static final Logger logger = LoggerFactory.getLogger(AnnotatedBean.class);
@Value("${timeout:200}")
private int timeout; private int timeout;
private int batch; private int batch;
@PostConstruct
void initialize() {
logger.info("timeout is initialized as {}", timeout);
logger.info("batch is initialized as {}", batch);
}
@Value("${batch:100}") @Value("${batch:100}")
public void setBatch(int batch) { public void setBatch(int batch) {
logger.info("updating batch, old value: {}, new value: {}", this.batch, batch);
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 @Override
public String toString() { public String toString() {
......
...@@ -8,6 +8,6 @@ import org.springframework.context.annotation.Configuration; ...@@ -8,6 +8,6 @@ import org.springframework.context.annotation.Configuration;
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
@Configuration @Configuration
@EnableApolloConfig(value = "FX.apollo", order = 11) @EnableApolloConfig(value = "TEST1.apollo", order = 11)
public class AnotherAppConfig { public class AnotherAppConfig {
} }
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());
}
}
}
package com.ctrip.framework.apollo.demo.spring.javaConfigDemo; 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 org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.Scanner; import java.util.Scanner;
...@@ -8,14 +15,19 @@ import java.util.Scanner; ...@@ -8,14 +15,19 @@ import java.util.Scanner;
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class AnnotationApplication { public class AnnotationApplication {
public static void main(String[] args) { public static void main(String[] args) throws IOException {
new AnnotationConfigApplicationContext("com.ctrip.framework.apollo.demo.spring.common", ApplicationContext context = new AnnotationConfigApplicationContext("com.ctrip.framework.apollo.demo.spring.common");
"com.ctrip.framework.apollo.demo.spring.javaConfigDemo"); AnnotatedBean annotatedBean = context.getBean(AnnotatedBean.class);
onKeyExit();
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(annotatedBean.toString());
System.out.println("Press Enter to exit..."); }
new Scanner(System.in).nextLine();
} }
} }
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 {
}
package com.ctrip.framework.apollo.demo.spring.springBootDemo; 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.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder; 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) * @author Jason Song(song_s@ctrip.com)
...@@ -13,13 +21,21 @@ import java.util.Scanner; ...@@ -13,13 +21,21 @@ import java.util.Scanner;
}) })
public class SpringBootSampleApplication { public class SpringBootSampleApplication {
public static void main(String[] args) { public static void main(String[] args) throws IOException {
new SpringApplicationBuilder(SpringBootSampleApplication.class).run(args); ApplicationContext context = new SpringApplicationBuilder(SpringBootSampleApplication.class).run(args);
onKeyExit(); 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(annotatedBean.toString());
System.out.println("Press Enter to exit..."); System.out.println(redisConfig.toString());
new Scanner(System.in).nextLine(); }
} }
} }
package com.ctrip.framework.apollo.demo.spring.springBootDemo.refresh; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -12,6 +7,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; ...@@ -12,6 +7,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.context.scope.refresh.RefreshScope; import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.stereotype.Component; 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) * @author Jason Song(song_s@ctrip.com)
*/ */
...@@ -20,9 +19,6 @@ import org.springframework.stereotype.Component; ...@@ -20,9 +19,6 @@ import org.springframework.stereotype.Component;
public class SpringBootApolloRefreshConfig { public class SpringBootApolloRefreshConfig {
private static final Logger logger = LoggerFactory.getLogger(SpringBootApolloRefreshConfig.class); private static final Logger logger = LoggerFactory.getLogger(SpringBootApolloRefreshConfig.class);
@Autowired
private ApolloRefreshConfig apolloRefreshConfig;
@Autowired @Autowired
private SampleRedisConfig sampleRedisConfig; private SampleRedisConfig sampleRedisConfig;
...@@ -31,6 +27,17 @@ public class SpringBootApolloRefreshConfig { ...@@ -31,6 +27,17 @@ public class SpringBootApolloRefreshConfig {
@ApolloConfigChangeListener @ApolloConfigChangeListener
public void onChange(ConfigChangeEvent changeEvent) { 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()); logger.info("before refresh {}", sampleRedisConfig.toString());
refreshScope.refresh("sampleRedisConfig"); refreshScope.refresh("sampleRedisConfig");
logger.info("after refresh {}", sampleRedisConfig.toString()); logger.info("after refresh {}", sampleRedisConfig.toString());
......
package com.ctrip.framework.apollo.demo.spring.xmlConfigDemo; 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 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) * @author Jason Song(song_s@ctrip.com)
*/ */
public class XmlApplication { public class XmlApplication {
public static void main(String[] args) { public static void main(String[] args) throws IOException {
new ClassPathXmlApplicationContext("spring.xml"); ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
onKeyExit(); 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(xmlBean.toString());
System.out.println("Press Enter to exit..."); }
new Scanner(System.in).nextLine();
} }
} }
...@@ -29,4 +29,9 @@ public class XmlBean { ...@@ -29,4 +29,9 @@ public class XmlBean {
public int getBatch() { public int getBatch() {
return batch; return batch;
} }
@Override
public String toString() {
return String.format("[XmlBean] timeout: %d, batch: %d", timeout, batch);
}
} }
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()));
}
}
}
apollo: apollo:
bootstrap: bootstrap:
enabled: true enabled: true
# will inject 'application' and 'FX.apollo' namespaces in bootstrap phase # will inject 'application' and 'TEST1.apollo' namespaces in bootstrap phase
namespaces: application,FX.apollo namespaces: application,TEST1.apollo
...@@ -7,18 +7,12 @@ ...@@ -7,18 +7,12 @@
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd 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"> http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd">
<apollo:config order="10"/> <apollo:config order="10"/>
<apollo:config namespaces="FX.apollo" order="11"/> <apollo:config namespaces="TEST1.apollo" order="11"/>
<bean class="com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean"> <bean class="com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean">
<property name="timeout" value="${timeout:200}"/> <property name="timeout" value="${timeout:200}"/>
<property name="batch" value="${batch:100}"/> <property name="batch" value="${batch:100}"/>
</bean> </bean>
<bean class="com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.refresh.ManualRefreshUtil"/> <context:annotation-config />
<!-- to support RefreshScope -->
<bean class="org.springframework.cloud.autoconfigure.RefreshAutoConfiguration"/>
<context:component-scan
base-package="com.ctrip.framework.apollo.demo.spring.common.bean,com.ctrip.framework.apollo.demo.spring.common.refresh"/>
</beans> </beans>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment