PAYATHON 2026

为何仅在 Intellij 调试时出现 XMLConfigurationException?

排障赵师傅

先检查 IntelliJ IDEA 的异常断点和调试表达式求值。现有堆栈更像是调试器截获了 XML 解析过程中的内部异常,或在求值时调用了方法,还不能据此认定存在 Maven 依赖冲突、Log4j 配置错误或 IDEA 缺陷。

调试器暂停后,先继续执行,看看应用能否完成启动、日志是否正常。如果都正常,这通常是内部代码已经处理、但在调试时被显示出来的异常;如果继续执行后应用仍然退出,就需要从最终未捕获的异常查起。

为什么正常运行时看不到

Java 程序抛出异常后,上层代码可以捕获并处理它。这类异常在正常运行时未必会输出到控制台;如果调试器设置为在“已捕获异常”处暂停,就会在异常被处理前停下来。

从堆栈中的调用关系看,异常发生在 Log4j 初始化、解析 XML 配置的过程中:

Log4j XmlConfiguration
  → DocumentBuilderImpl.parse
  → XIncludeHandler.copyFeatures
  → ParserConfigurationSettings.getFeature

http://apache.org/xml/features/xinclude-aware 是解析器使用的 feature 标识符。异常消息中出现这个标识符,不代表程序正在访问该网址,也不表示需要在 Log4j XML 中添加同名配置。

当前对象的配置集合中没有某个 feature,也不足以证明配置损坏。还需要查看父级配置、feature 检查逻辑,以及调用方如何处理 XMLConfigurationException。

堆栈中还有:

com.intellij.rt.debugger.MethodInvoker.invokeInternal
com.intellij.rt.debugger.MethodInvoker.invoke2

这些栈帧说明调试器的方法调用机制参与了当前调用,可以优先检查表达式求值和变量展示。不过,仅凭这些栈帧,还无法确定是哪项调试功能触发了异常。

第一步:确认是否只是异常断点暂停

在 IDEA 中打开 Run → View Breakpoints,检查 Java 异常断点,尤其是:

  • Any Exception;
  • 针对 XMLConfigurationException 的异常断点;
  • 启用了 Caught exception 的宽泛异常断点。

临时禁用相关异常断点,再重新启动调试。也可以先点击 Resume Program,观察应用能否继续启动。

如果应用启动成功,控制台和文件日志也都正常,这次暂停很可能只是让内部处理过程显示了出来。可以删除范围过宽的异常断点,或按需设置为只在 Uncaught exception 时暂停。

禁用异常断点只会改变调试器的暂停行为,不会修复或消除程序实际抛出的异常,所以仍需检查启动结果和日志是否符合预期。

第二步:排除 Watches 和变量求值的影响

如果关闭异常断点后,问题仍然只在调试时出现,可以暂时移除或关闭:

  • Watches 中调用方法的表达式;
  • 断点条件、求值后记录日志等功能中的方法调用;
  • 自定义变量 renderer,以及会调用方法的对象展示功能。

重新启动调试后,先不要展开 XML 解析器相关对象,也不要主动调用它们的方法。观察异常是否还会出现,再逐项恢复这些设置。

调试表达式会执行真实方法。例如,在 Evaluate Expression 中调用:

configuration.getFeature("http://apache.org/xml/features/xinclude-aware")

如果对象不识别这个 feature,求值操作本身就可能抛出异常。这不能直接说明应用在正常执行路径中也会启动失败。

第三步:确认异常在哪里被处理

需要进一步确认时,可以在与实际运行 JDK 匹配的源码中查看:

ParserConfigurationSettings.getFeature(...)
XIncludeHandler.copyFeatures1(...)
XIncludeHandler.copyFeatures(...)

检查抛出异常的位置外围是否有捕获 XMLConfigurationException 的逻辑,以及捕获后会忽略该 feature、采用默认值,还是继续向外抛出。

如果内部代码处理了异常,XML 解析和应用启动也都成功,就有依据将它归为内部 feature 查询过程中出现的异常。如果异常最终向外传播,导致启动失败,则应根据最终异常链和 Log4j 错误输出排查。当前堆栈还无法确定实际走了哪条处理路径。

第四步:若应用确实失败,再比较运行环境

问题只在 Debug 时出现,也可能与启动配置差异有关。逐项比较 Run 和 Debug 使用的:

  • JRE 路径及完整 JDK 版本;
  • VM options、环境变量和工作目录;
  • classpath、Spring profiles 和实际加载的日志配置文件。

当前日志配置使用了 ${env:LOG_FOLDER_PATH} 和 ${env:HOSTNAME},需要确认两种启动方式下的值一致。不过,当前堆栈还不能证明这些变量导致了 xinclude-aware 异常。

可以临时添加以下 VM options,分别查看 JAXP 实现选择和 Log4j 初始化信息:

-Djaxp.debug=1
-Dlog4j2.debug=true

也可以在相同 classpath 和启动环境下运行一个独立探针:

import javax.xml.parsers.DocumentBuilderFactory;

public class XmlFactoryProbe {
    public static void main(String[] args) {
        DocumentBuilderFactory factory =
                DocumentBuilderFactory.newInstance();

        System.err.println("java.home=" +
                System.getProperty("java.home"));
        System.err.println("java.version=" +
                System.getProperty("java.version"));
        System.err.println("factory=" +
                factory.getClass().getName());
        System.err.println("module=" +
                factory.getClass().getModule().getName());
    }
}

比较 Run 和 Debug 的输出,确认实际选择的解析器是否一致。

为什么现有两种尝试未必有效

强制指定解析器未必会改变 feature 查询行为

如果确实需要通过系统属性指定工厂,VM option 应写成一个完整参数,-D 后不要留空格:

-Djavax.xml.parsers.DocumentBuilderFactory=com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl

当前堆栈已经显示,解析代码来自 JDK 的 java.xml 模块。再次指定同一实现,未必会改变 feature 查询行为,也不会改变异常断点的暂停规则。

这个参数可以用于对照诊断,但不宜未经验证就作为长期修复,因为 com.sun.org.apache.xerces.internal.* 属于 JDK 内部实现。

grep xml 无法证明解析器依赖是否冲突

输出中的不少匹配来自 com.fasterxml.jackson 的 fasterxml,并不意味着这些依赖提供了 XML 解析器。存在 JAXB API,也不足以证明 Xerces 冲突。

先查看完整依赖树:

mvn dependency:tree

需要缩小范围时,可以辅助搜索:

mvn dependency:tree | grep -Ei 'xerces|xalan|xml-apis|log4j'

即使发现相关依赖,也需要结合实际加载的工厂实现判断,不能仅凭依赖名称就排除或升级它们。JDK 自带的 java.xml 模块也不会作为普通 Maven 依赖列出。

解决时应注意什么

先关闭宽泛异常断点、清理调试求值,再观察启动结果,有助于区分调试器显示了内部异常,还是调试环境确实改变了程序行为。

确认应用启动失败后,再根据完整异常链处理解析器选择、依赖或日志配置问题。不要为了消除暂停就直接禁用 XInclude、放宽 XML 安全设置,或随意更换解析器。

另外,需要核对 Spring Boot 3.25 的版本写法,以 pom.xml 或有效 POM 中的完整版本号为准。现有信息不足以将问题归因于某个特定版本,也无法断言升级 IDEA 或 JDK 一定能解决。

备注:内容仅供参考。