Gradle多模块项目无法编译,因为找不到javafx.stage.Stage的类文件

移动开发 2026-07-09

我正在搭建一个Gradle 9.5.0的多模块项目,使用Java 25。

在这个项目中,我使用Spring Boot 4.0.5和 JavaFX 26.0.1。我已经设置了 spring.main.web-application-type: none,因为我正在构建一个桌面应用,GUI使用JavaFX。

settings.gradle.kts

rootProject.name = "media-dashboard"
startParameter.excludedTaskNames += "bootJar"
startParameter.excludedTaskNames += "bootStartScripts"
startParameter.excludedTaskNames += "bootDistTar"
startParameter.excludedTaskNames += "bootDistZip"

module("application")
module("desktop")

fun module(name: String, directory: String = name, buildFileName: String = "$name.gradle.kts") {
    val moduleDir = File(settingsDir, directory)
    val buildFile = File(moduleDir, buildFileName)

    when {
        !moduleDir.isDirectory -> throw IllegalArgumentException("$moduleDir is not a directory!")
        !buildFile.isFile -> throw IllegalArgumentException("$buildFile is not a file!")
    }

    include(name)
    project(":$name").projectDir = moduleDir
    project(":$name").buildFileName = buildFile.name
}

我创建了一个带有 buildScr 模块的Gradle根项目,使其成为一个Composite Build。

buildSrc/build.gradle.kts

plugins {
    `kotlin-dsl`
}

repositories {
    gradlePluginPortal()
}

buildSrc/src/main/kotlin/java-common-conventions.gradle.kts

plugins {
    `java-library`
}

repositories {
    mavenCentral()
}

dependencies {
    testImplementation(platform("org.junit:junit-bom:6.0.0"))
    testImplementation("org.junit.jupiter:junit-jupiter")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(25)
    }
}

tasks.compileJava {
    options.encoding = "UTF-8"
}

tasks.jar {
    archiveFileName = "${rootProject.name}-${project.name}-${project.version}.jar"
}

tasks.withType<Test> {
    useJUnitPlatform()
}

我还有两个其他的Gradle子项目:applicationdesktop,每个都包含一个Java模块。这将让我把应用逻辑(后端代码)与GUI逻辑(前端代码)分离。

application/application.gradle.kts

plugins {
    application
    id("java-common-conventions")
    id("org.springframework.boot") version "4.0.5"
    id("io.spring.dependency-management") version "1.1.7"
    id("org.openjfx.javafxplugin") version "0.1.0"
}

group = "be.mnt.mediadashboard"
version = "0.0.1"

application {
    mainModule.set("be.mnt.mediadashboard.application")
    mainClass.set("be.mnt.mediadashboard.application.SpringApplication")
    applicationDefaultJvmArgs = listOf("--enable-native-access=javafx.graphics")
}

javafx {
    version = "26.0.1"
    modules = listOf("javafx.graphics") // Change to "javafx.controls" for working example.
}

dependencies {
    implementation(project(":desktop"))
    implementation("org.springframework.boot:spring-boot-starter")
}

application/src/main/java/module-info.java

open module be.mnt.mediadashboard.application {

    requires be.mnt.mediadashboard.desktop;

    requires javafx.graphics; // Change to "javafx.controls" for working example.

    requires spring.boot;
    requires spring.boot.autoconfigure;
    requires spring.context;
    requires spring.core;

    requires org.apache.commons.logging;
    requires org.slf4j;
    requires org.yaml.snakeyaml;
}

desktop/desktop.gradle.kts

plugins {
    `java-library`
    id("java-common-conventions")
    id("org.openjfx.javafxplugin") version "0.1.0"
}

group = "be.mnt.mediadashboard"
version = "0.0.1"

javafx {
    version = "26.0.1"
    modules = listOf("javafx.controls", "javafx.fxml")
}

dependencies {

}

desktop/src/main/java/module-info.java

open module be.mnt.mediadashboard.desktop {
    requires javafx.controls;
    requires javafx.fxml;

    exports be.mnt.mediadashboard.desktop;
}

为了访问FXML资源,desktop 的module-info声明中必须使用 open 修饰符来访问 desktop/src/main/resources/fxml/dashboard.fxml

application 模块中,我使用自动配置来搭建Spring

application/src/main/java/be/mnt/mediadashboard/application/SpringApplication.java

@SpringBootApplication
public class SpringApplication {

    static void main(String[] args) {
        Application.launch(JavaFXApplication.class, args);
    }

}

并扩展 javafx.application.Application 来设置JavaFX应用。

application/src/main/java/be/mnt/mediadashboard/application/JavaFXApplication.java

import be.mnt.mediadashboard.desktop.DashboardStage;
import javafx.application.Application;
import javafx.stage.Stage;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

public class JavaFXApplication extends Application {

    private DashboardStage dashboardStage;

    private ConfigurableApplicationContext applicationContext;

    @Override
    public void init() {
        applicationContext = new SpringApplicationBuilder(SpringApplication.class).run();
    }

    @Override
    public void stop() {
        applicationContext.close();
        dashboardStage.close();
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        System.out.println("dashboardStage: " + primaryStage);
        this.dashboardStage = new DashboardStage(primaryStage); // <-- can't compile!?
        this.dashboardStage.show();
    }
}

desktop 模块中,我创建了一个 DashboardStage.java 类来设置JavaFX的主舞台,该舞台由启动JavaFX线程的JavaFX应用类调用。

现在当我尝试构建上述内容时,在对上述 application 模块的代码进行编译时会出现错误:

