Java标准库中的javax.xml.transform.TransformerFactory是处理XSLT转换和XML恒等转换的核心工厂类。它封装了底层XSLT处理器的创建细节,开发者通过它获得Transformer对象,再把Source转换为Result。实际项目里TransformerFactory的默认配置并不安全,尤其在处理不可信XML时,可能被外部实体攻击,因此配置安全属性要比直接调用transform更优先。

一、基础用法:从工厂到转换结果
使用TransformerFactory进行XML转换的第一步是获取工厂实例,然后创建Transformer对象。如果只是想把XML内容原样输出,不附加任何XSLT样式表,那么直接调用newTransformer()即可。这种恒等转换常用于XML规范化、去除多余空白,或者把DOM树重新序列化为字符串。
下面的代码演示了如何读取一个XML文件,将其内容写入StringWriter并打印出来。这里使用StreamSource指向文件,使用StreamResult接收输出,整个调用链路非常直观。
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import java.io.StringWriter;
public class BasicTransform {
public static void main(String[] args) throws Exception {
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
Source xml = new StreamSource("input.xml");
StringWriter sw = new StringWriter();
Result output = new StreamResult(sw);
transformer.transform(xml, output);
System.out.println(sw.toString());
}
}
转换结果的格式可以通过Transformer的输出属性来控制。常用的OutputKeys.ENCODING决定XML声明中的编码,INDENT控制是否缩进,OMIT_XML_DECLARATION控制是否省略XML声明。设置方式如下:
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
需要特别注意的是,Transformer对象不是线程安全的。如果多个线程共享同一个Transformer进行转换,可能出现输出错乱或参数串扰。对于并发场景,应当让每个线程使用独立的Transformer实例,或者使用稍后介绍的Templates来获取新的Transformer。
二、安全配置:关闭外部实体解析与限制资源访问
TransformerFactory默认允许处理DTD和外部实体,这在处理不可信XML时会带来XXE风险。攻击者可以在XML中声明外部实体,读取服务器上的敏感文件,甚至发起服务端请求伪造。对于没有XSLT的恒等转换,Transformer底层的XML解析器也可能解析DOCTYPE和外部实体,因此安全配置不能忽略。
Java提供了FEATURE_SECURE_PROCESSING安全处理特性,以及ACCESS_EXTERNAL_DTD和ACCESS_EXTERNAL_STYLESHEET两个约束属性。把它们设置为空字符串可以禁止外部DTD和外部样式表。建议在获取工厂后立即调用如下配置:
import javax.xml.XMLConstants;
import javax.xml.transform.TransformerFactory;
public class SecureTransformerFactory {
public static TransformerFactory createSecureFactory() throws Exception {
TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
return factory;
}
}
FEATURE_SECURE_PROCESSING开启后,处理器会限制一些不安全的XML结构,比如外部实体和过深的嵌套。ACCESS_EXTERNAL_DTD和ACCESS_EXTERNAL_STYLESHEET的值可以是一个允许访问的URL列表,也可以为空字符串表示完全禁止外部访问。某些旧版JDK的Xalan实现对这些属性支持不完整,遇到异常时可以单独设置属性并捕获TransformerConfigurationException。
如果转换的Source是DOMSource,还需要保证构建Document对象时使用的DocumentBuilderFactory也是安全的。因为TransformerFactory只负责转换阶段,不会重新解析DOM树。下面的代码展示了如何在解析阶段关闭外部DTD和外部Schema:
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import javax.xml.transform.dom.DOMSource;
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse("input.xml");
Source xml = new DOMSource(doc);
这样双层防护可以更稳妥地避免XML解析阶段的XXE风险,尤其是在解析和转换分离的架构中。
三、XSLT转换实战:编译模板与传递参数
实际业务中大多数场景需要把XML转换为HTML、文本或其他结构,这时就要提供XSLT样式表。TransformerFactory的newTemplates方法会编译XSLT,返回一个Templates对象。Templates是线程安全的,可以跨线程共享,而每次调用newTransformer()再生成新的Transformer执行转换。这种模式避免了重复编译样式表的开销。
下面的示例演示了完整的XSLT转换流程:先安全配置工厂,再编译style.xsl,然后从Templates获取Transformer,最后传入XML数据并输出HTML文件。
import javax.xml.XMLConstants;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
public class XsltTransform {
public static void main(String[] args) throws Exception {
TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
Source xslt = new StreamSource("style.xsl");
Templates templates = factory.newTemplates(xslt);
Transformer transformer = templates.newTransformer();
transformer.setParameter("title", "订单报表");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
Source xml = new StreamSource("data.xml");
Result result = new StreamResult("output.html");
transformer.transform(xml, result);
}
}
XSLT中可以在顶层定义参数,通过Transformer.setParameter传入值。样式表使用<xsl:param>声明参数,再在模板中通过$参数名引用。例如style.xsl的内容可以如下:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8" indent="yes"/>
<xsl:param name="title"/>
<xsl:template match="/">
<html>
<head><title><xsl:value-of select="$title"/></title></head>
<body>
<h1><xsl:value-of select="$title"/></h1>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="item">
<p><xsl:value-of select="."/></p>
</xsl:template>
</xsl:stylesheet>
参数值可以是字符串、数字或Node对象,根据需要灵活使用。如果样式表里没有声明对应参数,setParameter不会报错,但也不产生效果。对于高并发服务,建议在启动阶段完成Templates编译,并在每次请求中只创建Transformer,这样可以显著提升吞吐量。
四、配置排查与性能细节
配置TransformerFactory时经常会遇到两个问题:一是安全属性不起作用,二是相对路径找不到资源。对于第一个问题,要确认JDK中实际使用的XSLT实现。Oracle JDK默认使用Xalan,部分低版本对ACCESS_EXTERNAL_STYLESHEET支持并不完整,可以通过启动参数或引入Apache Xalan进行替换。捕获异常并按实现降级处理,是兼容多环境的一种做法。
对于相对路径问题,StreamSource可以直接使用文件路径,但在Web容器或有复杂资源加载逻辑的环境中,建议显式设置systemId。systemId不仅帮助处理器解析相对引用,也用于错误定位。例如:
StreamSource xslt = new StreamSource("style.xsl");
xslt.setSystemId(new java.io.File("style.xsl").toURI().toString());
另一个常见的性能问题是每次转换都调用newTemplates。XSLT编译涉及解析样式表、构建模板匹配树等操作,成本远高于单次transform。把Templates缓存为单例或连接池中的共享对象,可以避免重复编译。同时注意Transformer本身仍然不是线程安全对象,不能在多线程环境下共享。
输出中文乱码通常不是TransformerFactory的问题,而是Result的Writer或OutputStream没有使用UTF-8。StreamResult如果接收OutputStream,XML声明中的编码需要与实际字节编码一致;如果接收Writer,则优先使用Writer自身的编码。设置OutputKeys.ENCODING为UTF-8并配合OutputStream,同时把文件或网络输出流声明为UTF-8,可以保证中文正常显示。
把这些配置、编译和输出细节串起来,TransformerFactory就能在安全性和性能之间取得较好的平衡。日常开发中应把安全配置抽成公共工具方法,避免在各个业务代码中重复犯错。
TransformerFactoryXML转换XSLT修改时间:2026-09-30 07:08:30