Apache XMLBeans 5.4.1 快速入门与集成示例

本文以 Apache XMLBeans 5.4.1 为对象,在 Ubuntu 22.04 + OpenJDK 17 环境下实测 Maven 依赖引入、scomp 强类型生成、XmlObject 解析、XPath/XQuery 查询的完整快速入门链路,全部命令与回显真实可复现。

Apache XMLBeansApache XMLBeans2026/10/15

Apache XMLBeans 5.4.1 快速入门与集成示例

目标:在 Ubuntu 22.04 + OpenJDK 17 + Maven 3.6.3 环境下,实测 Apache XMLBeans 5.4.1 的依赖引入、通用解析(XmlObject)、scomp 强类型生成、XPath/XQuery 查询四条主线,全部代码与回显真实可复现。

一、环境信息

实测环境与版本基线(2026-10-15):

项值
操作系统Ubuntu 22.04.5 LTS(x86_64)
JDKOpenJDK 17.0.20.1(apt 包 openjdk-17-jdk)
MavenApache Maven 3.6.3
XMLBeansorg.apache.xmlbeans:xmlbeans:5.4.1(Maven Central 最新 release)
Saxonnet.sf.saxon:Saxon-HE:12.10(XPath/XQuery 引擎,5.4.1 POM 中为 provided 作用域)
工作目录/work(示例:/work/schema、/work/mvn-quick、/work/mvn-demo)
# java -version
openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment (build 17.0.20.1+1-1~22.04)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1~22.04, mixed mode, sharing)
# mvn -v | head -3
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.20.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64

XMLBeans 5.4.1 的字节码基线是 Java 8(jar 内 XmlObject.class 实测 major=52),JDK 8 与 17 均可运行;本文以 JDK 17 为主环境,JDK 8 兼容性见《Apache XMLBeans 5.4.1 scomp 编译与进阶实战详解》第七节实测。

贯穿全文的业务示例:一张订单(order.xml)与它的 Schema(order.xsd,内含对共享类型文件 types.xsd 的 include):

/work/schema/types.xsd:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:ord="http://example.com/order"
           targetNamespace="http://example.com/order"
           elementFormDefault="qualified">

  <xs:complexType name="AddressType">
    <xs:sequence>
      <xs:element name="province" type="xs:string"/>
      <xs:element name="city" type="xs:string"/>
      <xs:element name="street" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>

</xs:schema>

/work/schema/order.xsd:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:ord="http://example.com/order"
           targetNamespace="http://example.com/order"
           elementFormDefault="qualified">

  <xs:include schemaLocation="types.xsd"/>

  <xs:element name="order" type="ord:OrderType"/>

  <xs:complexType name="OrderType">
    <xs:sequence>
      <xs:element name="customer" type="ord:CustomerType"/>
      <xs:element name="shippingAddress" type="ord:AddressType"/>
      <xs:element name="item" type="ord:ItemType" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="id" type="xs:string" use="required"/>
    <xs:attribute name="date" type="xs:date" use="required"/>
  </xs:complexType>

  <xs:complexType name="CustomerType">
    <xs:sequence>
      <xs:element name="name" type="xs:string"/>
      <xs:element name="email" type="xs:string"/>
    </xs:sequence>
    <xs:attribute name="vip" type="xs:boolean" default="false"/>
  </xs:complexType>

  <xs:complexType name="ItemType">
    <xs:sequence>
      <xs:element name="name" type="xs:string"/>
      <xs:element name="price" type="xs:double"/>
    </xs:sequence>
    <xs:attribute name="sku" type="xs:string" use="required"/>
    <xs:attribute name="qty" type="xs:int" use="required"/>
  </xs:complexType>

</xs:schema>

/work/schema/order.xml:

