为何仅在 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 一定能解决。
备注:内容仅供参考。
