未验证 提交 6ecbdab3 编写于 作者: 张正 提交者: GitHub

Merge pull request #1 from nobodyiam/json-support-minor-refactor

minor refactor and add more ut
...@@ -9,6 +9,7 @@ import com.ctrip.framework.apollo.spi.DefaultConfigFactoryManager; ...@@ -9,6 +9,7 @@ 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.config.ConfigPropertySourceFactory;
import com.ctrip.framework.apollo.spring.property.PlaceholderHelper; import com.ctrip.framework.apollo.spring.property.PlaceholderHelper;
import com.ctrip.framework.apollo.spring.property.SpringValueRegistry;
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;
...@@ -64,6 +65,7 @@ public class DefaultInjector implements Injector { ...@@ -64,6 +65,7 @@ public class DefaultInjector implements Injector {
bind(RemoteConfigLongPollService.class).in(Singleton.class); bind(RemoteConfigLongPollService.class).in(Singleton.class);
bind(PlaceholderHelper.class).in(Singleton.class); bind(PlaceholderHelper.class).in(Singleton.class);
bind(ConfigPropertySourceFactory.class).in(Singleton.class); bind(ConfigPropertySourceFactory.class).in(Singleton.class);
bind(SpringValueRegistry.class).in(Singleton.class);
} }
} }
} }
...@@ -32,12 +32,10 @@ public class ApolloAnnotationProcessor extends ApolloProcessor { ...@@ -32,12 +32,10 @@ public class ApolloAnnotationProcessor extends ApolloProcessor {
ReflectionUtils.makeAccessible(field); ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, bean, config); ReflectionUtils.setField(field, bean, config);
} }
@Override @Override
protected void processMethod(final Object bean, String beanName, final Method method) { protected void processMethod(final Object bean, String beanName, final Method method) {
ApolloConfigChangeListener annotation = AnnotationUtils ApolloConfigChangeListener annotation = AnnotationUtils
.findAnnotation(method, ApolloConfigChangeListener.class); .findAnnotation(method, ApolloConfigChangeListener.class);
if (annotation == null) { if (annotation == null) {
...@@ -53,17 +51,17 @@ public class ApolloAnnotationProcessor extends ApolloProcessor { ...@@ -53,17 +51,17 @@ public class ApolloAnnotationProcessor extends ApolloProcessor {
ReflectionUtils.makeAccessible(method); ReflectionUtils.makeAccessible(method);
String[] namespaces = annotation.value(); String[] namespaces = annotation.value();
ConfigChangeListener configChangeListener = new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
ReflectionUtils.invokeMethod(method, bean, changeEvent);
}
};
for (String namespace : namespaces) { for (String namespace : namespaces) {
Config config = ConfigService.getConfig(namespace); Config config = ConfigService.getConfig(namespace);
config.addChangeListener(new ConfigChangeListener() { config.addChangeListener(configChangeListener);
@Override
public void onChange(ConfigChangeEvent changeEvent) {
ReflectionUtils.invokeMethod(method, bean, changeEvent);
}
});
} }
} }
} }
...@@ -35,8 +35,7 @@ public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar { ...@@ -35,8 +35,7 @@ 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(), BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(),
ApolloJSONValueProcessor.class); ApolloJsonValueProcessor.class);
} }
} }
...@@ -7,13 +7,28 @@ import java.lang.annotation.RetentionPolicy; ...@@ -7,13 +7,28 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* Create by zhangzheng on 2018/2/6 * Use this annotation to inject json property from Apollo, support the same format as Spring @Value.
*
* <p>Usage example:</p>
* <pre class="code">
* // Inject the json property value for type SomeObject.
* // Suppose SomeObject has 2 properties, someString and someInt, then the possible config
* // in Apollo is someJsonPropertyKey={"someString":"someValue", "someInt":10}.
* &#064;ApolloJsonValue("${someJsonPropertyKey:someDefaultValue}")
* private SomeObject someObject;
* </pre>
*
* Create by zhangzheng on 2018/3/6
*
* @see org.springframework.beans.factory.annotation.Value
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD}) @Target({ElementType.FIELD, ElementType.METHOD})
@Documented @Documented
public @interface ApolloJSONValue { public @interface ApolloJsonValue {
/**
* The actual value expression: e.g. "${someJsonPropertyKey:someDefaultValue}".
*/
String value(); String value();
} }
package com.ctrip.framework.apollo.spring.annotation; package com.ctrip.framework.apollo.spring.annotation;
import com.ctrip.framework.apollo.build.ApolloInjector; import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.spring.config.AutoUpdateConfigChangeListener; import com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener;
import com.ctrip.framework.apollo.spring.property.PlaceholderHelper; import com.ctrip.framework.apollo.spring.property.PlaceholderHelper;
import com.ctrip.framework.apollo.spring.property.SpringValue; import com.ctrip.framework.apollo.spring.property.SpringValue;
import com.ctrip.framework.apollo.spring.property.SpringValueRegistry;
import com.ctrip.framework.apollo.util.ConfigUtil; import com.ctrip.framework.apollo.util.ConfigUtil;
import com.ctrip.framework.foundation.internals.Utils;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Type; import java.lang.reflect.Type;
...@@ -18,96 +19,103 @@ import org.springframework.beans.BeansException; ...@@ -18,96 +19,103 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment; import org.springframework.util.ReflectionUtils;
/** /**
* Create by zhangzheng on 2018/2/6 * Create by zhangzheng on 2018/2/6
*/ */
public class ApolloJSONValueProcessor extends ApolloProcessor implements EnvironmentAware,BeanFactoryAware { public class ApolloJsonValueProcessor extends ApolloProcessor implements BeanFactoryAware {
private Logger logger = LoggerFactory.getLogger(ApolloJSONValueProcessor.class); private static final Logger logger = LoggerFactory.getLogger(ApolloJsonValueProcessor.class);
private static final Gson gson = new Gson();
private static Gson gson = new Gson();
private Environment environment;
private final ConfigUtil configUtil; private final ConfigUtil configUtil;
private final PlaceholderHelper placeholderHelper; private final PlaceholderHelper placeholderHelper;
private final SpringValueRegistry springValueRegistry;
private ConfigurableBeanFactory beanFactory; private ConfigurableBeanFactory beanFactory;
public ApolloJSONValueProcessor() { public ApolloJsonValueProcessor() {
configUtil = ApolloInjector.getInstance(ConfigUtil.class); configUtil = ApolloInjector.getInstance(ConfigUtil.class);
placeholderHelper = ApolloInjector.getInstance(PlaceholderHelper.class); placeholderHelper = ApolloInjector.getInstance(PlaceholderHelper.class);
springValueRegistry = ApolloInjector.getInstance(SpringValueRegistry.class);
} }
@Override @Override
protected void processField(Object bean,String beanName, Field field) { protected void processField(Object bean, String beanName, Field field) {
ApolloJSONValue apolloJSONValue = AnnotationUtils.getAnnotation(field, ApolloJSONValue.class); ApolloJsonValue apolloJsonValue = AnnotationUtils.getAnnotation(field, ApolloJsonValue.class);
if (apolloJSONValue == null) { if (apolloJsonValue == null) {
return; return;
} }
try { String placeholder = apolloJsonValue.value();
String placeHolder = apolloJSONValue.value(); Object propertyValue = placeholderHelper
String propertyValue = beanFactory.resolveEmbeddedValue(placeHolder); .resolvePropertyValue(beanFactory, beanName, placeholder);
if(!Utils.isBlank(propertyValue)){
boolean accessible = field.isAccessible(); // propertyValue will never be null, as @ApolloJsonValue will not allow that
field.setAccessible(true); if (!(propertyValue instanceof String)) {
field.set(bean, gson.fromJson(propertyValue, field.getGenericType())); return;
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);
} }
boolean accessible = field.isAccessible();
field.setAccessible(true);
ReflectionUtils
.setField(field, bean, parseJsonValue((String)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);
springValueRegistry.register(key, springValue);
logger.debug("Monitoring {}", springValue);
}
}
} }
@Override @Override
protected void processMethod(Object bean, String beanName, Method method) { protected void processMethod(Object bean, String beanName, Method method) {
ApolloJsonValue apolloJsonValue = AnnotationUtils.getAnnotation(method, ApolloJsonValue.class);
if (apolloJsonValue == null) {
return;
}
String placeHolder = apolloJsonValue.value();
ApolloJSONValue apolloJSONValue = AnnotationUtils.getAnnotation(method, ApolloJSONValue.class); Object propertyValue = placeholderHelper
if (apolloJSONValue == null) { .resolvePropertyValue(beanFactory, beanName, placeHolder);
// propertyValue will never be null, as @ApolloJsonValue will not allow that
if (!(propertyValue instanceof String)) {
return; return;
} }
try {
String placeHolder = apolloJSONValue.value(); Type[] types = method.getGenericParameterTypes();
String propertyValue = beanFactory.resolveEmbeddedValue(placeHolder); Preconditions.checkArgument(types.length == 1,
if(!Utils.isBlank(propertyValue)){ "Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters",
boolean accessible = method.isAccessible(); bean.getClass().getName(), method.getName(), method.getParameterTypes().length);
method.setAccessible(true);
Type[] types = method.getGenericParameterTypes(); boolean accessible = method.isAccessible();
Preconditions.checkArgument(types.length == 1, "Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters", method.setAccessible(true);
bean.getClass().getName(), method.getName(), method.getParameterTypes().length); ReflectionUtils.invokeMethod(method, bean, parseJsonValue((String)propertyValue, types[0]));
method.invoke(bean, gson.fromJson(propertyValue, types[0])); method.setAccessible(accessible);
method.setAccessible(accessible);
} if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
if(configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()){ Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeHolder);
Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeHolder); for (String key : keys) {
for(String key:keys){ SpringValue springValue = new SpringValue(key, apolloJsonValue.value(), bean, beanName,
SpringValue springValue = new SpringValue(key, apolloJSONValue.value(), bean, beanName, method, true); method, true);
AutoUpdateConfigChangeListener.monitor.put(key, springValue); springValueRegistry.register(key, springValue);
logger.debug("Monitoring ", springValue); logger.debug("Monitoring {}", springValue);
}
} }
} catch (Exception e) {
logger.error("set json value exception", e);
} }
} }
private Object parseJsonValue(String json, Type targetType) {
try {
@Override return gson.fromJson(json, targetType);
public void setEnvironment(Environment environment) { } catch (Throwable ex) {
this.environment = environment; logger.error("Parsing json '{}' to type {} failed!", json, targetType, ex);
throw ex;
}
} }
@Override @Override
......
...@@ -35,21 +35,18 @@ public abstract class ApolloProcessor implements BeanPostProcessor, PriorityOrde ...@@ -35,21 +35,18 @@ public abstract class ApolloProcessor implements BeanPostProcessor, PriorityOrde
/** /**
* subclass should implement this method to process field * subclass should implement this method to process field
* @param bean
* @param field
*/ */
protected abstract void processField(Object bean, String beanName, Field field); protected abstract void processField(Object bean, String beanName, Field field);
/** /**
* subclass should implement this method to process method * subclass should implement this method to process method
* @param bean
* @param method
*/ */
protected abstract void processMethod(Object bean, String beanName, Method method); protected abstract void processMethod(Object bean, String beanName, Method method);
@Override @Override
public int getOrder() { public int getOrder() {
//make it as late as possible
return Ordered.LOWEST_PRECEDENCE; return Ordered.LOWEST_PRECEDENCE;
} }
......
package com.ctrip.framework.apollo.spring.annotation; package com.ctrip.framework.apollo.spring.annotation;
import com.ctrip.framework.apollo.spring.config.AutoUpdateConfigChangeListener; import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener;
import com.ctrip.framework.apollo.spring.property.PlaceholderHelper;
import com.ctrip.framework.apollo.spring.property.SpringValue;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinition;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
import com.ctrip.framework.apollo.spring.property.SpringValueRegistry;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
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;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
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.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.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySource;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySourceFactory;
import com.ctrip.framework.apollo.spring.property.PlaceholderHelper;
import com.ctrip.framework.apollo.spring.property.SpringValue;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinition;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
/** /**
* Spring value processor of field or method which has @Value and xml config placeholders. * Spring value processor of field or method which has @Value and xml config placeholders.
...@@ -53,26 +30,29 @@ import com.google.common.collect.Multimap; ...@@ -53,26 +30,29 @@ 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 extends ApolloProcessor implements BeanFactoryPostProcessor { public class SpringValueProcessor extends ApolloProcessor implements BeanFactoryPostProcessor {
private static final Logger logger = LoggerFactory.getLogger(SpringValueProcessor.class); private static final Logger logger = LoggerFactory.getLogger(SpringValueProcessor.class);
private final ConfigUtil configUtil; private final ConfigUtil configUtil;
private final PlaceholderHelper placeholderHelper; private final PlaceholderHelper placeholderHelper;
private final SpringValueRegistry springValueRegistry;
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);
springValueRegistry = ApolloInjector.getInstance(SpringValueRegistry.class);
} }
@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();
} }
} }
...@@ -102,7 +82,7 @@ public class SpringValueProcessor extends ApolloProcessor implements BeanFactor ...@@ -102,7 +82,7 @@ public class SpringValueProcessor extends ApolloProcessor implements BeanFactor
for (String key : keys) { for (String key : keys) {
SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false); SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
AutoUpdateConfigChangeListener.monitor.put(key, springValue); springValueRegistry.register(key, springValue);
logger.debug("Monitoring {}", springValue); logger.debug("Monitoring {}", springValue);
} }
} }
...@@ -132,13 +112,12 @@ public class SpringValueProcessor extends ApolloProcessor implements BeanFactor ...@@ -132,13 +112,12 @@ public class SpringValueProcessor extends ApolloProcessor implements BeanFactor
for (String key : keys) { for (String key : keys) {
SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method, false); SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method, false);
AutoUpdateConfigChangeListener.monitor.put(key, springValue); springValueRegistry.register(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);
...@@ -156,7 +135,7 @@ public class SpringValueProcessor extends ApolloProcessor implements BeanFactor ...@@ -156,7 +135,7 @@ public class SpringValueProcessor extends ApolloProcessor implements BeanFactor
} }
SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(), SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(),
bean, beanName, method, false); bean, beanName, method, false);
AutoUpdateConfigChangeListener.monitor.put(definition.getKey(), springValue); springValueRegistry.register(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(),
......
...@@ -2,7 +2,7 @@ package com.ctrip.framework.apollo.spring.config; ...@@ -2,7 +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 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;
...@@ -26,8 +26,8 @@ public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor ...@@ -26,8 +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(), BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(),
ApolloJSONValueProcessor.class); ApolloJsonValueProcessor.class);
processSpringValueDefinition(registry); processSpringValueDefinition(registry);
} }
...@@ -41,7 +41,5 @@ public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor ...@@ -41,7 +41,5 @@ 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.spring.property.AutoUpdateConfigChangeListener;
import com.ctrip.framework.apollo.util.ConfigUtil; 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;
...@@ -10,6 +11,7 @@ import com.ctrip.framework.apollo.Config; ...@@ -10,6 +11,7 @@ import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService; import com.ctrip.framework.apollo.ConfigService;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
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;
...@@ -35,6 +37,7 @@ import java.util.Iterator; ...@@ -35,6 +37,7 @@ import java.util.Iterator;
*/ */
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered { public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {
private static final Multimap<Integer, String> NAMESPACE_NAMES = LinkedHashMultimap.create(); private static final Multimap<Integer, String> NAMESPACE_NAMES = LinkedHashMultimap.create();
private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false);
private final ConfigPropertySourceFactory configPropertySourceFactory = ApolloInjector private final ConfigPropertySourceFactory configPropertySourceFactory = ApolloInjector
.getInstance(ConfigPropertySourceFactory.class); .getInstance(ConfigPropertySourceFactory.class);
...@@ -47,16 +50,14 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir ...@@ -47,16 +50,14 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir
@Override @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
initializePropertySources(); if (INITIALIZED.compareAndSet(false, true)) {
if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) { initializePropertySources();
List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources();
for (ConfigPropertySource configPropertySource : configPropertySources) { initializeAutoUpdatePropertiesFeature(beanFactory);
configPropertySource.addChangeListener(new AutoUpdateConfigChangeListener(environment, beanFactory));
}
} }
} }
protected void initializePropertySources() { private void initializePropertySources() {
if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME)) { if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME)) {
//already initialized //already initialized
return; return;
...@@ -86,6 +87,20 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir ...@@ -86,6 +87,20 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir
} }
} }
private void initializeAutoUpdatePropertiesFeature(ConfigurableListableBeanFactory beanFactory) {
if (!configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
return;
}
AutoUpdateConfigChangeListener autoUpdateConfigChangeListener = new AutoUpdateConfigChangeListener(
environment, beanFactory);
List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources();
for (ConfigPropertySource configPropertySource : configPropertySources) {
configPropertySource.addChangeListener(autoUpdateConfigChangeListener);
}
}
@Override @Override
public void setEnvironment(Environment environment) { public void setEnvironment(Environment environment) {
//it is safe enough to cast as all known environment is derived from ConfigurableEnvironment //it is safe enough to cast as all known environment is derived from ConfigurableEnvironment
...@@ -95,6 +110,7 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir ...@@ -95,6 +110,7 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir
//only for test //only for test
private static void reset() { private static void reset() {
NAMESPACE_NAMES.clear(); NAMESPACE_NAMES.clear();
INITIALIZED.set(false);
} }
@Override @Override
......
package com.ctrip.framework.apollo.spring.config; package com.ctrip.framework.apollo.spring.property;
import com.ctrip.framework.apollo.ConfigChangeListener; import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.model.ConfigChange; import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent; import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor; 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 com.google.gson.Gson;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Type; import java.lang.reflect.Type;
...@@ -16,11 +14,8 @@ import java.util.Set; ...@@ -16,11 +14,8 @@ import java.util.Set;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeConverter; 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.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.Scope; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
...@@ -28,24 +23,26 @@ import org.springframework.util.CollectionUtils; ...@@ -28,24 +23,26 @@ import org.springframework.util.CollectionUtils;
* Create by zhangzheng on 2018/3/6 * Create by zhangzheng on 2018/3/6
*/ */
public class AutoUpdateConfigChangeListener implements ConfigChangeListener{ 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 static final Logger logger = LoggerFactory.getLogger(SpringValueProcessor.class);
private final boolean typeConverterHasConvertIfNecessaryWithFieldParameter;
private Gson gson = new Gson();
public AutoUpdateConfigChangeListener(Environment environment, BeanFactory beanFactory){ private final boolean typeConverterHasConvertIfNecessaryWithFieldParameter;
typeConverterHasConvertIfNecessaryWithFieldParameter = testTypeConverterHasConvertIfNecessaryWithFieldParameter(); private final Environment environment;
this.beanFactory = (ConfigurableBeanFactory) beanFactory; private final ConfigurableBeanFactory beanFactory;
private final TypeConverter typeConverter;
private final PlaceholderHelper placeholderHelper;
private final SpringValueRegistry springValueRegistry;
private final Gson gson;
public AutoUpdateConfigChangeListener(Environment environment, ConfigurableListableBeanFactory beanFactory){
this.typeConverterHasConvertIfNecessaryWithFieldParameter = testTypeConverterHasConvertIfNecessaryWithFieldParameter();
this.beanFactory = beanFactory;
this.typeConverter = this.beanFactory.getTypeConverter(); this.typeConverter = this.beanFactory.getTypeConverter();
this.environment = environment; this.environment = environment;
this.placeholderHelper = ApolloInjector.getInstance(PlaceholderHelper.class);
this.springValueRegistry = ApolloInjector.getInstance(SpringValueRegistry.class);
this.gson = new Gson();
} }
@Override @Override
public void onChange(ConfigChangeEvent changeEvent) { public void onChange(ConfigChangeEvent changeEvent) {
Set<String> keys = changeEvent.changedKeys(); Set<String> keys = changeEvent.changedKeys();
...@@ -54,7 +51,7 @@ public class AutoUpdateConfigChangeListener implements ConfigChangeListener{ ...@@ -54,7 +51,7 @@ public class AutoUpdateConfigChangeListener implements ConfigChangeListener{
} }
for (String key : keys) { for (String key : keys) {
// 1. check whether the changed key is relevant // 1. check whether the changed key is relevant
Collection<SpringValue> targetValues = monitor.get(key); Collection<SpringValue> targetValues = springValueRegistry.get(key);
if (targetValues == null || targetValues.isEmpty()) { if (targetValues == null || targetValues.isEmpty()) {
continue; continue;
} }
...@@ -67,28 +64,11 @@ public class AutoUpdateConfigChangeListener implements ConfigChangeListener{ ...@@ -67,28 +64,11 @@ public class AutoUpdateConfigChangeListener implements ConfigChangeListener{
// 3. update the value // 3. update the value
for (SpringValue val : targetValues) { for (SpringValue val : targetValues) {
if(val.isJson()){ updateSpringValue(val);
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) { private void updateSpringValue(SpringValue springValue) {
try { try {
Object value = resolvePropertyValue(springValue); Object value = resolvePropertyValue(springValue);
...@@ -106,35 +86,37 @@ public class AutoUpdateConfigChangeListener implements ConfigChangeListener{ ...@@ -106,35 +86,37 @@ public class AutoUpdateConfigChangeListener implements ConfigChangeListener{
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency(org.springframework.beans.factory.config.DependencyDescriptor, java.lang.String, java.util.Set, org.springframework.beans.TypeConverter) * @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) { private Object resolvePropertyValue(SpringValue springValue) {
String strVal = beanFactory.resolveEmbeddedValue(springValue.getPlaceholder()); // value will never be null, as @Value and @ApolloJsonValue will not allow that
Object value; Object value = placeholderHelper
.resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder());
BeanDefinition bd = (beanFactory.containsBean(springValue.getBeanName()) ? beanFactory
.getMergedBeanDefinition(springValue.getBeanName()) : null);
value = evaluateBeanDefinitionString(strVal, bd);
if (springValue.isField()) { if (springValue.isJson()) {
// org.springframework.beans.TypeConverter#convertIfNecessary(java.lang.Object, java.lang.Class, java.lang.reflect.Field) is available from Spring 3.2.0+ value = parseJsonValue((String)value, springValue.getGenericType());
if (typeConverterHasConvertIfNecessaryWithFieldParameter) { } else {
value = this.typeConverter if (springValue.isField()) {
.convertIfNecessary(value, springValue.getTargetType(), springValue.getField()); // 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 { } else {
value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType()); value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType(),
springValue.getMethodParameter());
} }
} else {
value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType(),
springValue.getMethodParameter());
} }
return value; return value;
} }
private Object evaluateBeanDefinitionString(String value, BeanDefinition beanDefinition) { private Object parseJsonValue(String json, Type targetType) {
if (beanFactory.getBeanExpressionResolver() == null) { try {
return value; return gson.fromJson(json, targetType);
} catch (Throwable ex) {
logger.error("Parsing json '{}' to type {} failed!", json, targetType, ex);
throw ex;
} }
Scope scope = (beanDefinition != null ? beanFactory.getRegisteredScope(beanDefinition.getScope()) : null);
return beanFactory.getBeanExpressionResolver().evaluate(value, new BeanExpressionContext(beanFactory, scope));
} }
private boolean testTypeConverterHasConvertIfNecessaryWithFieldParameter() { private boolean testTypeConverterHasConvertIfNecessaryWithFieldParameter() {
...@@ -146,6 +128,4 @@ public class AutoUpdateConfigChangeListener implements ConfigChangeListener{ ...@@ -146,6 +128,4 @@ public class AutoUpdateConfigChangeListener implements ConfigChangeListener{
return true; return true;
} }
} }
...@@ -4,18 +4,15 @@ import com.google.common.base.Strings; ...@@ -4,18 +4,15 @@ import com.google.common.base.Strings;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import java.util.Set; import java.util.Set;
import java.util.Stack; import java.util.Stack;
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.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
* Extract keys from placeholder, e.g. * Placeholder helper functions.
* <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 { public class PlaceholderHelper {
...@@ -26,6 +23,45 @@ public class PlaceholderHelper { ...@@ -26,6 +23,45 @@ public class PlaceholderHelper {
private static final String EXPRESSION_PREFIX = "#{"; private static final String EXPRESSION_PREFIX = "#{";
private static final String EXPRESSION_SUFFIX = "}"; private static final String EXPRESSION_SUFFIX = "}";
/**
* Resolve placeholder property values, e.g.
* <br />
* <br />
* "${somePropertyValue}" -> "the actual property value"
*/
public Object resolvePropertyValue(ConfigurableBeanFactory beanFactory, String beanName, String placeholder) {
// resolve string value
String strVal = beanFactory.resolveEmbeddedValue(placeholder);
BeanDefinition bd = (beanFactory.containsBean(beanName) ? beanFactory
.getMergedBeanDefinition(beanName) : null);
// resolve expressions like "#{systemProperties.myProp}"
return evaluateBeanDefinitionString(beanFactory, strVal, bd);
}
private Object evaluateBeanDefinitionString(ConfigurableBeanFactory beanFactory, 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));
}
/**
* 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 Set<String> extractPlaceholderKeys(String propertyString) { public Set<String> extractPlaceholderKeys(String propertyString) {
Set<String> placeholderKeys = Sets.newHashSet(); Set<String> placeholderKeys = Sets.newHashSet();
......
...@@ -31,9 +31,9 @@ public class SpringValue { ...@@ -31,9 +31,9 @@ public class SpringValue {
this.key = key; this.key = key;
this.placeholder = placeholder; this.placeholder = placeholder;
this.targetType = field.getType(); this.targetType = field.getType();
this.isJson = isJson;
if(isJson){ if(isJson){
this.genericType = field.getGenericType(); this.genericType = field.getGenericType();
this.isJson = isJson;
} }
} }
...@@ -45,9 +45,9 @@ public class SpringValue { ...@@ -45,9 +45,9 @@ 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];
this.isJson = isJson;
if(isJson){ if(isJson){
this.genericType = method.getGenericParameterTypes()[0]; this.genericType = method.getGenericParameterTypes()[0];
this.isJson = isJson;
} }
} }
......
package com.ctrip.framework.apollo.spring.property;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import java.util.Collection;
public class SpringValueRegistry {
private final Multimap<String, SpringValue> registry = LinkedListMultimap.create();
public void register(String key, SpringValue springValue) {
registry.put(key, springValue);
}
public Collection<SpringValue> get(String key) {
return registry.get(key);
}
}
...@@ -59,5 +59,6 @@ public class MockInjector implements Injector { ...@@ -59,5 +59,6 @@ public class MockInjector implements Injector {
public static void reset() { public static void reset() {
classMap.clear(); classMap.clear();
classTable.clear(); classTable.clear();
delegate = null;
} }
} }
...@@ -5,6 +5,7 @@ import static org.mockito.Mockito.when; ...@@ -5,6 +5,7 @@ import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.internals.ConfigRepository; import com.ctrip.framework.apollo.internals.ConfigRepository;
import com.ctrip.framework.apollo.internals.DefaultInjector;
import com.ctrip.framework.apollo.internals.SimpleConfig; import com.ctrip.framework.apollo.internals.SimpleConfig;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor; import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
import com.ctrip.framework.apollo.util.ConfigUtil; import com.ctrip.framework.apollo.util.ConfigUtil;
...@@ -119,6 +120,7 @@ public abstract class AbstractSpringIntegrationTest { ...@@ -119,6 +120,7 @@ public abstract class AbstractSpringIntegrationTest {
ReflectionUtils.invokeMethod(CONFIG_SERVICE_RESET, null); ReflectionUtils.invokeMethod(CONFIG_SERVICE_RESET, null);
MockInjector.reset(); MockInjector.reset();
MockInjector.setInstance(ConfigManager.class, new MockConfigManager()); MockInjector.setInstance(ConfigManager.class, new MockConfigManager());
MockInjector.setDelegate(new DefaultInjector());
} }
protected static void doTearDown() { protected static void doTearDown() {
......
...@@ -2,13 +2,14 @@ package com.ctrip.framework.apollo.spring; ...@@ -2,13 +2,14 @@ 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 static org.junit.Assert.assertTrue;
import com.ctrip.framework.apollo.build.MockInjector; import com.ctrip.framework.apollo.build.MockInjector;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.internals.SimpleConfig; import com.ctrip.framework.apollo.internals.SimpleConfig;
import com.ctrip.framework.apollo.spring.JavaConfigPlaceholderTest.JsonBean; import com.ctrip.framework.apollo.spring.JavaConfigPlaceholderTest.JsonBean;
import com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest.TestXmlBean; import com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest.TestXmlBean;
import com.ctrip.framework.apollo.spring.annotation.ApolloJSONValue; import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import com.ctrip.framework.apollo.util.ConfigUtil; import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
...@@ -626,7 +627,7 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -626,7 +627,7 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
String someString = "someString"; String someString = "someString";
String someNewString = "someNewString"; String someNewString = "someNewString";
String someJsonProperty = "[{\"a\":\"astring\", \"b\":10},{\"a\":\"astring2\", \"b\":20}]"; String someJsonProperty = "[{\"a\":\"astring\", \"b\":10},{\"a\":\"astring2\", \"b\":20}]";
String someNewJsonProperty = "[{\"a\":\"newString\", \"b\":10},{\"a\":\"astring2\", \"b\":20}]"; String someNewJsonProperty = "[{\"a\":\"newString\", \"b\":20},{\"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);
...@@ -664,6 +665,7 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -664,6 +665,7 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
assertEquals(someString, bean.getStringProperty()); assertEquals(someString, bean.getStringProperty());
assertEquals(someDate, bean.getDateProperty()); assertEquals(someDate, bean.getDateProperty());
assertEquals("astring", bean.getJsonBeanList().get(0).getA()); assertEquals("astring", bean.getJsonBeanList().get(0).getA());
assertEquals(10, bean.getJsonBeanList().get(0).getB());
Properties newProperties = new Properties(); Properties newProperties = new Properties();
newProperties.setProperty("intProperty", String.valueOf(someNewInt)); newProperties.setProperty("intProperty", String.valueOf(someNewInt));
...@@ -694,6 +696,91 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -694,6 +696,91 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
assertEquals(someNewString, bean.getStringProperty()); assertEquals(someNewString, bean.getStringProperty());
assertEquals(someNewDate, bean.getDateProperty()); assertEquals(someNewDate, bean.getDateProperty());
assertEquals("newString", bean.getJsonBeanList().get(0).getA()); assertEquals("newString", bean.getJsonBeanList().get(0).getA());
assertEquals(20, bean.getJsonBeanList().get(0).getB());
}
@Test
public void testAutoUpdateJsonValueWithInvalidValue() throws Exception {
String someValidValue = "{\"a\":\"someString\", \"b\":10}";
String someInvalidValue = "someInvalidValue";
Properties properties = assembleProperties("jsonProperty", someValidValue);
SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig10.class);
TestApolloJsonValue bean = context.getBean(TestApolloJsonValue.class);
JsonBean jsonBean = bean.getJsonBean();
assertEquals("someString", jsonBean.getA());
assertEquals(10, jsonBean.getB());
Properties newProperties = assembleProperties("jsonProperty", someInvalidValue);
config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties);
TimeUnit.MILLISECONDS.sleep(50);
// should not change anything
assertTrue(jsonBean == bean.getJsonBean());
}
@Test
public void testAutoUpdateJsonValueWithNoValueAndNoDefaultValue() throws Exception {
String someValidValue = "{\"a\":\"someString\", \"b\":10}";
Properties properties = assembleProperties("jsonProperty", someValidValue);
SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig10.class);
TestApolloJsonValue bean = context.getBean(TestApolloJsonValue.class);
JsonBean jsonBean = bean.getJsonBean();
assertEquals("someString", jsonBean.getA());
assertEquals(10, jsonBean.getB());
Properties newProperties = new Properties();
config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties);
TimeUnit.MILLISECONDS.sleep(50);
// should not change anything
assertTrue(jsonBean == bean.getJsonBean());
}
@Test
public void testAutoUpdateJsonValueWithNoValueAndDefaultValue() throws Exception {
String someValidValue = "{\"a\":\"someString\", \"b\":10}";
Properties properties = assembleProperties("jsonProperty", someValidValue);
SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig11.class);
TestApolloJsonValueWithDefaultValue bean = context.getBean(TestApolloJsonValueWithDefaultValue.class);
JsonBean jsonBean = bean.getJsonBean();
assertEquals("someString", jsonBean.getA());
assertEquals(10, jsonBean.getB());
Properties newProperties = new Properties();
config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties);
TimeUnit.MILLISECONDS.sleep(50);
JsonBean newJsonBean = bean.getJsonBean();
assertEquals("defaultString", newJsonBean.getA());
assertEquals(1, newJsonBean.getB());
} }
@Configuration @Configuration
...@@ -812,6 +899,26 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -812,6 +899,26 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
} }
} }
@Configuration
@EnableApolloConfig
static class AppConfig10 {
@Bean
TestApolloJsonValue testApolloJsonValue() {
return new TestApolloJsonValue();
}
}
@Configuration
@EnableApolloConfig
static class AppConfig11 {
@Bean
TestApolloJsonValueWithDefaultValue testApolloJsonValue() {
return new TestApolloJsonValueWithDefaultValue();
}
}
static class TestJavaConfigBean { static class TestJavaConfigBean {
@Value("${timeout:100}") @Value("${timeout:100}")
...@@ -939,15 +1046,6 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -939,15 +1046,6 @@ 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}")
...@@ -980,7 +1078,7 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -980,7 +1078,7 @@ 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}") @ApolloJsonValue("${jsonProperty}")
private List<JsonBean> jsonBeanList; private List<JsonBean> jsonBeanList;
public int getIntProperty() { public int getIntProperty() {
...@@ -1027,4 +1125,25 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati ...@@ -1027,4 +1125,25 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
return jsonBeanList; return jsonBeanList;
} }
} }
static class TestApolloJsonValue {
@ApolloJsonValue("${jsonProperty}")
private JsonBean jsonBean;
public JsonBean getJsonBean() {
return jsonBean;
}
}
static class TestApolloJsonValueWithDefaultValue {
@ApolloJsonValue("${jsonProperty:{\"a\":\"defaultString\", \"b\":1}}")
private JsonBean jsonBean;
public JsonBean getJsonBean() {
return jsonBean;
}
}
} }
...@@ -8,10 +8,11 @@ import static org.mockito.Mockito.when; ...@@ -8,10 +8,11 @@ import static org.mockito.Mockito.when;
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.ApolloJSONValue; import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import java.util.List; import java.util.List;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
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;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
...@@ -284,15 +285,14 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -284,15 +285,14 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
assertEquals(someValue, bean.getNestedProperty()); assertEquals(someValue, bean.getNestedProperty());
} }
@Test @Test
public void testJsonDeserialization() { public void testApolloJsonValue() {
String someJson = "[{\"a\":\"astring\", \"b\":10},{\"a\":\"astring2\", \"b\":20}]"; String someJson = "[{\"a\":\"astring\", \"b\":10},{\"a\":\"astring2\", \"b\":20}]";
String otherJson = "[{\"a\":\"otherString\", \"b\":10},{\"a\":\"astring2\", \"b\":20}]"; String otherJson = "[{\"a\":\"otherString\", \"b\":10},{\"a\":\"astring2\", \"b\":20}]";
Config config = mock(Config.class); Config config = mock(Config.class);
when(config.getProperty(eq(JSON_PROPERTY), anyString())).thenReturn(String.valueOf(someJson)); when(config.getProperty(eq(JSON_PROPERTY), anyString())).thenReturn(someJson);
when(config.getProperty(eq(OTHER_JSON_PROPERTY), anyString())).thenReturn(String.valueOf(otherJson)); when(config.getProperty(eq(OTHER_JSON_PROPERTY), anyString())).thenReturn(otherJson);
when(config.getProperty(eq("a"), anyString())).thenReturn(JSON_PROPERTY); when(config.getProperty(eq("a"), anyString())).thenReturn(JSON_PROPERTY);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config); mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config);
...@@ -301,13 +301,37 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -301,13 +301,37 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
TestJsonPropertyBean testJsonPropertyBean = context.getBean(TestJsonPropertyBean.class); TestJsonPropertyBean testJsonPropertyBean = context.getBean(TestJsonPropertyBean.class);
assertEquals(2, testJsonPropertyBean.getJsonBeanList().size()); assertEquals(2, testJsonPropertyBean.getJsonBeanList().size());
assertEquals("astring", testJsonPropertyBean.getJsonBeanList().get(0).a); assertEquals("astring", testJsonPropertyBean.getJsonBeanList().get(0).getA());
assertEquals("astring", testJsonPropertyBean.getEmbeddedJsonBeanList().get(0).a); assertEquals(10, testJsonPropertyBean.getJsonBeanList().get(0).getB());
assertEquals("otherString", testJsonPropertyBean.getOtherJsonBeanList().get(0).a); assertEquals("astring2", testJsonPropertyBean.getJsonBeanList().get(1).getA());
assertEquals(20, testJsonPropertyBean.getJsonBeanList().get(1).getB());
assertEquals(testJsonPropertyBean.getJsonBeanList(), testJsonPropertyBean.getEmbeddedJsonBeanList());
assertEquals("otherString", testJsonPropertyBean.getOtherJsonBeanList().get(0).getA());
assertEquals(10, testJsonPropertyBean.getOtherJsonBeanList().get(0).getB());
assertEquals("astring2", testJsonPropertyBean.getOtherJsonBeanList().get(1).getA());
assertEquals(20, testJsonPropertyBean.getOtherJsonBeanList().get(1).getB());
}
@Test(expected = BeanCreationException.class)
public void testApolloJsonValueWithInvalidJson() throws Exception {
String someInvalidJson = "someInvalidJson";
Config config = mock(Config.class);
when(config.getProperty(eq(JSON_PROPERTY), anyString())).thenReturn(someInvalidJson);
when(config.getProperty(eq(OTHER_JSON_PROPERTY), anyString())).thenReturn(someInvalidJson);
when(config.getProperty(eq("a"), anyString())).thenReturn(JSON_PROPERTY);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config);
new AnnotationConfigApplicationContext(AppConfig8.class).getBean(TestJsonPropertyBean.class);
} }
@Test(expected = BeanCreationException.class)
public void testApolloJsonValueWithNoPropertyValue() throws Exception {
Config config = mock(Config.class);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config);
new AnnotationConfigApplicationContext(AppConfig8.class);
}
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);
...@@ -395,12 +419,11 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -395,12 +419,11 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
static class AppConfig8 { static class AppConfig8 {
@Bean @Bean
TestJsonPropertyBean testJavaConfigBean4() { TestJsonPropertyBean testJavaConfigBean() {
return new TestJsonPropertyBean(); return new TestJsonPropertyBean();
} }
} }
@Component @Component
static class TestJavaConfigBean { static class TestJavaConfigBean {
@Value("${timeout:100}") @Value("${timeout:100}")
...@@ -476,12 +499,12 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -476,12 +499,12 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
static class TestJsonPropertyBean { static class TestJsonPropertyBean {
@ApolloJSONValue("${jsonProperty}") @ApolloJsonValue("${jsonProperty}")
private List<JsonBean> jsonBeanList; private List<JsonBean> jsonBeanList;
private List<JsonBean> otherJsonBeanList; private List<JsonBean> otherJsonBeanList;
@ApolloJSONValue("${${a}}") @ApolloJsonValue("${${a}}")
private List<JsonBean> embeddedJsonBeanList; private List<JsonBean> embeddedJsonBeanList;
...@@ -489,9 +512,8 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -489,9 +512,8 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
return jsonBeanList; return jsonBeanList;
} }
@ApolloJSONValue("${otherJsonProperty}") @ApolloJsonValue("${otherJsonProperty}")
public void setOtherJsonBeanList( public void setOtherJsonBeanList(List<JsonBean> otherJsonBeanList) {
List<JsonBean> otherJsonBeanList) {
this.otherJsonBeanList = otherJsonBeanList; this.otherJsonBeanList = otherJsonBeanList;
} }
...@@ -510,7 +532,7 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -510,7 +532,7 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
private String a; private String a;
private int b; private int b;
public String getA() { String getA() {
return a; return a;
} }
...@@ -518,12 +540,36 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest { ...@@ -518,12 +540,36 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
this.a = a; this.a = a;
} }
public int getB() { int getB() {
return b; return b;
} }
public void setB(int b) { public void setB(int b) {
this.b = b; this.b = b;
} }
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
JsonBean jsonBean = (JsonBean) o;
if (b != jsonBean.b) {
return false;
}
return a != null ? a.equals(jsonBean.a) : jsonBean.a == null;
}
@Override
public int hashCode() {
int result = a != null ? a.hashCode() : 0;
result = 31 * result + b;
return result;
}
} }
} }
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.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import com.google.gson.Gson;
import java.util.List; 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; private List<JsonBean> jsonBeans;
/**
* ApolloJsonValue annotated on fields example, the default value is specified as empty list - []
* <br />
* jsonBeanProperty=[{"someString":"hello","someInt":100},{"someString":"world!","someInt":200}]
*/
@ApolloJsonValue("${jsonBeanProperty:[]}")
private List<JsonBean> anotherJsonBeans;
@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); logger.info("updating batch, old value: {}, new value: {}", this.batch, batch);
...@@ -34,31 +38,33 @@ public class AnnotatedBean { ...@@ -34,31 +38,33 @@ public class AnnotatedBean {
this.timeout = timeout; this.timeout = timeout;
} }
/**
* ApolloJsonValue annotated on methods example, the default value is specified as empty list - []
* <br />
* jsonBeanProperty=[{"someString":"hello","someInt":100},{"someString":"world!","someInt":200}]
*/
@ApolloJsonValue("${jsonBeanProperty:[]}")
public void setJsonBeans(List<JsonBean> jsonBeans) {
logger.info("updating json beans, old value: {}, new value: {}", this.jsonBeans, jsonBeans);
this.jsonBeans = jsonBeans;
}
@Override @Override
public String toString() { public String toString() {
return String.format("[AnnotatedBean] timeout: %d, batch: %d, jsonBeans: %s", timeout, batch, new Gson().toJson(jsonBeans)); return String.format("[AnnotatedBean] timeout: %d, batch: %d, jsonBeans: %s", timeout, batch, jsonBeans);
} }
static class JsonBean{ private 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() { private String someString;
return b; private int someInt;
}
public void setB(int b) { @Override
this.b = b; public String toString() {
return "JsonBean{" +
"someString='" + someString + '\'' +
", someInt=" + someInt +
'}';
} }
} }
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册