Apache Axiom 1.4.0 常用工具与 API 详解
Apache Axiom 1.4.0 常用工具与 API 详解:Axiom 无 CLI 工具,全部能力经 Java API 使用,逐类速查 OMFactory/OMElement/OMXMLBuilderFactory/StAXUtils/OMOutputFormat/Attachments/DocumentNavigator 核心类,含方法表、代码示例、1.4.0 版本差异与 FAQ,全程实测。
Apache Axiom 1.4.0 常用工具与 API 详解
目标:详解 Apache Axiom 1.4.0 的核心 API 与工具。Axiom 以「类库 + jar」形态提供,没有
axiom命令行可执行程序,对 XML 的全部操作通过 Java 代码调用其 API 完成。本文按类速查OMFactory/OMElement/OMXMLBuilderFactory/StAXUtils/OMOutputFormat/Attachments/DocumentNavigator等关键类,给出方法表、可独立执行的代码示例、1.4.0 相对 1.2.x 的 API 差异与 FAQ。所有示例在 Ubuntu 22.04.5 + OpenJDK 17 环境实测。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(jammy)
- Java:OpenJDK 17.0.20.1
- 构建工具:Apache Maven 3.6.3
- Axiom 版本:1.4.0(axiom-api 1.4.0 + axiom-impl 1.4.0 + axiom-dom 1.4.0)
- 传递依赖:commons-logging 1.2、jaxen 1.2.0、apache-mime4j-core 0.8.6、jakarta.activation-api 1.2.2、woodstox-core 6.2.8、stax2-api 4.2.1
- 示例工程:
/root/axiom-demo/ - 演示账号:root
二、工具概览
Axiom 没有命令行工具,全部能力经 Java API 使用。按职责划分的核心类如下:
| 类别 | 代表类 | 作用 |
|---|---|---|
| 工厂 | OMFactory / OMAbstractFactory / OMMetaFactory | 创建元素、属性、命名空间、文档等一切节点 |
| 树模型 | OMElement / OMNode / OMAttribute / OMNamespace / OMDocument | XML 树上的节点对象 |
| 解析 | OMXMLBuilderFactory / OMXMLParserWrapper | 从 InputStream/Reader/Source/Node 构建 OM 树 |
| StAX 工具 | StAXUtils(org.apache.axiom.om.util) | 统一入口创建 XMLInputFactory/XMLStreamReader |
| 序列化 | OMOutputFormat 与 serialize 系列方法 | 控制字符集、XML 声明、SOAP/MTOM 输出 |
| 附件 | Attachments(org.apache.axiom.attachments) | MIME multipart / MTOM 附件读写 |
| XPath | DocumentNavigator(org.apache.axiom.om.xpath)+ Jaxen | 对 OM 树执行 XPath 查询 |
| SOAP | SOAPFactory / SOAP11Factory / SOAP12Factory | SOAP 信封专用工厂 |
💡 说明:jar 里每个类都是 OSGi bundle(MANIFEST 含
Bundle-SymbolicName: org.apache.ws.commons.axiom.axiom-api等),可直接部署进 OSGi 环境;非 OSGi 场景按普通 classpath 使用。
三、前置条件
需要一个带 Axiom 1.4.0 依赖并编译通过的 Maven 工程(创建步骤与完整 pom 见《Apache Axiom 1.4.0 构建并运行示例应用指南》二,依赖引入验证见《Apache Axiom 1.4.0 安装指南》四)。验证依赖就位:
cd /root/axiom-demo
mvn -q dependency:build-classpath -Dmdep.outputFile=/tmp/cp.txt
tr ":" "\n" < /tmp/cp.txt | grep axiom
实测回显:
/root/.m2/repository/org/apache/ws/commons/axiom/axiom-api/1.4.0/axiom-api-1.4.0.jar
/root/.m2/repository/org/apache/ws/commons/axiom/axiom-impl/1.4.0/axiom-impl-1.4.0.jar
/root/.m2/repository/org/apache/ws/commons/axiom/axiom-dom/1.4.0/axiom-dom-1.4.0.jar
四、核心 API 详解
4.1 OMAbstractFactory 与 OMFactory(工厂)
OMAbstractFactory 是工厂入口:无参 getOMFactory() 拿默认(LLOM)工厂,getMetaFactory(OMAbstractFactory.FEATURE_DOM) 拿 DOM 实现工厂。OMFactory 负责创建一切节点(javap 实测,方法众多,摘常用):
| 方法 | 说明 |
|---|---|
createOMElement(String localName, OMNamespace ns, OMContainer parent) | 建元素并挂到父节点 |
createOMElement(QName) | 按 QName 建元素 |
createOMNamespace(String uri, String prefix) | 建命名空间对象 |
createOMAttribute(String localName, OMNamespace ns, String value) | 建属性 |
createOMDocument() | 建文档节点(文档级序列化才有 XML 声明) |
createOMText(String) / createOMText(OMElement parent, String text) | 建文本节点 |
建树与序列化完整示例(src/main/java/demo/BuildTree.java):
package demo;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import java.io.StringWriter;
public class BuildTree {
public static void main(String[] args) throws Exception {
OMFactory factory = OMAbstractFactory.getOMFactory();
OMNamespace ns = factory.createOMNamespace("http://demo.example.com/book", "b");
OMElement root = factory.createOMElement("bookstore", ns);
OMElement book = factory.createOMElement("book", ns, root);
book.addAttribute("category", "programming", null);
factory.createOMElement("title", ns, book).setText("Axiom in Action");
factory.createOMElement("price", ns, book).setText("39.90");
StringWriter out = new StringWriter();
root.serialize(out);
System.out.println(out);
}
}
mvn -q compile
mvn -q compile exec:java -Dexec.mainClass=demo.BuildTree
实测回显:
<b:bookstore xmlns:b="http://demo.example.com/book"><b:book category="programming"><b:title>Axiom in Action</b:title><b:price>39.90</b:price></b:book></b:bookstore>
验证手段:输出的 XML 与代码构造层级一致、命名空间前缀 b 声明正确即 API 正常。SOAP 场景把 OMFactory 换成 OMAbstractFactory.getSOAP11Factory(),用 createSOAPEnvelope() 等方法即可得带 soap:Envelope 结构的树。
4.2 OMElement(树节点查询与操作)
OMElement 是最常打交道的类,继承自 OMNamedInformationItem/OMContainer/OMNode。常用方法分组(javap 实测签名摘录):
查询组:
| 方法 | 说明 |
|---|---|
getLocalName() / getQName() / getNamespace() | 元素名与命名空间 |
getFirstChildWithName(QName) | 按名取第一个子元素(无则 null) |
getChildrenWithName(QName) | 按名取所有子元素迭代器 |
getChildElements() | 所有子元素迭代器 |
getChildren() | 所有子节点(含文本)迭代器 |
getAttributeValue(QName) | 取属性值(无则 null) |
getAllAttributes() | 全部属性迭代器 |
getText() | 聚合直接文本 |
getDefaultNamespace() / findNamespaceURI(prefix) | 命名空间查询 |
修改组:
| 方法 | 说明 |
|---|---|
setText(String) | 设置/覆盖文本 |
addAttribute(String name, String value, OMNamespace ns) | 加属性 |
removeAttribute(OMAttribute) | 删属性 |
declareNamespace(String uri, String prefix) / declareDefaultNamespace(String uri) | 声明命名空间 |
addChild(OMNode) | 挂子节点 |
detach() | 从父树摘除(删除节点) |
build() | 强制全量构建子树 |
遍历 + 修改 + 删除示例(src/main/java/demo/TraverseModify.java,完整源码与递归走树见《Apache Axiom 1.4.0 构建并运行示例应用指南》五)关键片段:
QName TITLE = new QName("http://demo.example.com/book", "title");
QName PRICE = new QName("http://demo.example.com/book", "price");
Iterator<OMElement> it = root.getChildElements(); // 遍历
while (it.hasNext()) {
OMElement book = it.next();
System.out.println(book.getFirstChildWithName(TITLE).getText()); // 查询
}
second.getFirstChildWithName(PRICE).setText("49.00"); // 改文本
second.addAttribute("discount", "0.8", null); // 加属性
books.get(2).detach(); // 删除
实测回显(修改删除后的序列化结果):
after modify: <b:bookstore xmlns:b="http://demo.example.com/book"><b:book category="tech"><b:title>Axiom Basics</b:title><b:price>39.9</b:price></b:book><b:book category="tech" discount="0.8"><b:title>XML Deep Dive</b:title><b:price>49.00</b:price></b:book></b:bookstore>
验证手段:第三本书已不在输出中、第二本价格与 discount 属性就位。
4.3 OMXMLBuilderFactory / OMXMLParserWrapper(解析)
解析入口 OMXMLBuilderFactory,返回的 OMXMLParserWrapper 代表「一个正在延迟解析的树」。常用重载(javap 实测):
| 方法 | 说明 |
|---|---|
createOMBuilder(InputStream) | 从字节流建树(默认实现、默认解析配置) |
createOMBuilder(StAXParserConfiguration, InputStream) | 带解析预设建树 |
createOMBuilder(OMFactory, InputStream) | 指定实现工厂建树(切 DOM 用) |
createOMBuilder(Reader) | 从字符流建树 |
createOMBuilder(Source) / createOMBuilder(Node, boolean) | 从 javax.xml.transform.Source / w3c Node 建树 |
createStAXOMBuilder(XMLStreamReader) | 自备 StAX reader 建树 |
createStAXOMBuilder(OMFactory, XMLStreamReader) | 指定工厂 + reader 建树 |
OMXMLParserWrapper 关键方法:
| 方法 | 说明 |
|---|---|
getDocumentElement() | 取根元素(延迟,不强制全量) |
getDocumentElement(boolean buildAll) | 取根元素并可选全量构建 |
getDocument() | 取文档节点 |
isCompleted() | 解析器是否已到文档末尾 |
close() | 释放解析资源 |
解析与延迟验证示例(src/main/java/demo/ParseDeferred.java,CountingInputStream 包装流统计底层读取字节):
CountingInputStream in = new CountingInputStream(new ByteArrayInputStream(data));
OMXMLParserWrapper builder = OMXMLBuilderFactory.createOMBuilder(in);
OMElement root = builder.getDocumentElement();
System.out.println("root=" + root.getLocalName() + ", bytes read=" + in.count);
OMElement first = root.getFirstElement();
System.out.println("first book id=" + first.getAttributeValue(new QName("id"))
+ ", bytes read=" + in.count);
root.build();
System.out.println("after build: bytes read=" + in.count);
实测回显(文档 317799 字节、5000 个 book):
after getDocumentElement(): root=catalog, bytes read=4000, isCompleted=false
after getFirstElement(): first book id=0, bytes read=4000
after root.build(): bytes read=317799 (all bytes read)
💡 技术拓展:XML 解析的两大模型对比——
模型 代表 行为 取舍 推模式 SAX org.xml.sax 解析器推进事件、回调监听者 省内存但代码反直觉、不可回退 拉模式 StAX javax.xml.stream 调用方 next() 拉取事件、随时停 控制权在调用方,Axiom 建树即基于它 Axiom 在 StAX 拉模式之上做了「按需建树」:只把访问过的部分物化为 OM 树(本例仅读 4000 字节即可定位首个子元素),这是它与全量建树型库的本质差异。
4.4 StAXUtils(StAX 工具类)
org.apache.axiom.om.util.StAXUtils 统一管理 StAX 工厂与 reader 创建(1.4.0 中位于 axiom-api)。实测常用方法与行为:
| 方法 | 实测行为 |
|---|---|
getXMLInputFactory() | 返回共享输入工厂,外层为 org.apache.axiom.util.stax.wrapper.ImmutableXMLInputFactory(防改动包装) |
createXMLStreamReader(InputStream[, encoding]) | 创建 reader,底层实现按服务发现:classpath 有 Woodstox 则 Woodstox,否则 JDK 内置 |
createXMLStreamReader(StAXParserConfiguration, InputStream) | 带预设创建(COALESCING 等,见下) |
getXMLOutputFactory() | 输出工厂 |
解析预设对 CDATA 的影响实测(src/main/java/demo/StAXConfigDemo.java,完整源码见《Apache Axiom 1.4.0 配置参数详解》5.2 节):
COALESCING -> CHARACTERS text=hello <world>
PRESERVE_CDATA_SECTIONS -> CDATA text=hello <world>
NON_COALESCING -> CDATA text=hello <world>
六预设全集(javap 实测常量):DEFAULT / COALESCING / NON_COALESCING / PRESERVE_CDATA_SECTIONS / STANDALONE / SOAP。
4.5 OMOutputFormat(序列化配置)
控制输出的字符集、XML 声明与 MIME 行为,配合 serialize(OutputStream, OMOutputFormat) 使用。核心项(javap 实测):
| 项 | 说明 |
|---|---|
setCharSetEncoding(String) | 输出字符集;文档级序列化体现在声明 |
setIgnoreXMLDeclaration(boolean) | 去掉 XML 声明 |
setXmlVersion(String) | XML 版本(实测 1.4.0 声明中不生效,见 7.4 节 FAQ) |
setSOAP11(boolean) | SOAP 1.1 content type |
isOptimized() | MTOM/XOP 优化标记 |
getMimeBoundary() / getNextContentId() | MIME 边界与 part id 自动生成 |
元素级与文档级序列化差异实测(src/main/java/demo/DocSerializeDemo.java):
doc default: <?xml version="1.0" encoding="utf-8"?><d:msg xmlns:d="http://demo.example.com">hi</d:msg>
doc no decl: <d:msg xmlns:d="http://demo.example.com">hi</d:msg>
element : <d:msg xmlns:d="http://demo.example.com">hi</d:msg>
utf16 head bytes: fe ff
要点:文档级序列化默认带声明;元素级默认无声明;UTF-16 输出以 BOM(fe ff)开头。
4.6 Attachments(MIME 附件)
org.apache.axiom.attachments.Attachments 解析 multipart/related(MTOM/XOP、SOAP 附件),构造参数即缓存策略(fileCacheEnable / repoDir / sizeThreshold)。核心方法:
| 方法 | 说明 |
|---|---|
getDataHandler(String cid) | 按 Content-ID 取附件(id 不带尖括号) |
getContentIDList() | 所有附件 Content-ID 列表 |
getRootPartContentID() | 根 part 的自动生成 id |
getSOAPPartInputStream() | 根 part 输入流 |
getSOAPPartContentType() | 根 part content type |
实测(构造一个含 file.txt 附件的 MIME 消息后读取,完整源码见《Apache Axiom 1.4.0 配置参数详解》八):
content id list = [firstPart_00b6e098f957e848ef9b22db90b53b944366b6539b841da7@apache.org]
file.txt content = hello attachment
SOAP part content type = application/xop+xml; charset=UTF-8; type="text/xml"
4.7 DocumentNavigator + Jaxen(XPath 查询)
org.apache.axiom.om.xpath.DocumentNavigator 把 OM 树适配给 Jaxen XPath 引擎(axiom-api 传递依赖 jaxen 1.2.0)。示例(src/main/java/demo/XpathDemo.java):
package demo;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.xpath.DocumentNavigator;
import org.jaxen.BaseXPath;
import java.util.List;
public class XpathDemo {
public static void main(String[] args) throws Exception {
OMFactory f = OMAbstractFactory.getOMFactory();
OMNamespace ns = f.createOMNamespace("http://demo.example.com/book", "b");
OMElement root = f.createOMElement("bookstore", ns);
String[] titles = {"Axiom Basics", "XML Deep Dive"};
for (String t : titles) {
OMElement book = f.createOMElement("book", ns, root);
f.createOMElement("title", ns, book).setText(t);
}
BaseXPath xpath = new BaseXPath("//b:title", new DocumentNavigator());
xpath.addNamespace("b", ns.getNamespaceURI());
List<?> result = xpath.selectNodes(root);
System.out.println("//b:title matched " + result.size() + " nodes");
for (Object o : result) {
System.out.println(" title text = " + ((OMElement) o).getText());
}
}
}
mvn -q compile
mvn -q compile exec:java -Dexec.mainClass=demo.XpathDemo
实测回显:
//b:title matched 2 nodes
title text = Axiom Basics
title text = XML Deep Dive
验证手段:命中数与文本与建树数据一致。三个关键用法:用 BaseXPath(org.jaxen.XPath 是接口不能 new)、带前缀表达式必须 addNamespace、结果元素可强转回 OMElement 继续操作。
4.8 SOAPFactory(SOAP 消息)
OMMetaFactory.getSOAP11Factory() / getSOAP12Factory() 返回 SOAP 专用工厂,按信封结构创建对象(createSOAPEnvelope/createSOAPHeader/createSOAPBody 等),并自带 SOAP 编码规则处理。快速验证:
org.apache.axiom.soap.SOAPFactory sf = OMAbstractFactory.getSOAP11Factory();
org.apache.axiom.soap.SOAPEnvelope env = sf.getDefaultEnvelope();
System.out.println(env.getClass().getName());
env.serialize(System.out);
实测回显:
org.apache.axiom.soap.impl.llom.soap11.AxiomSOAP11EnvelopeImpl
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Header/><soapenv:Body/></soapenv:Envelope>
(getDefaultEnvelope() 生成带空 Header/Body 的标准 SOAP 1.1 信封,可直接往 Body 挂业务元素。)
五、1.4.0 相对 1.2.x 的 API 差异
升级老工程时重点核对以下差异(均为 1.4.0 实测结论):
| 差异点 | 1.2.x | 1.4.0 | 迁移动作 |
|---|---|---|---|
| OMNavigator | org.apache.axiom.om.OMNavigator 全树游标 | 已移除,jar 中无此类 | 改用 getChildElements()/getChildren() 递归,或 DocumentNavigator+XPath |
| 工厂类 | LLOM 与 DOM 各有独立 Factory 类 | 共用 om.impl.common.factory.OMFactoryImpl | 不再以工厂类判断实现,改看节点类或强转 w3c |
| OMElement 建树 | setFirstChild 等大量低层方法 | 收敛为 addChild/父子构造器 | 用 addChild(OMNode) |
| 激活框架 | javax.activation(JDK 内置) | jakarta.activation-api 1.2.2 显式依赖 | 确认 classpath 含 jakarta.activation-api |
| StAX | 常配合 JDK5 时代实现 | 推荐 Woodstox 6.x(传递依赖) | 保留 woodstox-core 即可 |
验证 OMNavigator 确已移除:
unzip -l /root/.m2/repository/org/apache/ws/commons/axiom/axiom-api/1.4.0/axiom-api-1.4.0.jar | grep -c Navigator
unzip -l /root/.m2/repository/org/apache/ws/commons/axiom/axiom-api/1.4.0/axiom-api-1.4.0.jar | grep Navigator
实测回显:
2
1454 org/apache/axiom/om/xpath/DocumentNavigator$OMNamespaceEx.class
11309 org/apache/axiom/om/xpath/DocumentNavigator.class
(仅剩 XPath 用途的 DocumentNavigator,无 OMNavigator。)
六、注意事项
- 实现依赖必须落 classpath:编译只需 axiom-api,运行必须有 axiom-impl(默认实现注册文件在其 jar 内
META-INF/axiom.xml),缺它报No meta factory found for feature 'default'; - 延迟解析与流生命周期:builder 未 close 且流未读完前,底层 InputStream 不可复用/关闭;读完后再 build() 是空操作;
- 不要在迭代子元素时结构性改动树:需要删多个节点时先收集到 List 再逐个 detach(TraverseModify 示例的做法);
- 线程安全:OM 树非线程安全,一棵树同一时刻只应被一个线程修改;只读访问并发安全;
- 字符集:解析阶段从流探测编码,输出阶段由 OMOutputFormat 决定,两者独立,别混为一谈。
七、常见问题(FAQ)
7.1 OMNavigator 去哪了
1.4.0 移除了 1.2.x 的 org.apache.axiom.om.OMNavigator 全树游标类(5 节实测 jar 中已无此类)。替代方案:getChildElements()/getChildren() 组合递归(遍历统计场景),或 DocumentNavigator + Jaxen XPath(按路径定位场景,4.7 节实测)。
7.2 XPath 报 UnresolvableException: Cannot resolve namespace prefix
表达式用了带前缀的路径(如 //b:title)但未注册前缀映射。用 BaseXPath.addNamespace("b", uri) 注册(4.7 节示例);对默认命名空间文档可改用 //*[local-name()='title'] 这类不依赖前缀的写法。
7.3 LLOM 元素强转 org.w3c.dom.Element 报 ClassCastException
正常行为:LLOM(axiom-impl)节点不是 w3c DOM。需要 w3c 互操作时,用 OMAbstractFactory.getMetaFactory(OMAbstractFactory.FEATURE_DOM).getOMFactory() 建树(axiom-dom 实现,实测可强转并调用 getElementsByTagName 等方法)。
7.4 setXmlVersion("1.1") 不生效
实测 1.4.0 中 OMOutputFormat 的 setXmlVersion 未体现在序列化声明中(声明仍为 version="1.0",见 4.5 节回显)。需要 1.1 声明时在应用层自行拼装输出头,或升级后复查新版行为。
7.5 getFirstChildWithName 返回 null 但元素明明存在
getFirstChildWithName(QName) 按完整 QName 匹配——包括命名空间 URI。文档用了默认命名空间而查询 QName 未带 URI 时匹配失败。先用 root.getQName() 打印实际命名空间,再以同 URI 构造 QName 查询。
八、参考链接
- Apache Axiom 项目主页:https://ws.apache.org/axiom/
- Axiom API Javadoc(1.4.0 源码内 apidocs 模块):https://github.com/apache/ws-axiom/tree/1.4.0/apidocs
- Jaxen XPath 引擎:https://github.com/jaxen-xpath/jaxen
- 配套安装说明:《Apache Axiom 1.4.0 安装指南》
- 配置参数详解:《Apache Axiom 1.4.0 配置参数详解》