<?xml version="1.0" encoding="UTF-8"?>
<ord:order xmlns:ord="http://example.com/order" id="A-2026-1015" date="2026-10-15">
  <ord:customer vip="true">
    <ord:name>张伟</ord:name>
    <ord:email>zhangwei@example.com</ord:email>
  </ord:customer>
  <ord:shippingAddress>
    <ord:province>广东省</ord:province>
    <ord:city>深圳市</ord:city>
    <ord:street>科技园南路 88 号</ord:street>
  </ord:shippingAddress>
  <ord:item sku="SKU-1001" qty="2">
    <ord:name>机械键盘</ord:name>
    <ord:price>399.0</ord:price>
  </ord:item>
  <ord:item sku="SKU-2002" qty="1">
    <ord:name>27 寸显示器</ord:name>
    <ord:price>1899.0</ord:price>
  </ord:item>
  <ord:item sku="SKU-3003" qty="3">
    <ord:name>USB-C 扩展坞</ord:name>
    <ord:price>259.0</ord:price>
  </ord:item>
</ord:order>

二、10 分钟最小可用

2.1 工程与依赖

# mkdir -p /work/mvn-quick/src/main/java/demo
# cd /work/mvn-quick

/work/mvn-quick/pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>demo</groupId>
  <artifactId>xmlbeans-quick</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.release>17</maven.compiler.release>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.apache.xmlbeans</groupId>
      <artifactId>xmlbeans</artifactId>
      <version>5.4.1</version>
    </dependency>
    <dependency>
      <groupId>net.sf.saxon</groupId>
      <artifactId>Saxon-HE</artifactId>
      <version>12.10</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.11.0</version>
      </plugin>
    </plugins>
  </build>
</project>

要点:显式固定 maven-compiler-plugin 3.11.0(Maven 3.6.3 内置的旧版插件不认识 --release 参数);Saxon-HE 在 5.4.1 的 POM 里是 provided 作用域,不声明它就无法使用完整 XPath。

2.2 第一个程序

/work/mvn-quick/src/main/java/demo/GenericDemo.java:

package demo;

import org.apache.xmlbeans.SimpleValue;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;

import javax.xml.namespace.QName;
import java.io.File;

public class GenericDemo {

    static final String NS = "http://example.com/order";
    static final QName Q_ORDER = new QName(NS, "order");
    static final QName Q_CUSTOMER = new QName(NS, "customer");
    static final QName Q_EMAIL = new QName(NS, "email");
    static final QName Q_ID = new QName("", "id");

    public static void main(String[] args) throws Exception {
        XmlObject doc = XmlObject.Factory.parse(new File("/work/schema/order.xml"));
        System.out.println("解析完成,schemaType = " + doc.schemaType());

        XmlObject order = doc.selectChildren(Q_ORDER)[0];
        System.out.println("selectChildren(order) 命中 = " + doc.selectChildren(Q_ORDER).length);

        System.out.println("selectAttribute(id).getStringValue() = "
                + ((SimpleValue) order.selectAttribute(Q_ID)).getStringValue());

        XmlObject email = order.selectChildren(Q_CUSTOMER)[0].selectChildren(Q_EMAIL)[0];
        System.out.println("customer/email 文本值 = " + ((SimpleValue) email).getStringValue());

        System.out.println("缩进序列化首行 = "
                + doc.xmlText(new XmlOptions().setSavePrettyPrint()).split("\n")[0]);
    }
}

2.3 编译与运行

# mvn -q -B compile
# echo $?
0
# M2=~/.m2/repository
# java -Dfile.encoding=UTF-8 -cp target/classes:$M2/org/apache/xmlbeans/xmlbeans/5.4.1/xmlbeans-5.4.1.jar:$M2/org/apache/logging/log4j/log4j-api/2.26.1/log4j-api-2.26.1.jar demo.GenericDemo
解析完成,schemaType = N=
selectChildren(order) 命中 = 1
selectAttribute(id).getStringValue() = A-2026-1015
customer/email 文本值 = zhangwei@example.com
缩进序列化首行 = <?xml version="1.0" encoding="UTF-8"?>

