首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Java 解析帶節點引用的 .properties 文件:支持 ${key} 變量替換的完整實現

     編輯:程式設計綜合問答

需求:.properties 文件包含節點(Section)與變量引用(%key%),如:

[Default]
team = happy team

[Project]
Dev = Harry
From = %team%
age = 20
Des = %Dev%, from %From%, %age% years old

調用 getString("Project", "Des") 返回 "Harry, from happy team, 20 years old"。

解析策略:兩遍掃描 + 正則替換

  1. 第一遍:按行讀取,識別 [Section],構建 Map> 結構,存儲原始值。
  2. 第二遍:遍歷所有節點的所有鍵值,用正則 %([A-Za-z]+)% 匹配引用,遞歸查找並替換,直到無引用。
  3. 提供 getString(section, key)、getInteger(section, key) 便捷方法。

核心代碼(精簡版)

public class PropertiesParser {
    private final Map> data = new LinkedHashMap<>();
    private final Pattern refPattern = Pattern.compile("%([A-Za-z]+)%");

    public void parse(String filePath) throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line, currentSection = null;
            while ((line = br.readLine()) != null) {
                line = line.trim();
                if (line.isEmpty() || line.startsWith("#")) continue;
                if (line.startsWith("[") && line.endsWith("]")) {
                    currentSection = line.substring(1, line.length() - 1);
                    data.putIfAbsent(currentSection, new LinkedHashMap<>());
                } else if (currentSection != null) {
                    int eq = line.indexOf('=');
                    if (eq > 0) {
                        String key = line.substring(0, eq).trim();
                        String val = line.substring(eq + 1).trim();
                        data.get(currentSection).put(key, val);
                    }
                }
            }
        }
        // 第二遍:解析引用
        resolveReferences();
    }

    private void resolveReferences() {
        boolean changed;
        do {
            changed = false;
            for (Map section : data.values()) {
                for (Map.Entry entry : section.entrySet()) {
                    String val = entry.getValue();
                    Matcher m = refPattern.matcher(val);
                    StringBuffer sb = new StringBuffer();
                    while (m.find()) {
                        String refKey = m.group(1);
                        String refVal = findValueInAllSections(refKey);
                        if (refVal != null) {
                            m.appendReplacement(sb, Matcher.quoteReplacement(refVal));
                            changed = true;
                        }
                    }
                    m.appendTail(sb);
                    entry.setValue(sb.toString());
                }
            }
        } while (changed); // 支持鏈式引用 %a%→%b%→value
    }

    private String findValueInAllSections(String key) {
        for (Map sec : data.values()) {
            if (sec.containsKey(key)) return sec.get(key);
        }
        return null;
    }

    public String getString(String section, String key) {
        return data.getOrDefault(section, Collections.emptyMap()).get(key);
    }
    public int getInteger(String section, String key) {
        String v = getString(section, key);
        return v != null ? Integer.parseInt(v) : -1;
    }
}

測試驗證

PropertiesParser parser = new PropertiesParser();
parser.parse("pro.properties");
System.out.println(parser.getString("Project", "Des")); 
// 輸出: Harry, from happy team, 20 years old
System.out.println(parser.getInteger("Project", "age")); // 20

擴展建議

  • 支持 ${key} 語法(Spring 風格),只需改正則。
  • 添加循環引用檢測(%a% → %b% → %a%),防死循環。
  • 整合到 Spring PropertySource 體系,實現 @Value("${Project.Des}") 直接注入。

這份解析器展示了「配置文件預處理」的通用模式:**分層存儲 → 延遲解析 → 正則替換 → 緩存結果**,可直接用於項目配置管理模塊。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved