Commit f2d85126 by zhangzheng

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

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