验证点:解析、QName 选取子元素/属性、取文本值、缩进序列化全部工作。schemaType = N= 说明当前类路径没有该文档的 Schema 类型系统(N = NoType),通用解析不依赖 XSD;第四节生成类型 jar 后这一行会变为 D=order@http://example.com/order。

依赖解析结果(确认 XMLBeans 只传递一个编译期依赖 log4j-api):

# mvn -B dependency:tree
...
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ xmlbeans-quick ---
[INFO] demo:xmlbeans-quick:jar:1.0.0
[INFO] +- org.apache.xmlbeans:xmlbeans:jar:5.4.1:compile
[INFO] |  \- org.apache.logging.log4j:log4j-api:jar:2.26.1:compile
[INFO] \- net.sf.saxon:Saxon-HE:jar:12.10:compile
[INFO]    \- org.xmlresolver:xmlresolver:jar:5.3.3:compile
[INFO]       \- org.xmlresolver:xmlresolver:data:jar:5.3.3:compile
...
[INFO] BUILD SUCCESS

三、Maven 集成关键点与高频坑

实测在本版本(5.4.1)上集中踩到四类问题,先列结论再逐条给实测证据:

问题现象正确姿势
compiler 插件过老Fatal error compiling: invalid flag: --release显式固定 maven-compiler-plugin 3.11.0
旧 API 失效getStringValue()/setSavePretty() 编译不过(SimpleValue) obj).getStringValue() / setSavePrettyPrint()
漏日志门面NoClassDefFoundError: org/apache/logging/log4j/LoggerMaven 自动带;jar 直跑必须加 log4j-api
漏 SaxonXPath 谓词报 NoClassDefFoundError: net/sf/saxon/sxpath/XPathStaticContext声明 Saxon-HE 12.10

其中两条直接编译不过。用一个临时验证类 /work/mvn-quick/src/main/java/demo/OldApiDemo.java 复现(核心两行就是旧写法 order.getStringValue() 与 new XmlOptions().setSavePretty()):

# mvn -B compile
...
[ERROR] COMPILATION ERROR :
[ERROR] /work/mvn-quick/src/main/java/demo/OldApiDemo.java:[13,33] cannot find symbol
[ERROR]   symbol:   method getStringValue()
[ERROR]   location: variable order of type org.apache.xmlbeans.XmlObject
[ERROR] /work/mvn-quick/src/main/java/demo/OldApiDemo.java:[14,48] cannot find symbol
[ERROR]   symbol:   method setSavePretty()
[ERROR]   location: class org.apache.xmlbeans.XmlOptions

原因与修复:5.x 把"取简单类型文本值"放在 SimpleValue 接口(XmlObject 的兄弟子接口链上),缩进序列化选项名为 setSavePrettyPrint()。用 javap 可直接核实:

# javap -cp ~/.m2/repository/org/apache/xmlbeans/xmlbeans/5.4.1/xmlbeans-5.4.1.jar org.apache.xmlbeans.XmlObject | grep -c stringvalue
0
# javap -cp ~/.m2/repository/org/apache/xmlbeans/xmlbeans/5.4.1/xmlbeans-5.4.1.jar org.apache.xmlbeans.SimpleValue | grep getStringValue
public abstract java.lang.String getStringValue();
# javap -cp ~/.m2/repository/org/apache/xmlbeans/xmlbeans/5.4.1/xmlbeans-5.4.1.jar org.apache.xmlbeans.XmlOptions | grep -i pretty
public org.apache.xmlbeans.XmlOptions setSavePrettyPrint()
public org.apache.xmlbeans.XmlOptions setSavePrettyPrintIndent(int)

💡 技术拓展:网上大量教程基于 2.x~3.x 的 API,XmlObject.getStringValue() 是当年存在的;XMLBeans 5.x 清理了这批方法。遇到编译错误先 javap 实测当前 jar 的真实签名,再改写法,比搜索旧博客可靠。

