在构建 Java 9 模块的 Gradle 项目中,资源文件在哪里? [英] Where do resource files go in a Gradle project that builds a Java 9 module?

查看:27
本文介绍了在构建 Java 9 模块的 Gradle 项目中,资源文件在哪里?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

从 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:

  • 如果资源名称以.class"结尾那么它就没有被封装.
  • 包名源自资源名称.如果包名称是 则该资源只能由该方法的调用者在包为 打开到至少调用者的模块.如果资源不在模块的包中,则不会封装资源.
  • 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).

  • 单独的模块:可能更容易配置(compileTestJavatestcompileJavarun 的配置大致相同代码>);然而,这仅允许黑盒测试"由于模块系统不允许拆分包(即您只能测试公共 API).
  • 修补模块:允许白盒测试";但更难配置.由于您不会有任何用于测试依赖项的 requires 指令,因此您必须添加适当的 --add-modules--add-reads 参数.然后你必须考虑到大多数测试框架都需要反射访问;由于您不太可能将主模块作为开放模块,因此您还必须添加适当的 --add-opens 参数.
  • Separate module: Probably easier to configure (roughly the same configuration for compileTestJava and test as for compileJava and run); 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 任务类没有办法指定这一点.一种选择是使用doLastexec 手动调用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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