> Task :application:compileJava FAILED
C:\Dev\Workspace\mnt-media-dashboard\application\src\main\java\be\mnt\mediadashboard\application\JavaFXApplication.java:29: error: cannot access Stage
        this.dashboardStage = new DashboardStage(primaryStage);
                              ^
  class file for javafx.stage.Stage not found
1 error

从Gradle构建的调试日志中你可以看到 JavaFX Graphics 位于模块路径上(我把 --module-path略写了)。

2026-05-12T11:16:30.232+0200 [INFO] [org.gradle.jvm.toolchain.internal.DefaultToolchainJavaCompiler] Compiling with toolchain 'C:\Program Files\Java\jdk-25.0.2'.
2026-05-12T11:16:30.235+0200 [DEBUG] [org.gradle.api.internal.tasks.compile.NormalizingJavaCompiler] Compiler arguments: -source 25 -target 25 -d C:\Dev\Workspace\mnt-media-dashboard\application\build\classes\java\main -encoding UTF-8 -h C:\Dev\Workspace\mnt-media-dashboard\application\build\generated\sources\headers\java\main -g -sourcepath C:\Dev\Workspace\mnt-media-dashboard\application\src\main\java -proc:none -s C:\Dev\Workspace\mnt-media-dashboard\application\build\generated\sources\annotationProcessor\java\main -XDuseUnsharedTable=true -classpath C:\Users\K\.gradle\caches\modules-2\files-2.1\org.osgi\org.osgi.resource\1.0.0\343c88949132984b3f2d4175a72c40b77dc65619\org.osgi.resource-1.0.0.jar;C:\Users\K\.gradle\caches\modules-2\files-2.1\org.osgi\org.osgi.service.serviceloader\1.0.0\3728ff37909c6a3c1870b3e851342d9f2bd3cc63\org.osgi.service.serviceloader-1.0.0.jar --module-path C:\Dev\Workspace\mnt-media-dashboard\desktop\build\libs\media-dashboard-desktop-0.0.1.jar; ... ;C:\Users\K\.gradle\caches\modules-2\files-2.1\org.openjfx\javafx-graphics\26.0.1\5ed8530d00b9dbd1713b018fcd8820982ab714ea\javafx-graphics-26.0.1-win.jar; ... -parameters C:\Dev\Workspace\mnt-media-dashboard\application\src\main\java\be\mnt\mediadashboard\application\JavaFXApplication.java C:\Dev\Workspace\mnt-media-dashboard\application\src\main\java\be\mnt\mediadashboard\application\JpaConfiguration.java C:\Dev\Workspace\mnt-media-dashboard\application\src\main\java\be\mnt\mediadashboard\application\SpringApplication.java C:\Dev\Workspace\mnt-media-dashboard\application\src\main\java\module-info.java

如下所述的我的 回答 中所述,当我再添加模块 javafx.controles 并在我的 application/src/main/java/module-info.java 中require它时,我可以编译并运行整个项目。

我的问题是:为什么在编译时 javafx.stage.Stage 类不可用,而我确实在模块路径上有javafx.graphics。

我已经把我的GitHub仓库设为公开,并为上述最小可复现示例创建了一个独立分支,地址为 https://github.com/KevinVertongen/mnt-media-dashboard/tree/issues/MRE-Missing-JavaFX-Stage-class

解决方案

我可以重现你所看到的编译错误。正确的做法是把你的 desktop 项目的module-info描述符改为:

open module be.mnt.mediadashboard.desktop {
  requires transitive javafx.graphics; // <-- added directive
  requires javafx.controls;
  requires javafx.fxml;

  exports be.mnt.mediadashboard.desktop;
}

DashboardStage 类位于一个导出包中,公开暴露自 javafx.graphics 模块的一个类型。当模块A 从模块B 暴露类型时,模块A 必须对模块B 进行 传递性 依赖。因此添加 requires transitive javafx.graphics 解决了问题。

此外,如果你用 -Xlint:exports 编译 desktop 项目,你会看到以下警告:

warning: [exports] class Stage in module javafx.graphics is not indirectly exported using 'requires transitive'
  public DashboardStage(Stage stage) {
                        ^
1 warning

也就是说:

  • 我不清楚为什么没有那个指令会特定导致以下错误:

none class file for javafx.stage.Stage not found

在编译 application 项目时,尤其是因为它也需要 javafx.graphics 模块,而且该模块已经正确放置在模块路径上(通过查看Gradle的调试输出可以确认)。

  • 我也不清楚为什么把你们的 application 项目中的module-info描述符里的 requires javafx.graphics 改为 requires javafx.controls 就能解决问题。

另外,顺便说一句,获取FXML资源并不需要对你的模块进行 open。你现在这样做是因为你正在使用 ClassLoader::getResource 获取资源。但你其实是在模块内部获取资源。更合适的做法是使用 Class::getResource。我有一个 最近的回答,里面对资源封装有更详细的讲解。

不过,考虑到你使用的Spring目前并不是模块化的,我会重新考虑把自己的代码做成模块化是否值得。我发现只有当你所有的依赖项也使用JPMS模块(或至少所有非服务提供者依赖)时,在你自己的代码中使用JPMS模块才真正有意义。尤其是因为自动模块不能与 jlink 一起使用。

注意,你仍然可以把JavaFX作为命名模块。org.openjfx.javafxplugin 插件在开发阶段(至少在 compileJavarun 任务中)会确保JavaFX作为命名模块被解析。你也可以配置像 jpackage 这样的工具,在部署阶段执行相同的操作。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章