知識點:如何執(zhí)行被注解
ApolloConfigChangeListener
標(biāo)注的方法
一憔足、起因
事情的故事的是這個樣子的,前面的文章我們自定義了一個starter①酪耕,并且集成了Apollo导梆,可以自動從Apollo中獲取配置,但是我們發(fā)現(xiàn)它沒辦法保證在Apollo修改了配置之后,同步更新到本地看尼。我們找到官方的WIKI介紹②递鹉,我用人話重新組織了下他們的語言:我們“不支持@ConfigurationProperties
自動注入,需要自己寫點東西才行藏斩,具體是啥躏结,想知道直接查看附錄的超鏈接。
二狰域、方案
根據(jù)附錄鏈接媳拴,我很快定位并寫了測試方法,很快....真的很快兆览!代碼如下屈溉,經(jīng)過測試發(fā)現(xiàn),隨時修改抬探,隨時變化了语婴,達到了我想要的效果
package com.example.fg;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @author cattle - 稻草鳥人
* @date 2020/4/14 下午2:01
*/
@Slf4j
@Component
public class ApolloRefreshListener implements ApplicationContextAware {
private ApplicationContext applicationContext;
@ApolloConfigChangeListener(value = {"application","promotion"})
private void onChange(ConfigChangeEvent changeEvent) {
for (String key : changeEvent.changedKeys()) {
ConfigChange change = changeEvent.getChange(key);
log.info("change - {}", change.toString());
}
this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
但是....事情沒完淫奔!
從上面的代碼我們發(fā)現(xiàn)@ApolloConfigChangeListener
注解中的value寫死了,這可愁死我了坑填,我可不想要寫死蚓耽,為啥呢?這話可能扯的有點遠疑枯,涉及到我對軟件架構(gòu)的思路,以后有機會寫寫「攘。總之,我不想寫死憋他。所以我就開始了翻看apollo-client源碼孩饼,這才會有了這篇文章
三、學(xué)習(xí)
1)執(zhí)行過程
跟著@ApolloConfigChangeListener
經(jīng)過一番云雨...不對竹挡,一頓操作镀娶,讓我找到了它的祖宗,大概明白了該注解到底是怎么起作用的揪罕。下面我大概說明一下apollo-client整個工程的啟動順序
ApolloApplicationContextInitializer實現(xiàn)了ApplicationContextInitializer接口用于初始化一些內(nèi)容梯码。
在初始化之前會先去判斷apollo.bootstrap.enabled
是否為true
然后會獲取apollo.bootstrap.namespaces
配置的namespace并分別獲取到所有namespaces的Config對象
for (String namespace : namespaceList) {
Config config = ConfigService.getConfig(namespace);
.....
}
下面我貼一下獲取Config執(zhí)行的代碼片段
ConfigService
public static Config getConfig(String namespace) {
return s_instance.getManager().getConfig(namespace);
}
DefaultConfigManager
public Config getConfig(String namespace) {
Config config = m_configs.get(namespace);
if (config == null) {
synchronized (this) {
config = m_configs.get(namespace);
if (config == null) {
ConfigFactory factory = m_factoryManager.getFactory(namespace);
config = factory.create(namespace);
m_configs.put(namespace, config);
}
}
}
return config;
}
DefaultConfigFactory
public Config create(String namespace) {
ConfigFileFormat format = determineFileFormat(namespace);
if (ConfigFileFormat.isPropertiesCompatible(format)) {
return new DefaultConfig(namespace, createPropertiesCompatibleFileConfigRepository(namespace, format));
}
return new DefaultConfig(namespace, createLocalConfigRepository(namespace));
}
DefaultConfig
public DefaultConfig(String namespace, ConfigRepository configRepository) {
m_namespace = namespace;
m_resourceProperties = loadFromResource(m_namespace);
m_configRepository = configRepository;
m_configProperties = new AtomicReference<>();
m_warnLogRateLimiter = RateLimiter.create(0.017); // 1 warning log output per minute
initialize();
}
private void initialize() {
try {
updateConfig(m_configRepository.getConfig(), m_configRepository.getSourceType());
} catch (Throwable ex) {
Tracer.logError(ex);
logger.warn("Init Apollo Local Config failed - namespace: {}, reason: {}.",
m_namespace, ExceptionUtil.getDetailMessage(ex));
} finally {
//register the change listener no matter config repository is working or not
//so that whenever config repository is recovered, config could get changed
m_configRepository.addChangeListener(this);
}
}
從上面的代碼我們知道ApolloApplicationContextInitializer啟動的時候獲取了一個或者多個DefaultConfig。DefaultConfig再初始化的時候m_configRepository.addChangeListener(this);
好啰。這段代碼成功的引起的我的注意轩娶,就像一個仙女從你面前路過,就算是直男也應(yīng)該有所反應(yīng)框往,除非他在打游戲鳄抒!這段代碼成功的將當(dāng)前DefaultConfig對象加入到了抽象類AbstractConfigRepository
中RepositoryChangeListener
的集合中,
我們仔細觀察一下AbstractConfigRepository
的幾個方法,它基本上就是一個標(biāo)準的模板方法模式靶斫Α瓤鼻!通過調(diào)用trySync()方法執(zhí)行sync(),sync()是抽象方法說明它是由子類實現(xiàn)的闹司。
[圖片上傳失敗...(image-5b8c3a-1587292509810)]
這里娱仔,我們介紹下RemoteConifgRepository
。它是在什么時候初始化的呢游桩,我們繼續(xù)擼...發(fā)現(xiàn)DefaultConfigFactory在new DefaultConfig(namespace, createLocalConfigRepository(namespace))
時createLocalConfigRepository(namespace)里面實例化一個RemoteConifgRepository
牲迫。
OK,到這里我們簡單在總結(jié)下過程借卧,首先項目啟動的時候初始化了RemoteConifgRepository
然后初始化DefaultConfig
盹憎,并且將DefaultConfig
加入到了RepositoryChangeListener
的集合中。RemoteConfigRepository
的初始化方法很有意思铐刘,我們注意到它的構(gòu)造器有如下三個方法都會執(zhí)行trySync(),而trySync()方法會執(zhí)行sync(),sync()方法則會執(zhí)行fireRepositoryChange
然后執(zhí)行onRepositoryChange
再執(zhí)行fireConfigChange
最后執(zhí)行ConfigChangeListener
的onChange
方法.....
public RemoteConfigRepository(String namespace) {
....
//啟動時執(zhí)行一次
this.trySync();
//定時執(zhí)行
this.schedulePeriodicRefresh();
//長連接拉取
this.scheduleLongPollingRefresh();
}
終于到正題了陪每,到底是哪個ConfigChangeListener
執(zhí)行onChange
方法,onChange
方法的參數(shù)ConfigChangeEvent
又是怎么得到的呢
2)添加ConfigChangeListener
在步驟一中镰吵,我們看到了在自定義的onChange方法上增加了一個ApolloConfigChangeListener
注解檩禾,那么這個注解是如何生效并且添加ConfigChangeListener的呢
它是通過Service Provider Interface(SPI)機制,實現(xiàn)了一個DefaultApolloConfigRegistrarHelper
疤祭,這里我們關(guān)注ApolloAnnotationProcessor
類盼产。ApolloAnnotationProcessor
的父親也就是BeanPostProcessor
利用了BeanPostProcessor
功能,在類初始化之前執(zhí)行了自己的processMethod
方法勺馆。該方法則會將自定義的listener方法添加到DefaultConfig
中
protected void processMethod(final Object bean, String beanName, final Method method) {
ApolloConfigChangeListener annotation = AnnotationUtils
.findAnnotation(method, ApolloConfigChangeListener.class);
if (annotation == null) {
return;
}
....
String[] namespaces = annotation.value();
String[] annotatedInterestedKeys = annotation.interestedKeys();
String[] annotatedInterestedKeyPrefixes = annotation.interestedKeyPrefixes();
ConfigChangeListener configChangeListener = new ConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
ReflectionUtils.invokeMethod(method, bean, changeEvent);
}
};
....
// 添加自定義的方法到DefaultConfig中的ConfigChangeListener集合屬性中
for (String namespace : namespaces) {
Config config = ConfigService.getConfig(namespace);
if (interestedKeys == null && interestedKeyPrefixes == null) {
config.addChangeListener(configChangeListener);
} else {
config.addChangeListener(configChangeListener, interestedKeys, interestedKeyPrefixes);
}
}
}
到這里我們基本了解作者的意圖了戏售,目的就是為了 執(zhí)行被注解ApolloConfigChangeListener
標(biāo)注的自定義的方法,執(zhí)行過程我們也了如指掌了草穆。那么.....問題來了灌灾,到底怎樣做ApolloConfigChangeListener
上的namespace才可以不寫死呢,隨著項目配置自動獲取所有的namespace呢悲柱?
四锋喜、附錄
①創(chuàng)建屬于自己的starter(https://mp.weixin.qq.com/s/X3kJyFHrn7tul4PiGlfT5g)
② Apollo指南(https://github.com/ctripcorp/apollo/wiki/Java客戶端使用指南#323-spring-annotation支持)