Han*_*nes 6 scala java-platform-module-system scalafx openjdk-11
环境:
我scalafx-hello-world从GitHub签出了,并在IntelliJ中构建并运行了它,一切正常。这里快速介绍重要的应用程序实现:
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
}
}
)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
编辑:我的build.sbt:
// 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
)
Run Code Online (Sandbox Code Playgroud)
之后,我将实现更改为:
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: _*)
}
}
Run Code Online (Sandbox Code Playgroud)
在这里,我得到以下错误:
Exception in Application start method
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:567)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:464)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:363)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:567)
at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1051)
Caused by: java.lang.RuntimeException: Exception in Application start method
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:900)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
at java.base/java.lang.Thread.run(Thread.java:835)
Caused by: java.lang.IllegalAccessError: superclass access check failed: class com.sun.javafx.scene.control.ControlHelper (in unnamed module @0x40ac0fa0) cannot access class com.sun.javafx.scene.layout.RegionHelper (in module javafx.graphics) because module javafx.graphics does not export com.sun.javafx.scene.layout to unnamed module @0x40ac0fa0
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:151)
at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:802)
at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:700)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:623)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
at javafx.scene.control.Control.<clinit>(Control.java:86)
at hello.ScalaFXHelloWorld.start(ScalaFXHelloWorld.scala:39)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:846)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:455)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:389)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
... 1 more
Exception running application hello.ScalaFXHelloWorld
Run Code Online (Sandbox Code Playgroud)
现在我的问题是:ScalaFX什么不会发生模块问题?
添加乔纳森·克罗斯默的答案:
对类和对象进行不同命名的原因是,如果主类扩展,Java 启动器实际上会采取特殊的行为javafx.application.Application。如果您有可用的 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)
Run Code Online (Sandbox Code Playgroud)
第一个方法有一个检查,检查主类是否扩展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 运行时放在模块路径上,但实际上它确实将其放在类路径上。这意味着 JavaFX 类对 JVM 可见,只是不能作为模块加载。毕竟
模块化 JAR 文件在所有方面都与普通 JAR 文件相似,只是它的根目录中还包含 module-info.class 文件。
(来自模块系统的状态)
然而,如果一个方法碰巧调用,比如说,Java 会很乐意从类路径Application.launch(...)加载。同样可以访问 JavaFX 的其余部分,一切顺利。javafx.application.ApplicationApplication.launch(...)
这也是无需分叉即可运行 JavaFX 应用程序的原因。在这种情况下,SBT 将始终public static void main(...)直接调用,这意味着不会触发 java 启动器的特殊行为,并且将在类路径上找到 JavaFX 运行时。
以下是查看上述行为实际效果的片段:
主.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
}
}
Run Code Online (Sandbox Code Playgroud)
构建.sbt:
name := "JavaFXLoadTest"
version := "0.1"
scalaVersion := "2.13.2"
libraryDependencies += "org.openjfx" % "javafx-controls" % "14"
fork := true
Run Code Online (Sandbox Code Playgroud)
我无法准确地重现您的问题,但我已经能够获得一个仅使用JavaFX(即,它不使用ScalaFX)来构建和运行的项目。
这是我正在使用的(其他所有内容都在构建文件中指定):
(我确实尝试使用Zulu OpenJDK 12 来构建和运行该项目,这也有效。但是,最好使用与JDK匹配的OpenJFX版本。)
当我尝试您的原始来源和时,从命令行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
Run Code Online (Sandbox Code Playgroud)
正如我在对你的问题的最初评论中提到的。
我认为这很奇怪,因为代码已编译,这意味着编译器能够很好地找到JavaFX运行时。
然后,我尝试通过注释掉构建文件中的来运行程序而不使用fork 。fork := true你猜怎么了?程序运行没有错误!
关于将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"
)
Run Code Online (Sandbox Code Playgroud)
这是通过将下载的ivy管理的JavaFX jar 文件添加到Java的模块路径来实现的。然而,这对于运行独立应用程序来说并不是一个好的解决方案。也许可以为sbt-native-packager已完成的应用程序的运行提供必要的环境,但我还没有尝试过。
让我知道这是否有帮助。同时,我将研究SBT对JDK 9+ 模块的支持,看看是否有更简单的解决方案......
更新:
我向SBT团队提出了一个问题 (#4941),以便更详细地研究这个问题。
更新2
我修复了一个导致该解决方案无法在Linux上运行的问题。执行git pull来更新源。
更新3
我还应该提到,最好让IntelliJ使用SBT运行应用程序,这样可以使事情变得简单并确保应用程序的环境配置正确。
为此,请进入IntelliJ Run菜单,然后选择Edit Configurations...选项。单击对话框左上角的+按钮,从 **Add New Configuration 下的列表中选择 sbt Task”,然后配置如下:
如果需要,这将首先编译并构建应用程序。
注意: _VM 参数用于运行SBT ,与SBT如何运行分叉应用程序无关。
(您还可以添加SBT运行配置来测试您的代码。)
| 归档时间: |
|
| 查看次数: |
264 次 |
| 最近记录: |