四、scomp 生成强类型类并使用

4.1 生成类型 jar

scomp 编译器已内置在 xmlbeans-5.4.1.jar 中,命令行入口为主类 org.apache.xmlbeans.impl.tool.SchemaCompiler(注意:jar 内同包的 XMLBean 类是 Ant 任务、没有 main 方法,实测确认):

# cd /work
# java -cp xmlbeans-5.4.1.jar:log4j-api-2.26.1.jar org.apache.xmlbeans.impl.tool.SchemaCompiler -out order-xmlbeans.jar schema/order.xsd
Time to build schema type system: 0.387 seconds
Time to generate code: 0.048 seconds
Time to compile code: 1.0 seconds
Compiled types to: order-xmlbeans.jar
# echo $?
0
# jar tf order-xmlbeans.jar | head -8
com/
com/example/
com/example/order/
com/example/order/OrderType.class
com/example/order/OrderDocument.class
com/example/order/impl/
com/example/order/impl/ItemTypeImpl.class
com/example/order/impl/OrderDocumentImpl.class
# jar tf order-xmlbeans.jar | wc -l
52

关键事实:命名空间 http://example.com/order 自动映射为 Java 包 com.example.order;order.xsd 通过 xs:include 引用的 types.xsd 被自动带入(jar 的元数据目录里能看到两个 xsd 源)。类名对应关系:

Schema生成的 Java
全局元素 orderOrderDocument(文档接口) + OrderDocument.Order(元素内容)
命名类型 OrderType/CustomerType/ItemType/AddressType同名接口 + impl 包实现类

(scomp 完整选项与失败样例见《Apache XMLBeans 5.4.1 scomp 编译与进阶实战详解》。)

4.2 安装到本地仓库并引入工程

# cd /work/mvn-quick
# mvn -q -B install:install-file -Dfile=/work/order-xmlbeans.jar -DgroupId=com.example -DartifactId=order-xmlbeans -Dversion=1.0.0 -Dpackaging=jar -DgeneratePom=true
# echo $?
0

在 pom 的 <dependencies> 里追加:

    <dependency>
      <groupId>com.example</groupId>
      <artifactId>order-xmlbeans</artifactId>
      <version>1.0.0</version>
    </dependency>

4.3 强类型读写与校验

/work/mvn-quick/src/main/java/demo/TypedDemo.java:

package demo;

import com.example.order.AddressType;
import com.example.order.CustomerType;
import com.example.order.ItemType;
import com.example.order.OrderDocument;
import com.example.order.OrderType;
import org.apache.xmlbeans.XmlError;
import org.apache.xmlbeans.XmlOptions;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class TypedDemo {

    public static void main(String[] args) throws Exception {
        OrderDocument doc = OrderDocument.Factory.parse(new File("/work/schema/order.xml"));

        OrderType order = doc.getOrder();
        System.out.println("id = " + order.getId()
                + " date = " + order.getDate().getTime());

        CustomerType customer = order.getCustomer();
        System.out.println("customer = " + customer.getName()
                + " <" + customer.getEmail() + "> vip = " + customer.getVip());

        AddressType addr = order.getShippingAddress();
        System.out.println("地址 = " + addr.getProvince() + " " + addr.getCity()
                + " " + addr.getStreet());

        double total = 0;
        for (ItemType item : order.getItemList()) {
            System.out.println("item " + item.getSku() + " qty=" + item.getQty()
                    + " price=" + item.getPrice() + " name=" + item.getName());
            total += item.getQty() * item.getPrice();
        }
        System.out.println("合计 = " + total);

        System.out.println("---- 修改并校验 ----");
        order.setId("B-2026-1101");
        order.getItemList().get(0).setQty(3);
        ItemType extra = order.addNewItem();
        extra.setSku("SKU-4004");
        extra.setQty(1);
        extra.setName("无线鼠标");
        extra.setPrice(199.0);

        List<XmlError> errors = new ArrayList<>();
        XmlOptions opts = new XmlOptions();
        opts.setErrorListener(errors);
        boolean ok = doc.validate(opts);
        System.out.println("validate = " + ok + " errors = " + errors.size());

        doc.save(new File("/work/schema/order-typed-modified.xml"));
        System.out.println("已保存 /work/schema/order-typed-modified.xml");

        System.out.println("---- 校验非法实例 ----");
        OrderDocument bad = OrderDocument.Factory.parse(new File("/work/schema/order-invalid.xml"));
        List<XmlError> badErrors = new ArrayList<>();
        XmlOptions badOpts = new XmlOptions();
        badOpts.setErrorListener(badErrors);
        System.out.println("validate = " + bad.validate(badOpts)
                + " errors = " + badErrors.size());
        for (XmlError e : badErrors) {
            System.out.println("  line " + e.getLine() + ", col " + e.getColumn()
                    + ": " + e.getMessage());
        }
    }
}

