从IDEA 2018.2.1开始,IDE启动错误提示包“
在模块图中”(来自已模块化的依赖项)。我向项目中添加了module-info.java
文件,并添加了
必要的requires
语句,但是我现在无法访问
我的src/main/resources
目录中的资源文件。
(有关完整示例,请参见this GitHub project。)
当我使用./gradlew run
或./gradlew installDist
+结果时
包装脚本,我能够读取资源文件,但是当我运行我的
不是来自IDE的应用程序。
我提交了issue
使用JetBrains,我了解到IDEA正在使用该模块
路径,而Gradle默认情况下使用的是classpath。通过添加
在我的build.gradle
的以下代码块中,我能够获得Gradle
也会也……无法读取任何资源文件。
run {
inputs.property("moduleName", moduleName)
doFirst {
jvmArgs = [
'--module-path', classpath.asPath,
'--module', "$moduleName/$mainClassName"
]
classpath = files()
}
}
我尝试export
-将我感兴趣的资源目录
“ package”,并且在编译时由于以下原因导致构建失败:
错误:软件包为空或不存在:mydir
尽管降级了,但使用opens
而不是exports
遇到了相同的错误
发出警告。
我什至尝试将mydir
资源目录移到src/main/java
下,
但这会产生相同的错误/警告,并导致
资源没有复制到build
目录中。
Java 9应该在哪里使用资源,我该如何访问它们?
注意:在继续执行以下操作后,我已经对该问题进行了大量修改
研究问题。在最初的问题中,我还试图
找出如何在资源目录中列出文件,但要在
在调查过程中,我确定那是一条红鲱鱼-
首先,因为读取资源目录仅在
正在从file:///
URL中读取资源(可能甚至没有),
第二个原因是普通文件也不起作用,所以很明显
问题是一般的资源文件,而不是专门的
目录。
解决方案:
每Slaw's answer,我将以下内容添加到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()
}
}
旁注::有趣的是,如果我不继续添加Slaw的代码,以使run
任务针对JAR执行,请尝试阅读现在,throws an IOException
运行任务中的资源目录InputStream
而不是提供文件列表。 (相对于JAR,它只是得到一个空的InputStream
。)
答案 0 :(得分:11)
从Gradle's epic开始,有关拼图支持,我了解到一个插件可以简化以下所述的过程:gradle-modules-plugin。该史诗还提到了其他插件,例如chainsaw(这是Experimenting-jigsaw插件的分支)。不幸的是,到目前为止,我还没有尝试过使用它们中的任何一个,因此我无法评论它们如何很好地处理资源。
在您的赏识中,您需要有关使用Gradle和Jigsaw模块处理资源的“正确方法”的官方文档。据我所知,答案是没有“正确的方法”,因为Gradle still (从4.10-rc-2版本开始)没有对Jigsaw模块的一流支持。最接近的是Building Java 9 Modules文档。
但是,您mention是关于从模块内部(即,不是从外部模块)访问资源。通过简单的build.gradle
配置,修复起来并不难。
默认情况下,Gradle为类和资源分隔输出目录。看起来像这样:
build/
|--classes/
|--resources/
使用run
任务时,classpath
是sourceSets.main.runtimeClasspath
的值。该值包括目录,并且由于类路径的工作方式而起作用。您可以将其视为类路径只是一个巨大的模块。
但是,在使用模块路径时,这不起作用,因为从技术上讲,resources
中的文件不属于classes
内的模块。我们需要一种方法来告诉模块系统resources
是模块的一部分。幸运的是,这里有--patch-module
。此选项(引自java --help-extra
):
使用JAR文件或目录中的类和资源覆盖或扩展模块。
并且具有以下格式(我假设;
分隔符取决于平台):
--patch-module <module>=<file>(;<file>)*
要允许您的模块访问其自身的资源,只需配置run
任务,如下所示:
run {
input.property('moduleName', moduleName)
doFirst {
jvmArgs = [
'--module-path', classpath.asPath,
'--patch-module', "$moduleName=" + files(sourceSets.main.output.resourcesDir).asPath,
'--module', "$moduleName/$mainClassName"
]
classpath = files()
}
}
这就是我一直在做的事情,到目前为止效果很好。
但是从Gradle启动应用程序时,如何允许外部模块访问模块中的资源?这会涉及更多。
当您要允许外部模块访问资源时,您的模块必须opens
(请参阅Eng.Fouad's answer)将资源的软件包至少包装到读取模块(这仅适用于encapsulated资源) 。但是,正如您所发现的,这会导致编译警告和运行时错误。
opens
某个模块系统不存在的软件包。
opens
指令的软件包。
--patch-module
选项,也会发生这种情况。我猜想模块系统在应用补丁之前会做一些完整性检查。 注意:“仅资源”是指没有.java
/ .class
文件的软件包。
要解决编译警告,您只需在--patch-module
任务内再次使用compileJava
。这次,您将使用资源的源目录,而不是输出目录。
compileJava {
inputs.property('moduleName', moduleName)
doFirst {
options.compilerArgs = [
'--module-path', classpath.asPath,
'--patch-module', "$moduleName=" + files(sourceSets.main.resources.srcDirs).asPath,
'--module-version', "$version"
]
}
}
对于运行时错误,有两个选项。第一种选择是将资源输出目录与类的输出目录“合并”。
sourceSets {
main.output.resourcesDir = main.java.outputDir
}
jar {
// I've had bad experiences when "merging" output directories
// where the jar task ends up creating duplicate entries in the JAR.
// Use this option to combat that.
duplicateStrategy = DuplicatesStrategy.EXCLUDE
}
第二个选项是配置run
任务以执行JAR文件,而不是展开目录。之所以可行,是因为像第一个选项一样,它将类和资源合并到同一位置,因此资源是模块的一部分。
run {
dependsOn += jar
inputs.property('moduleName', moduleName)
doFirst {
// add JAR file and runtime classpath. The runtime classpath includes the output of the main source set
// so we remove that to avoid having two of the same module on the modulepath
def modulepath = files(jar.archivePath) + (sourceSets.main.runtimeClasspath - sourceSets.main.output)
jvmArgs = [
'--module-path', modulepath.asPath,
'--module', "$moduleName/$mainClassName"
]
classpath = files()
}
}
这两个选项都可以代替在--patch-module
任务中使用run
(在本答案的第一部分中说明)。
作为奖励,这就是我一直在我的模块化JAR中添加--main-class
属性的方法:
jar {
inputs.property('mainClassName', mainClassName)
doLast {
exec {
executable = 'jar'
args = [
'--update', '--file', "$archivePath",
'--main-class', "$mainClassName"
]
}
}
}
这使您可以使用java -m module.name
而不是java -m module.name/some.package.Main
。另外,如果将run
任务配置为执行JAR,则可以更改:
'--module', "$moduleName/$mainClassName"
收件人:
'--module', "$moduleName"
P.S。如果有更好的方法,请告诉我。
答案 1 :(得分:2)
在@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");
}