[享學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是按照固定的順序去查找、定位文件:

  1. user home
  2. 當前工廠classpath
  3. 系統classpath

2.x讓這變得更加的靈活:允許應用程式自定義文件定位過程,這就是這個介面的作用。它有如下實現類:

  1. ClasspathLocationStrategy:從classpath下去載入文件(常用)
  2. AbsoluteNameLocationStrategy:絕對路徑。所以直接使用new File(locator.getFileName())去載入
  3. HomeDirectoryLocationStrategy:從user.home里去查找
  4. BasePathLocationStrategy:使用basePath+fileName的方式new File()
  5. FileSystemLocationStrategy:使用文件系統定位。比如windows是帶盤符的
  6. ProvidedURLLocationStrategy:直接是URL的方式。所以它也可以存在於網路上~
  7. CombinedLocationStrategy:真正使用的。它是一個聚合,這些實現類可以構成一個掃描鏈來進行按照其順序進行組合掃描,之前講過很多類似的設計模式了

這些順序你可以自由組合,甚至還可以自定義。比如:

List<FileLocationStrategy> subs = Arrays.asList(          new ProvidedURLLocationStrategy(),          new FileSystemLocationStrategy(),          new ClasspathLocationStrategy());  FileLocationStrategy strategy = new CombinedLocationStrategy(subs);

這樣最終定位strategy 策略翻譯如下:

  1. 使用自己的URL定位(比如有完整的URL路徑)
  2. 文件系統
  3. 當前工程classpath(比如僅僅只有一個名字)

插值器

1.x核心API是:ConfigurationInterpolator 2.x核心API是:ConfigurationInterpolator和InterpolatorSpecification、Lookup…

例子暫略。


總結

關於2.x版本相較於1.x有哪些不一樣就先介紹到這,本文是站在一個使用者的角度,對比兩者在使用上的不一樣,這對新手是特別具有引導意義的。 若想了解起深點的原理和訂製,請參閱前面七篇文章,文末直接附有鏈接地址,直達閱讀。

Tags: