JSP XML 数据处理
在 JSP 里用 DOM / SAX 解析 XML、用 XPath 取值、输出 XML。
Key Points小文件用 DOM 整棵树读进内存,大文件用 SAX 流式解析;取值用 XPath 最省代码。
示例 XML
XML
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book id="1">
<title>深入理解计算机系统</title>
<price>139.00</price>
</book>
<book id="2">
<title>设计数据密集型应用</title>
<price>128.00</price>
</book>
</books>DOM 解析
JSP
<%@ page contentType="text/html;charset=UTF-8"
import="javax.xml.parsers.*, org.w3c.dom.*, java.io.*" %>
<%
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
// 关掉外部实体,防 XXE
f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder b = f.newDocumentBuilder();
Document doc = b.parse(application.getResourceAsStream("/WEB-INF/books.xml"));
NodeList books = doc.getElementsByTagName("book");
for (int i = 0; i < books.getLength(); i++) {
Element e = (Element) books.item(i);
String id = e.getAttribute("id");
String title = e.getElementsByTagName("title").item(0).getTextContent();
String price = e.getElementsByTagName("price").item(0).getTextContent();
%>
<p><%= id %> — <%= title %> — ¥<%= price %></p>
<%
}
%>Output
1 — 深入理解计算机系统 — ¥139.00
2 — 设计数据密集型应用 — ¥128.00The disallow-doctype-decl line is mandatory. By default a DOCTYPE can reference external entities, letting an attacker read arbitrary server files (XXE) or probe the internal network.
XPath 取值
JSP
<%@ page import="javax.xml.xpath.*" %>
<%
XPath xp = XPathFactory.newInstance().newXPath();
NodeList titles = (NodeList) xp.evaluate(
"//book[price > 130]/title", doc, XPathConstants.NODESET);
for (int i = 0; i < titles.getLength(); i++) {
out.println("<p>" + titles.item(i).getTextContent() + "</p>");
}
String first = (String) xp.evaluate("string(//book[1]/title)", doc,
XPathConstants.STRING);
%>Output
深入理解计算机系统XPath collapses traversal and filtering into one expression: "titles of books over 130" is //book[price > 130]/title instead of a loop with comparisons.
SAX 流式解析
Java
public class BookHandler extends DefaultHandler {
private final List<String> titles = new ArrayList<>();
private StringBuilder buf;
private boolean inTitle;
@Override
public void startElement(String uri, String local, String qName, Attributes a) {
if ("title".equals(qName)) { inTitle = true; buf = new StringBuilder(); }
}
@Override
public void characters(char[] ch, int start, int len) {
if (inTitle) buf.append(ch, start, len);
}
@Override
public void endElement(String uri, String local, String qName) {
if ("title".equals(qName)) { inTitle = false; titles.add(buf.toString()); }
}
public List<String> getTitles() { return titles; }
}| SAX | ||
|---|---|---|
| Memory | 整棵树进内存 | 只保留当前状态 |
| Speed | 慢,要建树 | 快,一遍过 |
| Modify | 可以增删节点 | 只读 |
| Best for | 配置、小文件 | 日志、大文件 |
直接输出 XML
JSP
<%@ page contentType="application/xml;charset=UTF-8" %>
<?xml version="1.0" encoding="UTF-8"?>
<users>
<c:forEach var="u" items="${users}">
<user id="${u.id}"><c:out value="${u.name}"/></user>
</c:forEach>
</users>For XML output the
contentType must be application/xml, and no blank line may precede the XML declaration — parsers reject any leading whitespace.示例 XML
XML
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book id="1">
<title>深入理解计算机系统</title>
<price>139.00</price>
</book>
<book id="2">
<title>设计数据密集型应用</title>
<price>128.00</price>
</book>
</books>DOM 解析
JSP
<%@ page contentType="text/html;charset=UTF-8"
import="javax.xml.parsers.*, org.w3c.dom.*, java.io.*" %>
<%
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
// 关掉外部实体,防 XXE
f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder b = f.newDocumentBuilder();
Document doc = b.parse(application.getResourceAsStream("/WEB-INF/books.xml"));
NodeList books = doc.getElementsByTagName("book");
for (int i = 0; i < books.getLength(); i++) {
Element e = (Element) books.item(i);
String id = e.getAttribute("id");
String title = e.getElementsByTagName("title").item(0).getTextContent();
String price = e.getElementsByTagName("price").item(0).getTextContent();
%>
<p><%= id %> — <%= title %> — ¥<%= price %></p>
<%
}
%>Output
1 — 深入理解计算机系统 — ¥139.00
2 — 设计数据密集型应用 — ¥128.00disallow-doctype-decl 这行必须加。默认配置下,XML 里的 DOCTYPE 可以引用外部实体,攻击者能用它读取服务器上的任意文件(XXE 漏洞),甚至发起内网请求。
XPath 取值
JSP
<%@ page import="javax.xml.xpath.*" %>
<%
XPath xp = XPathFactory.newInstance().newXPath();
NodeList titles = (NodeList) xp.evaluate(
"//book[price > 130]/title", doc, XPathConstants.NODESET);
for (int i = 0; i < titles.getLength(); i++) {
out.println("<p>" + titles.item(i).getTextContent() + "</p>");
}
String first = (String) xp.evaluate("string(//book[1]/title)", doc,
XPathConstants.STRING);
%>Output
深入理解计算机系统XPath 把「遍历 + 判断」压成一个表达式。查「价格大于 130 的书名」用 DOM 要写循环加比较,XPath 一句 //book[price > 130]/title 就够了。
SAX 流式解析
Java
public class BookHandler extends DefaultHandler {
private final List<String> titles = new ArrayList<>();
private StringBuilder buf;
private boolean inTitle;
@Override
public void startElement(String uri, String local, String qName, Attributes a) {
if ("title".equals(qName)) { inTitle = true; buf = new StringBuilder(); }
}
@Override
public void characters(char[] ch, int start, int len) {
if (inTitle) buf.append(ch, start, len);
}
@Override
public void endElement(String uri, String local, String qName) {
if ("title".equals(qName)) { inTitle = false; titles.add(buf.toString()); }
}
public List<String> getTitles() { return titles; }
}| DOM | SAX | |
|---|---|---|
| 内存 | 整棵树进内存 | 只保留当前状态 |
| 速度 | 慢,要建树 | 快,一遍过 |
| 可修改 | 可以增删节点 | 只读 |
| 适用 | 配置、小文件 | 日志、大文件 |
直接输出 XML
JSP
<%@ page contentType="application/xml;charset=UTF-8" %>
<?xml version="1.0" encoding="UTF-8"?>
<users>
<c:forEach var="u" items="${users}">
<user id="${u.id}"><c:out value="${u.name}"/></user>
</c:forEach>
</users>JSP 输出 XML 时
contentType 必须是 application/xml,且文件第一行不能有空行——XML 声明必须出现在最开头,前面有任何空白都会导致解析失败。