非法实例 /work/schema/order-invalid.xml(缺必填属性 id,且 qty="abc"):

<?xml version="1.0" encoding="UTF-8"?>
<ord:order xmlns:ord="http://example.com/order" date="2026-10-15">
  <ord:customer vip="true">
    <ord:name>张伟</ord:name>
    <ord:email>zhangwei@example.com</ord:email>
  </ord:customer>
  <ord:shippingAddress>
    <ord:province>广东省</ord:province>
    <ord:city>深圳市</ord:city>
    <ord:street>科技园南路 88 号</ord:street>
  </ord:shippingAddress>
  <ord:item sku="SKU-1001" qty="abc">
    <ord:name>机械键盘</ord:name>
    <ord:price>399.0</ord:price>
  </ord:item>
</ord:order>

把本系列示例类(GenericDemo、CursorDemo、TypedDemo、QNameSchemaDemo、QueryDemo,其中两类详见《XmlObject 与 Schema API 详解》)放入工程后编译并运行:

# mvn -B clean compile
...
[INFO] --- maven-compiler-plugin:3.11.0:compile (default-compile) @ xmlbeans-quick ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 5 source files with javac [debug release 17] to target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.371 s
[INFO] Finished at: 2026-10-15T12:00:28Z
[INFO] ------------------------------------------------------------------------
# M2=~/.m2/repository
# java -Dfile.encoding=UTF-8 -cp target/classes:$M2/org/apache/xmlbeans/xmlbeans/5.4.1/xmlbeans-5.4.1.jar:$M2/org/apache/logging/log4j/log4j-api/2.26.1/log4j-api-2.26.1.jar:$M2/com/example/order-xmlbeans/1.0.0/order-xmlbeans-1.0.0.jar demo.TypedDemo
id = A-2026-1015 date = Thu Oct 15 00:00:00 GMT 2026
customer = 张伟 <zhangwei@example.com> vip = true
地址 = 广东省 深圳市 科技园南路 88 号
item SKU-1001 qty=2 price=399.0 name=机械键盘
item SKU-2002 qty=1 price=1899.0 name=27 寸显示器
item SKU-3003 qty=3 price=259.0 name=USB-C 扩展坞
合计 = 3474.0
---- 修改并校验 ----
validate = true errors = 0
已保存 /work/schema/order-typed-modified.xml
---- 校验非法实例 ----
validate = false errors = 2
  line -1, col -1: Expected attribute: id in element order@http://example.com/order
  line -1, col -1: Invalid decimal value: unexpected char '97'

验证点全部达成:强类型 getter/setter、addNewItem() 追加、合计 3474.0(3×399+1899+3×259)、合法修改校验通过、非法实例精确报出两类错误(缺必填属性、qty 含非法字符 97 即 'a')。line/col 为 -1 是 validate() 错误监听器的已知行为,需要行列号时改用 CLI 校验器(见 scomp 进阶篇第六节,实测能给出 2:66、12:37)。

