From f2d85126918ec14eaf2c196771a5c945c4c5dd70 Mon Sep 17 00:00:00 2001 From: zhangzheng Date: Thu, 8 Mar 2018 16:13:46 +0800 Subject: [PATCH] =?UTF-8?q?1=20=E5=B0=86SpringValueProcessor=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E5=85=B3=E4=BA=8E=E8=87=AA=E5=8A=A8=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E9=80=BB=E8=BE=91=E6=8A=BD=E7=A6=BB=E5=88=B0=E4=BA=86?= =?UTF-8?q?AutoUpdateConfigChangeListener=E4=B8=AD=202=20=E6=8A=BD?= =?UTF-8?q?=E7=A6=BB=E4=BA=86=E4=B8=80=E4=B8=AA=E5=85=AC=E5=85=B1=E7=88=B6?= =?UTF-8?q?=E7=B1=BBApolloProcessor=EF=BC=8C=E7=94=A8=E6=9D=A5=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=8F=8D=E5=B0=84=E7=9B=B8=E5=85=B3=E7=9A=84=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E3=80=82=20ApolloJsonValueProcessor,ApolloAnnotationP?= =?UTF-8?q?rocessor,SpringValueProcessor=E9=83=BD=E7=BB=A7=E6=89=BF?= =?UTF-8?q?=E5=AE=83=203=20=E5=9C=A8PropertySourceProcessor=E4=B8=AD?= =?UTF-8?q?=E6=B3=A8=E5=86=8C=E8=87=AA=E5=8A=A8=E6=9B=B4=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E7=9B=91=E5=90=AC=E5=99=A8AutoUpdateConfigChangeListener=204?= =?UTF-8?q?=20SpringValue=E5=A2=9E=E5=8A=A0isJson=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=EF=BC=8C=E8=A1=A8=E7=A4=BA=E8=BF=99=E4=B8=AA=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E6=98=AFJson=E7=B1=BB=E5=9E=8B=E7=9A=84=205?= =?UTF-8?q?=20=E5=A2=9E=E5=8A=A0=E4=BA=86=E5=8D=95=E5=85=83=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../annotation/ApolloAnnotationProcessor.java | 102 +++----- .../annotation/ApolloConfigRegistrar.java | 4 + .../spring/annotation/ApolloJSONValue.java | 19 ++ .../annotation/ApolloJSONValueProcessor.java | 117 +++++++++ .../spring/annotation/ApolloProcessor.java | 77 ++++++ .../annotation/SpringValueProcessor.java | 238 ++++-------------- .../AutoUpdateConfigChangeListener.java | 151 +++++++++++ .../ConfigPropertySourcesProcessor.java | 5 + .../config/PropertySourcesProcessor.java | 9 + .../apollo/spring/property/SpringValue.java | 23 +- .../JavaConfigPlaceholderAutoUpdateTest.java | 41 ++- .../spring/JavaConfigPlaceholderTest.java | 100 +++++++- .../spring/common/bean/AnnotatedBean.java | 32 ++- 13 files changed, 649 insertions(+), 269 deletions(-) create mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloJSONValue.java create mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloJSONValueProcessor.java create mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloProcessor.java create mode 100644 apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/AutoUpdateConfigChangeListener.java diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java index e6fc7f9c2..fb6238cfa 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java @@ -1,89 +1,69 @@ 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; - } } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigRegistrar.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigRegistrar.java index af6ca19e2..d56e0ca2c 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigRegistrar.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloConfigRegistrar.java @@ -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); + } } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloJSONValue.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloJSONValue.java new file mode 100644 index 000000000..ddfc4fffe --- /dev/null +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloJSONValue.java @@ -0,0 +1,19 @@ +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(); + +} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloJSONValueProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloJSONValueProcessor.java new file mode 100644 index 000000000..36c3147e4 --- /dev/null +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloJSONValueProcessor.java @@ -0,0 +1,117 @@ +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 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 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; + } +} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloProcessor.java new file mode 100644 index 000000000..39a8442c1 --- /dev/null +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloProcessor.java @@ -0,0 +1,77 @@ +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 findAllField(Class clazz) { + final List 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 findAllMethod(Class clazz) { + final List res = new LinkedList<>(); + ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() { + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + res.add(method); + } + }); + return res; + } +} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/SpringValueProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/SpringValueProcessor.java index ff0d54f06..810f682df 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/SpringValueProcessor.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/SpringValueProcessor.java @@ -1,5 +1,6 @@ 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 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 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 declaredFields) { - for (Field field : declaredFields) { - // register @Value on field - Value value = field.getAnnotation(Value.class); - if (value == null) { - continue; - } - Set 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 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 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 keys = placeholderHelper.extractPlaceholderKeys(value.value()); + Set 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 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 findAllField(Class clazz) { - final List 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 findAllMethod(Class clazz) { - final List 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 keys = changeEvent.changedKeys(); - if (CollectionUtils.isEmpty(keys)) { - return; - } - for (String key : keys) { - // 1. check whether the changed key is relevant - Collection 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 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; - } } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/AutoUpdateConfigChangeListener.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/AutoUpdateConfigChangeListener.java new file mode 100644 index 000000000..842c50d78 --- /dev/null +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/AutoUpdateConfigChangeListener.java @@ -0,0 +1,151 @@ +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 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 keys = changeEvent.changedKeys(); + if (CollectionUtils.isEmpty(keys)) { + return; + } + for (String key : keys) { + // 1. check whether the changed key is relevant + Collection 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; + } + + +} diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySourcesProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySourcesProcessor.java index f6666fb94..ed55e9ba2 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySourcesProcessor.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/ConfigPropertySourcesProcessor.java @@ -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); + + } } diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/PropertySourcesProcessor.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/PropertySourcesProcessor.java index d87094c01..b6640f50a 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/PropertySourcesProcessor.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/config/PropertySourcesProcessor.java @@ -1,6 +1,7 @@ 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 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 configPropertySources = configPropertySourceFactory.getAllConfigPropertySources(); + for (ConfigPropertySource configPropertySource : configPropertySources) { + configPropertySource.addChangeListener(new AutoUpdateConfigChangeListener(environment, beanFactory)); + } + } } protected void initializePropertySources() { diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValue.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValue.java index 956e993e8..b5db8b036 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValue.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/spring/property/SpringValue.java @@ -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()) { diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigPlaceholderAutoUpdateTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigPlaceholderAutoUpdateTest.java index f5cc4c823..f09de67fd 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigPlaceholderAutoUpdateTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigPlaceholderAutoUpdateTest.java @@ -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 jsonBeanList; + + public List 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 jsonBeanList; + public int getIntProperty() { return intProperty; } @@ -1003,5 +1022,9 @@ public class JavaConfigPlaceholderAutoUpdateTest extends AbstractSpringIntegrati public Date getDateProperty() { return dateProperty; } + + public List getJsonBeanList() { + return jsonBeanList; + } } } diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigPlaceholderTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigPlaceholderTest.java index 495238d9a..3c08cac49 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigPlaceholderTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/spring/JavaConfigPlaceholderTest.java @@ -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 jsonBeanList; + + private List otherJsonBeanList; + + @ApolloJSONValue("${${a}}") + private List embeddedJsonBeanList; + + + public List getJsonBeanList() { + return jsonBeanList; + } + + @ApolloJSONValue("${otherJsonProperty}") + public void setOtherJsonBeanList( + List otherJsonBeanList) { + this.otherJsonBeanList = otherJsonBeanList; + } + + public List getOtherJsonBeanList() { + return otherJsonBeanList; + } + + public List 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; + } + } } diff --git a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/common/bean/AnnotatedBean.java b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/common/bean/AnnotatedBean.java index 6b7bf6d44..137022490 100644 --- a/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/common/bean/AnnotatedBean.java +++ b/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/common/bean/AnnotatedBean.java @@ -1,19 +1,26 @@ 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 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; + } + } + + } -- GitLab