[享學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有哪些不一樣就先介紹到這,本文是站在一個使用者的角度,對比兩者在使用上的不一樣,這對新手是特別具有引導意義的。 若想了解起深點的原理和訂製,請參閱前面七篇文章,文末直接附有鏈接地址,直達閱讀。

