资源文件在构建Java 9模块的Gradle项目中位于何处?

时间:2018-08-15 18:32:29

标签: java gradle embedded-resource java-9 java-module

从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。)

2 个答案:

答案 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任务时,classpathsourceSets.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资源) 。但是,正如您所发现的,这会导致编译警告和运行时错误。

  1. 编译警告是因为您正在尝试opens某个模块系统不存在的软件包。
    • 这是可以预期的,因为默认情况下编译时不包括资源目录(假定仅资源包)。
  2. 运行时错误是因为模块系统找不到您已声明了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");
}