你应该思考:新长出来的东西,哪些是成就,哪些是赘肉。
代码下载地址:https://github.com/f641385712/netflix-learning
为了给讲解Netflix的Archaius
做铺垫,本专栏首当其冲的先拿Apache Commons Configuration
开刷,因为它是基础,不掌握它有点寸步难行之感。
Commons Configuration
软件库提供了一个通用的配置接口,该接口使Java应用程序可以从各种来源读取配置数据。支持从一下来源来读取配置:
以上被划线的是现行几乎不使用的来源,而其它来源使用还较多,可以用到各个地方。
说明:1.x并没有支持到
YAML
这种来源,但2.x有给与支持,后面也会讲述2.x的基本使用和原理。
Apache Commons Configuration
从2004年一直发展至今,1.x版本发展到了1.10
版本(2013.10,已停更),而2.x版本目前2.6版本并且持续更新中。
由于Netflix的套件使用的配置基础依赖都是基于1.x版本的,因此本系列不能落下1.x。当然喽,2.x版本在后面也必然会讲述。
说明:2.x并不向下兼容1.x。但提供的功能是大致一样的,主要在并发性能上做了提升,当然API层面也稍有改变。
Commons Configuration
允许您从各种不同的来源访问配置属性。无论它们存储在属性文件,XML文档还是JNDI树中,都可以通过通用Configuration
接口以相同的方式对其进行访问。
Commons Configuration
的另一个优势是它能够混合来自不同来源的配置,并将其视为单个逻辑配置(这点很重要)。你可以随意组合它们的优先级。
说明:这个
Configuration
抽象和Spring的org.springframework.core.env.Environment
特别像。并且单个逻辑配置和Spring的MutablePropertySources
也是很大的相似之处
它的GAV:
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.10</version>
</dependency>
相关依赖截图:
可见它对应依赖的是较老的commons-lang
,而非commons-lang3
(2.x依赖的是lang3)。
最主要的接口。可以对比Spring的org.springframework.core.env.Environment
来理解,因为有很多操作都是类似的。
public interface Configuration {
// 返回另外一个Configuration,把前缀去掉
// 比如之前是aaa.name=xxx,调用subset("aaa")后新的对象里的是:name=xxx
Configuration subset(String prefix);
boolean isEmpty();
boolean containsKey(String key);
void addProperty(String key, Object value);
void setProperty(String key, Object value);
void clearProperty(String key); // 删除某一个Key
void clear(); // 删除所有key
// 读取方法
Object getProperty(String key);
Iterator<String> getKeys(String prefix);
Iterator<String> getKeys();
Properties getProperties(String key);
boolean getBoolean(String key);
boolean getBoolean(String key, boolean defaultValue);
...
// 省略getDouble/getInt/getBigDecimal/getList...等一连串方法
}
它的实现类非常的多,这里举例说几个具有代表性的子类。
顾名思义,它的k-v信息来自于一个properties
文件,文件路径可以是相对的,也可以是绝对的。
public class PropertiesConfiguration extends AbstractFileConfiguration {
// #或!打头的都被单过注释处理
static final String COMMENT_CHARS = "#!";
// 默认分隔符,其实还支持使用:'=' ':' 'WHITE_SPACE'等作为分隔符
static final String DEFAULT_SEPARATOR = " = ";
private static final char[] SEPARATORS = new char[] {'=', ':'};
private static final char[] WHITE_SPACE = new char[]{' ', '\t', '\f'};
// 强大的特性,支持include关键字:引入另外一个properties文件
// 然后最终合并他俩到一个configuration里来,下面有示例
// 这个名字你可以通过静态方法setInclude(...)自定义~~~
private static String include = "include";
private boolean includesAllowed = true;
// 默认编码ISO-8859-1 --> 中文会有乱码
private static final String DEFAULT_ENCODING = "ISO-8859-1";
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
// 构造器们
// 可使用空构造器,后期手动调用load(...)方法也是可以的
public PropertiesConfiguration() { ... }
// 构造器里指定文件名,使用较多
// 注意:文件默认是从classpath里去找的哦
public PropertiesConfiguration(String fileName) throws ConfigurationException { ... }
... // 省略其它重载构造器
// 把当前配置写出去(按照指定的layout模版),可写到文件
public void save(Writer writer) { ... }
// 这个basePath include也会遵从哦~~~
public void setBasePath(String basePath) { ... }
}
它用于读取properties配置文件,同时也可把Configuration
里面的配置写进文件里(按照指定的layout模版),可读可写。
文件1.properties
:
# this is mark
! this is mark too
common.name = YourBatman
common.age=18
common.addr :China
common.count 4
# 可以使用${}引用其它属性
common.fullname = ${common.name}-Bryant
# include 关键字
include = 2.properties
文件2.properties
:
java.version = 1.8.123
这两个文件均放在当前工程的classpath下面:
测试程序:
@Test
public void fun1() throws ConfigurationException {
Configuration configuration = new PropertiesConfiguration("1.properties");
// PropertiesConfiguration configuration = new PropertiesConfiguration("1.properties");
System.out.println(configuration.getString("common.name"));
System.out.println(configuration.getString("common.fullname"));
System.out.println(configuration.getInt("common.age"));
System.out.println(configuration.getString("common.addr"));
System.out.println(configuration.getLong("common.count"));
// 打印include的内容
System.out.println(configuration.getString("java.version"));
System.out.println();
System.out.println("=====使用subset方法得到一个子配置类=====");
Configuration subConfig = configuration.subset("common");
subConfig.getKeys().forEachRemaining((k) -> {
System.out.println(k + "-->" + subConfig.getString(k));
});
}
运行程序,完美输出:
YourBatman
YourBatman-Bryant
18
China
4
1.8.123
=====使用subset方法得到一个子配置类=====
name-->YourBatman
age-->18
addr-->China
count-->4
fullname-->YourBatman-Bryant
注意:在subConfig里java.version
这个并没有过来,因为它前缀不匹配嘛~
加载xml的配置,使用相对较少,略。
使用javax.naming.Context
等API实现,略。
它的配置信息来自于javax.servlet.ServletConfig
。
public class ServletConfiguration extends BaseWebConfiguration {
protected ServletConfig config;
// 核心API是getInitParameter
public Object getProperty(String key) {
return handleDelimiters(config.getInitParameter(key));
}
...
}
类似的还有:
ServletContextConfiguration
--> ServletContext#getInitParameterServletRequestConfiguration
--> ServletRequest#getParameterValuesServletFilterConfiguration
--> FilterConfig#getInitParameter配置信息存储到数据库,比如表:
CREATE TABLE myconfig (
`key` VARCHAR NOT NULL PRIMARY KEY,
`value` VARCHAR
);
依靠javax.sql.DataSource
给查出来。虽然它的优点是方便动态化,但现实情况几乎不会这么使用,略~
这是一个非常通用的Configuration
:配置信息存在在java.util.Map
里。形如这样:
@Test
public void fun2() {
Map<String, Object> source = new HashMap<>();
source.put("common.name", "YourBatman");
source.put("common.age", 18);
Configuration configuration = new MapConfiguration(source);
System.out.println(configuration.getString("common.name"));
System.out.println(configuration.getInt("common.age"));
}
输出:
YourBatman
18
看似多此一举?其实不是,你可以理解它是一个适配器,可以把任何K-V结构的适配为Configuration
来使用。比如它的如下两个子类:
A configuration based on the system properties.
public class SystemConfiguration extends MapConfiguration {
// System.getProperties()的结果是个Properties,也是个Map
public SystemConfiguration() {
super(System.getProperties());
}
// 静态工具方法:可以允许批量设置系统属性
// 底层是:System.setProperty(key, value);
public static void setSystemProperties(String fileName) throws Exception {
setSystemProperties(null, fileName);
}
public static void setSystemProperties(PropertiesConfiguration systemConfig) { ... }
}
简单使用示例:
@Test
public void fun3(){
Configuration configuration = new SystemConfiguration();
System.out.println(configuration.getString("user.home"));
}
正常打印:C:\Users\xxx
public class EnvironmentConfiguration extends MapConfiguration {
// System.getenv()返回的是Map<String,String> 所以需要转换一把
public EnvironmentConfiguration() {
super(new HashMap<String, Object>(System.getenv()));
}
// 任何修改动作,对环境变量都是不允许的
@Override
protected void addPropertyDirect(String key, Object value) {
throw new UnsupportedOperationException("EnvironmentConfiguration is read-only!");
}
@Override
public void clearProperty(String key) {
throw new UnsupportedOperationException("EnvironmentConfiguration is read-only!");
}
@Override
public void clear() {
throw new UnsupportedOperationException("EnvironmentConfiguration is read-only!");
}
}
顾名思义,它一种组合模式的实现,内部代理着多个Configuration
:
private List<Configuration> configList = new LinkedList<>();
并且能对持有的Configuration
做增删改查等操作,它能给你带来的好处是:用同一个配置Configuration类,操作多种数据源,并且还可控制器优先级顺序。
组合模式在包括Spring等各大框架里经常使用,相信你也不陌生,此处就不再鳌诉。
现在比较流行的创建实例方式是Builder
模式,该接口目的就是构建一个新的Configuration
实例。
public interface ConfigurationBuilder {
Configuration getConfiguration() throws ConfigurationException;
}
它只有唯一实现类:DefaultConfigurationBuilder
。但实际情况是:此Builder
并非你“认识”的标准构建器,所以实际场景中几乎使用不到,因此本处忽略(同时你也可以忽略此builder)。
一个混杂有各种各样方法的工具类。这里介绍几个相对常用的方法并给使用示例:
ConfigurationUtils:
// 把Configuration打印输出到标注输出流:控制台
public static void dump(Configuration configuration, PrintStream out) {
dump(configuration, new PrintWriter(out));
}
public static String toString(Configuration configuration) {
StringWriter writer = new StringWriter();
dump(configuration, new PrintWriter(writer));
return writer.toString();
}
若你想输出Configuration
里面的所有k-v,那就不用麻烦啦,使用工具类更方便:
@Test
public void fun6() throws ConfigurationException {
Configuration configuration = new PropertiesConfiguration("1.properties");
System.out.println(configuration.getProperty("common.fullname"));
System.out.println(configuration.getString("common.fullname"));
System.out.println(" ================ ");
ConfigurationUtils.dump(configuration,System.out);
// ConfigurationUtils.toString(configuration);
}
控制台输出:
${common.name}-Bryant
YourBatman-Bryant
================
common.name=YourBatman
common.age=18
common.addr=China
common.count=4
common.fullname=${common.name}-Bryant
java.version=1.8.123
从结果顺便也能看出getProperty()
和getString()
方法的区别,前者并不处理占位符。而该工具类使用的是getProperty()
而非getString()
。
ConfigurationUtils:
public static void copy(Configuration source, Configuration target) { ... }
// 把source里的属性添加到target里面去,使用addProperty()方法完成
public static void append(Configuration source, Configuration target) { ... }
// 克隆一个 它依赖于clone方法,注意和copy的区别
public static Configuration cloneConfiguration(Configuration config) { ... }
这个几个方法使用起来没有啥歧义,不用举例说明。
ConfigurationUtils:
public static URL getURL(String basePath, String file) throws MalformedURLException {
return FileSystem.getDefaultFileSystem().getURL(basePath, file);
}
// 从用户home目录、当前classpath、System系统classpath
// 这就是你为何文件放在项目的classpth下也能被直接找到的原因
public static URL locate(String name) {
return locate(null, name);
}
public static URL locate(String base, String name) {
return locate(FileSystem.getDefaultFileSystem(), base, name);
}
public static URL locate(FileSystem fileSystem, String base, String name) { ... }
// URL可以是个本地地址,也可以是个网络地址
public static File fileFromURL(URL url) { ... }
这个是和文件系统相关的方法,虽然Apache Commons Configuration
允许你指定文件系统FileSystem
,但一般都使用默认的。
@Test
public void fun7() {
URL url = ConfigurationUtils.locate("userHome.properties");
System.out.println(url.toString());
}
最重要的方法便是这个locate
方法,因为new PropertiesConfiguration("1.properties")
底层都是通过它去定位到文件位置。
上面案例结果输出可能出现如下情况:
file:/C:/Users/xxx/userHome.properties
file:/E:/work/xxxxxxx/target/classes/userHome.properties
所以可得出结论,定位文件的优先级是:家目录 > 项目classpath > 系统classpath。
关于Apache Commons Configuration
的一个整体就少就到这了,读完此篇相信你又增加了一项读取properties
文件的能力了吧。
不仅如此,你还可以使用统一的API来读取各个地方的配置文件,包括但不限于properties、XML、系统属性、YAML(2.x提供支持)、甚至网络文件等。
Apache Commons Configuration
作为一个通用的配置文件读取库,被不少第三方框架所使用,典型的就是Netflix OSS套件系列(当然Spring没有用它而选择了自己抽象一套),所以掌握它投入产出比还是蛮高的。
原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭
。