Apache Xalan-J 在 Ubuntu 22.04 集成使用指南
在 Ubuntu 22.04 上集成 Apache Xalan-J 2.7.3 的完整实测指南:Maven 与手动 jar 两种集成方式、Transformer API 最小变换示例、与 Xerces-J 搭配的 SAXSource/DOMSource 用法,含真实踩坑记录。
Apache Xalan-J 在 Ubuntu 22.04 集成使用指南
目标:在真实的 Ubuntu 22.04 服务器上,从零完成 Apache Xalan-J 2.7.3 的安装与集成,跑通 Transformer API 最小变换示例、Maven 项目集成、手动 jar 集成、以及与 Xerces-J 搭配的 SAXSource/DOMSource 数据源示例。每一步的命令与回显均为真机实测输出。
一、环境信息
| 项 | 值 |
|---|---|
| 操作系统 | Ubuntu 22.04.5 LTS(x86_64) |
| JDK | OpenJDK 17.0.20.1(Ubuntu 发行版) |
| 构建工具 | Apache Maven 3.6.3 |
| Xalan-J | 2.7.3(Maven Central 最新 release,官方二进制包同步版本) |
$ head -2 /etc/os-release
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
$ uname -m
x86_64
$ java -version 2>&1
openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment (build 17.0.20.1+1-1-22.04-Ubuntu)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1-22.04-Ubuntu, mixed mode, sharing)
$ mvn -version | head -2
Apache Maven 3.6.3
Maven home: /usr/share/maven
💡 技术拓展:最小化安装的系统里
mvn -version会显示platform encoding: ANSI_X3.4-1968,这是缺 UTF-8 locale 的信号。Xalan 的样式表里常含中文文本,建议先执行export LANG=C.UTF-8再做后续实验,否则 Java 控制台输出的中文会退化为问号(本文所有输出均在LANG=C.UTF-8下采集)。
二、前置条件
安装 JDK 17 与 Maven(apt 源即可,包名 openjdk-17-jdk 与 maven):
$ export LANG=C.UTF-8
$ DEBIAN_FRONTEND=noninteractive apt-get install -y openjdk-17-jdk maven | tail -4
Reading state information...
maven is already the newest version (3.6.3-5).
openjdk-17-jdk is already the newest version (17.0.20.1+1-1~22.04).
0 upgraded, 0 newly installed, 0 to remove and 2 not upgraded.
(重复执行幂等,"already the newest version" 表示已安装。)同时准备一个实验目录并创建示例文件,本文后续所有命令都在 /root 下进行:
$ mkdir -p /root/xalan-lab/lib && cd /root/xalan-lab
示例数据 hello.xml(内容如下,保存到 /root/xalan-lab/hello.xml):
<?xml version="1.0" encoding="UTF-8"?>
<greeting>
<name>Xalan-J</name>
</greeting>
示例样式表 hello.xsl(保存到 /root/xalan-lab/hello.xsl):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/greeting">
<message>Hello, <xsl:value-of select="name"/>! Your XSLT engine is <xsl:value-of select="system-property('xsl:vendor')"/>.</message>
</xsl:template>
</xsl:stylesheet>
样式表里的 system-property('xsl:vendor') 是 XSLT 标准函数,能在输出中直接显示当前变换引擎的身份,后文用它区分 JDK 内置引擎与独立 Xalan-J。
三、获取 Xalan-J
有两条获取途径,内容一致,按团队习惯二选一或都备。
途径一:官方二进制包(含命令行工具与离线文档,推荐服务器手工部署时使用)
$ cd /root
$ curl -L -O --max-time 300 -w "HTTP %{http_code}, %{size_download} bytes, %{time_total}s\n" \
https://archive.apache.org/dist/xalan/xalan-j/binaries/xalan-j_2_7_3-bin.tar.gz
HTTP 200, 13570932 bytes, 10.773798s
$ curl -L -O --max-time 60 -w "HTTP %{http_code}, %{size_download} bytes\n" \
https://archive.apache.org/dist/xalan/xalan-j/binaries/xalan-j_2_7_3-bin.tar.gz.sha512
HTTP 200, 276 bytes
$ cat xalan-j_2_7_3-bin.tar.gz.sha512
build\xalan-j_2_7_3-bin.tar.gz: 18373698 936776E9 CE5818E9 FFBF4EFE 56C44D52
CA094B85 FA438A59 528C369A CB27D07A FBC85295
BBC44698 87688761 6F65D7BC 6364DEFA 08B36561
E8B3DDC7
$ sha512sum xalan-j_2_7_3-bin.tar.gz
18373698936776e9ce5818e9ffbf4efe56c44d52ca094b85fa438a59528c369acb27d07afbc85295bbc44698876887616f65d7bc6364defa08b36561e8b3ddc7 xalan-j_2_7_3-bin.tar.gz
$ tar xzf xalan-j_2_7_3-bin.tar.gz
$ ls -l /root/xalan-j_2_7_3/
-rw-r--r-- 1 root root 51416 Mar 25 2023 LICENSE.txt
-rw-r--r-- 1 root root 3955 Mar 25 2023 NOTICE.txt
drwxr-xr-x 8 root root 4096 Oct 16 00:26 docs
drwxr-xr-x 25 root root 4096 Oct 16 00:26 samples
-rw-r--r-- 1 root root 283536 Mar 25 2023 serializer.jar
-rw-r--r-- 1 root root 3455761 Mar 25 2023 xalan.jar
-rw-r--r-- 1 root root 1446149 Mar 25 2023 xercesImpl.jar
-rw-r--r-- 1 root root 228394 Mar 25 2023 xml-apis.jar
sha512 去掉空格换行后与本地计算值一致,校验通过。四个 jar 的分工:xalan.jar 是处理器本体(内含解释器引擎、XSLTC 引擎与命令行工具),serializer.jar 提供输出序列化器,xercesImpl.jar 是 XML 解析器,xml-apis.jar 是接口定义兼容包。
途径二:Maven 坐标(代码集成时使用)
$ curl -s https://repo1.maven.org/maven2/xalan/xalan/maven-metadata.xml | grep -E "latest|release"
<latest>2.7.3</latest>
<release>2.7.3</release>
坐标为 xalan:xalan:2.7.3,序列化器为 xalan:serializer:2.7.3。注意:这两个坐标互为运行期必需,却没有传递依赖关系,Maven 引入时必须成对声明(第四章有完整实测)。
四、集成方式一:Maven
4.1 创建工程
$ cd /root/xalan-lab
$ mkdir -p xalan-demo/src/main/java/com/example && cp hello.xsl hello.xml xalan-demo/ 2>/dev/null
$ cd xalan-demo
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>com.example</groupId>
<artifactId>xalan-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>xalan</groupId>
<artifactId>xalan</artifactId>
<version>2.7.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</build>
</project>
4.2 主程序
src/main/java/com/example/XalanDemo.java:
package com.example;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import java.io.File;
public class XalanDemo {
public static void main(String[] args) throws Exception {
String xsl = args[0], xml = args[1], out = args[2];
TransformerFactory factory = TransformerFactory.newInstance();
System.out.println("TransformerFactory 实现类: " + factory.getClass().getName());
// Templates 是线程安全的编译产物,可全局复用;每个线程取自己的 Transformer
Templates templates = factory.newTemplates(new StreamSource(new File(xsl)));
Transformer transformer = templates.newTransformer();
System.out.println("Transformer 实现类: " + transformer.getClass().getName());
transformer.transform(new StreamSource(new File(xml)), new StreamResult(new File(out)));
System.out.println("变换完成,输出文件: " + out);
}
}
4.3 第一版运行:依赖不完整的真实报错
先看依赖树,再运行(参数为员工花名册变换):
$ mvn -B dependency:tree 2>&1 | grep -E "xalan|serializer|BUILD"
[INFO] -----------------------< com.example:xalan-demo >-----------------------
[INFO] Building xalan-demo 1.0.0
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ xalan-demo ---
[INFO] com.example:xalan-demo:jar:1.0.0
[INFO] \- xalan:xalan:jar:2.7.3:compile
[INFO] BUILD SUCCESS
$ mvn -B compile exec:java -Dexec.mainClass=com.example.XalanDemo -Dexec.args="employees2html.xsl employees.xml out.html" 2>&1 | grep -vE "^\[INFO\] Download|Progress \(" | grep -E "NoClassDefFound|Caused by|ERROR|TransformerFactory|Exception" | head -8
TransformerFactory 实现类: org.apache.xalan.processor.TransformerFactoryImpl
java.lang.NoClassDefFoundError: org/apache/xalan/transformer/TransformerImpl
at org.apache.xalan.processor.TransformerFactoryImpl.newTemplates (TransformerFactoryImpl.java:917)
Caused by: java.lang.ClassNotFoundException: org.apache.xalan.transformer.TransformerImpl
at org.apache.xalan.processor.TransformerFactoryImpl.newTemplates (TransformerFactoryImpl.java:917)
[ERROR] Failed to execute goal org.codehaus.mojo:exec-maven-plugin:3.1.0:java (default-cli) on project xalan-demo: An exception occurred while executing the Java class. org/apache/xalan/transformer/TransformerImpl: org.apache.xalan.transformer.TransformerImpl -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
依赖树里只有 xalan:xalan 自己,newTemplates 一步就报 NoClassDefFoundError: org/apache/xalan/transformer/TransformerImpl。有意思的是,这个类本体就在 xalan-2.7.3.jar 里(unzip -l 可证),真正缺的是它链接所需的接口——用 javap 看 TransformerImpl 的声明,它实现了 org.apache.xml.serializer.SerializerTrace,而该接口在 serializer.jar 中:
$ unzip -l ~/.m2/repository/xalan/xalan/2.7.3/xalan-2.7.3.jar | grep TransformerImpl
52054 2023-03-25 19:56 org/apache/xalan/transformer/TransformerImpl.class
$ javap -cp ~/.m2/repository/xalan/xalan/2.7.3/xalan-2.7.3.jar org.apache.xalan.transformer.TransformerImpl | head -2
Compiled from "TransformerImpl.java"
public class org.apache.xalan.transformer.TransformerImpl extends javax.xml.transform.Transformer implements java.lang.Runnable,org.apache.xml.dtm.DTMWSFilter,org.apache.xpath.ExtensionsProvider,org.apache.xml.serializer.SerializerTrace {
上述报错在干净工程上复现过两次,形态一致。官方二进制包靠 Manifest 的 Class-Path 自动带上兄弟 jar,而 Maven 仓库里的 jar 是"孤身一人"的——这就是把发行目录当类路径能跑、Maven 单依赖报错的根因。验证 Manifest(实测):
$ unzip -p /root/xalan-j_2_7_3/xalan.jar META-INF/MANIFEST.MF | grep -A1 "Class-Path"
Class-Path: xercesImpl.jar xml-apis.jar serializer.jar
4.4 第二版运行:补上 serializer 依赖
在 <dependencies> 中追加:
<dependency>
<groupId>xalan</groupId>
<artifactId>serializer</artifactId>
<version>2.7.3</version>
</dependency>
再次验证依赖树与运行:
$ mvn -B dependency:tree 2>&1 | grep -E "xalan|serializer|BUILD"
[INFO] -----------------------< com.example:xalan-demo >-----------------------
[INFO] Building xalan-demo 1.0.0
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ xalan-demo ---
[INFO] com.example:xalan-demo:jar:1.0.0
[INFO] +- xalan:xalan:jar:2.7.3:compile
[INFO] \- xalan:serializer:jar:2.7.3:compile
[INFO] BUILD SUCCESS
$ mvn -B compile exec:java -Dexec.mainClass=com.example.XalanDemo -Dexec.args="employees2html.xsl employees.xml out.html" 2>&1 | tail -8
Transformer 实现类: org.apache.xalan.transformer.TransformerImpl
变换完成,输出文件: out.html
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.391 s
[INFO] Finished at: 2026-10-16T00:57:20Z
[INFO] ------------------------------------------------------------------------
(完整 stdout 共 9 行,首行 TransformerFactory 实现类: org.apache.xalan.processor.TransformerFactoryImpl 被 tail -8 截去,如实注明。)
输出文件 out.html 内容:
$ cat out.html
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>员工花名册</title>
</head>
<body>
<h1>员工花名册(共 3 人)</h1>
<table cellpadding="4" border="1">
<tr>
<th>工号</th><th>姓名</th><th>部门</th><th>月薪(元)</th>
</tr>
<tr>
<td>103</td><td>Carol Wang</td><td>Engineering</td><td align="right">25,000</td>
</tr>
<tr>
<td>101</td><td>Alice Zhang</td><td>Engineering</td><td align="right">22,000</td>
</tr>
<tr>
<td>102</td><td>Bob Li</td><td>Sales</td><td align="right">15,000</td>
</tr>
</table>
</body>
</html>
工厂是 org.apache.xalan.processor.TransformerFactoryImpl(Apache 独立版),vendor 显示 Apache Software Foundation——Maven 方式集成成功。本地仓库中实际下载的构件:
$ find /root/.m2/repository/xalan -name "*.jar"
/root/.m2/repository/xalan/serializer/2.7.3/serializer-2.7.3.jar
/root/.m2/repository/xalan/xalan/2.7.3/xalan-2.7.3.jar
五、集成方式二:手动 jar
适合无 Maven 环境、部署脚本化、或需要精确控制 jar 布局的场景。类路径惯例是四个 jar 一起上:
$ cd /root/xalan-lab
$ export CP=/root/xalan-j_2_7_3/xalan.jar:/root/xalan-j_2_7_3/serializer.jar:/root/xalan-j_2_7_3/xercesImpl.jar:/root/xalan-j_2_7_3/xml-apis.jar
最小示例 HelloXalan.java(保存到 /root/xalan-lab):
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import java.io.File;
public class HelloXalan {
public static void main(String[] args) throws Exception {
TransformerFactory factory = TransformerFactory.newInstance();
System.out.println("TransformerFactory 实现类: " + factory.getClass().getName());
Transformer transformer = factory.newTransformer(new StreamSource(new File("hello.xsl")));
transformer.transform(new StreamSource(new File("hello.xml")), new StreamResult(System.out));
}
}
$ javac -encoding UTF-8 -cp $CP -d classes HelloXalan.java
场景 1:类路径不含 xalan.jar(JDK 内置引擎作为对照)
$ java -cp classes HelloXalan
TransformerFactory 实现类: com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
<?xml version="1.0" encoding="UTF-8"?><message>Hello, Xalan-J! Your XSLT engine is Apache Software Foundation (Xalan XSLTC).</message>
场景 2:xalan.jar 在类路径(独立 Xalan-J 引擎)
$ java -cp classes:$CP HelloXalan
TransformerFactory 实现类: org.apache.xalan.processor.TransformerFactoryImpl
<?xml version="1.0" encoding="UTF-8"?><message>Hello, Xalan-J! Your XSLT engine is Apache Software Foundation.</message>
同一份代码,工厂实现类与 vendor 完全不同——TransformerFactory.newInstance() 通过 SPI 查找 META-INF/services/javax.xml.transform.TransformerFactory(xalan.jar 内登记的是 org.apache.xalan.processor.TransformerFactoryImpl),类路径优先级高于 JDK 默认。
场景 3:用系统属性强制指定工厂
$ java -Djavax.xml.transform.TransformerFactory=org.apache.xalan.processor.TransformerFactoryImpl \
-cp classes:$CP HelloXalan
TransformerFactory 实现类: org.apache.xalan.processor.TransformerFactoryImpl
<?xml version="1.0" encoding="UTF-8"?><message>Hello, Xalan-J! Your XSLT engine is Apache Software Foundation.</message>
$ java -Djavax.xml.transform.TransformerFactory=net.sf.saxon.TransformerFactoryImpl -cp classes HelloXalan
Exception in thread "main" javax.xml.transform.FactoryConfigurationError: Provider net.sf.saxon.TransformerFactoryImpl not found
at java.xml/javax.xml.transform.FactoryFinder.newInstance(FactoryFinder.java:178)
at java.xml/javax.xml.transform.FactoryFinder.find(FactoryFinder.java:213)
at java.xml/javax.xml.transform.TransformerFactory.newInstance(TransformerFactory.java:86)
at HelloXalan.main(HelloXalan.java:7)
Caused by: java.lang.ClassNotFoundException: net.sf.saxon.TransformerFactoryImpl
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445)
at java.xml/javax.xml.transform.FactoryFinder$1.run(FactoryFinder.java:148)
at java.xml/javax.xml.transform.FactoryFinder$1.run(FactoryFinder.java:143)
at java.security.AccessController.doPrivileged(Native Method)
at java.xml/javax.xml.transform.FactoryFinder.findServiceProvider(FactoryFinder.java:143)
at java.xml/javax.xml.transform.FactoryFinder.find(FactoryFinder.java:211)
... 3 more
系统属性优先于类路径 SPI;指定不存在的实现会得到 TransformerFactoryConfigurationError(FactoryConfigurationError 的子类,实测输出如上)。这从侧面证明:属性指向的类必须真实在类路径上,否则实例化直接失败。
场景 4:只放 xalan.jar 且把发行目录当类路径
$ java -cp classes:/root/xalan-j_2_7_3/xalan.jar:/root/xalan-j_2_7_3/xercesImpl.jar:/root/xalan-j_2_7_3/xml-apis.jar \
Generic employees2html.xsl employees.xml 2>&1 | tail -6
<tr>
<td>102</td><td>Bob Li</td><td>Sales</td><td align="right">15,000</td>
</tr>
</table>
</body>
</html>
即便不写 serializer.jar 也能跑通本例(退出码 0 实测)——因为 xalan.jar 的 Manifest Class-Path: xercesImpl.jar xml-apis.jar serializer.jar 会自动从它所在目录加载兄弟 jar。这个机制是把双刃剑:布局得当则免配置,jar 被单独拷走(如 Maven 本地仓库)则缺类报错(见下文 4.3 的真实复现)。生产建议:类路径四个 jar 显式写全,不依赖隐式机制。
六、核心 API 与最小变换示例
javax.xml.transform 是 JAXP 的变换子 API,Xalan-J 是它的实现之一。核心对象三件套:
TransformerFactory:工厂,负责把样式表编译成Templates;Templates:编译后的样式表,线程安全可复用,多线程场景只编译一次;Transformer:一次变换的执行器,非线程安全,从Templates.newTransformer()获取。
上一章 XalanDemo.java 已演示完整链路:newTemplates(StreamSource) → newTransformer() → transform(StreamSource, StreamResult)。输出目标可以是 StreamResult(File/OutputStream/Writer)、DOMResult 或 SAXResult;输入同理有 StreamSource/DOMSource/SAXSource。带参数的样式表用 transformer.setParameter("dept", "Engineering") 注入。
七、与 Xerces-J 搭配(SAXSource / DOMSource)
默认情况下 StreamSource 让工厂自己挑选解析器。要显式指定 Xerces-J 作为解析入口(统一解析行为、利用 Xerces 特性开关时常用),用 SAXSource 传入自定义 XMLReader,或先构建 DOM 再用 DOMSource:
SaxDomSource.java(保存到 /root/xalan-lab):
import javax.xml.transform.*;
import javax.xml.transform.sax.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import org.xml.sax.InputSource;
import org.w3c.dom.Document;
import java.io.File;
public class SaxDomSource {
public static void main(String[] args) throws Exception {
TransformerFactory factory = TransformerFactory.newInstance();
System.out.println("TransformerFactory 实现类: " + factory.getClass().getName());
// 一、SAXSource:显式指定 Xerces 的 XMLReader 作为解析器
org.xml.sax.XMLReader reader = new org.apache.xerces.parsers.SAXParser();
System.out.println("XMLReader 实现类: " + reader.getClass().getName());
Transformer t1 = factory.newTransformer(new StreamSource(new File("employees2html.xsl")));
SAXSource saxSource = new SAXSource(reader, new InputSource(new File("employees.xml").toURI().toURL().toString()));
t1.transform(saxSource, new StreamResult(new File("out-sax.html")));
System.out.println("已生成 out-sax.html");
// 二、DOMSource:先用 Xerces 的 DocumentBuilder 构建 DOM 树,再交给 transformer
javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance();
System.out.println("DocumentBuilderFactory 实现类: " + dbf.getClass().getName());
Document doc = dbf.newDocumentBuilder().parse(new File("employees.xml"));
System.out.println("DOM 根元素: " + doc.getDocumentElement().getTagName()
+ ", 子节点数: " + doc.getDocumentElement().getChildNodes().getLength());
Transformer t2 = factory.newTransformer(new StreamSource(new File("employees2html.xsl")));
t2.transform(new DOMSource(doc), new StreamResult(new File("out-dom.html")));
System.out.println("已生成 out-dom.html");
}
}
数据源 employees.xml 为包含三个员工的公司花名册(id/姓名/部门/月薪),样式表 employees2html.xsl 将其渲染为按月薪降序的 HTML 表格。实测运行:
$ javac -encoding UTF-8 -cp $CP -d classes SaxDomSource.java
$ java -cp classes:$CP SaxDomSource
TransformerFactory 实现类: org.apache.xalan.processor.TransformerFactoryImpl
XMLReader 实现类: org.apache.xerces.parsers.SAXParser
已生成 out-sax.html
DocumentBuilderFactory 实现类: org.apache.xerces.jaxp.DocumentBuilderFactoryImpl
DOM 根元素: company, 子节点数: 7
已生成 out-dom.html
$ diff out-sax.html out-dom.html && echo "两者内容完全一致"
两者内容完全一致
out-sax.html 内容:
$ cat out-sax.html
<html><head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>员工花名册</title>
</head><body>
<h1>员工花名册(共 3 人)</h1>
<table border="1" cellpadding="4">
<tr><th>工号</th><th>姓名</th><th>部门</th><th>月薪(元)</th></tr>
<tr>
<td>103</td><td>Carol Wang</td><td>Engineering</td><td align="right">25,000</td>
</tr>
<tr>
<td>101</td><td>Alice Zhang</td><td>Engineering</td><td align="right">22,000</td>
</tr>
<tr>
<td>102</td><td>Bob Li</td><td>Sales</td><td align="right">15,000</td>
</tr>
</table>
</body></html>
两条路径输出逐字节一致:SAXSource 流式解析省内存,DOMSource 适合 XML 已在内存中的场景(如多份样式表对同一文档做多次变换)。
💡 技术拓展:把
xercesImpl.jar放进类路径后,DocumentBuilderFactory.newInstance()也会自动切到org.apache.xerces.jaxp.DocumentBuilderFactoryImpl(同为 SPI 机制)。若把它从类路径移除,new org.apache.xerces.parsers.SAXParser()会直接NoClassDefFoundError。另外注意 Xerces 有个包名相近的遗留类org.apache.xml.serialize(单数),与 Xalan 的org.apache.xml.serializer(复数)无关。
八、扩展函数示例(EXLT node-set + Java 扩展)
XSLT 1.0 的常见痛点是「结果树片段(RTF)不能当节点集遍历」,EXSLT 的 node-set 与 Xalan 的 Java 扩展命名空间正好补位。exslt-test.xsl:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common"
xmlns:math="http://xml.apache.org/xalan/java/java.lang.Math"
exclude-result-prefixes="exslt math">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/company">
<xsl:variable name="rtf">
<item>XSLT 1.0</item>
<item>XPath 1.0</item>
</xsl:variable>
<report>
<java-ext>
<sqrt2><xsl:value-of select="math:sqrt(2)"/></sqrt2>
<round><xsl:value-of select="math:round(2.7)"/></round>
</java-ext>
<exslt>
<node-set-count><xsl:value-of select="count(exslt:node-set($rtf)/item)"/></node-set-count>
<first-item><xsl:value-of select="exslt:node-set($rtf)/item[1]"/></first-item>
</exslt>
</report>
</xsl:template>
</xsl:stylesheet>
$ java -cp classes:$CP Generic exslt-test.xsl employees.xml
<?xml version="1.0" encoding="UTF-8"?><report>
<java-ext>
<sqrt2>1.4142135623730951</sqrt2>
<round>3</round>
</java-ext>
<exslt>
<node-set-count>2</node-set-count>
<first-item>XSLT 1.0</first-item>
</exslt>
</report>
同一份样式表在 JDK 内置引擎下同样跑通(其内部 Xalan 也实现了这套扩展命名空间,输出缩进为 4 空格,由内置引擎的序列化器排版)。安全场景可用 -SECURE 或 FEATURE_SECURE_PROCESSING 关闭 Java 扩展,实测关闭时会报 Extension function: '{http://xml.apache.org/xalan/java/java.lang.Math}sqrt' can not be invoked when the XMLConstants.FEATURE_SECURE_PROCESSING feature is set to true.(详见《Apache Xalan-J 命令行工具详解》-SECURE 一节)。
九、注意事项
- 依赖成对声明:Maven 必须同时引入
xalan:xalan:2.7.3与xalan:serializer:2.7.3,否则运行期NoClassDefFoundError: org/apache/xalan/transformer/TransformerImpl(解释器引擎核心类链接需要serializer.jar中的SerializerTrace接口,本章 4.3 节实测)。 - JDK 17 模块封装:不要通过反射实例化
com.sun.org.apache.xalan.internal.*(会得到IllegalAccessException: module java.xml does not export ...);切换引擎用类路径 SPI 或系统属性即可。确需反射调试时用--add-exports java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED放开。 - Manifest Class-Path 隐式依赖:
xalan.jar会自动加载同目录的xercesImpl.jar xml-apis.jar serializer.jar;拷贝 jar 时保持发行包目录布局,或显式写全-cp。 - 中文乱码:
export LANG=C.UTF-8;源码编译加-encoding UTF-8;Maven 工程设置project.build.sourceEncoding=UTF-8。 - Templates 复用:样式表编译代价高,全局缓存
Templates、按线程取Transformer是标准做法。 - 判空防御:样式表有编译错误时
newTransformer可能返回 null 而不是抛异常(详见《Apache Xalan-J 2.7.3 配置参数详解》ErrorListener 一节实测),生产代码务必对返回值判空。
十、参考链接
- 《Apache Xalan-J 介绍与选型指南》——Xalan-J 定位、与 JDK 内置 Xalan 的关系、选型对比。
- 《Apache Xalan-J 2.7.3 配置参数详解》——工厂特性/属性、输出属性、ErrorListener、URIResolver 逐项实测。
- 《Apache Xalan-J 命令行工具详解》——Process/Compile/Transform 逐参数实测。
- Apache Xalan 官网:https://xalan.apache.org/
- Xalan-J 二进制下载:https://archive.apache.org/dist/xalan/xalan-j/binaries/
- Maven Central:https://repo1.maven.org/maven2/xalan/xalan/