細數Java項目中用過的配置文件(properties篇)

1. 在不重啟服務的前提下,如何讓配置修改生效的呢?有什麼奇技淫巧嗎?


2. 在 Java 項目中,總能看到以 .properties 為後綴的文件蹤影,這類配置文件是怎麼加載的呢?

項目研發過程中,總會遇到一些經常改變的參數,比如要連接的數據庫的連接地址、名稱、用戶名、密碼;再比如訪問三方服務的 URL 等等。考慮到程序的通用性,這些參數往往不能直接寫死在程序裡,通常藉助配置文件來優雅處理。

在 Java 項目中,properties 文件當屬使用較簡單一類,不過雖然簡單,還是要好好說說項目中都是怎麼使用的,嘗試通過源碼解讀,讓你真正懂它,並帶你深刻體會 Java 中重載的意義。


雖說簡單,部分同學卻與它素未謀面。


相比上次談及的 ini 配置文件,properties 文件格式沒有 Section (節)的概念,反而是簡單了不少。

細數Java項目中用過的配置文件(properties篇)

上圖是一個 jdbc 連接所需要的配置,其中以 # 開始的每一行是註釋信息,而以等號分割的每行配置,就是常說的鍵-值對,等號左邊的為 key(代碼中的變量),等號右邊的為 value(是依據實際場景而配置的值)。


雖說簡單,Java 源碼還是要去看看。


在 Java 中提供了 java.util.Properties 類,主要用於對配置文件的讀寫操作。

細數Java項目中用過的配置文件(properties篇)

一圖掌握血緣關係,很顯然 Properties 繼承自 Hashtable,歸根結底是個 Map,而 Properties 最特殊的地方,就是它的鍵和值都是字符串類型。

從全局瞭解梗概,然後走進 JDK 源碼,按照思路,步步去深入。

首先,要看看寫好的配置文件是怎麼加載的?

細數Java項目中用過的配置文件(properties篇)

源碼很清晰,提供字符流 Reader、字節流 InputStream兩種方式加載配置文件(

方法重載的目的:讓使用者更方便),再深入去看最終會調用 Hashtable 的 put(key, value) 來設置鍵值對,最終完成配置文件中的加載。

細數Java項目中用過的配置文件(properties篇)

然後,要看看怎麼根據 key 獲得對應的 value(放進去了,還要考慮拿出來)?

細數Java項目中用過的配置文件(properties篇)

源碼很清晰,通過參數 key 獲得對應的 value,考慮到使用者的方便,對 getProperty 方法進行了重載,其中標註 2 的方法,當根據 key 獲得值是 null 時,會返回一個調用方法時傳入的默認值(很多場景下,確實很有用)。

知道了怎麼加載配置文件,知道了怎麼獲取 key 對應的值,按照常理說,項目中已經夠用了,但是有些時候項目啟動後,還真需要再額外設置一下參數的值,不過沒關係,因為 Java 已經想到了這一點,對外提供了 setProperty 的方法,讓額外設置參數成為可能。

細數Java項目中用過的配置文件(properties篇)

若能在項目研發中,熟練使用上面提到的這些 API,已經足矣。既然打開了源碼,索性把雜七雜八的都提提,說不定某些 API 也能解決你碰到的其它場景的問題呢。

細數Java項目中用過的配置文件(properties篇)

Properties 類不僅提供了 load 進行加載配置,而且還提供了把鍵值對寫到文件中的能力。如上面源碼所示,考慮到使用者的方便,對 store 方法進行重載,提供了面向字節流 OutputStream、字符流 Writer 兩種方式的 store。

細數Java項目中用過的配置文件(properties篇)

如上面源碼所示,Properties 除了提供對常規配置文件讀寫能力支撐,對 xml 配置文件加載、寫入也提供了支撐。

細數Java項目中用過的配置文件(properties篇)

如上圖源碼所示,Properties 類提供了重載的 list 方法,為了方便調試,可以把鍵值對列表給整齊的打印出來。


雖說簡單,不能賦予實踐一切都是扯淡。


場景一:在 APM 性能監控時,獲取 Java 應用畫像信息常用 API。

