[享学Netflix] 一、Apache Commons Configuration:你身边的配置管理专家

你应该思考:新长出来的东西,哪些是成就,哪些是赘肉。

–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/netflix-learning

前言

为了给讲解Netflix的Archaius做铺垫,本专栏首当其冲的先拿Apache Commons Configuration开刷,因为它是基础,不掌握它有点寸步难行之感。

Commons Configuration软件库提供了一个通用的配置接口,该接口使Java应用程序可以从各种来源读取配置数据。支持从一下来源来读取配置:

  1. Properties属性文件
  2. XML文件
  3. Windows INI文件
  4. 属性列表文件(plist)
  5. JNDI
  6. JDBC数据源
  7. 系统属性(Environment/Sytem)
  8. Applet参数
  9. Servlet参数

以上被划线的是现行几乎不使用的来源,而其它来源使用还较多,可以用到各个地方。

说明: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也是很大的相似之处


Commons Configuration

它的GAV:

<dependency>
    <groupId>commons-configuration</groupId>
    <artifactId>commons-configuration</artifactId>
    <version>1.10</version>
</dependency>

相关依赖截图:
在这里插入图片描述
可见它对应依赖的是较老的commons-lang,而非commons-lang3(2.x依赖的是lang3)。


Configuration接口

最主要的接口。可以对比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...等一连串方法
}

它的实现类非常的多,这里举例说几个具有代表性的子类。


PropertiesConfiguration

顾名思义,它的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这个并没有过来,因为它前缀不匹配嘛~


XMLConfiguration

加载xml的配置,使用相对较少,略。


JNDIConfiguration

使用javax.naming.Context等API实现,略。


ServletConfiguration

它的配置信息来自于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#getInitParameter
  • ServletRequestConfiguration --> ServletRequest#getParameterValues
  • ServletFilterConfiguration --> FilterConfig#getInitParameter

DatabaseConfiguration

配置信息存储到数据库,比如表:

CREATE TABLE myconfig (
    `key`   VARCHAR NOT NULL PRIMARY KEY,
    `value` VARCHAR
);

依靠javax.sql.DataSource给查出来。虽然它的优点是方便动态化,但现实情况几乎不会这么使用,略~


MapConfiguration

这是一个非常通用的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来使用。比如它的如下两个子类:


SystemConfiguration

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


EnvironmentConfiguration
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!");
    }
}

CompositeConfiguration

顾名思义,它一种组合模式的实现,内部代理着多个Configuration

private List<Configuration> configList = new LinkedList<>();

并且能对持有的Configuration做增删改查等操作,它能给你带来的好处是:用同一个配置Configuration类,操作多种数据源,并且还可控制器优先级顺序

组合模式在包括Spring等各大框架里经常使用,相信你也不陌生,此处就不再鳌诉。


ConfigurationBuilder

现在比较流行的创建实例方式是Builder模式,该接口目的就是构建一个新的Configuration实例。

public interface ConfigurationBuilder {
	Configuration getConfiguration() throws ConfigurationException;
}

它只有唯一实现类:DefaultConfigurationBuilder。但实际情况是:此Builder并非你“认识”的标准构建器,所以实际场景中几乎使用不到,因此本处忽略(同时你也可以忽略此builder)。


ConfigurationUtils

一个混杂有各种各样方法的工具类。这里介绍几个相对常用的方法并给使用示例:

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
  • 用户家目录不存在,但项目classpath下存在,输出: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没有用它而选择了自己抽象一套),所以掌握它投入产出比还是蛮高的。

分隔线

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭。你也可左边扫码加入我的 Java高工、架构师 系列群大家庭学习和交流。
往期精选

发布了299 篇原创文章 · 获赞 456 · 访问量 39万+

猜你喜欢

转载自blog.csdn.net/f641385712/article/details/104350308
今日推荐