簡介
通過前面的兩篇博客,我們知道了:什么是 Eureka?為什么使用 Eureka?如何使用 Eureka?今天,我們開始來研究 Eureka 的源碼,先從配置部分的源碼開始看,其他部分后面再補充。
補充一點,我更多地會從設計層面分析源碼,而不會順序地剖析每個過程的代碼。一方面是因為篇幅有限,另一方面是因為我認為這樣做更有意義一些。
項目環境
os:win 10
jdk:1.8.0_231
eureka:1.10.11
maven:3.6.3
從一個例子開始
ConcurrentCompositeConfiguration
這個類是 Eureka 配置體系的核心。在這個例子中,我們使用它對 property 進行增刪改查,並注冊了自定義監聽器來監聽 property 的改變。
@Test
public void test01() {
// 創建配置對象
final ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();
// 注冊監聽器監聽property的改變
config.addConfigurationListener(new ConfigurationListener() {
public void configurationChanged(ConfigurationEvent event) {
// 增加property
if(AbstractConfiguration.EVENT_ADD_PROPERTY == event.getType()
&& !event.isBeforeUpdate()) {
System.err.println("add property:" + event.getPropertyName() + "=" + event.getPropertyValue());
return;
}
// 刪除property
if(AbstractConfiguration.EVENT_CLEAR_PROPERTY == event.getType()) {
System.err.println("clear property:" + event.getPropertyName());
return;
}
// 更新property
if(AbstractConfiguration.EVENT_SET_PROPERTY == event.getType()
&& event.isBeforeUpdate()
&& !config.getString(event.getPropertyName()).equals(event.getPropertyValue())) {
System.err.println("update property:"
+ event.getPropertyName()
+ ":"
+ config.getString(event.getPropertyName())
+ "==>"
+ event.getPropertyValue()
);
return;
}
}
});
// 添加property
config.addProperty("author", "zzs");
// 獲取property
System.err.println(config.getString("author"));
// 更改property
config.setProperty("author", "zzf");
// 刪除property
config.clearProperty("author");
}
// 運行以上方法,控制台打印內容:
// add property:author=zzs
// zzs
// update property:author:zzs==>zzf
// clear property:author
可以看到,當我們更改了 property 時,監聽器中的方法被觸發了,利用這一點,我們可以實現動態配置。
后面就會發現,Eureka 底層使用ConcurrentCompositeConfiguration
來對配置參數進行增刪改查,並基於事件監聽的機制來支持動態配置。
另一個有意思的地方
我們再來看看一個 UML 圖。上面例子中說到ConcurrentCompositeConfiguration
的兩個功能,是通過實現Configuration
和繼承EventSource
來獲得的,這一點沒什么特別的,之所以深究它,是因為我發現了其他有趣的地方。

我們主要來關注下它的三個成員屬性(它們都是AbstractConfiguration
類型):
- configList:持有的配置對象集合。這個集合的配置對象存在優先級,舉個例子,如果我添加了 Configuration1 和 Configuration2,當我們
getProperty(String)
時,會優先從 Configuration1 獲取,實在找不到才會去 Configuration2 獲取。 - overrideProperties:最高優先級的配置對象。當我們
getProperty(String)
時,會先從這里獲取,實在沒有才會去 configList 里找。 - containerConfiguration:保底的配置對象。一般是 configList 的最后一個(注意,不一定是最后一個1),我們往
ConcurrentCompositeConfiguration
里增刪改 property,實際操作的就是這個對象。
為了更好理解它們的作用,我寫了個測試例子。
@Test
public void test02() {
// 創建配置對象
ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();
// 添加配置1
ConcurrentMapConfiguration config1 = new ConcurrentMapConfiguration();
config1.addProperty("author", "zzs");
config.addConfiguration(config1, "CONFIG_01");
// 添加配置2
ConcurrentMapConfiguration config2 = new ConcurrentMapConfiguration();
config2.addProperty("author", "zzf");
config.addConfiguration(config2, "CONFIG_02");
// 在默認的containerConfiguration中添加property
config.addProperty("author", "zhw");
// ============以下測試configList的優先級============
System.err.println(config.getString("author"));
// 刪除config1中的property
config1.clearProperty("author");
System.err.println(config.getString("author"));
// 刪除config2中的property
config2.clearProperty("author");
System.err.println(config.getString("author"));
// ============以下測試overrideProperties的優先級============
// 添加overrideProperties的property
config.setOverrideProperty("author", "lt");
System.err.println(config.getString("author"));
}
// 運行以上方法,控制台打印內容:
// zzs
// zzf
// zhw
// lt
這里補充一點,當我們創建ConcurrentCompositeConfiguration
時,就會生成一個 containerConfiguration,默認情況下,它會一直在集合最后面,每次添加新的配置對象,都是往 containerConfiguration 前面插入。
誰來加載配置
通過上面的例子可以知道,ConcurrentCompositeConfiguration
並不會主動地去加載配置,所以,Eureka 需要自己往ConcurrentCompositeConfiguration
里添加配置,而完成這件事的是另外一個類--ConfigurationManager
。

ConfigurationManager
作為一個單例對象使用,用來初始化配置對象,以及提供加載配置文件的方法(后面的DefaultEurekaClientConfig
、DefaultEurekaServerConfig
會來調用這些方法)。
下面我們看看配置對象的初始化。在ConfigurationManager
被加載時就會初始化配置對象,進入到它的靜態代碼塊就可以找到。我截取的是最關鍵部分的代碼。
private static AbstractConfiguration createDefaultConfigInstance() {
ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();
try {
// 加載指定url的配置
// 通過archaius.configurationSource.additionalUrls啟動參數設置url,多個逗號隔開
DynamicURLConfiguration defaultURLConfig = new DynamicURLConfiguration();
config.addConfiguration(defaultURLConfig, URL_CONFIG_NAME);
} catch (Throwable e) {
logger.warn("Failed to create default dynamic configuration", e);
}
if (!Boolean.getBoolean(DISABLE_DEFAULT_SYS_CONFIG)) {
// 加載System.getProperties()的配置
// 通過archaius.dynamicProperty.disableSystemConfig啟動參數可以控制是否添加
SystemConfiguration sysConfig = new SystemConfiguration();
config.addConfiguration(sysConfig, SYS_CONFIG_NAME);
}
if (!Boolean.getBoolean(DISABLE_DEFAULT_ENV_CONFIG)) {
// 加載System.getenv()的配置
// 通過archaius.dynamicProperty.disableEnvironmentConfig啟動參數可以控制是否添加
EnvironmentConfiguration envConfig = new EnvironmentConfiguration();
config.addConfiguration(envConfig, ENV_CONFIG_NAME);
}
// 這個是自定義的保底配置
ConcurrentCompositeConfiguration appOverrideConfig = new ConcurrentCompositeConfiguration();
config.addConfiguration(appOverrideConfig, APPLICATION_PROPERTIES);
config.setContainerConfigurationIndex(config.getIndexOfConfiguration(appOverrideConfig));// 這里可以更改保底配置
return config;
}
可以看到,Eureka 支持通過 url 來指定配置文件,只要指定啟動參數就行,這一點將有利於我們更靈活地對項目進行配置。默認情況下,它還會去加載所有的系統參數和環境參數。
另外,當我們設置以下啟動參數,就可以通過 JMX 的方式來更改配置。
-Darchaius.dynamicPropertyFactory.registerConfigWithJMX=true
配置對象初始化后,ConfigurationManager
提供了方法供我們加載配置文件(本地或遠程),如下。
// 這兩個的區別在於:前者會生成一個新的配置添加到configList;后者直接將property都加入到appOverrideConfig
public static void loadCascadedPropertiesFromResources(String configName) throws IOException;
public static void loadAppOverrideProperties(String appConfigName);
怎么拿到最新的參數
動態配置的內容直接看源碼不大好理解,我們先通過一個再簡單不過的例子開始來一步步實現我們自己的動態配置。在下面的方法中,我更改了 property,但是拿不到更新的值。原因嘛,我相信大家都知道。
@Test
public void test03() {
// 獲取配置對象
AbstractConfiguration config = ConfigurationManager.getConfigInstance();
// 添加一個property
config.addProperty("author", "zzs");
String author = config.getString("author", "");
System.err.println(author);
// 更改property
config.setProperty("author", "zzf");
System.err.println(author);
}
// 運行以上方法,控制台打印內容:
// zzs
// zzs
為了拿到更新的值,我把代碼改成這樣。我不定義變量來存放 property 的值,每次都重新獲取。顯然,這樣做可以成功。
@Test
public void test04() {
// 獲取配置對象
AbstractConfiguration config = ConfigurationManager.getConfigInstance();
// 添加一個property
config.addProperty("author", "zzs");
System.err.println(config.getString("author", ""));
// 更改property
config.setProperty("author", "zzf");
System.err.println(config.getString("author", ""));
}
// 運行以上方法,控制台打印內容:
// zzs
// zzf
但是上面的做法有個問題,我們都知道從ConcurrentCompositeConfiguration
中獲取 property 是比較麻煩的,因為我需要去遍歷 configList,以及進行參數的轉換等。每次都這樣拿,不大合理。
於是,我增加了緩存來減少這部分的開銷,當然,property 更改時我必須刷新緩存。
@Test
public void test05() {
// 緩存
Map<String, String> cache = new ConcurrentHashMap<String, String>();
// 獲取配置對象
AbstractConfiguration config = ConfigurationManager.getConfigInstance();
// 添加一個property
config.addProperty("author", "zzs");
String value = cache.computeIfAbsent("author", x -> config.getString(x, ""));
System.err.println(value);
// 添加監聽器監聽property的更改
config.addConfigurationListener(new ConfigurationListener() {
public void configurationChanged(ConfigurationEvent event) {
// 刪除property
if(AbstractConfiguration.EVENT_CLEAR_PROPERTY == event.getType()) {
cache.remove(event.getPropertyName());
return;
}
// 更新property
if(AbstractConfiguration.EVENT_SET_PROPERTY == event.getType()
&& !event.isBeforeUpdate()) {
cache.put(event.getPropertyName(), String.valueOf(event.getPropertyValue()));
return;
}
}
});
// 更改property
config.setProperty("author", "zzf");
System.err.println(cache.get("author"));
}
// 運行以上方法,控制台打印內容:
// zzs
// zzf
通過上面的例子,我們實現了動態配置。
現在我們再來看看 Eureka 是怎么實現的。這里用到了DynamicPropertyFactory
和DynamicStringProperty
兩個類,通過它們,也實現了動態配置。
@Test
public void test06() {
// 獲取配置對象
AbstractConfiguration config = ConfigurationManager.getConfigInstance();
// 添加一個property
config.addProperty("author", "zzs");
// 通過DynamicPropertyFactory獲取property
DynamicPropertyFactory dynamicPropertyFactory = DynamicPropertyFactory.getInstance();
DynamicStringProperty stringProperty = dynamicPropertyFactory.getStringProperty("author", "");
System.err.println(stringProperty.get());
// 更改property
config.setProperty("author", "zzf");
System.err.println(stringProperty.get());
}
// 運行以上方法,控制台打印內容:
// zzs
// zzf
至於原理,其實和我們上面的例子是差不多的。通過 UML 圖可以知道,DynamicProperty
中就放了一張緩存表,每次獲取 property 時,會優先從這里拿。

