技巧: 从类路径装入资源

SAX API 提供了用于定位 XML 文档中资源的
EntityResolver 接口。本技巧文章描述如何通过将本地 Java 类路径与该接口一起使用来解析实体。

SAX API 的基本构件之一是实体解析过程;该过程是通过
org.xml.sax.EntityResolver 接口处理的。遗憾的是,
除了最高级的 SAX 开发人员外,其余所有开发人员都常常会忽视
EntityResolver 接口。
不应该出现这种情况,因为该接口允许对您的应用程序进行所有类型的性能改进。如果您使用灵巧的
EntityResolver 实现,那么可以极大地加速解析过程并简化资源管理。

一个简单的实体解析

作为最简单的实现,
EntityResolver 告诉 SAX 解析器实现如何查找在 XML 文档中指定的资源。
清单 1显示了带实体引用的 XML 文档片段。

清单 1. 带实体引用的 XML

<footer>
<smallText>&copyright;</smallText>
</footer>

清单 1 中显示的这个简单的文档片段演示了一个实体引用(名为
copyright )。当 SAX 解析过程运行到该实体引用时,它必须将该实体解析成另一段内容。
解析过程首先查阅文档的 DTD 或 XML 模式,以获得类似
清单 2 中所示的定义。

清单 2. 实体引用定义

<!ENTITY copyright
PUBLIC "-//IBM//TEXT DeveloperWorks Copyright//EN"
"copyright.xml"
>

根据该引用,解析器得出实体引用的公共标识(
-//IBM//TEXT DeveloperWorks Copyright//EN )和系统标识(
copyright.xml )。
然后,解析过程检查
EntityResolver 实现是否已经在当前的
XMLReader 实现上注册。如果已经注册,
则它用从 DTD 或模式抽取的公共和系统标识调用
resolveEntity() 方法。
清单 3 显示了最简单的
EntityResolver 实现。

清单 3. 最简单的 EntityResolver

import java.io.IOException;
import org.xml.sax.SAXException;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
public class SimpleEntityResolver implements EntityResolver {
public InputSource resolveEntity(String publicId, String systemId)
throws SAXException, IOException {
// Returning null means use normal resolution
return null;
}
}

该实现每次都只返回
null ,这通知解析器应该进行正常的实体解析。
按照引用标识所指定的,通过因特网或本地文件系统查找公共标识和(随后)系统标识。
在本示例中,解析器通过因特网解析公共标识,如果解析失败,
解析器搜索名为
copyright.xml的本地文件。然后,将该过程的内容插入文档中。

在因特网上查找资源很耗时,而且每次解析文档时,都会使您的应用程序速度减慢。
为了防止这一问题,通常将所有必需的引用和资源下载到本地文件系统。
要确保使用这些本地文件而不是使用在线资源,
开发人员经常更改 XML 的约束集并将实体引用定义的系统标识指向文件或下载资源的本地副本。
遗憾的是,事实证明这是一个坏主意:它将引用连接到特定位置中特定文件系统上的特定文件。
一个更好的解决方案是不管文档的约束,而且将所有必需的资源以及应用程序中使用的 XML 文档和 Java 类都封装在一个 jar 文件中。
这意味着您将用 XML(象
copyright.xml 文件)引用库(象 XML 解析器)、应用程序文件(Java 类)、XML 文档和约束以及所有资源。当您使用 jar 文件时,部署非常简单,因为所有必需的资源都包括在该 jar 文件中。
然后,将该压缩文档添加到 Java 类路径中即可。

注册实体解析器

最后一步是注册在当前类路径中查找实体引用的实体解析器。
清单 4正好显示了这种解析器。

清单 4. 使用类路径解析实体

import java.io.InputStream;
import java.io.IOException;
import org.xml.sax.SAXException;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
public class ClassPathEntityResolver implements EntityResolver {
public InputSource resolveEntity(String publicId, String systemId)
throws SAXException, IOException {
InputSource inputSource = null;

try {
InputStream inputStream =
EntityResolver.class.getResourceAsStream(
systemId);
inputSource = new InputSource(inputStream);
} catch (Exception e) {
// No action; just let the null InputSource pass through
}
// If nothing found, null is returned, for normal processing
return inputSource;
}
}

如果 XML 引用的系统标识是
copyright.xml ,那么只要将资源放在 jar 文件的顶层,并确保其名称为
copyright.xml 。然后,用
XMLReader 注册
ClassPathEntityResolver 的实例,然后就可以进行语法解析(parsing)和实体解析(resolution)了。