Commit c9612f6d by nobodyiam

minor refactor and add more ut

parent f2d85126
......@@ -9,6 +9,7 @@ import com.ctrip.framework.apollo.spi.DefaultConfigFactoryManager;
import com.ctrip.framework.apollo.spi.DefaultConfigRegistry;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySourceFactory;
import com.ctrip.framework.apollo.spring.property.PlaceholderHelper;
import com.ctrip.framework.apollo.spring.property.SpringValueRegistry;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.ctrip.framework.apollo.util.http.HttpUtil;
......@@ -64,6 +65,7 @@ public class DefaultInjector implements Injector {
bind(RemoteConfigLongPollService.class).in(Singleton.class);
bind(PlaceholderHelper.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 {
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, bean, config);
}
@Override
protected void processMethod(final Object bean, String beanName, final Method method) {
ApolloConfigChangeListener annotation = AnnotationUtils
.findAnnotation(method, ApolloConfigChangeListener.class);
if (annotation == null) {
......@@ -53,17 +51,17 @@ public class ApolloAnnotationProcessor extends ApolloProcessor {
ReflectionUtils.makeAccessible(method);
String[] namespaces = annotation.value();
ConfigChangeListener configChangeListener = new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
ReflectionUtils.invokeMethod(method, bean, changeEvent);
}
};
for (String namespace : namespaces) {
Config config = ConfigService.getConfig(namespace);
config.addChangeListener(new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
ReflectionUtils.invokeMethod(method, bean, changeEvent);
}
});
config.addChangeListener(configChangeListener);
}
}
}
......@@ -35,8 +35,7 @@ public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar {
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(), SpringValueDefinitionProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJSONValueProcessor.class.getName(),
ApolloJSONValueProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(),
ApolloJsonValueProcessor.class);
}
}
......@@ -7,13 +7,28 @@ import java.lang.annotation.RetentionPolicy;
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)
@Target({ElementType.FIELD, ElementType.METHOD})
@Documented
public @interface ApolloJSONValue {
public @interface ApolloJsonValue {
/**
* The actual value expression: e.g. "${someJsonPropertyKey:someDefaultValue}".
*/
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.AutoUpdateConfigChangeListener;
import com.ctrip.framework.apollo.spring.property.PlaceholderHelper;
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.foundation.internals.Utils;
import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
......@@ -18,96 +19,103 @@ 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;
import org.springframework.util.ReflectionUtils;
/**
* 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 PlaceholderHelper placeholderHelper;
private final SpringValueRegistry springValueRegistry;
private ConfigurableBeanFactory beanFactory;
public ApolloJSONValueProcessor() {
public ApolloJsonValueProcessor() {
configUtil = ApolloInjector.getInstance(ConfigUtil.class);
placeholderHelper = ApolloInjector.getInstance(PlaceholderHelper.class);
springValueRegistry = ApolloInjector.getInstance(SpringValueRegistry.class);
}
@Override
protected void processField(Object bean,String beanName, Field field) {
ApolloJSONValue apolloJSONValue = AnnotationUtils.getAnnotation(field, ApolloJSONValue.class);
if (apolloJSONValue == null) {
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);
String placeholder = apolloJsonValue.value();
Object propertyValue = placeholderHelper
.resolvePropertyValue(beanFactory, beanName, placeholder);
// propertyValue will never be null, as @ApolloJsonValue will not allow that
if (!(propertyValue instanceof String)) {
return;
}
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
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);
if (apolloJSONValue == null) {
Object propertyValue = placeholderHelper
.resolvePropertyValue(beanFactory, beanName, placeHolder);
// propertyValue will never be null, as @ApolloJsonValue will not allow that
if (!(propertyValue instanceof String)) {
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);
}
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);
boolean accessible = method.isAccessible();
method.setAccessible(true);
ReflectionUtils.invokeMethod(method, bean, parseJsonValue((String)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);
springValueRegistry.register(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;
private Object parseJsonValue(String json, Type targetType) {
try {
return gson.fromJson(json, targetType);
} catch (Throwable ex) {
logger.error("Parsing json '{}' to type {} failed!", json, targetType, ex);
throw ex;
}
}
@Override
......
......@@ -35,21 +35,18 @@ public abstract class ApolloProcessor implements BeanPostProcessor, PriorityOrde
/**
* 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() {
//make it as late as possible
return Ordered.LOWEST_PRECEDENCE;
}
......
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.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySource;
import com.ctrip.framework.apollo.spring.config.ConfigPropertySourceFactory;
import com.ctrip.framework.apollo.spring.property.PlaceholderHelper;
import com.ctrip.framework.apollo.spring.property.SpringValue;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinition;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
/**
* Spring value processor of field or method which has @Value and xml config placeholders.
......@@ -53,26 +30,29 @@ import com.google.common.collect.Multimap;
* @author github.com/zhegexiaohuozi seimimaster@gmail.com
* @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 final ConfigUtil configUtil;
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() {
configUtil = ApolloInjector.getInstance(ConfigUtil.class);
placeholderHelper = ApolloInjector.getInstance(PlaceholderHelper.class);
springValueRegistry = ApolloInjector.getInstance(SpringValueRegistry.class);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
beanName2SpringValueDefinitions = SpringValueDefinitionProcessor.getBeanName2SpringValueDefinitions();
beanName2SpringValueDefinitions = SpringValueDefinitionProcessor
.getBeanName2SpringValueDefinitions();
}
}
......@@ -102,7 +82,7 @@ public class SpringValueProcessor extends ApolloProcessor implements BeanFactor
for (String key : keys) {
SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
AutoUpdateConfigChangeListener.monitor.put(key, springValue);
springValueRegistry.register(key, springValue);
logger.debug("Monitoring {}", springValue);
}
}
......@@ -132,13 +112,12 @@ public class SpringValueProcessor extends ApolloProcessor implements BeanFactor
for (String key : keys) {
SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method, false);
AutoUpdateConfigChangeListener.monitor.put(key, springValue);
springValueRegistry.register(key, springValue);
logger.debug("Monitoring {}", springValue);
}
}
private void processBeanPropertyValues(Object bean, String beanName) {
Collection<SpringValueDefinition> propertySpringValues = beanName2SpringValueDefinitions
.get(beanName);
......@@ -156,7 +135,7 @@ public class SpringValueProcessor extends ApolloProcessor implements BeanFactor
}
SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(),
bean, beanName, method, false);
AutoUpdateConfigChangeListener.monitor.put(definition.getKey(), springValue);
springValueRegistry.register(definition.getKey(), springValue);
logger.debug("Monitoring {}", springValue);
} catch (Throwable ex) {
logger.error("Failed to enable auto update feature for {}.{}", bean.getClass(),
......
......@@ -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.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.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
......@@ -26,8 +26,8 @@ public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
ApolloAnnotationProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJSONValueProcessor.class.getName(),
ApolloJSONValueProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(),
ApolloJsonValueProcessor.class);
processSpringValueDefinition(registry);
}
......@@ -41,7 +41,5 @@ public class ConfigPropertySourcesProcessor extends PropertySourcesProcessor
SpringValueDefinitionProcessor springValueDefinitionProcessor = new SpringValueDefinitionProcessor();
springValueDefinitionProcessor.postProcessBeanDefinitionRegistry(registry);
}
}
package com.ctrip.framework.apollo.spring.config;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.LinkedHashMultimap;
......@@ -10,6 +11,7 @@ import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
......@@ -35,6 +37,7 @@ import java.util.Iterator;
*/
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {
private static final Multimap<Integer, String> NAMESPACE_NAMES = LinkedHashMultimap.create();
private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false);
private final ConfigPropertySourceFactory configPropertySourceFactory = ApolloInjector
.getInstance(ConfigPropertySourceFactory.class);
......@@ -47,16 +50,14 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
initializePropertySources();
if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources();
for (ConfigPropertySource configPropertySource : configPropertySources) {
configPropertySource.addChangeListener(new AutoUpdateConfigChangeListener(environment, beanFactory));
}
if (INITIALIZED.compareAndSet(false, true)) {
initializePropertySources();
initializeAutoUpdatePropertiesFeature(beanFactory);
}
}
protected void initializePropertySources() {
private void initializePropertySources() {
if (environment.getPropertySources().contains(PropertySourcesConstants.APOLLO_PROPERTY_SOURCE_NAME)) {
//already initialized
return;
......@@ -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
public void setEnvironment(Environment environment) {
//it is safe enough to cast as all known environment is derived from ConfigurableEnvironment
......@@ -95,6 +110,7 @@ public class PropertySourcesProcessor implements BeanFactoryPostProcessor, Envir
//only for test
private static void reset() {
NAMESPACE_NAMES.clear();
INITIALIZED.set(false);
}
@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.build.ApolloInjector;
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;
......@@ -16,11 +14,8 @@ 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.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;
......@@ -28,24 +23,26 @@ 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;
private final boolean typeConverterHasConvertIfNecessaryWithFieldParameter;
private final Environment environment;
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.environment = environment;
this.placeholderHelper = ApolloInjector.getInstance(PlaceholderHelper.class);
this.springValueRegistry = ApolloInjector.getInstance(SpringValueRegistry.class);
this.gson = new Gson();
}
@Override
public void onChange(ConfigChangeEvent changeEvent) {
Set<String> keys = changeEvent.changedKeys();
......@@ -54,7 +51,7 @@ public class AutoUpdateConfigChangeListener implements ConfigChangeListener{
}
for (String key : keys) {
// 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()) {
continue;
}
......@@ -67,28 +64,11 @@ public class AutoUpdateConfigChangeListener implements ConfigChangeListener{
// 3. update the value
for (SpringValue val : targetValues) {
if(val.isJson()){
updateJsonValue(val);
}else{
updateSpringValue(val);
}
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);
......@@ -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)
*/
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);
// value will never be null, as @Value and @ApolloJsonValue will not allow that
Object value = placeholderHelper
.resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder());
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());
if (springValue.isJson()) {
value = parseJsonValue((String)value, springValue.getGenericType());
} else {
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());
value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType(),
springValue.getMethodParameter());
}
} 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;
private Object parseJsonValue(String json, Type targetType) {
try {
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() {
......@@ -146,6 +128,4 @@ public class AutoUpdateConfigChangeListener implements ConfigChangeListener{
return true;
}
}
......@@ -4,18 +4,15 @@ import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import java.util.Set;
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;
/**
* 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>
* Placeholder helper functions.
*/
public class PlaceholderHelper {
......@@ -26,6 +23,45 @@ public class PlaceholderHelper {
private static final String EXPRESSION_PREFIX = "#{";
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) {
Set<String> placeholderKeys = Sets.newHashSet();
......
......@@ -31,9 +31,9 @@ public class SpringValue {
this.key = key;
this.placeholder = placeholder;
this.targetType = field.getType();
this.isJson = isJson;
if(isJson){
this.genericType = field.getGenericType();
this.isJson = isJson;
}
}
......@@ -45,9 +45,9 @@ public class SpringValue {
this.placeholder = placeholder;
Class<?>[] paramTps = method.getParameterTypes();
this.targetType = paramTps[0];
this.isJson = isJson;
if(isJson){
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 {
public static void reset() {
classMap.clear();
classTable.clear();
delegate = null;
}
}
......@@ -5,6 +5,7 @@ import static org.mockito.Mockito.when;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.internals.ConfigRepository;
import com.ctrip.framework.apollo.internals.DefaultInjector;
import com.ctrip.framework.apollo.internals.SimpleConfig;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
import com.ctrip.framework.apollo.util.ConfigUtil;
......@@ -119,6 +120,7 @@ public abstract class AbstractSpringIntegrationTest {
ReflectionUtils.invokeMethod(CONFIG_SERVICE_RESET, null);
MockInjector.reset();
MockInjector.setInstance(ConfigManager.class, new MockConfigManager());
MockInjector.setDelegate(new DefaultInjector());
}
protected static void doTearDown() {
......
......@@ -2,13 +2,14 @@ package com.ctrip.framework.apollo.spring;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
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.ApolloJsonValue;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.common.primitives.Ints;
......@@ -626,7 +627,7 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
String someString = "someString";
String someNewString = "someNewString";
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";
Date someDate = assembleDate(2018, 2, 23, 20, 1, 2, 123);
......@@ -664,6 +665,7 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
assertEquals(someString, bean.getStringProperty());
assertEquals(someDate, bean.getDateProperty());
assertEquals("astring", bean.getJsonBeanList().get(0).getA());
assertEquals(10, bean.getJsonBeanList().get(0).getB());
Properties newProperties = new Properties();
newProperties.setProperty("intProperty", String.valueOf(someNewInt));
......@@ -694,6 +696,91 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
assertEquals(someNewString, bean.getStringProperty());
assertEquals(someNewDate, bean.getDateProperty());
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
......@@ -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 {
@Value("${timeout:100}")
......@@ -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 {
@Value("${intProperty}")
......@@ -980,7 +1078,7 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
@Value("#{new java.text.SimpleDateFormat('${dateFormat}').parse('${dateProperty}')}")
private Date dateProperty;
@ApolloJSONValue("${jsonProperty}")
@ApolloJsonValue("${jsonProperty}")
private List<JsonBean> jsonBeanList;
public int getIntProperty() {
......@@ -1027,4 +1125,25 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati
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;
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.ApolloJsonValue;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import java.util.List;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
......@@ -284,15 +285,14 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
assertEquals(someValue, bean.getNestedProperty());
}
@Test
public void testJsonDeserialization() {
public void testApolloJsonValue() {
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(JSON_PROPERTY), anyString())).thenReturn(someJson);
when(config.getProperty(eq(OTHER_JSON_PROPERTY), anyString())).thenReturn(otherJson);
when(config.getProperty(eq("a"), anyString())).thenReturn(JSON_PROPERTY);
mockConfig(ConfigConsts.NAMESPACE_APPLICATION, config);
......@@ -301,13 +301,37 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
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);
assertEquals("astring", testJsonPropertyBean.getJsonBeanList().get(0).getA());
assertEquals(10, testJsonPropertyBean.getJsonBeanList().get(0).getB());
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) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(annotatedClasses);
......@@ -395,12 +419,11 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
static class AppConfig8 {
@Bean
TestJsonPropertyBean testJavaConfigBean4() {
TestJsonPropertyBean testJavaConfigBean() {
return new TestJsonPropertyBean();
}
}
@Component
static class TestJavaConfigBean {
@Value("${timeout:100}")
......@@ -476,12 +499,12 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
static class TestJsonPropertyBean {
@ApolloJSONValue("${jsonProperty}")
@ApolloJsonValue("${jsonProperty}")
private List<JsonBean> jsonBeanList;
private List<JsonBean> otherJsonBeanList;
@ApolloJSONValue("${${a}}")
@ApolloJsonValue("${${a}}")
private List<JsonBean> embeddedJsonBeanList;
......@@ -489,9 +512,8 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
return jsonBeanList;
}
@ApolloJSONValue("${otherJsonProperty}")
public void setOtherJsonBeanList(
List<JsonBean> otherJsonBeanList) {
@ApolloJsonValue("${otherJsonProperty}")
public void setOtherJsonBeanList(List<JsonBean> otherJsonBeanList) {
this.otherJsonBeanList = otherJsonBeanList;
}
......@@ -510,7 +532,7 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
private String a;
private int b;
public String getA() {
String getA() {
return a;
}
......@@ -518,12 +540,36 @@ public class JavaConfigPlaceholderTest extends AbstractSpringIntegrationTest {
this.a = a;
}
public int getB() {
int getB() {
return b;
}
public void setB(int 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;
import com.ctrip.framework.apollo.spring.annotation.ApolloJSONValue;
import com.google.gson.Gson;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RefreshScope
@Component("annotatedBean")
public class AnnotatedBean {
private static final Logger logger = LoggerFactory.getLogger(AnnotatedBean.class);
private int timeout;
private int batch;
@ApolloJSONValue("${objectList}")
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}")
public void setBatch(int batch) {
logger.info("updating batch, old value: {}, new value: {}", this.batch, batch);
......@@ -34,31 +38,33 @@ public class AnnotatedBean {
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
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 String a;
private int b;
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
private static class JsonBean{
public int getB() {
return b;
}
private String someString;
private int someInt;
public void setB(int b) {
this.b = b;
@Override
public String toString() {
return "JsonBean{" +
"someString='" + someString + '\'' +
", someInt=" + someInt +
'}';
}
}
}
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