资源文件放在构建Java 9模块的Gradle项目中的哪个位置?

Dav*_*les 15 java embedded-resource gradle java-9 java-module

从IDEA 2018.2.1开始,IDE从模块化的依赖项开始错误突出显示"不在模块图"中的包.我module-info.java在项目中添加了一个文件并添加了必需的requires语句,但我现在无法访问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()
    }
}
Run Code Online (Sandbox Code Playgroud)

我尝试export将我感兴趣的资源目录作为"包",并在编译时遇到构建失败:

错误:包是空的或不存在:mydir

使用opens而不是exports获得相同的错误,但降级为警告.

我甚至尝试移动mydir资源目录src/main/java,但是这产生了相同的错误/警告,并且还导致资源没有被复制到build目录中.

应该在Java 9中使用哪些资源,以及如何访问它们?


注意:在继续研究问题之后,我已经相当大地编辑了这个问题.在最初的问题中,我还试图弄清楚如何在资源目录中列出文件,但在调查过程中我确定这是一个红色的鲱鱼 - 首先,因为阅读资源目录只有在资源是从file:///URL 读取(也许甚至不是),第二,因为普通文件也没有工作,所以很明显问题是资源文件一般而不是专门用于目录.


解:

按照Slaw的回答,我将以下内容添加到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 Code Online (Sandbox Code Playgroud)

旁注:有趣的是,如果我继续添加Slaw的代码使得run任务针对JAR执行,则尝试读取InputStream运行任务中的资源目录现在会抛出IOException而不是提供文件列表.(对JAR来说,它只是一个空洞InputStream.)

Sla*_*law 16

Gradle关于Jigsaw支持的史诗中我已经了解了一个可以简化下面描述的过程的插件:gradle-modules-plugin.史诗还提到了其他插件,如电锯(这是实验拼图插件的一个分支).不幸的是,我还没有尝试过任何一个,所以我不能评论他们如何处理资源,如果有的话.


在您的赏金中,您需要有关使用Gradle和Jigsaw模块处理资源的"正确方法"的官方文档.据我所知,答案是没有"正确的方法",因为Gradle 仍然(从4.10-rc-2开始)没有对Jigsaw模块的一流支持.最接近的是Building Java 9 Modules文档.

但是,您提到这是关于从模块内访问资源(即不是从外部模块).这不应该太难以通过简单的jlink配置来修复.

默认情况下,Gradle将类和资源的输出目录分开.它看起来像这样:

build/
|--classes/
|--resources/
Run Code Online (Sandbox Code Playgroud)

使用classes任务时module-info.class,值是resources.此值包括两个目录,这是因为类路径的工作方式.您可以将其视为类路径只是一个巨大的模块.

但是,这在使用模块路径时不起作用,因为从技术上讲,里面的文件classes不属于内部的模块opens.我们需要一种方法来告诉模块系统src/main/java是模块的一部分.幸运的是,有src/main/resources.此选项将(引自.java):

使用JAR文件或目录中的类和资源覆盖或扩充模块.

并具有以下格式(我假设.class分隔符取决于平台):

plugins {
    id("org.javamodularity.moduleplugin") version "..."
}
Run Code Online (Sandbox Code Playgroud)

要允许您的模块访问它自己的资源,只需配置您的opens任务,如下所示:

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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是我一直在做的事情,到目前为止它已经很好了.


但是,当从Gradle启动应用程序时,如何允许外部模块从模块访问资源?这需要更多参与.

当您希望允许外部模块访问资源时,您的模块必须.class(请参阅Eng.Fouad的答案)将资源的包至少读取模块(这仅适用于封装的资源).但是,正如您所发现的,这会导致编译警告和运行时错误.

  1. 编译警告是因为您正在尝试run根据模块系统不存在的包.
    • 这是预期的,因为默认情况下编译时不包括资源目录(假设仅资源包).
  2. 运行时错误是因为模块系统找不到您声明了--patch-module指令的包.
    • 再次,假设只有资源包.
    • 即使使用上述java选项,也会发生这种情况.我想模块系统在应用补丁之前会进行一些完整性检查.

注意:"仅限资源"是指没有compileTestJava/ testfiles的软件包.

要修复编译警告,您只需compileJavarun任务中再次使用.这次你将使用资源的源目录而不是输出目录.

sourceSets {
    main {
        output.resourcesDir = java.outputDir
    }
}
Run Code Online (Sandbox Code Playgroud)

对于运行时错误,有几个选项.第一个选项是将资源输出目录与类的输出目录"合并".

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 Code Online (Sandbox Code Playgroud)

第二个选项是将requires任务配置为执行JAR文件而不是展开的目录(ies).这是有效的,因为像第一个选项一样,它将类和资源组合到同一个地方,因此资源是模块的一部分.

build/
|--classes/
|--resources/
Run Code Online (Sandbox Code Playgroud)

可以使用这两个选项代替--add-modules--add-reads任务中使用(在本答案的第一部分中进行了解释).


作为奖励,这就是我将--add-opens模块化JAR 添加到我的模块中的方法:

plugins {
    id("org.javamodularity.moduleplugin") version "..."
}
Run Code Online (Sandbox Code Playgroud)

这允许你使用--module <module-name>而不是--module <module-name>/<mainclass-name>.此外,如果--main-class任务配置为执行JAR,您可以更改:

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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

至:

sourceSets {
    main {
        output.resourcesDir = java.outputDir
    }
}
Run Code Online (Sandbox Code Playgroud)

PS如果有更好的方法,请告诉我.