当前位置: 首页 > 面试题库 >

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

穆俊哲
2023-03-14
问题内容

从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()
    }
}

我尝试export-ing我感兴趣的资源目录作为“包”,并且在编译时由于以下原因导致构建失败:

错误:包为空或不存在: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()
  }
}

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


问题答案:

更新(2020年3月25日): 在获得JPMS适当支持方面已经取得了重大进展。每晚构建的Gradle 6.4现在包含使用Java
9模块本地开发的选项。见https://github.com/gradle/gradle/issues/890#issuecomment-603289940。

不幸的是,从6.0.1版开始,Gradle仍然没有对Java 9模块的一流支持,这可以从《构建Java
9模块》指南中看到。

Java 9的最令人兴奋的功能之一是它对开发和部署模块化Java软件的支持。Gradle还没有对Java 9模块的一流支持。

一些社区插件,例如java9-modularity插件,试图增加支持。本指南将在开发时更新有关如何使用内置Gradle支持的更多信息。

注意: 本指南过去更加详尽,并提供了有关如何“手动”自定义现有任务的示例。但是,此后更改为上面的建议,建议使用至少提供某些Java
9支持的第三方插件。这些社区插件中的某些似乎不仅仅提供模块支持,例如对使用jlinkGradle工具的支持。

Gradle项目有一个“史诗级”,据称可以跟踪Java
9模块支持:JPMS支持#890。

问题

找不到资源文件的原因是,默认情况下,Gradle将已编译的类和已处理的资源输出到不同的目录中。看起来像这样:

build/
|--classes/
|--resources/

classes目录是module- info.class文件放置。这会给模块系统带来问题,因为从技术上讲,目录下的resources文件不包含在目录中的模块内classes。当使用类路径而不是模块路径时,这不是问题,因为模块系统将整个类路径视为一个巨型模块(即所谓的
未命名模块 )。

如果opens为仅资源包添加指令,则在运行时会出现错误。错误的原因是由于上述目录布局,软件包在模块中不存在。出于相同的原因,在编译时会收到警告。该模块存在,src/main/java并且该模块下的资源文件在src/main/resources技术上不包括在该模块中。

注意: “仅资源包”是指包含资源但没有资源具有.java.class扩展名的包。

当然,如果仅模块本身可以访问资源,则opens无需添加指令。仅当需要使其他模块可以访问资源时,才需要为包含资源的软件包添加此类指令,因为模块中的资源需要封装。

可以 封装 命名模块中的资源,以使其他模块中的代码无法定位该资源。确定是否可以定位资源如下:

  • 如果资源名称以“ .class” 结尾,则不会被封装。
  • 包名
    是从资源名称的。如果程序包名称是模块中的程序包,则仅当程序包至少对调用方的模块打开时,该方法的调用方才能找到资源。如果资源不在模块的包装中,则不会封装该资源。

最终,解决方案是确保将资源视为模块的一部分。但是,有几种方法可以做到这一点。

使用插件

最简单的选择是使用现成的Gradle插件,该插件可以为您处理所有事情。《 构建Java 9模块》
指南提供了一个这样的插件的示例,我相信它是目前最全面的插件:gradle-modules-plugin。

plugins {
    id("org.javamodularity.moduleplugin") version "..."
}

您还可以查看其他可用的插件。

手动指定适当的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工具文档):

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

如果使用上面的示例,它将获得一个简单的Gradle项目,以在模块路径上运行。不幸的是,您考虑的越多,情况就越复杂:

  • 测试代码。您必须确定测试代码是放在自己的模块中还是被修补到主代码的模块中(假设您没有将所有内容都保留在类路径中以进行单元测试)。

    • 单独的模块:可能更容易配置(大致为相同的结构compileTestJavatest作为用于compileJavarun); 但是,由于模块系统不允许拆分包,因此,这仅允许“黑盒测试”(即,您只能测试公共API)。
    • 修补模块:允许进行“白盒测试”,但较难配置。由于您没有requires用于测试依赖项的任何指令,因此必须添加适当的--add-modules--add-reads参数。然后,您必须考虑到大多数测试框架都需要反射访问。由于您不太可能将主模块作为开放模块,因此还必须添加适当的--add-opens参数。
    • 打包。一个模块可以有一个主类,所以你只需要使用--module <module-name>代替--module <module-name>/<mainclass-name>。这是通过--main-class使用jar工具指定选项来完成的。不幸的是,Jar据我所知,Gradle 任务类没有指定此方法。一种选择是使用doLastexec手动调用该jar工具和--updateJAR文件。
  • application插件还添加了创建启动脚本的任务(例如批处理文件)。假设您需要这些脚本,则必须将其配置为使用模块路径而不是类路径。

基本上,我强烈建议您使用插件。

整合课程和资源

第三种选择是将已处理资源配置为具有与已编译类相同的输出目录。

sourceSets {
    main {
        output.setResourcesDir(java.outputDir)
    }
}

注意: 在将资源输出设置为与Java输出相同的情况下,可能有必要使用配置jar任务duplicatesStrategy = DuplicatesStrategy.EXCLUDE

我相信,如果您希望使用opens仅资源包,则可能需要这样做。即使--patch- module由于opens指令而在运行时也会出错,因为模块系统似乎在应用之前执行了一些完整性验证--patch- module。换句话说,仅资源包不会很快“存在”。我不确定是否有任何插件可以处理此用例。

但是,在编译时,允许一个opens软件包不存在,尽管它javac会发出警告。话虽如此,有可能通过--patch- modulecompileJava任务中使用来消除警告。

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在这方面要走得更远。



 类似资料:
  • 从IDEA 2018.2.1开始,IDE从模块化的依赖项中启动“不在模块图中”的错误突出显示包。我添加了一个文件到我的项目,并添加了必要的语句,但是我现在无法访问我的目录中的资源文件。 (有关完整示例,请参见此GitHub项目。) 当我使用或生成的包装器脚本时,我能够读取资源文件,但是当我从IDE运行我的应用程序时,我不能。 我向JetBrains提交了一个问题,我了解到IDEA正在使用模块路径,

  • 不管怎样,现在这个构建由于这个错误而失败,我不知道如何解决它: 错误:package some.package.impl不存在 导入一些。package.impl.roundaboutimpl; 提前感谢!

  • 资源模块 资源模块指的是图片、样式、html片段等非脚本模块(在 coolie 的世界里,一切皆是模块)。 使用方法: require('style.css', 'css'); 详细阅读点这里。 demo 初始化目录结构 新建coolie-demo6目录: . └── src 1 directory, 0 files 初始化文件 准备一张图片coolie.png,放在 src 目录下。 然后

  • 主要内容:1- 介绍,2- 示例模型,3-创建项目数学库,4- 创建另一个项目:MathWebApp,5- 创建GradleMain项目,6- 构建项目,7- 运行MathWebApp1- 介绍 本教程文章基于: Eclipse Java EE IDE for Web Developers. Version: Mars.1 Release (4.5.1) 如果您是初学者Gradle的。那么建议先看看初学者Gradle(Gradle Hello world)的文章: http://www.yiib

  • 这是用Gradle构建Kotlin+Java9项目的后续。在链接后使用Groovy的Gradle。在我的情况下,使用Kotlin DSL。 基本上我有一个梯度项目与以下结构(只有相关的内容在这里): 问题:如何让链接的post(或任何其他解决方案)中提供的解决方案运行,以便使用Java9/10环境编译带有Gradle的Kotlin DSL的Kotlin项目?

  • 我正在改变我们的项目,以使用gradle作为构建工具,但正在与2个问题斗争;