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

标签 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”,在编译时由于以下原因导致构建失败:

error: package is empty or does not exist: 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 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 Modules Sample,该链接还链接到其他各种相关文档。

Gradle和Java 9模块支持
不幸的是,从Building Java 9 Modules指南中可以看出,Gradle仍然(从6.0.1版本开始)对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.

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工具的支持。
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指令。仅当需要使其他模块可以访问资源时,才需要为包含资源的软件包添加此类指令,因为模块中的资源受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.



最终,解决方案是确保将资源视为模块的一部分。但是,有几种方法可以做到这一点。
使用插件
最简单的选择是使用现成的Gradle插件,该插件可以为您处理所有事情。 《构建Java 9模块》指南提供了一个这样的插件的示例,我相信它是目前最全面的插件:gradle-modules-plugin
plugins {
    id("org.javamodularity.moduleplugin") version "..."
}
您也可以 checkout other available plugins
手动指定适当的JVM选项
另一个选项是配置每个需要的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 tool documentation):

Overrides or augments a module with classes and resources in JAR files or directories.


如果使用上面的示例,它将获得一个简单的Gradle项目,以在模块路径上运行。不幸的是,您考虑的越多,情况就越复杂:
  • 测试代码。您必须确定测试代码是放在自己的模块中还是被修补到主代码的模块中(假设您没有将所有内容都保留在类路径中以进行单元测试)。
  • 单独的模块:可能易于配置(compileTestJavatest的配置与compileJavarun的配置大致相同);但是,由于模块系统不允许拆分包,因此,这仅允许进行“黑箱测试”(即,您只能测试公共(public)API)。
  • 修补模块:允许“白盒测试”,但较难配置。由于您没有用于测试依赖项的requires指令,因此必须添加适当的--add-modules--add-reads参数。然后,您必须考虑到大多数测试框架都需要反射访问。由于您不太可能将主模块作为开放模块,因此还必须添加适当的--add-opens参数。

  • 包装。模块可以有一个主类,因此您只需要使用--module <module-name>而不是--module <module-name>/<mainclass-name>即可。这是通过使用--main-class工具指定jar选项来完成的。不幸的是,据我所知,Gradle Jar任务类没有指定此方法。一种选择是使用doLastexec手动调用jar工具,并使用--update JAR文件。
  • application插件还添加了创建启动脚本的任务(例如批处理文件)。假设您需要这些脚本,则必须将其配置为使用模块路径而不是类路径。

  • 基本上,我强烈建议您使用插件。
    整合类(class)和资源
    第三种选择是将已处理资源配置为具有与已编译类相同的输出目录。
    sourceSets {
        main {
            output.setResourcesDir(java.outputDir)
        }
    }
    
    注意:在将资源输出设置为与Java输出相同时,可能需要使用jar配置duplicatesStrategy = DuplicatesStrategy.EXCLUDE任务。
    我相信,如果您希望使用opens仅资源包,则可能需要这样做。即使使用--patch-module,由于opens指令,在运行时也会出现错误,因为模块系统似乎在应用--patch-module之前执行了一些完整性验证。换句话说,仅资源包不会很快“存在”。我不确定是否有任何插件可以处理此用例。
    但是,在编译时,opens包不存在是允许的,尽管javac将发出警告。话虽如此,通过在--patch-module任务中使用compileJava可以消除警告。
    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在这方面会走得更远。

    关于java - 资源文件在构建Java 9模块的Gradle项目中位于何处?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51864473/

    相关文章:

    java - 通过 intent linkedin 共享因权限被拒绝而崩溃

    java - 寻找一种工具来查看/管理 JPA 创建的数据库

    java - 如何确定cucumber运行时的运行环境类型?

    gradle - 如何在 gradle 的应用程序默认 jvm 参数中获取 $ 未转义?

    javascript - 即使使用 document.getElementById ('xyz' ).playVideo() 也无法控制 Youtube 嵌入 - 不是函数?

    java - Jsch错误返回码不一致

    java - 将页面中的特定内容捕获为图像文件

    android - 在 Android Studio 中引用其他模块中的 .apk 类

    Java getClass().getResource ("file") 导致 NullPointerException

    c++ - 如何在 Visual C++ 2012 中读取用户定义的资源?