在Kotlin中处理XML配置文件时,DOM(Document Object Model)方式是一种直观且易于维护的选择。它把整个XML文档加载到内存中,形成一棵节点树,开发者可以像操作普通对象一样随机访问任意元素、属性和文本内容。对于配置项层级较深、需要反复读取不同节点的场景,DOM比顺序读取的SAX更方便。

一、DOM解析的基本原理与适用场景
DOM解析的核心是由解析器把XML文本转换成内存中的Document对象,该对象遵循W3C的DOM规范,根节点之下包含元素节点、属性节点和文本节点等。Kotlin作为JVM语言,直接调用Java的javax.xml.parsers包即可,不需要额外引入第三方库。
这种方式适合配置文件较小(通常几百KB以内)的情况。因为整树驻留内存,若文件过大可能造成OOM;但其随机访问特性让我们可以写类似doc.getElementsByTagName("db")这样的代码,快速定位目标。相比之下,SAX是事件驱动、只能顺次处理,不便于回查。
1.1 基础依赖与工厂类
JDK已内置DOM相关类,无需添加Gradle依赖。关键是使用DocumentBuilderFactory获得工厂实例,再生成DocumentBuilder。为了安全,应禁用外部实体加载,避免XXE攻击。
下面代码展示最基础的工厂配置,其中setFeature关闭了危险选项,是生产环境必备动作。
import javax.xml.parsers.DocumentBuilderFactory
fun createSafeFactory(): DocumentBuilderFactory {
val factory = DocumentBuilderFactory.newInstance()
// 关闭外部实体,防止XXE
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)
factory.setFeature("http://xml.org/sax/features/external-general-entities", false)
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false)
factory.isXIncludeAware = false
return factory
}
二、读取并解析XML配置文件的完整步骤
假设我们有如下简单的配置文件config.xml,存放于资源目录:
<?xml version="1.0" encoding="UTF-8"?>
<config>
<database url="jdbc:mysql://127.0.0.1:3306/test">
<user>root</user>
<password>123456</password>
</database>
<cache enabled="true">
<ttl>300</ttl>
</cache>
</config>
我们要用Kotlin读出数据库URL、用户名以及缓存开关。第一步是用上节的工厂构建Document,第二步通过标签名和属性方法提取数据。
2.1 加载文件为Document
在Kotlin中可以用File或InputStream传给parse方法。以下示例从绝对路径加载,实际项目建议用类加载器读资源。
import java.io.File
import javax.xml.parsers.DocumentBuilderFactory
import org.w3c.dom.Document
fun loadXml(path: String): Document {
val factory = createSafeFactory()
val builder = factory.newDocumentBuilder()
return builder.parse(File(path))
}
该函数返回Document后,整棵树就在内存里了。任何后续读取都不会再触碰磁盘,性能稳定。
2.2 提取元素与属性
getElementsByTagName返回NodeList,可用下标取首个匹配节点。属性通过getAttribute获取,文本用textContent。注意节点可能不存在,需做空判断。
import org.w3c.dom.Document
import org.w3c.dom.Element
fun readConfig(doc: Document) {
val db = doc.getElementsByTagName("database").item(0) as Element
val url = db.getAttribute("url")
val user = db.getElementsByTagName("user").item(0).textContent
val password = db.getElementsByTagName("password").item(0).textContent
val cache = doc.getElementsByTagName("cache").item(0) as Element
val cacheEnabled = cache.getAttribute("enabled").toBoolean()
val ttl = cache.getElementsByTagName("ttl").item(0).textContent.toInt()
println("url=$url, user=$user, pwd=$password")
println("cacheEnabled=$cacheEnabled, ttl=$ttl")
}
上述代码把配置打印出来。如果某些节点可选,应先判断item(0)是否为null,否则强转会抛异常。这样便完成了一次典型的DOM式配置读取。
三、遍历与进阶处理技巧
当配置文件结构不固定或需要批量处理同类节点时,手动写死标签名就不够灵活。可以借助递归或NodeList循环来处理。
3.1 使用NodeList遍历子节点
下面函数递归打印所有元素节点名称与文本,便于调试未知结构的XML。
import org.w3c.dom.Node
import org.w3c.dom.NodeList
fun walk(node: Node, depth: Int = 0) {
val list: NodeList = node.childNodes
for (i in 0 until list.length) {
val n = list.item(i)
if (n.nodeType == Node.ELEMENT_NODE) {
println(" ".repeat(depth) + n.nodeName + " : " + n.textContent.trim())
walk(n, depth + 1)
}
}
}
这种方式能清晰看到树形关系。但在生产提取中,仍推荐用明确路径,减少误读。DOM也支持XPath,可写出/config/database/@url这样的表达式,进一步简化定位。
3.2 XPath配合DOM
Kotlin中通过XPathFactory编译表达式,再在Document上求值,非常适合复杂配置。
import javax.xml.xpath.XPathFactory
import org.w3c.dom.Document
fun readByXPath(doc: Document): String? {
val xpath = XPathFactory.newInstance().newXPath()
return xpath.evaluate("/config/database/@url", doc)
}
使用XPath后,代码更简洁且不易因层级变动而失效。缺点是表达式写错时排查稍麻烦,建议封装成工具方法并写单元测试。
四、常见误区与注意事项
不少人在Kotlin里直接用textContent取节点文本,却忽略了子元素拼接会导致多余换行。若只要当前元素直接文本,应遍历childNodes过滤Node.TEXT_NODE。
另外,DOM解析默认保留空白文本节点,遍历时务必用nodeType判断,否则会处理到无意义空格。最后,解析不可信来源的XML必须按前文禁用DOCTYPE,否则有安全漏洞。掌握这些细节,Kotlin加DOM就能稳健地服务于配置读取需求。