Apache Axiom 1.4.0 配置参数详解

Apache Axiom 1.4.0 配置参数详解:Axiom 无配置文件,配置分三类——实现选择(OMMetaFactory/FEATURE_DOM/系统属性)、StAX 解析配置(StAXParserConfiguration 六预设/StAXUtils)、序列化与附件参数(OMOutputFormat/Attachments 缓存),全部附实测回显与常见问题。

Apache AxiomAxiom2026/10/13

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/SOAPDEFAULT
解析配置延迟解析不调用 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 库的核心配置——

实现制品节点类(实测)特点
LLOMaxiom-implorg.apache.axiom.om.impl.llom.AxiomElementImpl拉式解析按需建树,内存占用最低,不能转 w3c DOM
DOMaxiom-domorg.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 合并为 CHARACTERSCHARACTERS只关心文本内容、不区分 CDATA
NON_COALESCING不合并文本块,保留事件边界CDATA逐事件流式处理
PRESERVE_CDATA_SECTIONS保留 CDATA 节点类型(写回不失真)CDATA需要「读入再写出」保真
STANDALONE独立使用配置(不绑定 Axiom 内部状态)—自建 reader
SOAPSOAP 消息处理专用(容忍 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.0XML 版本
setIgnoreXMLDeclaration(boolean)false是否不写 XML 声明
setSOAP11(boolean)falseSOAP 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)
参数默认说明
fileCacheEnablefalsetrue=大附件落盘缓存;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 节实测回显),再按列表中的值调用。

十一、参考链接