在这篇文章中,我们将讨论Archaius,一个非常酷且易于使用的Netflix配置管理工具。
通常我们都是如何读取配置变量的呢?
一种是使用System.getProperty()方法获得JVM系统属性。例如下面这样:
String prop = System.getProperty("myProperty");
int x = DEFAULT_VALUE;
try {
x = Integer.parseInt(prop);
} catch (NumberFormatException e) {
// handle format issues
}
myMethod(x);
您还可以使用Spring读取属性文件 。或者你的数据库中有一个简单的键/值表,你可以从那里读取一些属性。又或者您从外部REST端点获取它们。除此之外还可以从其他类型的键/值存储中获取,比如Redis或Memcached。
无论情况如何,您的配置变量可能来自许多不同的来源,特别是如果您的应用程序使用多个,这可能会变得难以维护。另一个重要的问题,您不希望每次更改其中一个属性的值时需要重新部署,特别是在持续集成的时候。
为解决这些问题,Netflix提出了一个开源的解决方案:Archaius。Archaius是Apache公共配置库的扩展, 它允许您从多个动态源中检索属性,并且它解决了前面提到的所有问题(异构的属性源,运行时更改等)。
我们先从用Archaius读取属性文件最简单的示例开始。
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"));
}
}
该代码是读取类路径中某处的“config.properties”文件。请注意,您不需要告诉Archaius在哪里找到您的属性文件,因为他要查找的默认名称是“config.properties”。
如果您不想或不能将属性文件命名为“config.property”,该怎么办?在这种情况下,您需要告诉Archaius在哪里查找此文件。您可以轻松地更改系统属性’archaius.configurationSource.defaultFileName’,在启动应用程序时将其作为参数传递给vm:
java ... -Darchaius.configurationSource.defaultFileName=customName.properties
或者写在代码本身中:
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();
}
}
现在,如果你想读几个属性文件怎么办?您可以从首先加载的默认文件开始,轻松定义属性文件链及其加载顺序。从那里,您可以使用键“@ next = nextFile.properties”指定一个特殊属性来告诉Archaius哪个是应该加载的下一个文件。
在我们的示例中,我们可以在“customConfig.properties”文件中添加以下行:
@next=secondConfig.properties
并将相应的“secondConfig.properties”添加到我们的resources文件夹中,其中包含以下内容:
cascade.property=cascade value
我们可以通过在ApplicationConfigTest类中添加以下测试来验证:
@Test
public void shouldReadCascadeConfigurationFiles() {
String property = appConfig.getStringProperty("cascade.property", "not found");
assertThat(property, is("cascade value"));
}
请注意,我们从新文件中获取属性,而不对ApplicationConfig类进行任何其他更改。从客户的角度来看,这是完全透明的。
到目前为止,我们一直在从不同的属性文件中读取属性,但如果您想从不同的源读取它们会怎么样?在最常见的情况下,您可以通过实现“com.netflix.config.PolledConfigurationSource”来编写自己的逻辑。如果新源是可以通过JDBC访问的数据库,那么Archaius已经提供了可以使用的“JDBCConfigurationSource”。您只需要告诉他应该使用什么查询来获取属性以及哪些列表示属性键和属性值。
我们的例子如下:
@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来自动装配数据源,该数据源将使用具有简单键/值表的基于内存的H2数据库。注意我们是如何创建一个新的 PolledConfigurationSource的,使用Archaius已经提供的JDBCConfigurationSource,然后我们注册使用新的配置 ConfigurationManager。执行此操作后,我们可以从数据库中获取任何属性,就像我们对属性文件所做的那样(即使用 DynamicPropertyFactory)。
我们现在可以添加几个测试类来确保我们实际上从数据库中读取属性,并且我们可以更新它们的值并查看动态配置中反映的更改。
@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提供的另一个非常酷的功能是可以通过JMX 将我们的配置注册为 MBean。我们可以默认设置系统属性 archaius.dynamicPropertyFactory.registerConfigWithJMX = true或使用ConfigJMXManager.registerConfigMbean(config)进行编程。
执行此操作后,我们可以通过JConsole连接,不仅可以获取所有属性的值,还可以更新它们并查看它们在Archaius中反映的新值。例如,这将允许我们在运行时更改属性文件中静态定义的属性值,而无需服务器推送。我们可以稍微修改一下ApplicationConfig类来添加一个main方法,该方法将持续运行打印不同属性的值,这样将允许我们在JConsole中使用。
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进行更多操作,例如每次属性更改时的回调,与Zookeeper或其他服务的集成。您可以在GitHub上找到本文中显示的所有代码。