五、XPath 与 XQuery 查询

5.1 引擎分层:内置引擎与 Saxon 的边界

5.4.1 内置一个 XPath 子集引擎,无谓词的简单路径不需要 Saxon;带谓词([])的路径才走 Saxon。用下面的 QueryDemo 实测边界——先从类路径里去掉 Saxon:

/work/mvn-quick/src/main/java/demo/QueryDemo.java:

package demo;

import org.apache.xmlbeans.SimpleValue;
import org.apache.xmlbeans.XmlObject;

import java.io.File;

public class QueryDemo {

    static final String NS_DECL =
            "declare namespace ord='http://example.com/order'; ";

    public static void main(String[] args) throws Exception {
        XmlObject doc = XmlObject.Factory.parse(new File("/work/schema/order.xml"));

        System.out.println("== selectPath(XPath 2.0,Saxon) ==");
        XmlObject[] emails = doc.selectPath(
                NS_DECL + "$this/ord:order/ord:customer/ord:email");
        System.out.println("customer/email 命中 = " + emails.length
                + " 值 = " + ((SimpleValue) emails[0]).getStringValue());

        XmlObject[] skus = doc.selectPath(
                NS_DECL + "$this/ord:order/ord:item/@sku");
        System.out.print("全部 item/@sku 命中 " + skus.length + " 个: ");
        for (XmlObject sku : skus) {
            System.out.print(((SimpleValue) sku).getStringValue() + " ");
        }
        System.out.println();

        XmlObject[] dear = doc.selectPath(
                NS_DECL + "$this//ord:item[ord:price > 300]/ord:name");
        System.out.print("价格 > 300 的 item name 命中 " + dear.length + " 个: ");
        for (XmlObject d : dear) {
            System.out.print(((SimpleValue) d).getStringValue() + " ");
        }
        System.out.println();

        System.out.println("== execQuery(XQuery 1.0,Saxon) ==");
        XmlObject[] result = doc.execQuery(
                NS_DECL + "for $i in $this//ord:item "
                        + "where xs:double($i/ord:price) > 300 "
                        + "order by xs:double($i/ord:price) descending "
                        + "return $i/ord:name");
        System.out.println("XQuery 返回条数 = " + result.length);
        for (XmlObject r : result) {
            System.out.println("  " + ((SimpleValue) r).getStringValue());
        }
    }
}

类路径只有 xmlbeans + log4j-api 时(无 Saxon):

# java -Dfile.encoding=UTF-8 -cp target/classes:$M2/org/apache/xmlbeans/xmlbeans/5.4.1/xmlbeans-5.4.1.jar:$M2/org/apache/logging/log4j/log4j-api/2.26.1/log4j-api-2.26.1.jar:$M2/com/example/order-xmlbeans/1.0.0/order-xmlbeans-1.0.0.jar demo.QueryDemo
== selectPath(XPath 2.0,Saxon) ==
customer/email 命中 = 1 值 = zhangwei@example.com
全部 item/@sku 命中 3 个: SKU-1001 SKU-2002 SKU-3003
Exception in thread "main" java.lang.NoClassDefFoundError: net/sf/saxon/sxpath/XPathStaticContext
        at org.apache.xmlbeans.impl.xpath.XPathFactory.getCompiledPathSaxon(XPathFactory.java:142)
        at org.apache.xmlbeans.impl.xpath.XPathFactory.getCompiledPath(XPathFactory.java:97)
        at org.apache.xmlbeans.impl.xpath.XPathFactory.getCompiledPath(XPathFactory.java:59)
        at org.apache.xmlbeans.impl.store.Cursor._selectPath(Cursor.java:716)
        ...
Caused by: java.lang.ClassNotFoundException: net.sf.saxon.sxpath.XPathStaticContext
        ... 23 more

前两条简单路径命中(内置引擎),第三条带 [ord:price > 300] 谓词的路径在编译期回退到 Saxon 引擎并因缺类而失败——这就是"必须引入 Saxon"的实测边界。