細數Java項目中用過的配置文件(properties篇)

程序跑起來,部分輸出截圖示意如下。

細數Java項目中用過的配置文件(properties篇)

場景二:業務開發中,讓配置替代硬編碼,並考慮配置更新時,程序能夠讀到最新的值。

<code>import java.io.*;
import java.util.Hashtable;
import java.util.Properties;

/**
* 配置文件工具類

* 1. 支持加載 .properties文件、.ini文件
* 2. 支持配置文件更新
* @author 一猿小講
*/
public class PropertiesUtil {

// private static final Log4j LOG = .....;

private static Hashtable<string> propCache = new Hashtable<string>();

public static String getString(String propFile, String key) {
return getString(propFile, key, null);
}

public static String getString(String propFile, String key, String defaultValue) {
if ((!propFile.endsWith(".properties")) && (!propFile.endsWith(".ini"))) {
propFile = propFile + ".properties";
}
PropCache prop = propCache.get(propFile);
if (prop == null) {
try {
prop = new PropCache(propFile);
} catch (IOException e) {
// LOG.warn(e);
System.out.println(String.format("讀取 %s 出現異常%s", propFile,e));
return defaultValue;
}
propCache.put(propFile, prop);
}
String value = prop.getProperty(key, defaultValue);
if (value != null) {
value = value.trim();
}
return value;
}

public static void setString(String propFile, String key, String value)
throws IOException {
if ((!propFile.endsWith(".properties")) && (!propFile.endsWith(".ini"))) {
propFile = propFile + ".properties";
}
File file = new File(propFile);
Properties prop = new Properties();
try {
FileInputStream fis = new FileInputStream(file);
prop.load(fis);
fis.close();
} catch (FileNotFoundException e) {

// LOG.warn(e);
System.out.println(String.format("文件 %s 不存在", propFile));
}
FileOutputStream fos = new FileOutputStream(file);
prop.put(key, value);
prop.store(fos, null);
fos.close();
PropCache localPropCache = propCache.get(propFile);
if (localPropCache != null) {
localPropCache.reload();
}
}

private static class PropCache {

private String fileName;
private long lastLoad;
private Properties prop;

public PropCache(String propFileName) throws IOException {
File file = new File(propFileName);
FileInputStream fis = new FileInputStream(file);
this.prop = new Properties();
this.prop.load(fis);
fis.close();
this.fileName = file.getAbsolutePath();
this.lastLoad = file.lastModified();
}

public String getProperty(String key, String defaultValue) {
File file = new File(this.fileName);
if (this.lastLoad < file.lastModified()) {
reload();
}
return this.prop.getProperty(key, defaultValue);
}

public void reload() {
File file = new File(this.fileName);
try {
Properties prop = new Properties();
FileInputStream fis = new FileInputStream(file);
prop.load(fis);
fis.close();
this.prop.clear();
this.prop.putAll(prop);
this.lastLoad = file.lastModified();
} catch (IOException e) {
// PropertiesUtil.LOG.warn(e);
System.out.println(String.format("文件 %s 重新加載出現異常%s", this.fileName, e));

}
// PropertiesUtil.LOG.all(new Object[]{this.fileName, " reloaded."});
System.out.println(String.format("文件 %s 重新加載完畢", this.fileName));
}
}
}/<string>/<string>/<code>

藉助開篇提到的 jdbc 配置文件,進行驗證。嘗試獲取數據庫類型,默認配置為 db2,中途修改參數的值為 mysql,看看效果如何?

細數Java項目中用過的配置文件(properties篇)

程序跑起來,效果還是讓人很滿意。

細數Java項目中用過的配置文件(properties篇)

雖說簡單,洋洋灑灑分享一大篇。


有關配置文件的分享網上有很多,而我們的分享卻顯得不太一樣。

我們的初衷是:結合實際項目及源碼,說說這些年用過的那些有關配置的奇技淫巧,幫你提高研發能力(那怕是提高一丟丟,就算成功)。

它山之石可以攻玉,相信會對你有所幫助。後續將繼續結合實際項目,看看用到的其它形式的配置文件,敬請期待。


分享到:


相關文章: