
使用 StringEscapeUtils.unescapeXml() 无法一次性还原多重转义的 XML 实体(例如使用 `StringEscapeUtils.unescapeXml()` 无法一次性还原多重转义的 XML 实体(例如 `<` → `
在实际 XML 或 HTML 内容解析场景中,字符串可能因多次序列化/转义而嵌套多层实体编码。典型案例如 "
":
- 第一层 & 是 & 的 XML 实体,
- < 是 但整个字符串被双重转义——即原始
先被转为 <errors>,再将其中的 & 自身也被转义为 &,最终得到 。 此时,StringEscapeUtils.unescapeXml() 仅执行单次解码,将 。这是因为该方法设计上不递归处理,也不检测解码后是否仍含未解析实体。
✅ 正确做法是迭代解码,直到结果稳定(即连续两次解码结果相同):
import org.apache.commons.lang3.StringEscapeUtils; public class XmlUnescapeDemo { public static String unescapeXmlFully(String input) { if (input == null) return null; String result = input; String previous; do { previous = result; result = StringEscapeUtils.unescapeXml(result); } while (!result.equals(previous)); // 防止无限循环,确保收敛 return result; } public static void main(String[] args) { String input = "<errors>"; String output = unescapeXmlFully(input); System.out.println(output); // 输出: <errors> } }</errors></errors>⚠️ 注意事项:
- 避免无限循环:务必用 while (!result.equals(previous)) 判断终止条件,防止因非法输入(如
- 性能考量:绝大多数真实场景最多需 2–3 次解码,无需过度优化;若批量处理高频调用,可预估最大嵌套深度(如 Math.min(5, depth))设上限;
- 安全边界:该方法仅适用于可信来源或已校验的 XML 片段;切勿对未经清洗的用户输入直接递归解码并执行(如拼入 DOM 或反射调用),以防绕过 XSS 过滤;
- 版本兼容性:commons-lang3 ≥ 3.4 均支持此模式,但推荐升级至 3.12.0+ 以获得更严格的实体校验与 CVE 修复。
? 总结:XML 实体解码本质是“逆向序列化”,而多重转义意味着多层序列化。unescapeXml() 是原子操作,开发者需主动承担“解包”责任。采用收敛式迭代策略,兼顾正确性、健壮性与可维护性,是处理此类问题的标准实践。