5.2 补齐 Saxon 后完整运行

Maven 方式声明 net.sf.saxon:Saxon-HE:12.10 即可,其 POM 会传递引入 org.xmlresolver:xmlresolver:5.3.3(实测 dependency:tree 见第二节)。若像 jar 直跑那样手工拼类路径,必须连 xmlresolver 一起加,否则 Saxon 初始化时报错(实测复现):

Exception in thread "main" java.lang.NoClassDefFoundError: org/xmlresolver/Resolver
        at net.sf.saxon.lib.CatalogResourceResolver.<init>(CatalogResourceResolver.java:46)
        at net.sf.saxon.Configuration.init(Configuration.java:387)
        at net.sf.saxon.Configuration.<init>(Configuration.java:234)
        at org.apache.xmlbeans.impl.xpath.saxon.SaxonXPath.selectNodes(SaxonXPath.java:92)
        ...
Caused by: java.lang.ClassNotFoundException: org.xmlresolver.Resolver

补齐 xmlresolver-5.3.3.jar 后完整运行:

# M2=~/.m2/repository
# java -Dfile.encoding=UTF-8 -cp target/classes:$M2/org/apache/xmlbeans/xmlbeans/5.4.1/xmlbeans-5.4.1.jar:$M2/org/apache/logging/log4j/log4j-api/2.26.1/log4j-api-2.26.1.jar:$M2/com/example/order-xmlbeans/1.0.0/order-xmlbeans-1.0.0.jar:$M2/net/sf/saxon/Saxon-HE/12.10/Saxon-HE-12.10.jar:$M2/org/xmlresolver/xmlresolver/5.3.3/xmlresolver-5.3.3.jar demo.QueryDemo
== selectPath(XPath 2.0,Saxon) ==
customer/email 命中 = 1 值 = zhangwei@example.com
全部 item/@sku 命中 3 个: SKU-1001 SKU-2002 SKU-3003
价格 > 300 的 item name 命中 2 个: 机械键盘 27 寸显示器
== execQuery(XQuery 1.0,Saxon) ==
XQuery 返回条数 = 2
  27 寸显示器
  机械键盘

验证点:XPath 属性选取(@sku)、谓词过滤(price>300 命中 2 条)、XQuery FLWOR(where + order by descending 得到 1899 元的显示器在前)全部正确。

5.3 实测踩坑:无类型文档的排序按字符串比较

本例第一版查询写的是 order by $i/ord:price descending,实测输出顺序是机械键盘(399.0)在前、显示器(1899.0)在后——因为文档未绑定 Schema 类型(N= 解析),price 节点是无类型原子值,排序退化为字符串比较("399.0" > "1899.0")。修复:显式转换 xs:double($i/ord:price)(本文 QueryDemo 即修复后版本),where 子句同理。绑定类型 jar 后由 Schema 声明(xs:double)也能获得正确的数值排序。

六、方案选型建议

场景建议
有 XSD、要强类型、XML 结构频繁变动需编译期检查XMLBeans 5.4.1 + scomp(本文第四节)
只要读写不校验、无 XSD通用 XmlObject + XmlCursor(N= 解析,零依赖 XSD)
仅做轻量路径查询XPath 子集即可,可不引 Saxon(见 5.1 边界)
需要完整 XPath 2.0 / XQuery / 排序必须加 Saxon-HE 12.10 + xmlresolver 5.3.3
已在 JDK 8 遗留系统XMLBeans 5.4.1 兼容(字节码 major=52),《scomp 进阶篇》第七节实测

💡 技术拓展:与 JAXB 相比,XMLBeans 强项是保留完整游标与运行期 Schema 自省、对未绑定文档也能解析;弱项是生成类体积与学习曲线。JDK 11+ 项目若只需"XSD→类 + 校验",两者皆可;需要游标式增量修改(XmlCursor)时选 XMLBeans。

七、参考链接