JDK17中通过Spring Bean调用setResourceResolver触发非法反射访问问题
问题背景
我有一组包含多个<xsd:import>条目的复杂XSD文件,需通过ResourceResolver定位引用的XSD并注入SchemaFactory。简化测试中,直接在Main类调用setResourceResolver在JDK11和17下均正常运行;但通过Spring Bean配置注入时,JDK11出现非法反射访问警告,JDK17直接执行失败。
疑问1:为何直接调用正常,Spring调用会出问题?
二者看似调用同一方法,但执行上下文存在关键差异:
- 直接调用时,代码运行在应用类加载器上下文,调用
setResourceResolver属于直接方法调用,JDK模块系统不会拦截这种显式调用。 - Spring注入Bean时,会通过自身的
BeanWrapper机制反射调用setter方法。而SchemaFactory的默认实现(如com.sun.org.apache.xerces.internal.jaxp.validation.XMLSchemaFactory)属于JDK内部模块,JDK9+的模块系统对内部API的反射访问做了严格限制。Spring的反射调用触发了模块权限检查,导致警告或失败。
疑问2:Spring5.3+宣称支持JDK17,为何仍出现此问题?
Spring5.3的JDK17支持是核心功能层面的兼容,但其代码基线基于JDK8开发,未针对JDK17的模块系统权限收紧做完全适配。对于SchemaFactory这类依赖JDK内部实现的类,Spring的Bean注入机制在反射调用setter时,无法自动处理模块的opens权限,导致JDK17下直接触发访问被阻止的错误。
疑问3:除等待Spring6外,是否有其他解决方案?Spring6能否解决该问题?
替代解决方案
方案1:添加JVM权限参数
通过JVM参数开放JDK内部模块的反射访问权限,适用于JDK11和17:--add-opens java.xml/com.sun.org.apache.xerces.internal.jaxp.validation=ALL-UNNAMED该参数允许未命名模块(你的应用代码)反射访问指定的JDK内部包。
方案2:手动创建SchemaFactory Bean
绕过Spring的自动反射注入,在配置类中手动实例化SchemaFactory并调用setResourceResolver:@Configuration public class XsdConfiguration { @Bean public SchemaFactory schemaFactory(ResourceResolver resourceResolver) { SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); factory.setResourceResolver(resourceResolver); return factory; } }这种方式是主动调用方法而非反射,不会触发模块权限检查。
方案3:使用第三方SchemaFactory实现
替换JDK内置的实现为Apache Xerces的独立版本,摆脱JDK模块系统限制:- 引入Maven依赖:
<dependency> <groupId>xerces</groupId> <artifactId>xercesImpl</artifactId> <version>2.12.2</version> </dependency> - 指定使用Xerces的实现:
SchemaFactory factory = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI, "org.apache.xerces.jaxp.validation.XMLSchemaFactory", Thread.currentThread().getContextClassLoader() );
- 引入Maven依赖:
Spring6的解决情况
Spring6基于JDK17开发,完全适配JDK模块系统,会通过调整Bean注入逻辑或处理模块权限的方式避免这类非法反射问题,升级到Spring6可以彻底解决该问题。
内容的提问来源于stack exchange,提问作者nigelg

