1. 在不重啟服務的前提下,如何讓配置修改生效的呢?有什麼奇技淫巧嗎?
2. 在 Java 項目中,總能看到以 .properties 為後綴的文件蹤影,這類配置文件是怎麼加載的呢?
項目研發過程中,總會遇到一些經常改變的參數,比如要連接的數據庫的連接地址、名稱、用戶名、密碼;再比如訪問三方服務的 URL 等等。考慮到程序的通用性,這些參數往往不能直接寫死在程序裡,通常藉助配置文件來優雅處理。
在 Java 項目中,properties 文件當屬使用較簡單一類,不過雖然簡單,還是要好好說說項目中都是怎麼使用的,嘗試通過源碼解讀,讓你真正懂它,並帶你深刻體會 Java 中重載的意義。
雖說簡單,部分同學卻與它素未謀面。
相比上次談及的 ini 配置文件,properties 文件格式沒有 Section (節)的概念,反而是簡單了不少。
上圖是一個 jdbc 連接所需要的配置,其中以 # 開始的每一行是註釋信息,而以等號分割的每行配置,就是常說的鍵-值對,等號左邊的為 key(代碼中的變量),等號右邊的為 value(是依據實際場景而配置的值)。
雖說簡單,Java 源碼還是要去看看。
在 Java 中提供了 java.util.Properties 類,主要用於對配置文件的讀寫操作。
一圖掌握血緣關係,很顯然 Properties 繼承自 Hashtable,歸根結底是個 Map,而 Properties 最特殊的地方,就是它的鍵和值都是字符串類型。
從全局瞭解梗概,然後走進 JDK 源碼,按照思路,步步去深入。
首先,要看看寫好的配置文件是怎麼加載的?
源碼很清晰,提供字符流 Reader、字節流 InputStream兩種方式加載配置文件(
方法重載的目的:讓使用者更方便),再深入去看最終會調用 Hashtable 的 put(key, value) 來設置鍵值對,最終完成配置文件中的加載。然後,要看看怎麼根據 key 獲得對應的 value(放進去了,還要考慮拿出來)?
源碼很清晰,通過參數 key 獲得對應的 value,考慮到使用者的方便,對 getProperty 方法進行了重載,其中標註 2 的方法,當根據 key 獲得值是 null 時,會返回一個調用方法時傳入的默認值(很多場景下,確實很有用)。
知道了怎麼加載配置文件,知道了怎麼獲取 key 對應的值,按照常理說,項目中已經夠用了,但是有些時候項目啟動後,還真需要再額外設置一下參數的值,不過沒關係,因為 Java 已經想到了這一點,對外提供了 setProperty 的方法,讓額外設置參數成為可能。
若能在項目研發中,熟練使用上面提到的這些 API,已經足矣。既然打開了源碼,索性把雜七雜八的都提提,說不定某些 API 也能解決你碰到的其它場景的問題呢。
Properties 類不僅提供了 load 進行加載配置,而且還提供了把鍵值對寫到文件中的能力。如上面源碼所示,考慮到使用者的方便,對 store 方法進行重載,提供了面向字節流 OutputStream、字符流 Writer 兩種方式的 store。
如上面源碼所示,Properties 除了提供對常規配置文件讀寫能力支撐,對 xml 配置文件加載、寫入也提供了支撐。
如上圖源碼所示,Properties 類提供了重載的 list 方法,為了方便調試,可以把鍵值對列表給整齊的打印出來。
雖說簡單,不能賦予實踐一切都是扯淡。
場景一:在 APM 性能監控時,獲取 Java 應用畫像信息常用 API。
程序跑起來,部分輸出截圖示意如下。
場景二:業務開發中,讓配置替代硬編碼,並考慮配置更新時,程序能夠讀到最新的值。
<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,看看效果如何?
程序跑起來,效果還是讓人很滿意。
雖說簡單,洋洋灑灑分享一大篇。
有關配置文件的分享網上有很多,而我們的分享卻顯得不太一樣。
我們的初衷是:結合實際項目及源碼,說說這些年用過的那些有關配置的奇技淫巧,幫你提高研發能力(那怕是提高一丟丟,就算成功)。
它山之石可以攻玉,相信會對你有所幫助。後續將繼續結合實際項目,看看用到的其它形式的配置文件,敬請期待。
閱讀更多 一猿小講 的文章