在這篇文章中,我們將討論Archaius,一個(gè)非常酷且易于使用的Netflix配置管理工具。
通常我們都是如何讀取配置變量的呢?
一種是使用System.getProperty()方法獲得JVM系統(tǒng)屬性。例如下面這樣:
String prop = System.getProperty("myProperty");
int x = DEFAULT_VALUE;
try {
x = Integer.parseInt(prop);
} catch (NumberFormatException e) {
// handle format issues
}
myMethod(x);
您還可以使用Spring讀取屬性文件 ?;蛘吣愕臄?shù)據(jù)庫(kù)中有一個(gè)簡(jiǎn)單的鍵/值表,你可以從那里讀取一些屬性。又或者您從外部REST端點(diǎn)獲取它們。除此之外還可以從其他類(lèi)型的鍵/值存儲(chǔ)中獲取,比如Redis或Memcached。
無(wú)論情況如何,您的配置變量可能來(lái)自許多不同的來(lái)源,特別是如果您的應(yīng)用程序使用多個(gè),這可能會(huì)變得難以維護(hù)。另一個(gè)重要的問(wèn)題,您不希望每次更改其中一個(gè)屬性的值時(shí)需要重新部署,特別是在持續(xù)集成的時(shí)候。
為解決這些問(wèn)題,Netflix提出了一個(gè)開(kāi)源的解決方案:Archaius。Archaius是Apache公共配置庫(kù)的擴(kuò)展, 它允許您從多個(gè)動(dòng)態(tài)源中檢索屬性,并且它解決了前面提到的所有問(wèn)題(異構(gòu)的屬性源,運(yùn)行時(shí)更改等)。
我們先從用Archaius讀取屬性文件最簡(jiǎn)單的示例開(kāi)始。
public class ApplicationConfig {
public String getStringProperty(String key, String defaultValue) {
final DynamicStringProperty property = DynamicPropertyFactory.getInstance().getStringProperty(key,
defaultValue);
return property.get();
}
}
public class ApplicationConfigTest {
private ApplicationConfig appConfig = new ApplicationConfig();
@Test
public void shouldRetrieveThePropertyByKey() {
String property = appConfig.getStringProperty("hello.world.message", "default message");
assertThat(property, is("Hello Archaius World!"));
}
@Test
public void shouldRetrieveDefaultValueWhenKeyIsNotPresent() {
String property = appConfig.getStringProperty("some.key", "default message");
assertThat(property, is("default message"));
}
}
該代碼是讀取類(lèi)路徑中某處的“config.properties”文件。請(qǐng)注意,您不需要告訴Archaius在哪里找到您的屬性文件,因?yàn)樗檎业哪J(rèn)名稱(chēng)是“config.properties”。
如果您不想或不能將屬性文件命名為“config.property”,該怎么辦?在這種情況下,您需要告訴Archaius在哪里查找此文件。您可以輕松地更改系統(tǒng)屬性'archaius.configurationSource.defaultFileName',在啟動(dòng)應(yīng)用程序時(shí)將其作為參數(shù)傳遞給vm:
java ... -Darchaius.configurationSource.defaultFileName=customName.properties
或者寫(xiě)在代碼本身中:
public class ApplicationConfig {
static {
System.setProperty("archaius.configurationSource.defaultFileName", "customConfig.properties");
}
public String getStringProperty(String key, String defaultValue) {
final DynamicStringProperty property = DynamicPropertyFactory.getInstance().getStringProperty(key,
defaultValue);
return property.get();
}
}
現(xiàn)在,如果你想讀幾個(gè)屬性文件怎么辦?您可以從首先加載的默認(rèn)文件開(kāi)始,輕松定義屬性文件鏈及其加載順序。從那里,您可以使用鍵“@ next = nextFile.properties”指定一個(gè)特殊屬性來(lái)告訴Archaius哪個(gè)是應(yīng)該加載的下一個(gè)文件。
在我們的示例中,我們可以在“customConfig.properties”文件中添加以下行:
@next=secondConfig.properties
并將相應(yīng)的“secondConfig.properties”添加到我們的resources文件夾中,其中包含以下內(nèi)容:
cascade.property=cascade value
我們可以通過(guò)在ApplicationConfigTest類(lèi)中添加以下測(cè)試來(lái)驗(yàn)證:
@Test
public void shouldReadCascadeConfigurationFiles() {
String property = appConfig.getStringProperty("cascade.property", "not found");
assertThat(property, is("cascade value"));
}
請(qǐng)注意,我們從新文件中獲取屬性,而不對(duì)ApplicationConfig類(lèi)進(jìn)行任何其他更改。從客戶的角度來(lái)看,這是完全透明的。
到目前為止,我們一直在從不同的屬性文件中讀取屬性,但如果您想從不同的源讀取它們會(huì)怎么樣?在最常見(jiàn)的情況下,您可以通過(guò)實(shí)現(xiàn)“com.netflix.config.PolledConfigurationSource”來(lái)編寫(xiě)自己的邏輯。如果新源是可以通過(guò)JDBC訪問(wèn)的數(shù)據(jù)庫(kù),那么Archaius已經(jīng)提供了可以使用的“JDBCConfigurationSource”。您只需要告訴他應(yīng)該使用什么查詢來(lái)獲取屬性以及哪些列表示屬性鍵和屬性值。
我們的例子如下:
@Component
public class ApplicationConfig {
static {
System.setProperty("archaius.configurationSource.defaultFileName", "customConfig.properties");
}
private final DataSource dataSource;
@Autowired
public ApplicationConfig(DataSource dataSource) {
this.dataSource = dataSource;
installJdbcSource();
}
public String getStringProperty(String key, String defaultValue) {
final DynamicStringProperty property = DynamicPropertyFactory.getInstance().getStringProperty(key,
defaultValue);
return property.get();
}
private DynamicConfiguration installJdbcSource() {
if (!isConfigurationInstalled()) {
PolledConfigurationSource source = new JDBCConfigurationSource(dataSource,
"select distinct property_key, property_value from properties", "property_key", "property_value");
DynamicConfiguration configuration = new DynamicConfiguration(source,
new FixedDelayPollingScheduler(0, 10000, true));
ConfigurationManager.install(configuration);
return configuration;
}
return null;
}
}
我們使用Spring來(lái)自動(dòng)裝配數(shù)據(jù)源,該數(shù)據(jù)源將使用具有簡(jiǎn)單鍵/值表的基于內(nèi)存的H2數(shù)據(jù)庫(kù)。注意我們是如何創(chuàng)建一個(gè)新的 PolledConfigurationSource的,使用Archaius已經(jīng)提供的JDBCConfigurationSource,然后我們注冊(cè)使用新的配置 ConfigurationManager。執(zhí)行此操作后,我們可以從數(shù)據(jù)庫(kù)中獲取任何屬性,就像我們對(duì)屬性文件所做的那樣(即使用 DynamicPropertyFactory)。
我們現(xiàn)在可以添加幾個(gè)測(cè)試類(lèi)來(lái)確保我們實(shí)際上從數(shù)據(jù)庫(kù)中讀取屬性,并且我們可以更新它們的值并查看動(dòng)態(tài)配置中反映的更改。
@Test
public void shouldRetrievePropertyFromDB() {
String property = appConfig.getStringProperty("db.property", "default message");
assertThat(property, is("this is a db property"));
}
@Test
public void shouldReadTheNewValueAfterTheSpecifiedDelay() throws InterruptedException, SQLException {
template.update("update properties set property_value = 'changed value' where property_key = 'db.property'");
String propertyValue = (String) template.queryForObject(
"select property_value from properties where property_key = 'db.property'", java.lang.String.class);
System.out.println(propertyValue);
String property = appConfig.getStringProperty("db.property", "default message");
// We updated the value on the DB but Archaius polls for changes every 10000
// milliseconds so it still sees the old value
assertThat(property, is("this is a db property"));
Thread.sleep(30000);
property = appConfig.getStringProperty("db.property", "default message");
assertThat(property, is("changed value"));
}
Archaius提供的另一個(gè)非??岬墓δ苁强梢酝ㄟ^(guò)JMX 將我們的配置注冊(cè)為 MBean。我們可以默認(rèn)設(shè)置系統(tǒng)屬性 archaius.dynamicPropertyFactory.registerConfigWithJMX = true或使用ConfigJMXManager.registerConfigMbean(config)進(jìn)行編程。
執(zhí)行此操作后,我們可以通過(guò)JConsole連接,不僅可以獲取所有屬性的值,還可以更新它們并查看它們?cè)贏rchaius中反映的新值。例如,這將允許我們?cè)谶\(yùn)行時(shí)更改屬性文件中靜態(tài)定義的屬性值,而無(wú)需服務(wù)器推送。我們可以稍微修改一下ApplicationConfig類(lèi)來(lái)添加一個(gè)main方法,該方法將持續(xù)運(yùn)行打印不同屬性的值,這樣將允許我們?cè)贘Console中使用。
public class ApplicationConfig extends Thread {
private final DataSource dataSource;
@Autowired
public ApplicationConfig(DataSource dataSource) {
this.dataSource = dataSource;
cascadeDefaultConfiguration();
DynamicConfiguration jdbcSource = installJdbcSource();
registerMBean(jdbcSource);
}
public String getStringProperty(String key, String defaultValue) {
final DynamicStringProperty property = DynamicPropertyFactory.getInstance().getStringProperty(key,
defaultValue);
return property.get();
}
@Override
public void run() {
while (true) {
try {
sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
private void registerMBean(DynamicConfiguration jdbcSource) {
setDaemon(false);
ConfigJMXManager.registerConfigMbean(jdbcSource);
}
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("archaiusContext.xml");
ApplicationConfig applicationConfig = (ApplicationConfig) applicationContext.getBean("applicationConfig");
applicationConfig.start();
while (true) {
try {
System.out.println(applicationConfig.getStringProperty("hello.world.message", "default message"));
System.out.println(applicationConfig.getStringProperty("cascade.property", "default message"));
System.out.println(applicationConfig.getStringProperty("db.property", "default message"));
sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
您還可以使用Archaius進(jìn)行更多操作,例如每次屬性更改時(shí)的回調(diào),與Zookeeper或其他服務(wù)的集成。您可以在GitHub上找到本文中顯示的所有代碼。