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 AxiomAxiom2026/10/13

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 / OMDocumentXML 树上的节点对象
解析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 附件读写
XPathDocumentNavigator(org.apache.axiom.om.xpath)+ Jaxen对 OM 树执行 XPath 查询
SOAPSOAPFactory / SOAP11Factory / SOAP12FactorySOAP 信封专用工厂

💡 说明: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 解析的两大模型对比——

模型代表行为取舍
推模式 SAXorg.xml.sax解析器推进事件、回调监听者省内存但代码反直觉、不可回退
拉模式 StAXjavax.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.x1.4.0迁移动作
OMNavigatororg.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。)

六、注意事项

  1. 实现依赖必须落 classpath:编译只需 axiom-api,运行必须有 axiom-impl(默认实现注册文件在其 jar 内 META-INF/axiom.xml),缺它报 No meta factory found for feature 'default';
  2. 延迟解析与流生命周期:builder 未 close 且流未读完前,底层 InputStream 不可复用/关闭;读完后再 build() 是空操作;
  3. 不要在迭代子元素时结构性改动树:需要删多个节点时先收集到 List 再逐个 detach(TraverseModify 示例的做法);
  4. 线程安全:OM 树非线程安全,一棵树同一时刻只应被一个线程修改;只读访问并发安全;
  5. 字符集:解析阶段从流探测编码,输出阶段由 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 查询。

八、参考链接