[享学Netflix] 八、Apache Commons Configuration2.x相较于1.x使用上带来哪些差异?
- 2020 年 3 月 18 日
- 筆記
人不敬我,是我无才,我不敬人,是我无德,人不容我,是我无能,我不容人,是我无量,人不助我,是我无为,我不助人,是我无善。 代码下载地址:https://github.com/f641385712/netflix-learning
目录
前言
Commons Configuration
作为一个优秀的配置管理库,凭借着优秀的设计以及提供了热加载等使用功能,被不少其它组件作为基础配置管理组件使用,流行度较高。 从2004年发展至今,它一共有两个大版本:1.x和2.x。这两个大版本之前因为改过包名,并且GAV坐标也不一样,因此他俩:互不兼容,可以共存。
虽然2.x早已成为主流,但前面解释过1.x版本也有学习的意义,所以避免不了共存的情况。因此本文将从多个方面,站在使用的角度比较两者的诸多不同,让同学们可以同时驾驭。
说明:此文仅讲使用,所以本人希望你是已经看过前面1-7篇:详细介绍1.x和2.x版本。这里可乘坐电梯直达:直达电梯
正文
下面将站在使用者的角度,从主流的使用方式上,比较两者差异。
1.x和2.x的差异
2.x新增类特别介绍
2.x版本新增了几个非常使用的类,在这里做简单介绍。
JSONConfiguration
此类是2.2版本后才有的,非常的新。一个能够解析JSON的专门的层次结构配置类 文档。使用前,需要先导入依赖包:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.10.1</version> </dependency>
类路径下放置一个JSON文件:
{ "name": "YourBatman", "age": 18, "son": { "name": "${name}-son", "age": 2 } }
使用读取:
@Test public void fun1() throws ConfigurationException { // InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("1.json"); InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("1.json"); JSONConfiguration configuration = new JSONConfiguration(); configuration.read(inputStream); ConfigurationUtils.dump(configuration,System.out); }
运行程序打印:
name=YourBatman age=18 son.name=${name}-son son.age=2
说明:占位符并未生效
YAMLConfiguration
此类是2.2版本后才有的,非常的新。使用前,需要先导入依赖包:
<dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>1.25</version> </dependency>
步骤完全同上,只不过它解析的是Yaml格式文件,所以详细步骤略。 说明:它支持使用占位符
ConfigurationPropertySource
它只是作为org.springframework.core.env.PropertySource
的一个实现,继承自EnumerablePropertySource
,并且是用org.apache.commons.configuration2.Configuration
来管理配置喽。
public class ConfigurationPropertySource extends EnumerablePropertySource<Configuration> { ... // 省略构造器 @Override public Object getProperty(final String name) { return source.getProperty(name); } ... }
源码非常简单:使用Configuration
装载Spring的配置而已,因此它也可以同Map一样,作为一个Spring的配置源PropertySource
来来使用哦,例子略。
说明:如果不太熟悉Spring的Enviroment抽象以及属性源
PropertySource
的同学理解起来相对费解些,我给出两个建议:1、去了解它 2、忽略它
GAV坐标
1.x:
<dependency> <groupId>commons-configuration</groupId> <artifactId>commons-configuration</artifactId> <version>1.10</version> </dependency>
2.x:
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-configuration2</artifactId> <version>2.6</version> </dependency>
包名均发生了变化:
- 1.x:
org.apache.commons.configuration.Configuration

- 2.x:
org.apache.commons.configuration2.Configuration

因此他俩:互不兼容,可以共存。
运行时依赖
1.x:

2.x:

两者均仅需核心依赖均可完成绝大部分功能。但2.x新增提供的JSONConfiguration
和YAMLConfiguration
着实好用且紧跟主流,虽然它需要依赖额外的三方包~
说明:Spring Boot对Yaml的解析依赖的也是
org.yaml:snakeyaml
哦,所以契合得特别好 另外,Configuration Builder几乎一定会用,所以2.x请也导入commons-beanutils
这个依赖(1.x都是自己new的,所以不用导入)
基本使用
1.x:
@Test public void fun1() throws ConfigurationException { Configuration config = new PropertiesConfiguration("1.properties"); System.out.println(config.getString("common.name")); }
2.x:
@Test public void fun1() throws ConfigurationException { Configurations configs = new Configurations(); // 没有带参的构造器了,只能通过Configurations构建出来 // Configuration configuration = new PropertiesConfiguration("1.properties"); PropertiesConfiguration config = configs.properties("1.properties"); // configs.xml(); new SystemConfiguration(); new EnvironmentConfiguration(); System.out.println(config.getString("common.name")); }
说明:2.x使用
Configurations
必须额外导入commons-beanutils
这个jar。
另外,2.x大多时候构建Configuration
使用的应该是Builder模式,具体详情请参考前面5篇文章。
事件-监听
2.x
完全摒弃了1.x版本的设计,完全重新设计了一套。使用方式上自然也稍有变化:
1.x:
@Test public void fun4() throws ConfigurationException { PropertiesConfiguration configuration = new PropertiesConfiguration("1.properties"); // 注册一个监听器 configuration.addConfigurationListener(event -> { Object source = event.getSource(); // 事件源 int type = event.getType(); if (!event.isBeforeUpdate()) { // 只关心update后的事件,否则会执行两次哦,请务必注意 System.out.println("事件源:" + source.getClass()); System.out.println("事件type类型:" + type); // 处理你自己的逻辑 } }); // 增加一个属性,会同步触发监听器去执行 configuration.addProperty("common.addition", "additionOne"); System.out.println(configuration.getString("common.addition")); }
2.x:
@Test public void fun2() throws ConfigurationException { Configurations configs = new Configurations(); PropertiesConfiguration config = configs.properties("1.properties"); // ConfigurationEvent内置有很多事件类型可使用 // 若不满足条件,请你自定义事件类型 config.addEventListener(ConfigurationEvent.ADD_PROPERTY, event -> { Object source = event.getSource(); // 事件源 EventType<? extends Event> eventType = event.getEventType(); if (!event.isBeforeUpdate()) { System.out.println("事件源:" + source.getClass()); System.out.println("事件type类型:" + eventType); } }); // 添加属性 触发事件 config.addProperty("common.addition", "additionOne"); System.out.println(config.getString("common.addition")); }
执行结果,打印:
事件源:class org.apache.commons.configuration2.PropertiesConfiguration 事件type类型:EventType [ ADD_PROPERTY ] additionOne
热加载
1.x:
@Test public void fun5() throws ConfigurationException { PropertiesConfiguration configuration = new PropertiesConfiguration("1.properties"); // 监听到配置文件被重新加载了就输出一条日志喽~ configuration.addConfigurationListener(event -> { // 只监听到重新加载事件 if (event.getType() == PropertiesConfiguration.EVENT_RELOAD) { System.out.println("配置文件重载..."); configuration.getKeys().forEachRemaining(k -> { System.out.println("/t " + k + "-->" + configuration.getString(k)); }); } }); // 使用文件改变重载策略:让改变文件能热加载 FileChangedReloadingStrategy reloadingStrategy = new FileChangedReloadingStrategy(); reloadingStrategy.setRefreshDelay(3000L); // 设置最小事件间隔,单位是毫秒 configuration.setReloadingStrategy(reloadingStrategy); // 使用另外一个线程模拟去get otherThreadGet(configuration); // hold住main线程,不让程序终止 while (true) {} } private void otherThreadGet(PropertiesConfiguration configuration) { new Thread(() -> { while (true) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } configuration.getString("commmon.name"); } }).start(); }
2.x:
@Test public void fun22() { // 关联上1.properties这个文件 Map<String, Object> map = new HashMap<>(); map.put("fileName", "1.properties"); // 因fileHandler此例不需要FileBased,所以先用null吧 FileHandler fileHandler = new FileHandler(null, FileHandler.fromMap(map)); // 使用控制器ReloadingController 代理掉ReloadingDetector来使用,更好用 ReloadingController reloadingController = new ReloadingController(new FileHandlerReloadingDetector(fileHandler)); reloadingController.addEventListener(ReloadingEvent.ANY, event -> { ReloadingController currController = event.getController(); Object data = event.getData(); currController.resetReloadingState(); // 需要手动充值一下,否则下次文件改变就不会发送此事件啦 System.out.println((reloadingController == currController) + " data:" + data); }); // 准备定时器:用于监控文件的的变化:3秒看一次 注意一定要start()才能生效哦 new PeriodicReloadingTrigger(reloadingController, "自定义数据", 3, TimeUnit.SECONDS).start(); // hold住主线程 while (true) { } }
文件扫描策略
1.x是按照固定的顺序去查找、定位文件:
- user home
- 当前工厂classpath
- 系统classpath
2.x让这变得更加的灵活:允许应用程序自定义文件定位过程,这就是这个接口的作用。它有如下实现类:

- ClasspathLocationStrategy:从classpath下去加载文件(常用)
- AbsoluteNameLocationStrategy:绝对路径。所以直接使用
new File(locator.getFileName())
去加载 - HomeDirectoryLocationStrategy:从
user.home
里去查找 - BasePathLocationStrategy:使用basePath+fileName的方式new File()
- FileSystemLocationStrategy:使用文件系统定位。比如windows是带盘符的
- ProvidedURLLocationStrategy:直接是URL的方式。所以它也可以存在于网络上~
CombinedLocationStrategy
:真正使用的。它是一个聚合,这些实现类可以构成一个扫描链来进行按照其顺序进行组合扫描,之前讲过很多类似的设计模式了
这些顺序你可以自由组合,甚至还可以自定义。比如:
List<FileLocationStrategy> subs = Arrays.asList( new ProvidedURLLocationStrategy(), new FileSystemLocationStrategy(), new ClasspathLocationStrategy()); FileLocationStrategy strategy = new CombinedLocationStrategy(subs);
这样最终定位strategy 策略翻译如下:
- 使用自己的URL定位(比如有完整的URL路径)
- 文件系统
- 当前工程classpath(比如仅仅只有一个名字)
插值器
1.x核心API是:ConfigurationInterpolator
2.x核心API是:ConfigurationInterpolator
和InterpolatorSpecification
、Lookup
…
例子暂略。
总结
关于2.x版本相较于1.x有哪些不一样就先介绍到这,本文是站在一个使用者的角度,对比两者在使用上的不一样,这对新手是特别具有引导意义的。 若想了解起深点的原理和定制,请参阅前面七篇文章,文末直接附有链接地址,直达阅读。