Java Apollo是如何實現(xiàn)配置更新的
這篇文檔主要關(guān)注下配置修改后對應(yīng)的 Java 對象是如何更新,并不關(guān)注整體的配置改動流程
所有代碼都來自 apollo-client 項目
更新流程
在 Apollo 控制臺進行配置修改并發(fā)布后,對應(yīng)的 client 端拉取到更新后,會調(diào)用到 com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener#onChange 方法
在調(diào)用 onChange 會收到對應(yīng)的修改的配置信息 ConfigChangeEvent, 其中包含改動的 key 和 value, 則改動流程如下:
- 根據(jù)改動的配置的 key 從 springValueRegistry 找到對應(yīng)的關(guān)聯(lián)到這個 key 的 Spring Bean 信息,如果找不到則不處理
- 根據(jù)找到的 Spring Bean 信息,進行對應(yīng)關(guān)聯(lián)配置的更新
在第二步中會判斷關(guān)聯(lián)配置是用過屬性關(guān)聯(lián)還是方法進行關(guān)聯(lián)的,代碼如下
public void update(Object newVal) throws IllegalAccessException, InvocationTargetException { if (isField()) { injectField(newVal); } else { injectMethod(newVal); } }
在上面的問題中,還有兩個問題存疑
- 如何通過 key 找到對應(yīng)的 Spring Bean 信息
- 如何將 Apollo 的配置值轉(zhuǎn)換為 Spring 的識別的值
public class AutoUpdateConfigChangeListener implements ConfigChangeListener{ private static final Logger logger = LoggerFactory.getLogger(AutoUpdateConfigChangeListener.class); private final boolean typeConverterHasConvertIfNecessaryWithFieldParameter; private final Environment environment; private final ConfigurableBeanFactory beanFactory; private final TypeConverter typeConverter; private final PlaceholderHelper placeholderHelper; private final SpringValueRegistry springValueRegistry; private final Gson gson; public AutoUpdateConfigChangeListener(Environment environment, ConfigurableListableBeanFactory beanFactory){ this.typeConverterHasConvertIfNecessaryWithFieldParameter = testTypeConverterHasConvertIfNecessaryWithFieldParameter(); this.beanFactory = beanFactory; this.typeConverter = this.beanFactory.getTypeConverter(); this.environment = environment; this.placeholderHelper = SpringInjector.getInstance(PlaceholderHelper.class); this.springValueRegistry = SpringInjector.getInstance(SpringValueRegistry.class); this.gson = new Gson(); } @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 = springValueRegistry.get(beanFactory, key); if (targetValues == null || targetValues.isEmpty()) { continue; } // 2. update the value for (SpringValue val : targetValues) { updateSpringValue(val); } } } private void updateSpringValue(SpringValue springValue) { try { Object value = resolvePropertyValue(springValue); springValue.update(value); logger.info("Auto update apollo changed value successfully, new value: {}, {}", value, springValue); } 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) { // value will never be null, as @Value and @ApolloJsonValue will not allow that Object value = placeholderHelper .resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder()); if (springValue.isJson()) { value = parseJsonValue((String)value, springValue.getGenericType()); } else { if (springValue.isField()) { // org.springframework.beans.TypeConverter#convertIfNecessary(java.lang.Object, java.lang.Class, java.lang.reflect.Field) is available from Spring 3.2.0+ if (typeConverterHasConvertIfNecessaryWithFieldParameter) { value = this.typeConverter .convertIfNecessary(value, springValue.getTargetType(), springValue.getField()); } else { value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType()); } } else { value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), springValue.getMethodParameter()); } } return value; } private Object parseJsonValue(String json, Type targetType) { try { return gson.fromJson(json, targetType); } catch (Throwable ex) { logger.error("Parsing json '{}' to type {} failed!", json, targetType, ex); throw ex; } } private boolean testTypeConverterHasConvertIfNecessaryWithFieldParameter() { try { TypeConverter.class.getMethod("convertIfNecessary", Object.class, Class.class, Field.class); } catch (Throwable ex) { return false; } return true; } }
如何將配置 key 和 Spring Bean 關(guān)聯(lián)起來
在 Spring 常見配置包括 2 種
public class ApiConfig { // 1. 直接在 Field 是進行注入 @Value("${feifei.appId}") protected String appId; protected String predUrl; // 2. 在方法上進行注入 @Value("${predUrl}") public void setPredUrl(String predUrl) { this.predUrl = predUrl; } }
在 Apollo 代碼中,通過實現(xiàn) BeanPostProcessor
接口來檢測所有的Spring Bean 的創(chuàng)建過程,在 Spring Bean 創(chuàng)建的過程中會調(diào)用對應(yīng)的 org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization
和 org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization
方法。
Apollo 通過在 Bean 生成過程中,檢測 Bean 類中屬性和方法是否存在 @Value
注解,如果存在,提出其中的 key, 其處理方法在 processField
和 processMethod
分別處理 Field 和 Method 中可能出現(xiàn)的 @Value
注解。如果存在注解則將對應(yīng)的信息存到 SpringValue
對應(yīng) springValueRegistry
全局對象中,方便在其它地方可以直接獲取。
在屬性除了通過 @Value
注入,也可以用過 xml 進行配置,在這種情況通過 processBeanPropertyValues
方法來處理
通過兩種處理方式就可以將 key 和對應(yīng)的 Spring Bean 信息關(guān)聯(lián)起來
public class SpringValueProcessor extends ApolloProcessor implements BeanFactoryPostProcessor, BeanFactoryAware { private static final Logger logger = LoggerFactory.getLogger(SpringValueProcessor.class); private final ConfigUtil configUtil; private final PlaceholderHelper placeholderHelper; private final SpringValueRegistry springValueRegistry; private BeanFactory beanFactory; private Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions; public SpringValueProcessor() { configUtil = ApolloInjector.getInstance(ConfigUtil.class); placeholderHelper = SpringInjector.getInstance(PlaceholderHelper.class); springValueRegistry = SpringInjector.getInstance(SpringValueRegistry.class); beanName2SpringValueDefinitions = LinkedListMultimap.create(); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled() && beanFactory instanceof BeanDefinitionRegistry) { beanName2SpringValueDefinitions = SpringValueDefinitionProcessor .getBeanName2SpringValueDefinitions((BeanDefinitionRegistry) beanFactory); } } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) { super.postProcessBeforeInitialization(bean, beanName); processBeanPropertyValues(bean, beanName); } return bean; } @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()) { return; } for (String key : keys) { SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false); springValueRegistry.register(beanFactory, key, springValue); logger.debug("Monitoring {}", springValue); } } @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()); if (keys.isEmpty()) { return; } for (String key : keys) { SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method, false); springValueRegistry.register(beanFactory, key, springValue); logger.info("Monitoring {}", springValue); } } private void processBeanPropertyValues(Object bean, String beanName) { Collection<SpringValueDefinition> propertySpringValues = beanName2SpringValueDefinitions .get(beanName); if (propertySpringValues == null || propertySpringValues.isEmpty()) { return; } for (SpringValueDefinition definition : propertySpringValues) { try { PropertyDescriptor pd = BeanUtils .getPropertyDescriptor(bean.getClass(), definition.getPropertyName()); Method method = pd.getWriteMethod(); if (method == null) { continue; } SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(), bean, beanName, method, false); springValueRegistry.register(beanFactory, definition.getKey(), springValue); logger.debug("Monitoring {}", springValue); } catch (Throwable ex) { logger.error("Failed to enable auto update feature for {}.{}", bean.getClass(), definition.getPropertyName()); } } // clear beanName2SpringValueDefinitions.removeAll(beanName); } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } }
以上就是Java Apollo是如何實現(xiàn)配置更新的的詳細(xì)內(nèi)容,更多關(guān)于Java Apollo 配置更新的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java NumberFormat格式化float類型的bug
今天小編就為大家分享一篇關(guān)于Java NumberFormat格式化float類型的bug,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-10-10java struts2學(xué)習(xí)筆記之線程安全
這篇文章主要為大家詳細(xì)介紹了java struts2學(xué)習(xí)筆記之線程安全,感興趣的朋友可以參考一下2016-04-04Mybatis第三方PageHelper分頁插件的使用與原理
提到插件相信大家都知道,插件的存在主要是用來改變或者增強原有的功能,MyBatis中也一樣,下面這篇文章主要給大家介紹了關(guān)于Mybatis第三方PageHelper分頁插件的使用與原理,需要的朋友可以參考下2022-02-02Spring Security基于json登錄實現(xiàn)過程詳解
這篇文章主要介紹了Spring Security基于json登錄實現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-08-08springboot使用Thymeleaf報錯常見的幾種解決方案
這篇文章主要介紹了springboot使用Thymeleaf報錯常見的幾種解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11java 使用idea將工程打成jar并創(chuàng)建成exe文件類型執(zhí)行的方法詳解
這篇文章主要介紹了java 使用idea將工程打成jar并創(chuàng)建成exe文件類型執(zhí)行,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2020-09-09