Java Apollo是如何實(shí)現(xiàn)配置更新的
這篇文檔主要關(guān)注下配置修改后對(duì)應(yīng)的 Java 對(duì)象是如何更新,并不關(guān)注整體的配置改動(dòng)流程
所有代碼都來(lái)自 apollo-client 項(xiàng)目
更新流程
在 Apollo 控制臺(tái)進(jìn)行配置修改并發(fā)布后,對(duì)應(yīng)的 client 端拉取到更新后,會(huì)調(diào)用到 com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener#onChange 方法
在調(diào)用 onChange 會(huì)收到對(duì)應(yīng)的修改的配置信息 ConfigChangeEvent, 其中包含改動(dòng)的 key 和 value, 則改動(dòng)流程如下:
- 根據(jù)改動(dòng)的配置的 key 從 springValueRegistry 找到對(duì)應(yīng)的關(guān)聯(lián)到這個(gè) key 的 Spring Bean 信息,如果找不到則不處理
- 根據(jù)找到的 Spring Bean 信息,進(jìn)行對(duì)應(yīng)關(guān)聯(lián)配置的更新
在第二步中會(huì)判斷關(guān)聯(lián)配置是用過(guò)屬性關(guān)聯(lián)還是方法進(jìn)行關(guān)聯(lián)的,代碼如下
public void update(Object newVal) throws IllegalAccessException, InvocationTargetException {
if (isField()) {
injectField(newVal);
} else {
injectMethod(newVal);
}
}
在上面的問(wèn)題中,還有兩個(gè)問(wèn)題存疑
- 如何通過(guò) key 找到對(duì)應(yīng)的 Spring Bean 信息
- 如何將 Apollo 的配置值轉(zhuǎn)換為 Spring 的識(shí)別的值
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)起來(lái)
在 Spring 常見(jiàn)配置包括 2 種
public class ApiConfig {
// 1. 直接在 Field 是進(jìn)行注入
@Value("${feifei.appId}")
protected String appId;
protected String predUrl;
// 2. 在方法上進(jìn)行注入
@Value("${predUrl}")
public void setPredUrl(String predUrl) {
this.predUrl = predUrl;
}
}
在 Apollo 代碼中,通過(guò)實(shí)現(xiàn) BeanPostProcessor 接口來(lái)檢測(cè)所有的Spring Bean 的創(chuàng)建過(guò)程,在 Spring Bean 創(chuàng)建的過(guò)程中會(huì)調(diào)用對(duì)應(yīng)的 org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization 和 org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization 方法。
Apollo 通過(guò)在 Bean 生成過(guò)程中,檢測(cè) Bean 類(lèi)中屬性和方法是否存在 @Value 注解,如果存在,提出其中的 key, 其處理方法在 processField 和 processMethod 分別處理 Field 和 Method 中可能出現(xiàn)的 @Value 注解。如果存在注解則將對(duì)應(yīng)的信息存到 SpringValue 對(duì)應(yīng) springValueRegistry 全局對(duì)象中,方便在其它地方可以直接獲取。
在屬性除了通過(guò) @Value 注入,也可以用過(guò) xml 進(jìn)行配置,在這種情況通過(guò) processBeanPropertyValues 方法來(lái)處理
通過(guò)兩種處理方式就可以將 key 和對(duì)應(yīng)的 Spring Bean 信息關(guān)聯(lián)起來(lái)
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是如何實(shí)現(xiàn)配置更新的的詳細(xì)內(nèi)容,更多關(guān)于Java Apollo 配置更新的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java用自定義的類(lèi)作為HashMap的key值實(shí)例
下面小編就為大家?guī)?lái)一篇Java用自定義的類(lèi)作為HashMap的key值實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12
Java NumberFormat格式化float類(lèi)型的bug
今天小編就為大家分享一篇關(guān)于Java NumberFormat格式化float類(lèi)型的bug,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-10-10
java struts2學(xué)習(xí)筆記之線程安全
這篇文章主要為大家詳細(xì)介紹了java struts2學(xué)習(xí)筆記之線程安全,感興趣的朋友可以參考一下2016-04-04
Mybatis第三方PageHelper分頁(yè)插件的使用與原理
提到插件相信大家都知道,插件的存在主要是用來(lái)改變或者增強(qiáng)原有的功能,MyBatis中也一樣,下面這篇文章主要給大家介紹了關(guān)于Mybatis第三方PageHelper分頁(yè)插件的使用與原理,需要的朋友可以參考下2022-02-02
Spring Security基于json登錄實(shí)現(xiàn)過(guò)程詳解
這篇文章主要介紹了Spring Security基于json登錄實(shí)現(xiàn)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
springboot使用Thymeleaf報(bào)錯(cuò)常見(jiàn)的幾種解決方案
這篇文章主要介紹了springboot使用Thymeleaf報(bào)錯(cuò)常見(jiàn)的幾種解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
java 使用idea將工程打成jar并創(chuàng)建成exe文件類(lèi)型執(zhí)行的方法詳解
這篇文章主要介紹了java 使用idea將工程打成jar并創(chuàng)建成exe文件類(lèi)型執(zhí)行,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-09-09
java對(duì)同一個(gè)文件進(jìn)行讀寫(xiě)操作方法
在本篇文章里我們給大家詳細(xì)講述了java對(duì)同一個(gè)文件進(jìn)行讀寫(xiě)操作的方法和知識(shí)點(diǎn),需要的朋友們可以參考學(xué)習(xí)下。2018-10-10

