Scala 2.13项目转Maven后Java HttpClient需--add-exports参数的原因
Scala 2.13.8项目从SBT转Maven后java.net.http运行时模块导出错误分析
问题背景
我们维护一个Scala 2.13.8项目,基于java.net.http.HttpClient实现REST请求调用。此前用SBT构建时完全正常,甚至无需指定JDK11即可运行,IntelliJ直接启动也没有问题。但因SBT处理Log4j2存在问题,切换到Maven后出现运行时错误:
cannot access class jdk.internal.net.http.HttpRequestBuilderImpl (in module java.net.http) because module java.net.http does not export jdk.internal.net.http to unnamed module
目前我们通过构建胖Jar,并在启动命令中手动添加--add-exports java.net.http/jdk.internal.net.http=ALL-UNNAMED参数临时解决了问题,当前使用的scala-maven-plugin配置如下:
<plugin> <groupId>net.alchim31.maven</groupId> <artifactId>scala-maven-plugin</artifactId> <version>4.7.1</version> <configuration> <scalaVersion>2.13.8</scalaVersion> <args> <arg>-language:higherKinds</arg> <arg>-explaintypes</arg> <arg>-deprecation</arg> <!-- optimizations --> <arg>-optimize</arg> <arg>-opt:unreachable-code</arg> <arg>-opt:box-unbox</arg> <arg>-opt:l:inline</arg> <arg>-opt-inline-from:**</arg> <arg>-target:11</arg> </args> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>testCompile</goal> </goals> </execution> </executions> </plugin>
为什么SBT和IntelliJ运行正常?
核心差异在于JVM参数的自动传递和JPMS模块兼容处理:
- SBT的自动参数适配:SBT的
run任务会自动检测Scala项目在JDK9+上的JPMS兼容性问题,默认添加必要的--add-exports或--add-opens参数,允许unnamed module访问JDK内部模块的类。而Maven的默认运行目标不会自动做这个处理。 - IntelliJ的运行配置优化:IntelliJ在运行Scala项目时,会扫描代码依赖的JDK内部类引用,自动在运行配置中注入对应的
--add-exports参数,因此直接启动不会报错。 - 类加载与模块路径的差异:SBT默认将项目类和依赖放在类路径(而非模块路径)下运行,同时自动补充JPMS兼容参数;而Maven如果直接运行编译后的类或未配置参数的胖Jar,会以纯JPMS模式加载,触发内部类访问限制。
无需手动加参数的Maven修复方案
可以通过在Maven插件中配置默认JVM参数,避免每次启动都手动指定:
方案1:修改scala-maven-plugin添加运行时参数
在插件配置中增加<jvmArgs>节点,将--add-exports参数固化:
<plugin> <groupId>net.alchim31.maven</groupId> <artifactId>scala-maven-plugin</artifactId> <version>4.7.1</version> <configuration> <scalaVersion>2.13.8</scalaVersion> <args> <!-- 保留原有的编译参数 --> <arg>-language:higherKinds</arg> <arg>-explaintypes</arg> <arg>-deprecation</arg> <arg>-optimize</arg> <arg>-opt:unreachable-code</arg> <arg>-opt:box-unbox</arg> <arg>-opt:l:inline</arg> <arg>-opt-inline-from:**</arg> <arg>-target:11</arg> </args> <!-- 添加运行时JVM参数 --> <jvmArgs> <jvmArg>--add-exports java.net.http/jdk.internal.net.http=ALL-UNNAMED</jvmArg> </jvmArgs> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>testCompile</goal> <!-- 增加run目标,直接通过scala:run启动 --> <goal>run</goal> </goals> </execution> </executions> </plugin>
方案2:用exec-maven-plugin配置启动参数
如果习惯用exec:java启动,可配置该插件并注入JVM参数:
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <goals> <goal>java</goal> </goals> </execution> </executions> <configuration> <mainClass>your.package.MainClass</mainClass> <jvmArgs> <jvmArg>--add-exports java.net.http/jdk.internal.net.http=ALL-UNNAMED</jvmArg> </jvmArgs> </configuration> </plugin>
方案3:检查依赖库的间接引用
如果不是你的代码直接访问JDK内部类,而是依赖的第三方库(比如某些Scala HTTP封装库)做了反射或间接调用,可以排查依赖树,看是否有库需要适配JPMS,或者替换为更兼容的版本。
内容的提问来源于stack exchange,提问作者gifa
相关产品推荐
相关产品推荐

