Apache Axiom 1.4.0 配置参数详解
Apache Axiom 1.4.0 配置参数详解:Axiom 无配置文件,配置分三类——实现选择(OMMetaFactory/FEATURE_DOM/系统属性)、StAX 解析配置(StAXParserConfiguration 六预设/StAXUtils)、序列化与附件参数(OMOutputFormat/Attachments 缓存),全部附实测回显与常见问题。
Apache Axiom 1.4.0 配置参数详解
目标:讲清 Apache Axiom 1.4.0 的全部可配置点。Axiom 是纯 Java 库,没有配置文件,其「配置」由三类构成:实现选择(OMMetaFactory / FEATURE_DOM / OMMetaFactoryLocator / 系统属性)、解析配置(StAXParserConfiguration 预设与 StAXUtils 工厂参数)、输出与附件配置(OMOutputFormat 序列化选项 / Attachments 缓存参数),另有延迟解析与缓存开关贯穿其中。本文逐项给出 API、默认值与实测回显。所有输出均在 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 / axiom-impl / axiom-dom,均在运行期 classpath)
- StAX 实现:Woodstox 6.2.8(axiom-impl 传递依赖;去除后由 JDK 内置 StAX 兜底,见 5.1 节)
- 演示工程:
/root/axiom-demo/(Maven 工程,依赖引入方式见《Apache Axiom 1.4.0 安装指南》) - 演示账号:root
二、前置条件
本文示例可独立执行:需一个带 Axiom 1.4.0 依赖的 Maven 工程。若从零开始,按下列最小步骤创建(pom 与下述各节示例类配合使用):
mkdir -p /root/axiom-demo/src/main/java/demo && cd /root/axiom-demo
pom.xml 中 Axiom 依赖关键段(完整 pom 见《Apache Axiom 1.4.0 构建并运行示例应用指南》2.2 节,依赖引入与验证回显见《Apache Axiom 1.4.0 安装指南》四):
<dependency>
<groupId>org.apache.ws.commons.axiom</groupId>
<artifactId>axiom-api</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.ws.commons.axiom</groupId>
<artifactId>axiom-impl</artifactId>
<version>1.4.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.ws.commons.axiom</groupId>
<artifactId>axiom-dom</artifactId>
<version>1.4.0</version>
<scope>runtime</scope>
</dependency>
验证依赖就位:
mvn -q dependency:build-classpath -Dmdep.outputFile=/tmp/cp.txt
tr ":" "\n" < /tmp/cp.txt | grep -c axiom
实测回显:
3
(classpath 中含 axiom-api / axiom-impl / axiom-dom 三个 jar,其余为传递依赖。)
三、配置体系总览
Axiom 没有类似 axiom.conf 的配置文件,所有配置都发生在 JVM 系统属性、classpath 内容(jar 的 META-INF/axiom.xml 注册文件)与 API 参数三个层面。参数总览如下:
| 配置类别 | 配置点 | API/属性 | 默认值 |
|---|---|---|---|
| 实现选择 | 默认 OM 工厂 | OMAbstractFactory.getOMFactory() | axiom-impl(LLOM) |
| 实现选择 | 切换 DOM 实现 | getMetaFactory(OMAbstractFactory.FEATURE_DOM) | —(按需) |
| 实现选择 | 默认实现类名属性 | 系统属性 org.apache.axiom.om.OMMetaFactory | 未设置 |
| 实现选择 | 自定义定位器 | OMAbstractFactory.setMetaFactoryLocator(locator) | DefaultOMMetaFactoryLocator |
| 解析配置 | StAX 输入工厂 | StAXUtils.getXMLInputFactory() | Woodstox(服务发现) |
| 解析配置 | 解析器行为预设 | StAXParserConfiguration.DEFAULT/COALESCING/NON_COALESCING/PRESERVE_CDATA_SECTIONS/STANDALONE/SOAP | DEFAULT |
| 解析配置 | 延迟解析 | 不调用 build() 即为延迟 | 延迟(按需) |
| 解析配置 | 非缓存流式读 | getXMLStreamReaderWithoutCaching() | —(按需) |
| 输出配置 | 序列化格式 | OMOutputFormat(charset/xmlVersion/SOAP11/声明/MIME) | charset=utf-8,无声明(元素级) |
| 附件配置 | MIME 缓存 | new Attachments(in, contentType, fileCache, repoDir, threshold) | fileCache=false |
| JVM 层 | 堆大小 | -Xms/-Xmx(承载应用决定) | JVM 默认 |
💡 技术拓展:Axiom 的「双实现」架构中,实现切换是它区别于普通 DOM 库的核心配置——
实现 制品 节点类(实测) 特点 LLOM axiom-impl org.apache.axiom.om.impl.llom.AxiomElementImpl 拉式解析按需建树,内存占用最低,不能转 w3c DOM DOM axiom-dom org.apache.axiom.om.impl.dom.DOOMNSAwareElementImpl 可强转 org.w3c.dom.Element,与 w3c 生态互操作 1.4.0 起两实现共用工厂类
org.apache.axiom.om.impl.common.factory.OMFactoryImpl(实测OMFactory.getClass()输出一致),判断实现必须看节点类。本文主线用默认 LLOM,需要 w3c 互操作时切 DOM。
四、实现选择配置(OMMetaFactory)
4.1 API 层切换:FEATURE_DEFAULT 与 FEATURE_DOM
本节做什么:演示在同一个 JVM 里同时使用两种实现;预期得到两种节点类名与 w3c 互操作差异的实测证据。
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.OMMetaFactory;
import org.apache.axiom.om.OMXMLBuilderFactory;
import org.apache.axiom.om.OMXMLParserWrapper;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
public class MetaFactoryDemo {
public static void main(String[] args) throws Exception {
String xml = "<msg xmlns=\"http://demo.example.com\"><body>hello axiom</body></msg>";
System.out.println("META_FACTORY_NAME_PROPERTY = " + OMAbstractFactory.META_FACTORY_NAME_PROPERTY);
OMFactory llFactory = OMAbstractFactory.getOMFactory();
System.out.println("default OMFactory class = " + llFactory.getClass().getName());
OMMetaFactory domMeta = OMAbstractFactory.getMetaFactory(OMAbstractFactory.FEATURE_DOM);
OMFactory domFactory = domMeta.getOMFactory();
System.out.println("dom OMFactory class = " + domFactory.getClass().getName());
OMXMLParserWrapper llBuilder = OMXMLBuilderFactory.createOMBuilder(
new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
OMElement llRoot = llBuilder.getDocumentElement();
System.out.println("llom root node class = " + llRoot.getClass().getName());
OMXMLParserWrapper domBuilder = OMXMLBuilderFactory.createOMBuilder(domFactory,
new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
OMElement domRoot = domBuilder.getDocumentElement();
System.out.println("dom root node class = " + domRoot.getClass().getName());
try {
org.w3c.dom.Element w3c = (org.w3c.dom.Element) domRoot;
System.out.println("cast to w3c.dom.Element OK: tagName=" + w3c.getTagName());
} catch (ClassCastException e) {
System.out.println("cast FAILED: " + e);
}
try {
org.w3c.dom.Element w3c2 = (org.w3c.dom.Element) llRoot;
System.out.println("llom cast OK: " + (w3c2 != null));
} catch (ClassCastException e) {
System.out.println("llom root cast to w3c.dom.Element FAILED (expected, LLOM is not DOM)");
}
}
}
运行:
mvn -q compile
mvn -q compile exec:java -Dexec.mainClass=demo.MetaFactoryDemo
实测回显:
META_FACTORY_NAME_PROPERTY = org.apache.axiom.om.OMMetaFactory
default OMFactory class = org.apache.axiom.om.impl.common.factory.OMFactoryImpl
dom OMFactory class = org.apache.axiom.om.impl.common.factory.OMFactoryImpl
llom root node class = org.apache.axiom.om.impl.llom.AxiomElementImpl
dom root node class = org.apache.axiom.om.impl.dom.DOOMNSAwareElementImpl
cast to w3c.dom.Element OK: tagName=msg
llom root cast to w3c.dom.Element FAILED (expected, LLOM is not DOM)
验证手段:domRoot 强转 w3c 成功而 llomRoot 失败,即实现切换生效。逐项说明:getOMFactory() 等价 getMetaFactory(FEATURE_DEFAULT).getOMFactory();FEATURE_DOM 要求 classpath 含 axiom-dom(缺它抛 OMException);OMMetaFactory 还提供 getSOAP11Factory()/getSOAP12Factory() 用于 SOAP 消息场景。
4.2 系统属性 org.apache.axiom.om.OMMetaFactory(值为实现类名)
该系统属性决定 FEATURE_DEFAULT 落到哪个实现,但值必须是 OMMetaFactory 实现类的全名。实测传功能名 dom 会启动失败:
mvn -q compile
java -Dorg.apache.axiom.om.OMMetaFactory=dom -cp "target/classes:$(cat /tmp/cp.txt)" demo.MetaFactoryDemo 2>&1 | head -8
实测回显:
Oct 13, 2026 4:31:40 AM org.apache.axiom.locator.ImplementationFactory load
SEVERE: The class dom could not be loaded
java.lang.ClassNotFoundException: dom
at org.apache.axiom.locator.DefaultLoader.load(DefaultLoader.java:30)
at org.apache.axiom.locator.ImplementationFactory.load(ImplementationFactory.java:65)
at org.apache.axiom.locator.ImplementationFactory.createDefaultImplementation(ImplementationFactory.java:57)
at org.apache.axiom.locator.DefaultOMMetaFactoryLocator.<init>(DefaultOMMetaFactoryLocator.java:70)
定位与结论:DefaultOMMetaFactoryLocator 把属性值当类名加载(ClassNotFound 即失败)。该属性只用于注册第三方自实现的场景;切换 Axiom 内置的 LLOM/DOM 一律用 4.1 节的 feature API,不要用此属性。
4.3 自定义 OMMetaFactoryLocator
框架级集成(如需要按线程/按会话选实现)可实现 org.apache.axiom.om.OMMetaFactoryLocator 并注册:
OMAbstractFactory.setMetaFactoryLocator(myLocator); // 之后 getMetaFactory()/getOMFactory() 都走 myLocator
验证方式:注册后调用 OMAbstractFactory.getMetaFactory(OMAbstractFactory.FEATURE_DEFAULT),返回对象与 myLocator 的实现一致(用 == 断言或打印 getClass())。注意 locator 是全局静态设置,须在首次使用 OMAbstractFactory 前完成。
五、StAX 解析器配置(StAXUtils 与 StAXParserConfiguration)
5.1 StAXUtils 工厂获取
org.apache.axiom.om.util.StAXUtils 是 Axiom 对 StAX 工厂的统一入口,内部做实现发现与包装。核心方法(javap 实测签名):
| 方法 | 说明 |
|---|---|
getXMLInputFactory() | 取共享输入工厂(Woodstox 优先,JDK 内置兜底) |
getXMLInputFactory(boolean useServices) | 是否走服务发现 |
getXMLInputFactory(StAXParserConfiguration, boolean) | 按预设配置工厂 |
createXMLStreamReader(InputStream[, encoding]) | 从流建 reader |
createXMLStreamReader(StAXParserConfiguration, InputStream) | 带预设建 reader |
getXMLOutputFactory() / getXMLOutputFactory(boolean) | 输出工厂 |
验证默认工厂来源:
package demo;
import org.apache.axiom.om.util.StAXUtils;
import javax.xml.stream.XMLInputFactory;
public class FactoryCheck {
public static void main(String[] args) throws Exception {
XMLInputFactory f = StAXUtils.getXMLInputFactory();
System.out.println("factory = " + f.getClass().getName());
}
}
实测回显:
factory = org.apache.axiom.util.stax.wrapper.ImmutableXMLInputFactory
外层 ImmutableXMLInputFactory 是 Axiom 的防配置漂移包装,内层实际实现由服务发现决定——本工程 classpath 含 Woodstox 6.2.8,从异常栈可确认底层是 com.ctc.wstx.sr.BasicStreamReader(Woodstox)。去除 woodstox jar 后再取,JVM 默认实现为 com.sun.xml.internal.stream.XMLInputFactoryImpl(实测)。
5.2 StAXParserConfiguration 六预设详解
预设决定 XMLInputFactory 的关键行为开关。对同一份含 CDATA 的文档实测三种预设差异:
package demo;
import org.apache.axiom.om.util.StAXParserConfiguration;
import org.apache.axiom.om.util.StAXUtils;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
public class StAXConfigDemo {
static String evName(int t) {
switch (t) {
case XMLStreamConstants.START_ELEMENT: return "START_ELEMENT";
case XMLStreamConstants.CHARACTERS: return "CHARACTERS";
case XMLStreamConstants.CDATA: return "CDATA";
default: return "type=" + t;
}
}
public static void main(String[] args) throws Exception {
String xml = "<log><event><![CDATA[hello <world>]]></event></log>";
XMLStreamReader r1 = StAXUtils.createXMLStreamReader(StAXParserConfiguration.COALESCING,
new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
r1.nextTag(); r1.nextTag(); r1.next();
System.out.println("COALESCING -> " + evName(r1.getEventType()) + " text=" + r1.getText());
r1.close();
XMLStreamReader r2 = StAXUtils.createXMLStreamReader(StAXParserConfiguration.PRESERVE_CDATA_SECTIONS,
new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
r2.nextTag(); r2.nextTag(); r2.next();
System.out.println("PRESERVE_CDATA_SECTIONS -> " + evName(r2.getEventType()) + " text=" + r2.getText());
r2.close();
XMLStreamReader r3 = StAXUtils.createXMLStreamReader(StAXParserConfiguration.NON_COALESCING,
new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
r3.nextTag(); r3.nextTag(); r3.next();
System.out.println("NON_COALESCING -> " + evName(r3.getEventType()) + " text=" + r3.getText());
r3.close();
}
}
运行并实测回显:
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):
| 预设 | 行为 | 实测事件 | 适用 |
|---|---|---|---|
| DEFAULT | 常规配置,性能优先 | — | 通用默认 |
| COALESCING | 相邻文本与 CDATA 合并为 CHARACTERS | CHARACTERS | 只关心文本内容、不区分 CDATA |
| NON_COALESCING | 不合并文本块,保留事件边界 | CDATA | 逐事件流式处理 |
| PRESERVE_CDATA_SECTIONS | 保留 CDATA 节点类型(写回不失真) | CDATA | 需要「读入再写出」保真 |
| STANDALONE | 独立使用配置(不绑定 Axiom 内部状态) | — | 自建 reader |
| SOAP | SOAP 消息处理专用(容忍 SOAP 规范边角) | — | SOAP 栈 |
💡 说明:
OMXMLBuilderFactory.createOMBuilder(StAXParserConfiguration, InputStream)重载可以把预设直接用在建树上;不传预设时 Axiom 走DEFAULT。选型口诀:只读文本选 COALESCING;要原样回写选 PRESERVE_CDATA_SECTIONS;SOAP 网关场景选 SOAP。
六、延迟解析与缓存开关
6.1 延迟解析验证(deferred parsing)
Axiom 建树默认延迟:只访问的部分才解析。实测(5000 个 book、31.8 万字节的 catalog 文档,CountingInputStream 统计底层流读取字节数):
package demo;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMXMLBuilderFactory;
import org.apache.axiom.om.OMXMLParserWrapper;
import javax.xml.namespace.QName;
import java.io.ByteArrayInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
public class ParseDeferred {
static class CountingInputStream extends FilterInputStream {
long count = 0;
CountingInputStream(InputStream in) { super(in); }
public int read() throws IOException { int r = super.read(); if (r != -1) count++; return r; }
public int read(byte[] b, int off, int len) throws IOException {
int r = super.read(b, off, len);
if (r != -1) count += r;
return r;
}
}
public static void main(String[] args) throws Exception {
StringBuilder sb = new StringBuilder();
sb.append("<catalog>");
for (int i = 0; i < 5000; i++) {
sb.append("<book id=\"").append(i).append("\"><title>Book ").append(i)
.append("</title><price>").append(10 + i % 50).append("</price></book>");
}
sb.append("</catalog>");
byte[] data = sb.toString().getBytes(StandardCharsets.UTF_8);
System.out.println("document bytes = " + data.length);
CountingInputStream in = new CountingInputStream(new ByteArrayInputStream(data));
OMXMLParserWrapper builder = OMXMLBuilderFactory.createOMBuilder(in);
OMElement root = builder.getDocumentElement();
System.out.println("after getDocumentElement(): root=" + root.getLocalName()
+ ", bytes read=" + in.count + ", isCompleted=" + builder.isCompleted());
OMElement first = root.getFirstElement();
System.out.println("after getFirstElement(): first book id=" + first.getAttributeValue(new QName("id"))
+ ", bytes read=" + in.count);
root.build();
System.out.println("after root.build(): bytes read=" + in.count + " (all bytes read)");
}
}
实测回显:
document bytes = 317799
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)
结论:不访问的 4999 个 book 始终不建树;getDocumentElement(boolean buildAll) 重载可显式选择「取根时是否全量构建」,默认(无参)为延迟。
6.2 非缓存流式读取(getXMLStreamReaderWithoutCaching)
需要「只遍历一遍、不保留树」时,用 getXMLStreamReaderWithoutCaching() 直接拉事件流(实测 5000 book 文档流过 25003 个事件):
XMLStreamReader reader = root.getXMLStreamReaderWithoutCaching();
int events = 0;
while (reader.getEventType() != XMLStreamConstants.END_DOCUMENT) {
events++;
if (!reader.hasNext()) break;
reader.next();
}
reader.close();
System.out.println("streamed events=" + events);
实测回显:
streamed events=25003
缓存开关对照:getXMLStreamReader()(默认)边读边把事件缓存进树(可二次访问);getXMLStreamReaderWithoutCaching() 用完即弃、内存占用最低但树不可复用。大文档一次性转换场景选后者。
七、序列化输出配置(OMOutputFormat)
7.1 OMOutputFormat 可配置项
org.apache.axiom.om.OMOutputFormat 控制序列化行为,OMElement.serialize(OutputStream, OMOutputFormat) 与 OMDocument.serialize(OutputStream, OMOutputFormat) 接受它。主要方法(javap 实测):
| 方法 | 默认值 | 作用 |
|---|---|---|
setCharSetEncoding(String) | utf-8(文档级序列化时体现在声明) | 输出字符集 |
setXmlVersion(String) | 1.0 | XML 版本 |
setIgnoreXMLDeclaration(boolean) | false | 是否不写 XML 声明 |
setSOAP11(boolean) | false | SOAP 1.1 输出(影响 content type) |
setContentType(String) | — | 显式指定 content type |
isOptimized() / MTOM 相关 | false | 是否按 MTOM/XOP 优化(附件场景) |
getMimeBoundary() / getNextContentId() | 自动生成 | MIME 边界与 part id |
7.2 元素级与文档级序列化实测
package demo;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMDocument;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.OMOutputFormat;
import java.io.ByteArrayOutputStream;
public class DocSerializeDemo {
public static void main(String[] args) throws Exception {
OMFactory f = OMAbstractFactory.getOMFactory();
OMNamespace ns = f.createOMNamespace("http://demo.example.com", "d");
OMDocument doc = f.createOMDocument();
OMElement root = f.createOMElement("msg", ns, doc);
root.setText("hi");
ByteArrayOutputStream b1 = new ByteArrayOutputStream();
doc.serialize(b1);
System.out.println("doc default: " + b1.toString("UTF-8"));
OMOutputFormat fmt = new OMOutputFormat();
fmt.setCharSetEncoding("UTF-8");
ByteArrayOutputStream b2 = new ByteArrayOutputStream();
doc.serialize(b2, fmt);
System.out.println("doc utf8 : " + b2.toString("UTF-8"));
OMOutputFormat fmt2 = new OMOutputFormat();
fmt2.setIgnoreXMLDeclaration(true);
ByteArrayOutputStream b3 = new ByteArrayOutputStream();
doc.serialize(b3, fmt2);
System.out.println("doc no decl: " + b3.toString("UTF-8"));
OMElement el = f.createOMElement("msg", ns);
el.setText("hi");
ByteArrayOutputStream b4 = new ByteArrayOutputStream();
el.serialize(b4);
System.out.println("element : " + b4.toString("UTF-8"));
OMOutputFormat fmt3 = new OMOutputFormat();
fmt3.setCharSetEncoding("UTF-16");
ByteArrayOutputStream b5 = new ByteArrayOutputStream();
el.serialize(b5, fmt3);
byte[] raw = b5.toByteArray();
System.out.println("utf16 head bytes: " + String.format("%02x %02x", raw[0], raw[1]));
}
}
实测回显:
doc default: <?xml version="1.0" encoding="utf-8"?><d:msg xmlns:d="http://demo.example.com">hi</d:msg>
doc utf8 : <?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
逐项解读:
- OMDocument 级序列化默认写声明
version="1.0" encoding="utf-8";setCharSetEncoding("UTF-8")后声明里的编码随之变为UTF-8; setIgnoreXMLDeclaration(true)去掉声明,适合做 XML 片段拼接;- 元素级序列化(不含 OMDocument)默认不写 XML 声明;
setCharSetEncoding("UTF-16")时输出流以 BOM(fe ff,UTF-16BE)开头,与声明无关地落在字节层面;- ⚠️ 实测
setXmlVersion("1.1")未体现在声明中(声明仍为version="1.0")——需要 1.1 声明的场景应在应用层自行拼装,或确认目标版本行为。
八、附件配置(Attachments)
org.apache.axiom.attachments.Attachments 处理 MIME multipart(MTOM/XOP、SOAP with Attachments)。构造参数即附件缓存配置:
Attachments(InputStream in, String contentTypeString,
boolean fileCacheEnable, String attachmentRepoDir,
String attachmentSizeThreshold, int attachmentSizeThresholdInt)
| 参数 | 默认 | 说明 |
|---|---|---|
| fileCacheEnable | false | true=大附件落盘缓存;false=全内存 |
| attachmentRepoDir | —(空串) | 落盘缓存目录 |
| attachmentSizeThreshold | —(整数,字节) | 超过阈值的 part 落盘 |
| contentTypeString | 必填 | HTTP 头里的 multipart/related content type |
实测(构造含一个附件的 MIME 消息并读取):
package demo;
import org.apache.axiom.attachments.Attachments;
import javax.activation.DataHandler;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
public class AttachmentsDemo {
public static void main(String[] args) throws Exception {
String boundary = "MIMEBoundary";
String ct = "multipart/related; boundary=\"" + boundary + "\"; type=\"application/xop+xml\"";
StringBuilder sb = new StringBuilder();
sb.append("--").append(boundary).append("\r\n");
sb.append("Content-Type: application/xop+xml; charset=UTF-8; type=\"text/xml\"\r\n\r\n");
sb.append("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><echo/></soap:Body></soap:Envelope>\r\n");
sb.append("--").append(boundary).append("\r\n");
sb.append("Content-Type: text/plain\r\nContent-ID: <file.txt>\r\n\r\n");
sb.append("hello attachment\r\n");
sb.append("--").append(boundary).append("--\r\n");
Attachments atts = new Attachments(
new ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8)),
ct, false, "", "", 4096);
System.out.println("root part content id = " + atts.getRootPartContentID());
System.out.println("content id list = " + atts.getContentIDList());
DataHandler dh = atts.getDataHandler("file.txt");
InputStream is = dh.getInputStream();
byte[] buf = new byte[64];
int n = is.read(buf);
System.out.println("file.txt content = " + new String(buf, 0, n, StandardCharsets.UTF_8).trim());
is.close();
System.out.println("SOAP part content type = " + atts.getSOAPPartContentType());
}
}
实测回显:
root part content id = firstPart_00b6e098f957e848ef9b22db90b53b944366b6539b841da7@apache.org
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"
说明:root part 的 content id 由 Axiom 自动生成(firstPart_<随机串>@apache.org,每次运行不同);fileCacheEnable=false 时小附件全内存;生产大附件建议 true + 指定 repoDir + 阈值(如 4096 字节以上落盘),目录需应用账号可写。
九、JVM 内存与承载应用配置建议
Axiom 无独立进程,以下参数由承载它的应用 JVM 决定。演示与小规模 XML 处理建议 256–512MB 堆:
java -Xms256m -Xmx256m -cp "target/classes:$(cat /tmp/cp.txt)" demo.ParseDeferred
实测回显:
document bytes = 317799
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)
复核实际生效堆(进程存活窗口内另开终端执行):
jps -v | grep ParseDeferred
实测回显:
5213 ParseDeferred -Xms256m -Xmx256m
配置位置:脚本启动写进启动命令/JAVA_OPTS;systemd 托管写 Environment=JAVA_OPTS=-Xms256m -Xmx256m(每个单元文件一行一个单元);Compose 场景写 environment: 注入。极端情况下怀疑堆未生效,用 jps -v 或 ps -ef | grep [j]ava 直接核对进程参数。
十、常见问题(FAQ)
10.1 为什么两种实现的 OMFactory getClass() 输出一样
1.4.0 起 LLOM 与 DOM 实现重构为共享核心,工厂类统一为 org.apache.axiom.om.impl.common.factory.OMFactoryImpl(4.1 节实测)。判断当前实现看节点类:element.getClass().getName(),om.impl.llom.AxiomElementImpl 为 LLOM、om.impl.dom.DOOMNSAwareElementImpl 为 DOM;或尝试强转 org.w3c.dom.Element(仅 DOM 实现可成)。
10.2 isCompleted() 一直是 false
OMXMLParserWrapper.isCompleted() 表示解析器是否消费到文档末尾事件,不是「树是否可用」。实测遍历完 5000 个子元素后它仍为 false——只要业务访问触发了按需解析,树数据就是完整的;需要确定性全量构建时显式调用 build()(调用后底层流读完,见 6.1 节回显 317799 字节)。
10.3 创建的 DOM 元素标签名出现 ns1T6Oo 之类的怪前缀
现象:用 DOM 工厂 createOMElement(new QName(uri, "scratch")) 后转 w3c DOM 打印 tagName=ns1T6Oo:scratch。
原因:带命名空间但未指定前缀的元素,DOM 实现会生成临时前缀保证序列化合法;序列化输出时 Axiom 会按命名空间声明重新映射,实际输出仍是规范 XML。需要固定前缀时用 declareDefaultNamespace 或创建时带前缀的 QName。
10.4 setIgnoreXMLDeclaration(true) 后元素序列化没变化
正常。元素级(OMElement)序列化默认就不写 XML 声明(7.2 节实测 element 行无声明),该开关只在 OMDocument 级序列化可见效果(实测 doc 序列化默认有声明、开关后消失)。
10.5 附件读取报 Content ID not found
getDataHandler(cid) 的 cid 参数不带尖括号(示例传 file.txt 而非 <file.txt>),且必须与 MIME part 的 Content-ID 头一致。先用 getContentIDList() 打印实际 id 列表核对(见 8 节实测回显),再按列表中的值调用。
十一、参考链接
- Apache Axiom 项目主页:https://ws.apache.org/axiom/
- 源码仓库(含 userguide 配置章节):https://github.com/apache/ws-axiom/tree/1.4.0/userguide
- Woodstox 项目主页:https://github.com/FasterXML/woodstox
- 配套安装说明:《Apache Axiom 1.4.0 安装指南》
- 配套示例运行说明:《Apache Axiom 1.4.0 构建并运行示例应用指南》