ShihLei 阅读(33) 评论(0)

 

(编写不易,转载请注明: )

 

1 背景

Apollo(阿波罗)是携程框架部门研发的分布式配置中心,现在已经众多互联网公司普及。

 

SpringBoot集成ApolloClient配置中心,在中心更改,本地配置也会更新,即热更新。

 

项目实践中发现,公司用的版本0.9,居然不支持热更新,需要手动写Listener监听变化,网上查看,人更新要到1.0以后才支持。升级apollo-client版本到1.0不好使。

 

于是干脆自己写了个热更新组件,用来支持@Value热更新,及@ApolloJsonValue功能,提升开发效率。

 

注:

由于这个程序注定要淘汰,所以目前只支持Bean的成员变量更新,有条件的建议整体升级Apollo

 

2 代码实现

2.1 maven版本

 

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.4.RELEASE</version>
    <relativePath/>
</parent>

<dependencies>
	<dependency>
	    <groupId>com.ctrip.framework.apollo</groupId>
	    <artifactId>apollo-client</artifactId>
	    <version>0.9.2.3</version>
	</dependency>

	<dependency>
	    <groupId>com.google.guava</groupId>
	    <artifactId>guava</artifactId>
	    <version>${guava.version}</version>
	</dependency>
	<dependency>
	    <groupId>org.projectlombok</groupId>
	    <artifactId>lombok</artifactId>
	    <version>${lombok.version}</version>
	</dependency>
	<dependency>
	    <groupId>com.alibaba</groupId>
	    <artifactId>fastjson</artifactId>
	    <version>${fastjson.version}</version>
	</dependency>
	<dependency>
	    <groupId>mons</groupId>
	    <artifactId>commons-lang3</artifactId>
	    <version>${commons-lang3.version}</version>
	</dependency>

	<dependency>
	    <groupId>mons</groupId>
	    <artifactId>commons-collections4</artifactId>
	    <version>${commons-collections4.version}</version>
	</dependency>
</dependencies>

 

 2.2  程序

(1)@ApolloJsonValue

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApolloJsonValue {
    String value();

    String namespace() default ConfigConsts.NAMESPACE_APPLICATION;
}

 

 

(2)ApolloConfig

@Slf4j
@Component
@EnableApolloConfig
public class ApolloConfig implements BeanPostProcessor {

    public static final String PLACEHOLDER_PREFIX = "${";

    public static final String PLACEHOLDER_SUFFIX = "}";

    public static final String VALUE_SEPARATOR = ":";

    private Multimap<String, ValuedField> valuedFields = HashMultimap.create();

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 读取 @value
        Class<?> clazz = bean.getClass();

        processFields(bean, clazz.getDeclaredFields());

        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @ApolloConfigChangeListener(ConfigConsts.NAMESPACE_APPLICATION)
    private void onChange(ConfigChangeEvent changeEvent) {
        changeEvent.changedKeys().forEach(
                key -> {
                    Collection<ValuedField> sameNameValuedFields = valuedFields.get(key);
                    if (CollectionUtils.isEmpty(sameNameValuedFields)) {
                        return;
                    }

                    valuedFields.get(key)
                            .forEach(valuedField -> {
                                ConfigChange configChange = changeEvent.getChange(key);

                                String strValue = configChange.getNewValue();

                                Object propertyValue = null;
                                if (valuedField.isJson) {
                                    propertyValue = pareseJsonValue(strValue, valuedField.field.getGenericType());
                                } else {
                                    propertyValue = parseValue(strValue, valuedField.field.getType());
                                }

                                ReflectionUtils.makeAccessible(valuedField.field);
                                ReflectionUtils.setField(valuedField.field, valuedField.bean, propertyValue);

                                log.info("[config]:{}", configChange);
                            });
                }
        );
    }

    private Object parseValue(String strValue, Class type) {
        if (type == Integer.class || type == int.class) {
            return Integer.parseInt(strValue);
        } else if (type == String.class) {
            return strValue;
        } else if (type == Long.class || type == long.class) {
            return Long.parseLong(strValue);
        } else if (type == Double.class || type == double.class) {
            return Double.parseDouble(strValue);
        } else if (type == Boolean.class || type == boolean.class) {
            return Boolean.parseBoolean(strValue);
        } else if (type == Float.class || type == float.class) {
            return Float.parseFloat(strValue);
        } else {
            throw new RuntimeException("not support:" + type);
        }
    }

    private void processFields(Object bean, Field[] declaredFields) {
        for (Field field : declaredFields) {
            processValue(bean, field);
            processApolloJsonValue(bean, field);
        }
    }

    private void processValue(Object bean, Field field) {
        Value valueAnnotation = AnnotationUtils.getAnnotation(field, Value.class);
        if (Objects.isNull(valueAnnotation)) {
            return;
        }

        String placeHolder = extractPlaceHoler(valueAnnotation.value());

        if (StringUtils.contains(placeHolder, VALUE_SEPARATOR)) {
            placeHolder = StringUtils.substringBefore(placeHolder, VALUE_SEPARATOR);
        }

        valuedFields.put(placeHolder, new ValuedField(bean, field, false));
    }

    private void processApolloJsonValue(Object bean, Field field) {
        ApolloJsonValue apolloJsonValueAnnotation = AnnotationUtils.getAnnotation(field, ApolloJsonValue.class);
        if (Objects.isNull(apolloJsonValueAnnotation)) {
            return;
        }

        String placeHolder = extractPlaceHoler(apolloJsonValueAnnotation.value());

        String propertyName = placeHolder;
        String defaultValue = null;
        if (StringUtils.contains(placeHolder, VALUE_SEPARATOR)) {
            propertyName = StringUtils.substringBefore(placeHolder, VALUE_SEPARATOR);
            defaultValue = StringUtils.substringAfter(placeHolder, VALUE_SEPARATOR);
        }

        // read apollo
        Config config = ConfigService.getConfig(apolloJsonValueAnnotation.namespace());
        String apolloJsonValue = config.getProperty(propertyName, "");

        Object propertyValue = null;
        if (StringUtils.isNotBlank(apolloJsonValue)) {
            propertyValue = pareseJsonValue(apolloJsonValue, field.getGenericType());
        } else {
            propertyValue = pareseJsonValue(defaultValue, field.getGenericType());
        }

        // set value
        ReflectionUtils.makeAccessible(field);
        ReflectionUtils.setField(field, bean, propertyValue);

        valuedFields.put(propertyName, new ValuedField(bean, field, true));
    }

    private Object pareseJsonValue(String json, Type type) {
        if (StringUtils.isBlank(json)) {
            return null;
        }
        return JSON.parseObject(json, type);
    }

    static private String extractPlaceHoler(String text) {
        if (!StringUtils.startsWith(text, PLACEHOLDER_PREFIX)) {
            return text;
        }

        if (!StringUtils.endsWith(text, PLACEHOLDER_SUFFIX)) {
            return text;
        }

        text = StringUtils.substringAfter(text, PLACEHOLDER_PREFIX);
        text = StringUtils.substringBeforeLast(text, PLACEHOLDER_SUFFIX);
        return text;
    }


    @AllArgsConstructor
    static class ValuedField {
        private Object bean;
        private Field field;
        private boolean isJson;
    }

}