Commit f2d85126 by zhangzheng

1 将SpringValueProcessor中的关于自动更新的逻辑抽离到了AutoUpdateConfigChangeListener中

2 抽离了一个公共父类ApolloProcessor,用来处理反射相关的逻辑。 ApolloJsonValueProcessor,ApolloAnnotationProcessor,SpringValueProcessor都继承它 3 在PropertySourceProcessor中注册自动更新的监听器AutoUpdateConfigChangeListener 4 SpringValue增加isJson字段,表示这个属性是否是Json类型的 5 增加了单元测试
parent 1e232584
package com.ctrip.framework.apollo.spring.annotation; package com.ctrip.framework.apollo.spring.annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener; import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService; import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.model.ConfigChangeEvent; import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
/** /**
* Apollo Annotation Processor for Spring Application * Apollo Annotation Processor for Spring Application
* *
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class ApolloAnnotationProcessor implements BeanPostProcessor, PriorityOrdered { public class ApolloAnnotationProcessor extends ApolloProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Class clazz = bean.getClass();
processFields(bean, clazz.getDeclaredFields());
processMethods(bean, clazz.getDeclaredMethods());
return bean;
}
@Override @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { protected void processField(Object bean, String beanName, Field field) {
return bean; ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class);
} if (annotation == null) {
return;
}
private void processFields(Object bean, Field[] declaredFields) { Preconditions.checkArgument(Config.class.isAssignableFrom(field.getType()),
for (Field field : declaredFields) { "Invalid type: %s for field: %s, should be Config", field.getType(), field);
ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class);
if (annotation == null) {
continue;
}
Preconditions.checkArgument(Config.class.isAssignableFrom(field.getType()), String namespace = annotation.value();
"Invalid type: %s for field: %s, should be Config", field.getType(), field); Config config = ConfigService.getConfig(namespace);
String namespace = annotation.value(); ReflectionUtils.makeAccessible(field);
Config config = ConfigService.getConfig(namespace); ReflectionUtils.setField(field, bean, config);
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, bean, config);
}
} }
private void processMethods(final Object bean, Method[] declaredMethods) { @Override
for (final Method method : declaredMethods) { protected void processMethod(final Object bean, String beanName, final Method method) {
ApolloConfigChangeListener annotation = AnnotationUtils.findAnnotation(method, ApolloConfigChangeListener.class);
if (annotation == null) {
continue;
}
Class<?>[] parameterTypes = method.getParameterTypes(); ApolloConfigChangeListener annotation = AnnotationUtils
Preconditions.checkArgument(parameterTypes.length == 1, .findAnnotation(method, ApolloConfigChangeListener.class);
"Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length, method); if (annotation == null) {
Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]), return;
"Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0], method); }
Class<?>[] parameterTypes = method.getParameterTypes();
Preconditions.checkArgument(parameterTypes.length == 1,
"Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length,
method);
Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]),
"Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0],
method);
ReflectionUtils.makeAccessible(method);
String[] namespaces = annotation.value();
for (String namespace : namespaces) {
Config config = ConfigService.getConfig(namespace);
ReflectionUtils.makeAccessible(method); config.addChangeListener(new ConfigChangeListener() {
String[] namespaces = annotation.value(); @Override
for (String namespace : namespaces) { public void onChange(ConfigChangeEvent changeEvent) {
Config config = ConfigService.getConfig(namespace); ReflectionUtils.invokeMethod(method, bean, changeEvent);
}
});
config.addChangeListener(new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
ReflectionUtils.invokeMethod(method, bean, changeEvent);
}
});
}
} }
} }
@Override
public int getOrder() {
//make it as late as possible
return Ordered.LOWEST_PRECEDENCE;
}
} }
...@@ -34,5 +34,9 @@ public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar { ...@@ -34,5 +34,9 @@ public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar {
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class); BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(), SpringValueDefinitionProcessor.class); BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(), SpringValueDefinitionProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJSONValueProcessor.class.getName(),
ApolloJSONValueProcessor.class);
} }
} }
package com.ctrip.framework.apollo.spring.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Create by zhangzheng on 2018/2/6
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
@Documented
public @interface ApolloJSONValue {
String value();
}
package com.ctrip.framework.apollo.spring.annotation;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.spring.config.AutoUpdateConfigChangeListener;
import com.ctrip.framework.apollo.spring.property.PlaceholderHelper;
import com.ctrip.framework.apollo.spring.property.SpringValue;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.ctrip.framework.foundation.internals.Utils;
import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
/**
* Create by zhangzheng on 2018/2/6
*/
public class ApolloJSONValueProcessor extends ApolloProcessor implements EnvironmentAware,BeanFactoryAware {
private Logger logger = LoggerFactory.getLogger(ApolloJSONValueProcessor.class);
private static Gson gson = new Gson();
private Environment environment;
private final ConfigUtil configUtil;
private final PlaceholderHelper placeholderHelper;
private ConfigurableBeanFactory beanFactory;
public ApolloJSONValueProcessor() {
configUtil = ApolloInjector.getInstance(ConfigUtil.class);
placeholderHelper = ApolloInjector.getInstance(PlaceholderHelper.class);
}
@Override
protected void processField(Object bean,String beanName, Field field) {
ApolloJSONValue apolloJSONValue = AnnotationUtils.getAnnotation(field, ApolloJSONValue.class);
if (apolloJSONValue == null) {
return;
}
try {
String placeHolder = apolloJSONValue.value();
String propertyValue = beanFactory.resolveEmbeddedValue(placeHolder);
if(!Utils.isBlank(propertyValue)){
boolean accessible = field.isAccessible();
field.setAccessible(true);
field.set(bean, gson.fromJson(propertyValue, field.getGenericType()));
field.setAccessible(accessible);
}
if(configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()){
Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeHolder);
for(String key:keys){
SpringValue springValue = new SpringValue(key, placeHolder, bean, beanName, field, true);
AutoUpdateConfigChangeListener.monitor.put(key, springValue);
logger.debug("Monitoring ", springValue);
}
}
} catch (Exception e) {
logger.error("set json value exception", e);
}
}
@Override
protected void processMethod(Object bean, String beanName, Method method) {
ApolloJSONValue apolloJSONValue = AnnotationUtils.getAnnotation(method, ApolloJSONValue.class);
if (apolloJSONValue == null) {
return;
}
try {
String placeHolder = apolloJSONValue.value();
String propertyValue = beanFactory.resolveEmbeddedValue(placeHolder);
if(!Utils.isBlank(propertyValue)){
boolean accessible = method.isAccessible();
method.setAccessible(true);
Type[] types = method.getGenericParameterTypes();
Preconditions.checkArgument(types.length == 1, "Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters",
bean.getClass().getName(), method.getName(), method.getParameterTypes().length);
method.invoke(bean, gson.fromJson(propertyValue, types[0]));
method.setAccessible(accessible);
}
if(configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()){
Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeHolder);
for(String key:keys){
SpringValue springValue = new SpringValue(key, apolloJSONValue.value(), bean, beanName, method, true);
AutoUpdateConfigChangeListener.monitor.put(key, springValue);
logger.debug("Monitoring ", springValue);
}
}
} catch (Exception e) {
logger.error("set json value exception", e);
}
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
}
}
package com.ctrip.framework.apollo.spring.annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.util.ReflectionUtils;
/**
* Create by zhangzheng on 2018/2/6
*/
public abstract class ApolloProcessor implements BeanPostProcessor, PriorityOrdered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
Class clazz = bean.getClass();
for (Field field : findAllField(clazz)) {
processField(bean, beanName, field);
}
for (Method method : findAllMethod(clazz)) {
processMethod(bean, beanName, method);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* subclass should implement this method to process field
* @param bean
* @param field
*/
protected abstract void processField(Object bean, String beanName, Field field);
/**
* subclass should implement this method to process method
* @param bean
* @param method
*/
protected abstract void processMethod(Object bean, String beanName, Method method);
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
private List<Field> findAllField(Class clazz) {
final List<Field> res = new LinkedList<>();
ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
res.add(field);
}
});
return res;
}
private List<Method> findAllMethod(Class clazz) {
final List<Method> res = new LinkedList<>();
ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
res.add(method);
}
});
return res;
}
}
package com.ctrip.framework.apollo.spring.annotation; package com.ctrip.framework.apollo.spring.annotation;
import com.ctrip.framework.apollo.spring.config.AutoUpdateConfigChangeListener;
import java.beans.PropertyDescriptor; import java.beans.PropertyDescriptor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
...@@ -52,46 +53,26 @@ import com.google.common.collect.Multimap; ...@@ -52,46 +53,26 @@ import com.google.common.collect.Multimap;
* @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 extends ApolloProcessor implements BeanFactoryPostProcessor {
BeanFactoryAware, BeanFactoryPostProcessor {
private static final Logger logger = LoggerFactory.getLogger(SpringValueProcessor.class); private static final Logger logger = LoggerFactory.getLogger(SpringValueProcessor.class);
private final Multimap<String, SpringValue> monitor = LinkedListMultimap.create();
private final ConfigUtil configUtil; private final ConfigUtil configUtil;
private final PlaceholderHelper placeholderHelper; private final PlaceholderHelper placeholderHelper;
private final ConfigPropertySourceFactory configPropertySourceFactory;
private final boolean typeConverterHasConvertIfNecessaryWithFieldParameter;
private Environment environment;
private ConfigurableBeanFactory beanFactory;
private TypeConverter typeConverter;
private static Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions = LinkedListMultimap.create(); private static Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions = LinkedListMultimap.create();
public SpringValueProcessor() { public SpringValueProcessor() {
configUtil = ApolloInjector.getInstance(ConfigUtil.class); configUtil = ApolloInjector.getInstance(ConfigUtil.class);
placeholderHelper = ApolloInjector.getInstance(PlaceholderHelper.class); placeholderHelper = ApolloInjector.getInstance(PlaceholderHelper.class);
configPropertySourceFactory = ApolloInjector.getInstance(ConfigPropertySourceFactory.class);
typeConverterHasConvertIfNecessaryWithFieldParameter = testTypeConverterHasConvertIfNecessaryWithFieldParameter();
} }
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
this.typeConverter = this.beanFactory.getTypeConverter();
}
@Override
public void setEnvironment(Environment env) {
this.environment = env;
}
@Override @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) { if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
beanName2SpringValueDefinitions = SpringValueDefinitionProcessor.getBeanName2SpringValueDefinitions(); beanName2SpringValueDefinitions = SpringValueDefinitionProcessor.getBeanName2SpringValueDefinitions();
registerConfigChangeListener();
} }
} }
...@@ -99,71 +80,65 @@ public class SpringValueProcessor implements BeanPostProcessor, PriorityOrdered, ...@@ -99,71 +80,65 @@ public class SpringValueProcessor implements BeanPostProcessor, PriorityOrdered,
public Object postProcessBeforeInitialization(Object bean, String beanName) public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException { throws BeansException {
if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) { if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
Class clazz = bean.getClass(); super.postProcessBeforeInitialization(bean, beanName);
processFields(bean, beanName, findAllField(clazz));
processMethods(bean, beanName, findAllMethod(clazz));
processBeanPropertyValues(bean, beanName); processBeanPropertyValues(bean, beanName);
} }
return bean; return bean;
} }
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
private void processFields(Object bean, String beanName, List<Field> declaredFields) { @Override
for (Field field : declaredFields) { protected void processField(Object bean, String beanName, Field field) {
// register @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; return;
} }
Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value()); Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
if (keys.isEmpty()) { if (keys.isEmpty()) {
continue; return;
} }
for (String key : keys) { for (String key : keys) {
SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field); SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
monitor.put(key, springValue); AutoUpdateConfigChangeListener.monitor.put(key, springValue);
logger.debug("Monitoring {}", springValue); logger.debug("Monitoring {}", springValue);
}
} }
} }
private void processMethods(final Object bean, String beanName, List<Method> declaredMethods) { @Override
for (final Method method : declaredMethods) { protected void processMethod(Object bean, String beanName, Method method) {
//register @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; return;
} }
//skip Configuration bean methods //skip Configuration bean methods
if (method.getAnnotation(Bean.class) != null) { if (method.getAnnotation(Bean.class) != null) {
continue; return;
} }
if (method.getParameterTypes().length != 1) { if (method.getParameterTypes().length != 1) {
logger.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters", logger.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters",
bean.getClass().getName(), method.getName(), method.getParameterTypes().length); bean.getClass().getName(), method.getName(), method.getParameterTypes().length);
continue; return;
} }
Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value()); Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());
if (keys.isEmpty()) { if (keys.isEmpty()) {
continue; return;
} }
for (String key : keys) { for (String key : keys) {
SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method); SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method, false);
monitor.put(key, springValue); AutoUpdateConfigChangeListener.monitor.put(key, springValue);
logger.debug("Monitoring {}", springValue); logger.debug("Monitoring {}", springValue);
}
} }
} }
private void processBeanPropertyValues(Object bean, String beanName) { private void processBeanPropertyValues(Object bean, String beanName) {
Collection<SpringValueDefinition> propertySpringValues = beanName2SpringValueDefinitions Collection<SpringValueDefinition> propertySpringValues = beanName2SpringValueDefinitions
.get(beanName); .get(beanName);
...@@ -180,8 +155,8 @@ public class SpringValueProcessor implements BeanPostProcessor, PriorityOrdered, ...@@ -180,8 +155,8 @@ public class SpringValueProcessor implements BeanPostProcessor, PriorityOrdered,
continue; continue;
} }
SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(), SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(),
bean, beanName, method); bean, beanName, method, false);
monitor.put(definition.getKey(), springValue); AutoUpdateConfigChangeListener.monitor.put(definition.getKey(), springValue);
logger.debug("Monitoring {}", springValue); logger.debug("Monitoring {}", springValue);
} catch (Throwable ex) { } catch (Throwable ex) {
logger.error("Failed to enable auto update feature for {}.{}", bean.getClass(), logger.error("Failed to enable auto update feature for {}.{}", bean.getClass(),
...@@ -193,125 +168,4 @@ public class SpringValueProcessor implements BeanPostProcessor, PriorityOrdered, ...@@ -193,125 +168,4 @@ public class SpringValueProcessor implements BeanPostProcessor, PriorityOrdered,
beanName2SpringValueDefinitions.removeAll(beanName); beanName2SpringValueDefinitions.removeAll(beanName);
} }
private List<Field> findAllField(Class clazz) {
final List<Field> res = new LinkedList<>();
ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
res.add(field);
}
});
return res;
}
private List<Method> findAllMethod(Class clazz) {
final List<Method> res = new LinkedList<>();
ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
res.add(method);
}
});
return res;
}
private void registerConfigChangeListener() {
ConfigChangeListener changeListener = new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
Set<String> keys = changeEvent.changedKeys();
if (CollectionUtils.isEmpty(keys)) {
return;
}
for (String key : keys) {
// 1. check whether the changed key is relevant
Collection<SpringValue> targetValues = monitor.get(key);
if (targetValues == null || targetValues.isEmpty()) {
continue;
}
// 2. check whether the value is really changed or not (since spring property sources have hierarchies)
ConfigChange configChange = changeEvent.getChange(key);
if (!Objects.equals(environment.getProperty(key), configChange.getNewValue())) {
continue;
}
// 3. update the value
for (SpringValue val : targetValues) {
updateSpringValue(val);
}
}
}
};
List<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.config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor;
import com.ctrip.framework.apollo.spring.property.SpringValue;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import com.google.gson.Gson;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Objects;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;
/**
* Create by zhangzheng on 2018/3/6
*/
public class AutoUpdateConfigChangeListener implements ConfigChangeListener{
public static final Multimap<String, SpringValue> monitor = LinkedListMultimap.create();
private Environment environment;
private ConfigurableBeanFactory beanFactory;
private TypeConverter typeConverter;
private static final Logger logger = LoggerFactory.getLogger(SpringValueProcessor.class);
private final boolean typeConverterHasConvertIfNecessaryWithFieldParameter;
private Gson gson = new Gson();
public AutoUpdateConfigChangeListener(Environment environment, BeanFactory beanFactory){
typeConverterHasConvertIfNecessaryWithFieldParameter = testTypeConverterHasConvertIfNecessaryWithFieldParameter();
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
this.typeConverter = this.beanFactory.getTypeConverter();
this.environment = environment;
}
@Override
public void onChange(ConfigChangeEvent changeEvent) {
Set<String> keys = changeEvent.changedKeys();
if (CollectionUtils.isEmpty(keys)) {
return;
}
for (String key : keys) {
// 1. check whether the changed key is relevant
Collection<SpringValue> targetValues = monitor.get(key);
if (targetValues == null || targetValues.isEmpty()) {
continue;
}
// 2. check whether the value is really changed or not (since spring property sources have hierarchies)
ConfigChange configChange = changeEvent.getChange(key);
if (!Objects.equals(environment.getProperty(key), configChange.getNewValue())) {
continue;
}
// 3. update the value
for (SpringValue val : targetValues) {
if(val.isJson()){
updateJsonValue(val);
}else{
updateSpringValue(val);
}
}
}
}
private void updateJsonValue(SpringValue springValue){
try {
Type type = springValue.getGenericType();
String propertyValue = beanFactory.resolveEmbeddedValue(springValue.getPlaceholder());
Object val = gson.fromJson(propertyValue, type);
springValue.update(val);
logger.debug("Auto update apollo changed value successfully, new value: {}, {}", val,
springValue.toString());
} catch (Throwable ex) {
logger.error("Auto update apollo changed value failed, {}", springValue.toString(), ex);
}
}
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;
}
}
...@@ -2,6 +2,7 @@ package com.ctrip.framework.apollo.spring.config; ...@@ -2,6 +2,7 @@ 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 com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
import com.ctrip.framework.apollo.spring.annotation.ApolloJSONValueProcessor;
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;
...@@ -25,6 +26,8 @@ public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor ...@@ -25,6 +26,8 @@ public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor
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, ApolloJSONValueProcessor.class.getName(),
ApolloJSONValueProcessor.class);
processSpringValueDefinition(registry); processSpringValueDefinition(registry);
} }
...@@ -38,5 +41,7 @@ public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor ...@@ -38,5 +41,7 @@ public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor
SpringValueDefinitionProcessor springValueDefinitionProcessor = new SpringValueDefinitionProcessor(); SpringValueDefinitionProcessor springValueDefinitionProcessor = new SpringValueDefinitionProcessor();
springValueDefinitionProcessor.postProcessBeanDefinitionRegistry(registry); springValueDefinitionProcessor.postProcessBeanDefinitionRegistry(registry);
} }
} }
package com.ctrip.framework.apollo.spring.config; package com.ctrip.framework.apollo.spring.config;
import com.ctrip.framework.apollo.build.ApolloInjector; import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
...@@ -8,6 +9,7 @@ import com.google.common.collect.Multimap; ...@@ -8,6 +9,7 @@ import com.google.common.collect.Multimap;
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 java.util.List;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
...@@ -36,6 +38,7 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir ...@@ -36,6 +38,7 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir
private final ConfigPropertySourceFactory configPropertySourceFactory = ApolloInjector private final ConfigPropertySourceFactory configPropertySourceFactory = ApolloInjector
.getInstance(ConfigPropertySourceFactory.class); .getInstance(ConfigPropertySourceFactory.class);
private final ConfigUtil configUtil = ApolloInjector.getInstance(ConfigUtil.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) {
...@@ -45,6 +48,12 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir ...@@ -45,6 +48,12 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir
@Override @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
initializePropertySources(); initializePropertySources();
if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources();
for (ConfigPropertySource configPropertySource : configPropertySources) {
configPropertySource.addChangeListener(new AutoUpdateConfigChangeListener(environment, beanFactory));
}
}
} }
protected void initializePropertySources() { protected void initializePropertySources() {
......
...@@ -3,6 +3,7 @@ package com.ctrip.framework.apollo.spring.property; ...@@ -3,6 +3,7 @@ package com.ctrip.framework.apollo.spring.property;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Type;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
/** /**
...@@ -20,17 +21,23 @@ public class SpringValue { ...@@ -20,17 +21,23 @@ public class SpringValue {
private String key; private String key;
private String placeholder; private String placeholder;
private Class<?> targetType; private Class<?> targetType;
private Type genericType;
private boolean isJson;
public SpringValue(String key, String placeholder, Object bean, String beanName, Field field) { public SpringValue(String key, String placeholder, Object bean, String beanName, Field field, boolean isJson) {
this.bean = bean; this.bean = bean;
this.beanName = beanName; this.beanName = beanName;
this.field = field; this.field = field;
this.key = key; this.key = key;
this.placeholder = placeholder; this.placeholder = placeholder;
this.targetType = field.getType(); this.targetType = field.getType();
if(isJson){
this.genericType = field.getGenericType();
this.isJson = isJson;
}
} }
public SpringValue(String key, String placeholder, Object bean, String beanName, Method method) { public SpringValue(String key, String placeholder, Object bean, String beanName, Method method, boolean isJson) {
this.bean = bean; this.bean = bean;
this.beanName = beanName; this.beanName = beanName;
this.methodParameter = new MethodParameter(method, 0); this.methodParameter = new MethodParameter(method, 0);
...@@ -38,6 +45,10 @@ public class SpringValue { ...@@ -38,6 +45,10 @@ public class SpringValue {
this.placeholder = placeholder; this.placeholder = placeholder;
Class<?>[] paramTps = method.getParameterTypes(); Class<?>[] paramTps = method.getParameterTypes();
this.targetType = paramTps[0]; this.targetType = paramTps[0];
if(isJson){
this.genericType = method.getGenericParameterTypes()[0];
this.isJson = isJson;
}
} }
public void update(Object newVal) throws IllegalAccessException, InvocationTargetException { public void update(Object newVal) throws IllegalAccessException, InvocationTargetException {
...@@ -84,6 +95,14 @@ public class SpringValue { ...@@ -84,6 +95,14 @@ public class SpringValue {
return field; return field;
} }
public Type getGenericType() {
return genericType;
}
public boolean isJson() {
return isJson;
}
@Override @Override
public String toString() { public String toString() {
if (isField()) { if (isField()) {
......
...@@ -3,12 +3,21 @@ package com.ctrip.framework.apollo.spring; ...@@ -3,12 +3,21 @@ package com.ctrip.framework.apollo.spring;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; 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.JavaConfigPlaceholderTest.JsonBean;
import com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest.TestXmlBean;
import com.ctrip.framework.apollo.spring.annotation.ApolloJSONValue;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.common.primitives.Ints;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
...@@ -21,14 +30,6 @@ import org.springframework.context.annotation.FilterType; ...@@ -21,14 +30,6 @@ import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ImportResource; import org.springframework.context.annotation.ImportResource;
import org.springframework.stereotype.Component; 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 { public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrationTest {
private static final String TIMEOUT_PROPERTY = "timeout"; private static final String TIMEOUT_PROPERTY = "timeout";
...@@ -624,6 +625,8 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -624,6 +625,8 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
boolean someNewBoolean = !someBoolean; boolean someNewBoolean = !someBoolean;
String someString = "someString"; String someString = "someString";
String someNewString = "someNewString"; String someNewString = "someNewString";
String someJsonProperty = "[{\"a\":\"astring\", \"b\":10},{\"a\":\"astring2\", \"b\":20}]";
String someNewJsonProperty = "[{\"a\":\"newString\", \"b\":10},{\"a\":\"astring2\", \"b\":20}]";
String someDateFormat = "yyyy-MM-dd HH:mm:ss.SSS"; String someDateFormat = "yyyy-MM-dd HH:mm:ss.SSS";
Date someDate = assembleDate(2018, 2, 23, 20, 1, 2, 123); Date someDate = assembleDate(2018, 2, 23, 20, 1, 2, 123);
...@@ -642,6 +645,7 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -642,6 +645,7 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
properties.setProperty("stringProperty", String.valueOf(someString)); properties.setProperty("stringProperty", String.valueOf(someString));
properties.setProperty("dateFormat", String.valueOf(someDateFormat)); properties.setProperty("dateFormat", String.valueOf(someDateFormat));
properties.setProperty("dateProperty", simpleDateFormat.format(someDate)); properties.setProperty("dateProperty", simpleDateFormat.format(someDate));
properties.setProperty("jsonProperty", someJsonProperty);
SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties); SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties);
...@@ -659,6 +663,7 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -659,6 +663,7 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
assertEquals(someBoolean, bean.getBooleanProperty()); assertEquals(someBoolean, bean.getBooleanProperty());
assertEquals(someString, bean.getStringProperty()); assertEquals(someString, bean.getStringProperty());
assertEquals(someDate, bean.getDateProperty()); assertEquals(someDate, bean.getDateProperty());
assertEquals("astring", bean.getJsonBeanList().get(0).getA());
Properties newProperties = new Properties(); Properties newProperties = new Properties();
newProperties.setProperty("intProperty", String.valueOf(someNewInt)); newProperties.setProperty("intProperty", String.valueOf(someNewInt));
...@@ -672,6 +677,7 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -672,6 +677,7 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
newProperties.setProperty("stringProperty", String.valueOf(someNewString)); newProperties.setProperty("stringProperty", String.valueOf(someNewString));
newProperties.setProperty("dateFormat", String.valueOf(someDateFormat)); newProperties.setProperty("dateFormat", String.valueOf(someDateFormat));
newProperties.setProperty("dateProperty", simpleDateFormat.format(someNewDate)); newProperties.setProperty("dateProperty", simpleDateFormat.format(someNewDate));
newProperties.setProperty("jsonProperty", someNewJsonProperty);
config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties); config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties);
...@@ -687,6 +693,7 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -687,6 +693,7 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
assertEquals(someNewBoolean, bean.getBooleanProperty()); assertEquals(someNewBoolean, bean.getBooleanProperty());
assertEquals(someNewString, bean.getStringProperty()); assertEquals(someNewString, bean.getStringProperty());
assertEquals(someNewDate, bean.getDateProperty()); assertEquals(someNewDate, bean.getDateProperty());
assertEquals("newString", bean.getJsonBeanList().get(0).getA());
} }
@Configuration @Configuration
...@@ -932,6 +939,15 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -932,6 +939,15 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
} }
} }
static class TestJsonPropertyBean{
@ApolloJSONValue("${jsonPropery}")
private List<JsonBean> jsonBeanList;
public List<JsonBean> getJsonBeanList() {
return jsonBeanList;
}
}
static class TestAllKindsOfDataTypesBean { static class TestAllKindsOfDataTypesBean {
@Value("${intProperty}") @Value("${intProperty}")
...@@ -964,6 +980,9 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -964,6 +980,9 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
@Value("#{new java.text.SimpleDateFormat('${dateFormat}').parse('${dateProperty}')}") @Value("#{new java.text.SimpleDateFormat('${dateFormat}').parse('${dateProperty}')}")
private Date dateProperty; private Date dateProperty;
@ApolloJSONValue("${jsonProperty}")
private List<JsonBean> jsonBeanList;
public int getIntProperty() { public int getIntProperty() {
return intProperty; return intProperty;
} }
...@@ -1003,5 +1022,9 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -1003,5 +1022,9 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
public Date getDateProperty() { public Date getDateProperty() {
return dateProperty; return dateProperty;
} }
public List<JsonBean> getJsonBeanList() {
return jsonBeanList;
}
} }
} }
...@@ -6,6 +6,11 @@ import static org.mockito.Matchers.eq; ...@@ -6,6 +6,11 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.spring.annotation.ApolloJSONValue;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import java.util.List;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
...@@ -14,10 +19,6 @@ import org.springframework.context.annotation.Bean; ...@@ -14,10 +19,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
...@@ -30,6 +31,8 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -30,6 +31,8 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
private static final String BATCH_PROPERTY = "batch"; private static final String BATCH_PROPERTY = "batch";
private static final int DEFAULT_BATCH = 200; private static final int DEFAULT_BATCH = 200;
private static final String FX_APOLLO_NAMESPACE = "FX.apollo"; private static final String FX_APOLLO_NAMESPACE = "FX.apollo";
private static final String JSON_PROPERTY = "jsonProperty";
private static final String OTHER_JSON_PROPERTY = "otherJsonProperty";
@Test @Test
public void testPropertySourceWithNoNamespace() throws Exception { public void testPropertySourceWithNoNamespace() throws Exception {
...@@ -282,6 +285,30 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -282,6 +285,30 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
} }
@Test
public void testJsonDeserialization() {
String someJson = "[{\"a\":\"astring\", \"b\":10},{\"a\":\"astring2\", \"b\":20}]";
String otherJson = "[{\"a\":\"otherString\", \"b\":10},{\"a\":\"astring2\", \"b\":20}]";
Config config = mock(Config.class);
when(config.getProperty(eq(JSON_PROPERTY), anyString())).thenReturn(String.valueOf(someJson));
when(config.getProperty(eq(OTHER_JSON_PROPERTY), anyString())).thenReturn(String.valueOf(otherJson));
when(config.getProperty(eq("a"), anyString())).thenReturn(JSON_PROPERTY);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
AppConfig8.class);
TestJsonPropertyBean testJsonPropertyBean = context.getBean(TestJsonPropertyBean.class);
assertEquals(2, testJsonPropertyBean.getJsonBeanList().size());
assertEquals("astring", testJsonPropertyBean.getJsonBeanList().get(0).a);
assertEquals("astring", testJsonPropertyBean.getEmbeddedJsonBeanList().get(0).a);
assertEquals("otherString", testJsonPropertyBean.getOtherJsonBeanList().get(0).a);
}
private void check(int expectedTimeout, int expectedBatch, Class<?>... annotatedClasses) { private void check(int expectedTimeout, int expectedBatch, Class<?>... annotatedClasses) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(annotatedClasses); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(annotatedClasses);
...@@ -363,6 +390,18 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -363,6 +390,18 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
} }
} }
@Configuration
@EnableApolloConfig
static class AppConfig8 {
@Bean
TestJsonPropertyBean testJavaConfigBean4() {
return new TestJsonPropertyBean();
}
}
@Component
static class TestJavaConfigBean { static class TestJavaConfigBean {
@Value("${timeout:100}") @Value("${timeout:100}")
private int timeout; private int timeout;
...@@ -434,4 +473,57 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -434,4 +473,57 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
} }
} }
static class TestJsonPropertyBean {
@ApolloJSONValue("${jsonProperty}")
private List<JsonBean> jsonBeanList;
private List<JsonBean> otherJsonBeanList;
@ApolloJSONValue("${${a}}")
private List<JsonBean> embeddedJsonBeanList;
public List<JsonBean> getJsonBeanList() {
return jsonBeanList;
}
@ApolloJSONValue("${otherJsonProperty}")
public void setOtherJsonBeanList(
List<JsonBean> otherJsonBeanList) {
this.otherJsonBeanList = otherJsonBeanList;
}
public List<JsonBean> getOtherJsonBeanList() {
return otherJsonBeanList;
}
public List<JsonBean> getEmbeddedJsonBeanList() {
return embeddedJsonBeanList;
}
}
static class JsonBean {
private String a;
private int b;
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
}
} }
package com.ctrip.framework.apollo.demo.spring.common.bean; package com.ctrip.framework.apollo.demo.spring.common.bean;
import com.ctrip.framework.apollo.spring.annotation.ApolloJSONValue;
import com.google.gson.Gson;
import java.util.List;
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;
/** /**
* @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);
private int timeout; private int timeout;
private int batch; private int batch;
@ApolloJSONValue("${objectList}")
private List<JsonBean> jsonBeans;
@Value("${batch:100}") @Value("${batch:100}")
public void setBatch(int batch) { public void setBatch(int batch) {
...@@ -29,6 +36,29 @@ public class AnnotatedBean { ...@@ -29,6 +36,29 @@ public class AnnotatedBean {
@Override @Override
public String toString() { public String toString() {
return String.format("[AnnotatedBean] timeout: %d, batch: %d", timeout, batch); return String.format("[AnnotatedBean] timeout: %d, batch: %d, jsonBeans: %s", timeout, batch, new Gson().toJson(jsonBeans));
} }
static class JsonBean{
private String a;
private int b;
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
}
} }
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