既然有緩存,就應該有監聽器,沒錯,在DynamicProperty.initialize(DynamicPropertySupport)
方法中就可以看到。
static synchronized void initialize(DynamicPropertySupport config) {
dynamicPropertySupportImpl = config;
// 注冊監聽器
config.addConfigurationListener(new DynamicPropertyListener());
updateAllProperties();
}
Eureka有那幾類配置
在上面的分析中,我們用ConfigurationManager
來初始化配置對象,並使用DynamicPropertyFactory
來實現動態配置,這些東西構成了 Eureka 的配置體系的基礎,比較通用。基礎之上,是 Eureka 更具體的一些配置對象。
在 Eureka 里,配置分成了三種(理解這一點非常重要):
- EurekaInstanceConfig:當前實例身份的配置信息,即我是誰?
- EurekaServerConfig:一些影響當前Eureka Server和客戶端或對等節點交互行為的配置信息,即怎么交互?
- EurekaClientConfig:一些影響當前實例和Eureka Server交互行為的配置信息,即和誰交互?怎么交互?
這三個對象都持有了DynamicPropertyFactory
的引用,所以支持動態配置,另外,它們還是用ConfigurationManager
來加載自己想要的配置文件。例如,EurekaInstanceConfig
、EurekaClientConfig
負責加載eureka-client.properties
,而EurekaServerConfig
則負責加載eureka-server.properties
。

以上基本講完 Eureka 配置體系的源碼,可以看到,這是一套非常優秀的配置體系,實際項目中可以參考借鑒。
最后,感謝閱讀。
參考資料
本文為原創文章,轉載請附上原文出處鏈接:https://www.cnblogs.com/ZhangZiSheng001/p/14374005.html