在构建 Java 9 模块的 Gradle 项目中,资源文件在哪里? [英] 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
目录中的资源文件.
As of IDEA 2018.2.1, the IDE starts error-highlighting packages "not
in the module graph" from dependencies that have been modularized. I added a module-info.java
file to my project and added the
requisite requires
statements, but I'm now having trouble accessing
resource files in my src/main/resources
directory.
(有关完整示例,请参阅此 GitHub 项目.)
(For a complete example, see this GitHub project.)
当我使用 ./gradlew run
或 ./gradlew installDist
+ 结果包装脚本,我能够读取资源文件,但是当我运行我的来自 IDE 的应用程序,我不是.
When I used ./gradlew run
or ./gradlew installDist
+ the resulting
wrapper script, I was able to read resource files, but when I ran my
app from the IDE, I wasn't.
我提交了一个问题使用 JetBrains,我了解到 IDEA 正在使用该模块路径,而 Gradle 默认使用类路径.通过添加将以下块添加到我的 build.gradle
,我能够获得 Gradle也 ...无法读取任何资源文件.
I filed an issue
with JetBrains, and what I learned was that IDEA was using the module
path, while Gradle, by default, was using the classpath. By adding
the following block to my build.gradle
, I was able to get Gradle
to also … not be able to read any resource files.
run {
inputs.property("moduleName", moduleName)
doFirst {
jvmArgs = [
'--module-path', classpath.asPath,
'--module', "$moduleName/$mainClassName"
]
classpath = files()
}
}
我尝试了 export
- 将我感兴趣的资源目录作为包",并在编译时遇到构建失败:
I tried export
-ing the resource directory I was interested in as a
"package", and at compile time got a build failure with:
错误:包为空或不存在:mydir
error: package is empty or does not exist: mydir
使用 opens
而不是 exports
得到了同样的错误,虽然降级了到警告.
Using opens
instead of exports
got the same error, though downgraded
to a warning.
我什至尝试移动 src/main/java
下的 mydir
资源目录,但这产生了相同的错误/警告,并导致资源没有被复制到 build
目录.
I even tried moving the mydir
resource directory under src/main/java
,
but this produced the same errors / warnings, and also resulted in the
resources not being copied to the build
directory.
在 Java 9 中资源应该放在哪里,我如何访问它们?
Where are resources supposed to go in Java 9, and how do I access them?
注意:我在继续之后对这个问题进行了大量编辑研究问题.在最初的问题中,我也试图弄清楚如何列出资源目录中的文件,但在在调查过程中,我确定那是一条红鲱鱼——首先,因为读取资源目录仅在正在从 file:///
URL 读取资源(甚至可能不会),其次是因为普通文件也不起作用,所以很明显问题出在一般的资源文件上,而不是专门针对目录.
Note: I've edited this question considerably after continuing to
research the problem. In the initial question, I was also trying to
figure out how to list the files in a resource directory, but in the
course of the investigation I determined that was a red herring --
first, because reading resource directories only works when the
resource is being read from a file:///
URL (and maybe not even then),
and second because plain files weren't working either, so clearly the
problem was with resource files in general and not specifically with
directories.
解决方案:
根据 Slaw 的回答,我在 build.gradle
中添加了以下内容:
Per Slaw's answer, I added the following to 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
.)
Side note: Interestingly, if I don't continue on to add Slaw's code that makes the run
task execute against the JAR, attempting to read a resource directory InputStream
in the run task now throws an IOException
instead of providing the list of files. (Against the JAR, it simply gets an empty InputStream
.)
推荐答案
更新(2020 年 3 月 25 日):在正确支持 JPMS 方面取得了重大进展.Gradle 6.4 的每晚构建现在包括使用 Java 9 模块进行本地开发的选项.请参阅 https://github.com/gradle/gradle/issues/890#issuecomment-603289940 .
Update (25 March 2020): There has been significant progress towards proper JPMS support. A nightly build of Gradle 6.4 now includes options to develop with Java 9 modules natively. See https://github.com/gradle/gradle/issues/890#issuecomment-603289940 .
更新(2020 年 9 月 29 日):自 Gradle 6.4(本次更新的当前版本为 6.6.1)以来,您现在可以在 Gradle 项目中原生支持 JPMS 模块,但您必须明确激活此功能:
Update (29 September 2020): Since Gradle 6.4 (current release as of this update is 6.6.1) you can now support JPMS modules natively in Gradle projects, but you have to explicitly activate this feature:
java {
modularity.inferModulePath.set(true)
}
请参阅 Gradle 的 Java 模块示例,其中还包括各种其他相关文档的链接,以获取更多信息.
See Gradle's Java Modules Sample, which also links to various other pertinent documentation, for more information.
不幸的是,从 6.0.1 版本开始,Gradle 仍然没有对 Java 9 模块的一流支持,从 构建 Java 9 模块 指南.
Unfortunately, Gradle still—as of version 6.0.1—does not have first class support for Java 9 modules, as can be seen by the Building Java 9 Modules guide.
Java 9 最令人兴奋的特性之一是它支持开发和部署模块化 Java 软件.Gradle 还没有对 Java 9 模块的一流支持.
One of the most exciting features of Java 9 is its support for developing and deploying modular Java software. Gradle doesn’t have first-class support for Java 9 modules yet.
一些社区插件,例如java9-modularity 插件,尝试添加支持.本指南将更新有关如何在开发时使用内置 Gradle 支持的更多信息.
Some community plugins, like java9-modularity plugin, attempt to add support. This guide will be updated with more information on how to use built-in Gradle support when it is developed.
注意:本指南过去更为广泛,并提供了有关如何手动"自定义现有任务的示例.但是,它已更改为上述建议使用至少提供一些 Java 9 支持的第三方插件.其中一些社区插件似乎不仅仅提供模块支持,例如支持使用 Gradle 的 jlink
工具.
Note: This guide used to be more extensive and offered examples on how to customize existing tasks "manually". However, it has since changed to the above which recommends using third-party plugins that offer at least some Java 9 support. Some of these community plugins appear to offer more than just module support, such as support for using the jlink
tool from Gradle.
Gradle 项目有一个史诗"据称跟踪 Java 9 模块支持:JPMS Support #890.
The Gradle project has an "epic" which supposedly tracks Java 9 module support: JPMS Support #890.
你找不到你的资源文件的原因是因为Gradle默认输出不同目录下编译的类和处理过的资源.它看起来像这样:
The reason you can't find your resource files is because Gradle, by default, outputs the compiled classes and processed resources in different directories. It looks something like this:
build/
|--classes/
|--resources/
classes
目录是放置 module-info.class
文件的地方.这会导致模块系统出现问题,因为从技术上讲,resources
目录下的文件不包含在 classes
目录中的模块中.当使用类路径而不是模块路径时,这不是问题,因为模块系统将整个类路径视为一个巨大的模块(即所谓的未命名模块).
The classes
directory is where the module-info.class
file is placed. This causes a problem for the module system since technically the files under the resources
directory are not included within the module present in the classes
directory. This isn't a problem when using the classpath instead of the modulepath since the module system treats the whole classpath as one giant module (i.e. the so-called unnamed module).
如果您为纯资源包添加 opens
指令,您将在运行时收到错误.错误的原因是由于上述目录布局,模块中不存在该包.出于基本相同的原因,您在编译时收到警告;该模块存在于 src/main/java
中,并且 src/main/resources
下的资源文件在技术上不包含在该模块中.
If you add an opens
directive for a resource-only package you'll get an error at runtime. The cause of the error being that the package does not exist in the module because of the aforementioned directory layout. You get a warning at compile-time for basically the same reason; the module is present in src/main/java
and the resource files under src/main/resources
are not technically included in that module.
注意:通过仅资源包"我的意思是包含资源但没有资源具有 .java
或 .class
扩展名的包.
Note: By "resource-only package" I mean packages which contain resources but none of the resources have a .java
or .class
extension.
当然,如果资源仅供模块本身访问,则不需要添加 opens
指令.当其他模块需要访问资源时,您只需要为包含资源的包添加此类指令,因为模块中的资源受 封装.
Of course, if the resources are only to be accessible to the module itself then adding the opens
directive should not be necessary. You only need to add such directives for resource-containing packages when resources need to be accessible to other modules because resources in modules are subject to encapsulation.
命名模块中的资源可能被封装,因此它不能被其他模块中的代码定位.一个资源是否可以被定位如下:
A resource in a named module may be encapsulated so that it cannot be located by code in other modules. Whether a resource can be located or not is determined as follows:
- If the resource name ends with "
.class
" then it is not encapsulated. - A package name is derived from the resource name. If the package name is a package in the module then the resource can only be located by the caller of this method when the package is open to at least the caller's module. If the resource is not in a package in the module then the resource is not encapsulated.
解决方案
最终的解决方案是确保资源被视为模块的一部分.但是,有几种方法可以做到这一点.
Solution
Ultimately the solution is to ensure the resources are considered part of the module. However, there are a few ways in which to do that.
最简单的选择是使用现成的 Gradle 插件,它可以为您处理一切.Building Java 9 Modules 指南给出了一个这样的插件的例子,我认为它是目前最全面的:gradle-modules-plugin.
The easiest option is to use a ready-made Gradle plugin which handles everything for you. The Building Java 9 Modules guide gives an example of one such plugin, which I believe is currently the most comprehensive: gradle-modules-plugin.
plugins {
id("org.javamodularity.moduleplugin") version "..."
}
您还可以查看其他可用插件.
另一个选项是配置每个需要的 Gradle 任务来指定一些 JVM 选项.由于您主要关心从模块内部访问资源,因此您需要配置 run
任务以使用资源目录修补模块.这是一个示例(Kotlin DSL):
Another option is to configure each needed Gradle task to specify some JVM options. Since you're primarily concerned with accessing resources from within the module you need to configure the run
task to patch the module with the resources directory. Here's an example (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
工具文档):
The above uses --patch-module
(see java
tool documentation):
使用 JAR 文件或目录中的类和资源覆盖或扩充模块.
Overrides or augments a module with classes and resources in JAR files or directories.
如果你使用上面的例子,它会得到一个简单的 Gradle 项目在模块路径上运行.不幸的是,您考虑得越多,情况就会变得越复杂:
If you use the example above it will get a simple Gradle project to run on the module path. Unfortunately, this gets much more complicated the more you consider:
测试代码.您必须决定您的测试代码是在它自己的模块中还是被修补到主代码的模块中(假设您没有将所有内容都保留在类路径中以进行单元测试).
Test code. You have to decide if your test code will be in its own module or be patched into the main code's module (assuming you don't keep everything on the classpath for unit testing).
- 单独的模块:可能更容易配置(
compileTestJava
和test
与compileJava
和run
的配置大致相同代码>);然而,这仅允许黑盒测试"由于模块系统不允许拆分包(即您只能测试公共 API). - 修补模块:允许白盒测试";但更难配置.由于您不会有任何用于测试依赖项的
requires
指令,因此您必须添加适当的--add-modules
和--add-reads代码> 参数.然后你必须考虑到大多数测试框架都需要反射访问;由于您不太可能将主模块作为开放模块,因此您还必须添加适当的
--add-opens
参数.
- Separate module: Probably easier to configure (roughly the same configuration for
compileTestJava
andtest
as forcompileJava
andrun
); however, this only allows for "blackbox testing" due to the fact split packages are not allowed by the module system (i.e. you can only test public API). - Patched module: Allows for "whitebox testing" but is harder to configure. Since you won't have any
requires
directives for test dependencies you'll have to add the appropriate--add-modules
and--add-reads
arguments. Then you have to take into account that most testing frameworks require reflective access; since you're unlikely to have your main module as an open module you'll have to add appropriate--add-opens
arguments as well.
包装.一个模块可以有一个主类,所以你只需要使用 --module
而不是 --module
jar
工具指定 --main-class
选项来完成的.不幸的是,据我所知,Gradle Jar
任务类没有办法指定这一点.一种选择是使用doLast
和exec
手动调用jar
工具和--update
JAR 文件.
Packaging. A module can have a main class so you only have to use --module <module-name>
instead of --module <module-name>/<mainclass-name>
. This is done by specifying the --main-class
option with the jar
tool. Unfortunately, the Gradle Jar
task class does not have a way to specify this, as far as I can tell. One option is to use doLast
and exec
to manually call the jar
tool and --update
the JAR file.
application
插件还添加了创建启动脚本的任务(例如批处理文件).假设您需要这些脚本,则必须将其配置为使用模块路径而不是类路径.
The application
plugin also adds tasks to create start scripts (e.g. batch file). This will have to be configured to use the modulepath instead of the classpath, assuming you need these scripts.
基本上,我强烈建议使用插件.
Basically, I highly recommend using a plugin.
第三个选项是将处理后的资源配置为与编译后的类具有相同的输出目录.
A third option is to configure the processed resources to have the same output directory as the compiled classes.
sourceSets {
main {
output.setResourcesDir(java.outputDir)
}
}
注意:在设置duplicatesStrategy = DuplicatesStrategy.EXCLUDE
时可能需要配置jar
任务资源输出与 Java 输出相同.
Note: It may be necessary to configure the jar
task with duplicatesStrategy = DuplicatesStrategy.EXCLUDE
when setting the resources output the same as the Java output.
如果您希望打开
纯资源包,我相信这可能是必需的.即使使用 --patch-module
你也会因为 opens
指令在运行时得到错误,因为模块系统似乎在应用 之前执行了一些完整性验证 --补丁模块
.换句话说,仅资源包将不会足够快"存在.我不确定是否有任何插件处理这个用例.
I believe this may be required if you expect to opens
resource-only packages. Even with --patch-module
you'll get an error at runtime due to the opens
directive since the module system appears to perform some integrity validation before applying --patch-module
. In other words, the resource-only package won't exist "soon enough". I'm not sure if any plugin handles this use case.
然而,在编译时,允许 opens
包不存在,尽管 javac
会发出警告.话虽如此,可以通过在 compileJava
任务中使用 --patch-module
来消除警告.
At compile-time, however, it's permissible for an opens
package to not exist, though javac
will emit a warning. That being said, it's possible to get rid of the warning by using --patch-module
in the compileJava
task.
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 文件执行.
Another way to consolidate the resources and classes into the same place is to configure the run
task to execute against the JAR file built by the jar
task.
希望 Gradle 很快就能以一流的方式支持 Java 9 模块.我相信 Maven 在这方面走得更远.
Hopefully Gradle will support Java 9 modules in a first-class manner some time soon. I believe Maven is further along in this respect.
这篇关于在构建 Java 9 模块的 Gradle 项目中,资源文件在哪里?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!