早期 Java 專案常用 .properties (Key=Value) 或 INI 格式配置,但隨著系統複雜度提升,XML 憑藉層級結構、類型擴展性、命名空間隔離、Schema 驗證等優勢,成為事實標準。Tomcat server.xml、J2EE web.xml、Spring applicationContext.xml 皆採用 XML。本文實作一個基於 SAX 的通用配置解析器,適用於資料庫連線參數等簡單場景。
| 解析器 | 模式 | 記憶體 | 適用場景 | J2EE 常見實作 |
|---|---|---|---|---|
| DOM | 樹模型,一次性載入全文檔 | 高 (正比文件大小) | 小型文件、需隨機訪問、頻繁修改 | Xerces, JDK 內建 |
| SAX | 事件驅動,流式讀取 | 極低 (常數級) | 大型文件、單向讀取、配置啟動解析 | Xerces, JDK 內建 |
| StAX | 拉取模式,游標控制 | 低 | 需雙向導航、部分讀取、JDK 6+ | Woodstox, JDK 內建 |
配置文件通常較小 (<100KB) 且啟動時單次讀取,**SAX 以簡潔 API、低記憶體、標準化 (JAXP) 成為首選**。Tomcat 內部即使用 Sun JAXP (採用 Xerces 實作)。
<?xml version="1.0" encoding="UTF-8"?>
<myenv>
<datasource>
<dbhost>localhost</dbhost>
<dbname>sqlname</dbname>
<dbuser>username</dbuser>
<dbpassword>password</dbpassword>
</datasource>
</myenv>
放置於 WEB-INF/classes/myenv.xml,隨應用部署,透過類加載器獲取資源路徑。
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.util.Properties;
public class ConfigParser extends DefaultHandler {
private final Properties props = new Properties();
private String currentElement;
private final StringBuffer currentValue = new StringBuffer();
public Properties getProps() { return props; }
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
currentValue.setLength(0); // 清空緩衝區
currentElement = qName;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
currentValue.append(ch, start, length); // 累加字符內容
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
// 僅保存葉子節點值,忽略父節點 (datasource, myenv)
String value = currentValue.toString().trim();
if (!value.isEmpty()) {
props.put(qName.toLowerCase(), value);
}
}
}
關鍵點:
DefaultHandler 避免實作所有介面方法。characters() 可能多次調用 (大文本分段),必須用 StringBuffer 累加。endElement() 以標籤名為 Key 存入 Properties,轉小寫統一大小寫。import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.net.URL;
import java.util.Properties;
public class ParseXML {
private Properties props;
public Properties getProps() { return props; }
public void parse(String filename) throws Exception {
ConfigParser handler = new ConfigParser();
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(false); // 簡單配置無命名空間
factory.setValidating(false); // 不驗證 DTD/Schema,提速
SAXParser parser = factory.newSAXParser();
// 透過類加載器獲取資源絕對路徑 (支援 WAR 部署)
URL confURL = getClass().getClassLoader().getResource(filename);
if (confURL == null) throw new RuntimeException("Config not found: " + filename);
try {
parser.parse(confURL.toString(), handler);
props = handler.getProps();
} finally {
// 釋放引用助 GC
factory = null; parser = null; handler = null;
}
}
}
public class AppInit {
public static void main(String[] args) {
try {
ParseXML parser = new ParseXML();
parser.parse("myenv.xml");
Properties p = parser.getProps();
String url = "jdbc:mysql://" + p.getProperty("dbhost") + "/" + p.getProperty("dbname");
String user = p.getProperty("dbuser");
String pwd = p.getProperty("dbpassword");
System.out.println("JDBC URL: " + url);
// 後續建立 DataSource...
} catch (Exception e) {
throw new RuntimeException("Config parse failed", e);
}
}
}
若配置擴展為:
<datasource pool="true" maxActive="20">
<url>jdbc:mysql://...</url>
</datasource>
需在 startElement() 處理 attributes 參數,並在 endElement() 區分層級 (可用堆疊追蹤路徑)。
@ConfigurationProperties / @Value:類型安全綁定、SpEL 表達式、Profile 環境隔離,現代 Spring 應用首選。encoding="UTF-8",Java 讀取確保同編碼,避免中文亂碼。Properties,避免重複解析。dbhost) 應拋出異常阻止啟動,而非預設值掩蓋問題。XML 配置解析看似基礎,卻折射出 Java 生態從「硬編碼 → Properties → XML → 註解 → 雲原生配置」的演進軌跡。掌握 SAX 事件驅動機制,不僅能應對輕量級配置需求,更能深度理解框架底層原理。若專案已採用 Spring Boot 或 MicroProfile,**請優先使用框架原生配置綁定**,將精力聚焦於業務創新而非重複造輪子。