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


靈魂拷問:在不重啟服務的前提下,如何讓配置修改生效的呢?有什么奇技淫巧嗎?

靈魂拷問:在 Java 項目中,總能看到以 .properties 為后綴的文件蹤影,這類配置文件是怎么加載的呢?

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

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

 

1. 雖說簡單,格式還是要看一看。

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

 

 

 

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

 

2. 雖說簡單,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 方法,為了方便調試,可以把鍵值對列表給整齊的打印出來。

 

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

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

import java.util.Properties;

/**
 * @author 一猿小講
 */
public class JVMDetails {
    public static void main(String[] args) {
        // 獲取系統信息(JDK信息、Java虛擬機信息、Java提供商信息、當前計算機用戶信息)
        Properties properties = System.getProperties();
        // 把系統信息打印一下
        properties.list(System.out);
    }
}

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

 

 

 

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

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> propCache = new Hashtable<String, PropCache>();

    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));
        }
    }
}

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

/**
 * 測試類
 * @author 一猿小講
 */
public class M {
    public static void main(String[] args) {
        while(true) {
            System.out.println(PropertiesUtil.getString("db", "jdbc.type"));
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
            }
        }
    }
}

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

 

 

 

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

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

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

它山之石可以攻玉,相信會對你有所幫助。

為了能夠幫你提高研發能力(那怕是提高一丟丟呢),后續將繼續結合實際項目,看看用到的其它形式的配置文件,敬請期待。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM