环境:
我从GitHub中检出了CORS configuration
,在IntelliJ中构建并运行了它,一切正常。这里是重要的应用程序的快速实现:
scalafx-hello-world
编辑:我的build.sbt:
package hello
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.geometry.Insets
import scalafx.scene.Scene
import scalafx.scene.effect.DropShadow
import scalafx.scene.layout.HBox
import scalafx.scene.paint.Color._
import scalafx.scene.paint._
import scalafx.scene.text.Text
object ScalaFXHelloWorld extends JFXApp {
stage = new PrimaryStage {
// initStyle(StageStyle.Unified)
title = "ScalaFX Hello World"
scene = new Scene {
fill = Color.rgb(38, 38, 38)
content = new HBox {
padding = Insets(50, 80, 50, 80)
children = Seq(
new Text {
text = "Scala"
style = "-fx-font: normal bold 100pt sans-serif"
fill = new LinearGradient(
endX = 0,
stops = Stops(Red, DarkRed))
},
new Text {
text = "FX"
style = "-fx-font: italic bold 100pt sans-serif"
fill = new LinearGradient(
endX = 0,
stops = Stops(White, DarkGray)
)
effect = new DropShadow {
color = DarkGray
radius = 15
spread = 0.25
}
}
)
}
}
}
}
之后,我将实现更改为:
// Name of the project
name := "ScalaFX Hello World"
// Project version
version := "11-R16"
// Version of Scala used by the project
scalaVersion := "2.12.7"
// Add dependency on ScalaFX library
libraryDependencies += "org.scalafx" %% "scalafx" % "11-R16"
resolvers += Resolver.sonatypeRepo("snapshots")
scalacOptions ++= Seq("-unchecked", "-deprecation", "-Xcheckinit", "-encoding", "utf8", "-feature")
// Fork a new JVM for 'run' and 'test:run', to avoid JavaFX double initialization problems
fork := true
// Determine OS version of JavaFX binaries
lazy val osName = System.getProperty("os.name") match {
case n if n.startsWith("Linux") => "linux"
case n if n.startsWith("Mac") => "mac"
case n if n.startsWith("Windows") => "win"
case _ => throw new Exception("Unknown platform!")
}
// Add JavaFX dependencies
lazy val javaFXModules = Seq("base", "controls", "fxml", "graphics", "media", "swing", "web")
libraryDependencies ++= javaFXModules.map( m=>
"org.openjfx" % s"javafx-$m" % "11" classifier osName
)
在这里出现以下错误:
package hello
import javafx.application.Application
import javafx.scene.Scene
import javafx.scene.control.Label
import javafx.stage.Stage
class ScalaFXHelloWorld extends Application {
override def start(stage: Stage): Unit = {
stage.setTitle("Does it work?")
stage.setScene(new Scene(
new Label("It works!")
))
stage.show()
}
}
object ScalaFXHelloWorld {
def main(args: Array[String]): Unit = {
Application.launch(classOf[ScalaFXHelloWorld], args: _*)
}
}
现在我的问题是:ScalaFX什么不会发生模块问题?
答案 0 :(得分:4)
在乔纳森·克罗斯默的回答中:
命名类和对象的方法不同的原因是,如果主类扩展了javafx.application.Application
,则Java启动器实际上具有特殊的行为。如果您有可用的Java源代码,则可以在JAVA_HOME/lib/src.zip/java.base/sun/launcher/LauncherHelper.java
中找到相关的代码。特别是有两种有趣的方法:
public static Class<?> checkAndLoadMain(boolean, int ,String)
//In nested class FXHelper
private static void setFXLaunchParameters(String, int)
第一个方法具有适当的检查位置,以检查主类是否扩展了javafx.application.Application
。如果是这样,此方法将用嵌套类FXHelper
替换主类,该嵌套类具有自己的public static void main(String[] args)
。
第二种方法(由第一种方法直接调用)尝试加载JavaFX运行时。但是,这样做的方法是先通过javafx.graphics
加载模块java.lang.ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME)
。
如果此调用失败,则Java将抱怨找不到JavaFX运行时,然后立即通过System.exit(1)
退出。
回到SBT和Scala,其他一些细节正在发挥作用。首先,如果主对象和扩展javafx.application.Application
的类都具有相同的名称,则Scala编译器将生成一个扩展了Application
并具有public static void main(...)
的类文件。这意味着将触发上述特殊行为,并且Java启动器将尝试将JavaFX运行时作为模块加载。由于SBT当前没有关于模块的概念,因此JavaFX运行时将不在模块路径上,并且对findModule(...)
的调用将失败。
另一方面,如果主对象的名称与主类的名称不同,则Scala编译器会将public static void main(...)
放在不会扩展Application的类中,这又意味着main()方法将正常执行。
在继续之前,我们应该注意,尽管SBT并未将JavaFX运行时放在模块路径上,但实际上DID却将其放在类路径上。这意味着JavaFX类对JVM是可见的,只是不能作为模块加载。毕竟
模块化JAR文件在所有可能的方面都与普通JAR文件类似,除了它的根目录中还包含module-info.class文件。
(来自The State of the Module System)
但是,如果碰巧有一个方法调用,例如Application.launch(...)
,Java会很高兴地从类路径中加载javafx.application.Application
。 Application.launch(...)
同样可以访问其余JavaFX,一切正常。
这也是运行JavaFX应用而不进行分叉的原因。在那种情况下,SBT将始终直接调用public static void main(...)
,这意味着不会触发来自Java启动器的特殊行为,并且将在类路径中找到JavaFX运行时。
以下是查看上述行为的摘要:
Main.scala:
object Main {
def main(args: Array[String]): Unit = {
/*
Try to load the JavaFX runtime as a module. This is what happens if the main class extends
javafx.application.Application.
*/
val foundModule = ModuleLayer.boot().findModule("javafx.graphics").isPresent
println("ModuleLayer.boot().findModule(\"javafx.graphics\").isPresent = " + foundModule) // false
/*
Try to load javafx.application.Application directly, bypassing the module system. This is what happens if you
call Application.launch(...)
*/
var foundClass = false
try{
Class.forName("javafx.application.Application")
foundClass = true
}catch {
case e: ClassNotFoundException => foundClass = false
}
println("Class.forName(\"javafx.application.Application\") = " + foundClass) //true
}
}
build.sbt:
name := "JavaFXLoadTest"
version := "0.1"
scalaVersion := "2.13.2"
libraryDependencies += "org.openjfx" % "javafx-controls" % "14"
fork := true
答案 1 :(得分:3)
我无法完全重现您的问题,但是我已经能够获得一个仅使用 JavaFX 的项目(也就是说,它没有使用 ScalaFX )来构建和运行。
这就是我正在使用的内容(构建文件中指定了所有其他内容):
(我曾尝试使用 Zulu OpenJDK 12来构建和运行该项目,并且也能正常工作。但是,最好使用 OpenJFX 版本匹配 JDK 。)
当我尝试使用原始资源和build.sbt
时,从命令行执行sbt run
命令时遇到了以下错误:
D:\src\javafx11>sbt run
[info] Loading global plugins from {my home directory}\.sbt\1.0\plugins
[info] Loading project definition from D:\src\javafx11\project
[info] Loading settings for project javafx11 from build.sbt ...
[info] Set current project to JavaFX 11 Hello World (in build file:/D:/src/javafx11/)
[info] Running (fork) hello.ScalaFXHelloWorld
[error] Error: JavaFX runtime components are missing, and are required to run this application
[error] Nonzero exit code returned from runner: 1
[error] (Compile / run) Nonzero exit code returned from runner: 1
[error] Total time: 1 s, completed Aug 11, 2019, 3:17:07 PM
正如我在对您的问题的原始评论中提到的那样。
我认为这很奇怪,因为代码已编译,这意味着编译器能够很好地找到 JavaFX 运行时。
然后我通过注释掉构建文件中的fork := true
来尝试运行没有 fork 的程序。你猜怎么了?该程序运行没有错误!
关于将 SBT 与 JDK 9+版本一起使用,我可能会遗漏一些东西,但这表明 SBT 不能以某种方式运行正确处理。通过在构建文件的末尾添加以下内容,可以强制分叉的进程正确运行:
val fs = File.separator
val fxRoot = s"${sys.props("user.home")}${fs}.ivy2${fs}cache${fs}org.openjfx${fs}javafx-"
val fxPaths = javaFXModules.map {m =>
s"$fxRoot$m${fs}jars${fs}javafx-$m-11-$osName.jar"
}
javaOptions ++= Seq(
"--module-path", fxPaths.mkString(";"),
"--add-modules", "ALL-MODULE-PATH"
)
这可以通过将下载的 ivy 管理的 JavaFX jar文件添加到 Java 的模块路径中来进行。但是,这对于运行独立应用程序不是一个好的解决方案。 sbt-native-packager
可能会为完整的应用程序运行提供必要的环境,但我还没有尝试过。
我已在GitHub
上发布了完整的解决方案让我知道这是否有帮助。同时,我将研究 SBT 对 JDK 9+模块的支持,看看是否有更简单的解决方案...
更新:
我有raised an issue (#4941) with the SBT team对此进行了更详细的研究。
更新2
我修复了一个问题,该问题导致该解决方案无法在 Linux 上运行。执行 git pull 更新源。
更新3
我还应该提到,最好让 IntelliJ 使用 SBT 运行应用程序,这使事情变得简单,并确保正确配置了应用程序的环境。
要执行此操作,请进入 IntelliJ 运行菜单,然后选择 Edit Configurations ... 选项。单击对话框左上角的 + 按钮,从“添加新配置”下的列表中选择 sbt Task” ,然后进行如下配置:
如果需要,它将首先编译并构建应用程序。
注意:_VM参数用于运行 SBT ,与 SBT 如何运行分支的应用程序无关。
(您也可以添加 SBT 运行配置以测试您的代码。)
答案 2 :(得分:3)
我遇到了同样的问题,找到了一个令人困扰的奇怪而简单的解决方案。 tldr; 使主类的名称与JavaFX Application类的名称不同。首先是一个例子:
import javafx.application.Application
import javafx.event.ActionEvent
import javafx.event.EventHandler
import javafx.scene.Scene
import javafx.scene.control.Button
import javafx.scene.layout.StackPane
import javafx.stage.Stage
object HelloWorld {
def main(args: Array[String]): Unit = {
Application.launch(classOf[HelloWorld], args: _*)
}
}
// Note: Application class name must be different than main class name to avoid JavaFX path initialization problems! Try renaming HelloWorld -> HelloWorld2
class HelloWorld extends Application {
override def start(primaryStage: Stage): Unit = {
primaryStage.setTitle("Hello World!")
val btn = new Button
btn.setText("Say 'Hello World'")
btn.setOnAction(new EventHandler[ActionEvent]() {
override def handle(event: ActionEvent): Unit = {
System.out.println("Hello World!")
}
})
val root = new StackPane
root.getChildren.add(btn)
primaryStage.setScene(new Scene(root, 300, 250))
primaryStage.show()
}
}
上面编写的代码引发了原始问题的异常。如果我将类HelloWorld重命名为HelloWorld2(保留对象HelloWorld,并将启动调用更改为classOf [HelloWorld2]),则它运行良好。我怀疑这是使ScalaFX正常工作的“魔术”,因为它将JavaFX Application封装为自己的JFXApp类型,从而创建了一个隐藏的Application类。
为什么起作用?我不确定,但是当使用标准的Run config(右键单击HelloWorld和“ run HelloWorld.main()”)在IntelliJ中运行每段代码时,然后在输出中单击“ /home/jonathan/.jdks” /openjdk-14.0.1/bin/java ...“展开后显示了一条命令,其中包括“ --add-modules javafx.base,javafx.graphics”。在第二个版本中,对于重命名的HelloWorld2应用程序,该命令不包括此内容。我无法弄清楚IntelliJ如何决定使该命令与众不同,但我只能推测它与推断它是JavaFX应用程序有关,并通过自动添加“ --add-modules”来尝试提供帮助。 。?在任何情况下,模块列表都不包括所需的所有模块,因此例如创建一个按钮需要“ javafx.controls”,您会收到错误消息。但是,当主类与应用程序名称不匹配时,它所做的任何魔术推断都将关闭,并且build.sbt中的标准类路径也将起作用。
有趣的跟进:如果我使用sbt run
从sbt外壳运行应用程序,则模式是相同的(HelloWorld失败,但是重命名应用程序类可以解决该问题),但是错误消息则更多-直接但仍然无济于事“错误:缺少JavaFX运行时组件,并且是运行此应用程序所必需的”。因此,也许不完全是IntelliJ问题,而是与JavaFX和Jigsaw有关?无论如何,这都是一个谜,但至少我们有一个简单的解决方案。