![](/img/trans.png)
[英]Gradle project in eclipse builds fine but java files show compilation error
[英]Where do resource files go in a Gradle project that builds a Java 9 module?
从 IDEA 2018.2.1 开始,IDE 从已模块化的依赖项中启动“不在模块图中”的错误突出显示包。 我在我的项目中添加了一个module-info.java
文件并添加了必要的requires
语句,但我现在无法访问src/main/resources
目录中的src/main/resources
文件。
(有关完整示例,请参阅此 GitHub 项目。)
当我使用./gradlew run
或./gradlew installDist
+ 生成的包装器脚本时,我能够读取资源文件,但是当我从 IDE 运行我的应用程序时,我不能。
我向 JetBrains 提交了一个问题,我了解到 IDEA 使用的是模块路径,而 Gradle 默认使用的是类路径。 通过将以下块添加到我的build.gradle
,我能够让 Gradle也......无法读取任何资源文件。
run {
inputs.property("moduleName", moduleName)
doFirst {
jvmArgs = [
'--module-path', classpath.asPath,
'--module', "$moduleName/$mainClassName"
]
classpath = files()
}
}
我尝试export
我感兴趣的资源目录export
为“包”,并且在编译时遇到了构建失败:
错误:包为空或不存在:mydir
使用opens
而不是exports
得到相同的错误,但降级为警告。
我什至尝试移动src/main/java
下的mydir
资源目录,但这产生了相同的错误/警告,并且还导致资源没有被复制到build
目录。
Java 9 中的资源应该放在哪里,我如何访问它们?
注意:在继续研究该问题后,我对该问题进行了大量编辑。 在最初的问题中,我还试图弄清楚如何在资源目录中列出文件,但在调查过程中我确定这是一个红鲱鱼 - 首先,因为读取资源目录仅在资源为从file:///
URL 读取(甚至可能不是),其次是因为普通文件也不起作用,所以很明显问题出在一般的资源文件上,而不是专门针对目录。
解决方案:
根据Slaw 的回答,我在build.gradle
添加了以下build.gradle
:
// at compile time, put resources in same directories as classes
sourceSets {
main.output.resourcesDir = main.java.outputDir
}
// at compile time, include resources in module
compileJava {
inputs.property("moduleName", moduleName)
doFirst {
options.compilerArgs = [
'--module-path', classpath.asPath,
'--patch-module', "$moduleName="
+ files(sourceSets.main.resources.srcDirs).asPath,
'--module-version', "$moduleVersion"
]
classpath = files()
}
}
// at run time, make Gradle use the module path
run {
inputs.property("moduleName", moduleName)
doFirst {
jvmArgs = [
'--module-path', classpath.asPath,
'--module', "$moduleName/$mainClassName"
]
classpath = files()
}
}
旁注:有趣的是,如果我不继续添加使run
任务针对 JAR 执行的 Slaw 代码,尝试读取运行任务中的资源目录InputStream
现在会抛出IOException
而不是提供文件列表。 (针对 JAR,它只是获取一个空的InputStream
。)
更新(2020 年 3 月 25 日):在正确支持 JPMS 方面取得了重大进展。 Gradle 6.4 的夜间构建现在包括使用 Java 9 模块进行本地开发的选项。 请参阅https://github.com/gradle/gradle/issues/890#issuecomment-603289940 。
更新(2020 年 9 月 29 日):自 Gradle 6.4(此更新的当前版本为 6.6.1)以来,您现在可以在 Gradle 项目中原生支持 JPMS 模块,但您必须明确激活此功能:
java {
modularity.inferModulePath.set(true)
}
有关更多信息,请参阅 Gradle 的Java 模块示例,该示例还链接到各种其他相关文档。
不幸的是,从 6.0.1 版本开始,Gradle 仍然没有对 Java 9 模块的一流支持,如构建 Java 9 模块指南所示。
Java 9 最令人兴奋的特性之一是它支持开发和部署模块化 Java 软件。 Gradle 还没有对 Java 9 模块的一流支持。
一些社区插件,如java9-modularity 插件,试图添加支持。 本指南将更新有关如何在开发时使用内置 Gradle 支持的更多信息。
注意:本指南过去更为广泛,并提供了有关如何“手动”自定义现有任务的示例。 但是,它已更改为上述建议使用至少提供一些 Java 9 支持的第三方插件。 其中一些社区插件似乎不仅仅提供模块支持,例如支持使用来自 Gradle 的jlink
工具。
Gradle 项目有一个“史诗”,据称可以跟踪 Java 9 模块支持: JPMS Support #890 。
你找不到你的资源文件的原因是因为Gradle默认输出不同目录下编译的类和处理过的资源。 它看起来像这样:
build/
|--classes/
|--resources/
classes
目录是放置module-info.class
文件的地方。 这会导致模块系统出现问题,因为从技术上讲, resources
目录下的文件不包含在classes
目录中的模块中。 当使用类路径而不是模块路径时,这不是问题,因为模块系统将整个类路径视为一个巨大的模块(即所谓的未命名模块)。
如果您为纯资源包添加opens
指令,您将在运行时收到错误。 错误的原因是由于上述目录布局,模块中不存在该包。 出于基本相同的原因,您在编译时收到警告; 该模块存在于src/main/java
并且src/main/resources
下的资源文件在技术上不包含在该模块中。
注意: “仅资源包”是指包含资源但没有资源具有.java
或.class
扩展名的包。
当然,如果资源仅供模块本身访问,则不需要添加opens
指令。 当资源需要被其他模块访问时,你只需要为包含资源的包添加这样的指令,因为模块中的资源受到封装。
命名模块中的资源可以被封装,以便它不能被其他模块中的代码定位。 一个资源是否可以被定位的判断如下:
最终的解决方案是确保资源被视为模块的一部分。 但是,有几种方法可以做到这一点。
最简单的选择是使用现成的 Gradle 插件,它可以为您处理一切。 Building Java 9 Modules指南给出了一个这样的插件的例子,我认为它是目前最全面的: gradle-modules-plugin 。
plugins {
id("org.javamodularity.moduleplugin") version "..."
}
您还可以查看其他可用的插件。
另一种选择是配置每个需要的 Gradle 任务来指定一些 JVM 选项。 由于您主要关心从模块内部访问资源,因此您需要配置run
任务以使用资源目录修补模块。 这是一个示例(Kotlin DSL):
plugins {
application
}
group = "..."
version = "..."
java {
sourceCompatibility = JavaVersion.VERSION_13
}
application {
mainClassName = "<module-name>/<mainclass-name>"
}
tasks {
compileJava {
doFirst {
options.compilerArgs = listOf(
"--module-path", classpath.asPath,
"--module-version", "${project.version}"
)
classpath = files()
}
}
named<JavaExec>("run") {
doFirst {
val main by sourceSets
jvmArgs = listOf(
"--module-path", classpath.asPath,
"--patch-module", "<module-name>=${main.output.resourcesDir}",
"--module", application.mainClassName
)
classpath = files()
}
}
}
以上使用--patch-module
(参见java
工具文档):
使用 JAR 文件或目录中的类和资源覆盖或扩充模块。
如果您使用上面的示例,它将获得一个简单的 Gradle 项目以在模块路径上运行。 不幸的是,您考虑得越多,情况就会变得越复杂:
测试代码。 你必须决定你的测试代码是在它自己的模块中还是被修补到主代码的模块中(假设你没有在单元测试的类路径上保留所有东西)。
compileTestJava
和test
配置与compileJava
和run
配置大致相同); 然而,这仅允许“黑盒测试”,因为模块系统不允许拆分包(即您只能测试公共 API)。requires
测试依赖项的指令,因此您必须添加适当的--add-modules
和--add-reads
参数。 然后你必须考虑到大多数测试框架都需要反射访问; 由于您不太可能将主模块作为开放模块,因此您还必须添加适当的--add-opens
参数。 包装。 一个模块可以有一个主类,所以你只需要使用--module <module-name>
而不是--module <module-name>/<mainclass-name>
。 这是通过使用jar
工具指定--main-class
选项来完成的。 不幸的是,据我所知,Gradle Jar
任务类没有办法指定这一点。 一种选择是使用doLast
和exec
手动调用jar
工具并--update
JAR 文件。
application
插件还添加了创建启动脚本的任务(例如批处理文件)。 假设您需要这些脚本,则必须将其配置为使用模块路径而不是类路径。
基本上,我强烈建议使用插件。
第三个选项是将处理后的资源配置为与编译后的类具有相同的输出目录。
sourceSets {
main {
output.setResourcesDir(java.outputDir)
}
}
注意:在将资源输出设置为与 Java 输出相同时,可能需要将jar
任务配置为duplicatesStrategy = DuplicatesStrategy.EXCLUDE
。
如果您希望opens
纯资源包,我相信这可能是必需的。 即使使用--patch-module
您也会在运行时由于opens
指令出现错误,因为模块系统似乎在应用--patch-module
之前执行了一些完整性验证。 换句话说,纯资源包不会“很快”存在。 我不确定是否有任何插件处理这个用例。
然而,在编译时, opens
包不存在是允许的,尽管javac
会发出警告。 话虽如此,可以通过在compileJava
任务中使用--patch-module
来消除警告。
tasks.compileJava {
doFirst {
val main by sourceSets
options.compilerArgs = listOf(
"--module-path", classpath.asPath,
"--patch-module", "<module-name>=${main.resources.sourceDirectories.asPath}"
"--module-version", "${project.version}"
)
classpath = files()
}
}
将资源和类合并到同一位置的另一种方法是将run
任务配置为针对jar
任务构建的 JAR 文件执行。
希望 Gradle 很快就能以一流的方式支持 Java 9 模块。 我相信 Maven 在这方面走得更远。
在@Slaw 的回答旁边(感谢他),我不得不打开包含调用者模块资源的包。 如下( moduleone.name module-info.java
):
opens io.fouad.packageone to moduletwo.name;
否则,以下内容将返回null
:
A.class.getResource("/io/fouad/packageone/logging.properties");
考虑到类A
在模块moduletwo.name
,文件logging.properties
在模块moduleone.name
。
或者, moduleone.name
可以公开一个返回资源的实用方法:
public static URL getLoggingConfigFileAsResource()
{
return A.class.getResource("/io/fouad/packageone/logging.properties");
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.