Medio ambiente :

  • OpenJDK 64-BIT Server VM Zulu12.2 + 3-CA (Construir 12.0.1 + 12, Modo mixto, compartir)
  • SCALA 2.12.7
  • Windows 10 Professional, X86_64
  • IDEA INTELLIJ 2019.1.3 (Ultimate Edition)

Revisé el scalafx-hello-world de github, construido y lo ejecute en Intellij y Funcionó todo bien. Aquí rápidamente la implementación de la aplicación significativa:

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

  }
}

Editar: mi 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
)

Después de eso, cambié la implementación a:

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: _*)
  }
}

Aquí aparece el siguiente error:

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

Ahora, mi pregunta es: ¿qué hace Scalafx que no se produce el problema del módulo?

10
Hannes 26 jun. 2019 a las 16:46

3 respuestas

La mejor respuesta

No he podido reproducir exactamente su problema, pero he podido obtener un proyecto que usa Javafx en línea (es decir, no hace uso de Scalafx ) para construir y correr.

Esto es lo que estoy usando (todo lo demás se especifica en el archivo de compilación):

(Intenté usar Zulu OpenJDK 12 para construir y ejecutar el proyecto, y eso también funcionó. Sin embargo, es probable que sea mejor si usa la versión de OpenJFX que coincide con el <. em> jdk .)

Cuando intenté sus fuentes originales y build.sbt, encontré el siguiente error al ejecutar un comando sbt run de la línea de comandos:

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

Como mencioné en mis comentarios originales a tu pregunta.

Pensé que era extraño porque el código compilado, lo que significaba que el compilador pudo encontrar el tiempo de ejecución javafx .

Luego intenté ejecutar el programa sin Forking , comentando el fork := true en el archivo de compilación. ¿Adivina qué? ¡El programa corrió sin error!

JavaFX application running

Puede que me falte algo, con respecto a usar sbt con jdk versiones 9+, pero esto indicó que sbt no funcionó correctamente el proceso bifurcado. Podría forzar el proceso bifurcado para que se ejecute correctamente agregando lo siguiente al final del archivo de compilación:

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"
)

Esto funciona agregando la IVY los archivos JARCH JAVAFX a java 's Ruta del módulo. Sin embargo, esta no es una buena solución para ejecutar aplicaciones independientes. Puede ser posible que la sbt-native-packager proporcione el entorno necesario para que se ejecute la solicitud completa, pero no lo he intentado.

He publicado la solución completa en github

Déjame saber si esto ayuda. Mientras tanto, los examinaré en el soporte de SBT para JDK 9+ Módulos para ver si hay una solución más sencilla ...

ACTUALIZACIÓN :

Tengo recaudó un problema (# 4941) con el equipo sbt mirar esto con más detalle.

ACTUALIZACIÓN 2

Parché un problema que detuvo la solución al trabajar en Linux . Realice un GIT TRAT para actualizar las fuentes.

ACTUALIZACIÓN 3

También debería mencionar que es mejor tenerlo IntelliJ Ejecute la aplicación usando SBT , que mantiene las cosas simples y garantiza que el entorno de la aplicación esté configurado correctamente.

Para hacer esto, llegó al IntelliJ Ejecutar Menú, y seleccione la opción Editar configuraciones ... . Haga clic en el botón + en la esquina superior izquierda del diálogo, seleccione sbt tarea "de la lista en ** Agregar nueva configuración , luego configure lo siguiente:

Adding an SBT run configuration

Esto compilará y construirá la solicitud primero, si es necesario.

Note : Los parámetros _vm son para ejecutar sbt , y no se relacionan con cómo sbt ejecuta su aplicación bifurcada.

(También puede agregar sbt ejecutar configuraciones para probar su código, también).

4
Mike Allen 14 ago. 2019 a las 14:13

Corrí a través de este mismo problema exacto y he encontrado una solución inquietantemente extraña y fácil. tldr; Haz que la clase principal tenga un nombre diferente de la clase de aplicación Javafx. Primero un ejemplo:

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

El código tan escrito anteriormente lanza la excepción de la pregunta original. Si cambiamos el nombre de la clase Helloworld a Helloworld2 (manteniendo el objeto Helloworld y cambiando la llamada de lanzamiento a la clase [HelloWorld2]), se ejecuta bien. Sospecho que esta es la "magia" que también hace que Sc Sc Sc Sc Sc Sc Sc Sc Sc Sc Sc Scalafx sea la aplicación de Javafx en su propio tipo JFXAPP, creando una clase de aplicación oculta.

¿Por qué funciona? No estoy completamente seguro, pero al ejecutar cada pieza de código en IntelliJ utilizando una configuración de ejecución estándar (haga clic con el botón derecho en HelloWorld y "Ejecutar HelloWorld.main ()"), luego en la salida haciendo clic en "/Home/Jonathan/jdks /openjdk-14.0.1/bin/java ... "Para expandirla, muestra un comando que incluye" --add-módulos javafx.base, javafx.graphics ", entre otras cosas. En la segunda versión, con la aplicación Helloworld2 renombrada, el comando no incluye esto. No puedo entender cómo INTELLIJ ha decidido hacer diferente el comando, pero solo puedo especularlo, tiene algo que ver con inferir, es una aplicación Javafx y tratando de ser útil agregando automáticamente "--ad-módulos" .. . En cualquier caso, la lista de módulos no incluye todos los módulos necesarios, por lo que, por ejemplo, crear un botón requiere "javafx.controls", y obtiene el error. Pero cuando la clase principal no coincide con el nombre de la aplicación, cualquier inferencia mágica que sí se desactiva, y la conexión de clase estándar de la Build.SBT simplemente funciona.

Seguimiento de la diversión: si ejecuto la aplicación de la shell SBT usando sbt run, entonces el patrón es el mismo (falla el hiloloforld, pero cambiar el nombre de la clase de aplicación lo arregla), pero el mensaje de error es más directo, pero "Faltan los componentes de" Error: Javafx Runtime, y se requieren para ejecutar esta aplicación ". Entonces, tal vez no enteramente un problema de Intellij, ¡pero algo que ver con Javafx y Jigsaw? De todos modos, es un misterio, pero al menos tenemos una solución fácil.

3
Jonathan Crosmer 11 may. 2020 a las 22:20

Añadiendo a la respuesta de Jonathan Crosmer:

La razón por la que nombra a la clase y el objeto funciona de manera diferente es porque el lanzador Java en realidad tiene un comportamiento especial en su lugar si la clase principal se extiende javafx.application.Application. Si tiene las fuentes de Java disponibles, el código relevante se puede encontrar en JAVA_HOME/lib/src.zip/java.base/sun/launcher/LauncherHelper.java. En particular, hay dos métodos que son de interés:

public static Class<?> checkAndLoadMain(boolean, int ,String)

//In nested class FXHelper
private static void setFXLaunchParameters(String, int)

Los primeros métodos tienen un cheque en su lugar que se vea si la clase principal se extiende javafx.application.Application. Si lo hace, este método reemplaza a la clase principal con la clase anidada FXHelper, que tiene su propio public static void main(String[] args).

El segundo método, que se llama directamente por el primer método, intenta cargar el tiempo de ejecución de Javafx. Sin embargo, la forma en que hace esto es cargando primero el módulo javafx.graphics a través de java.lang.ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME). Si esta llamada falla, Java se quejará de no haber encontrado el tiempo de ejecución de Javafx y luego salga inmediatamente a través de System.exit(1).

Volviendo a SBT y SCALA, algunos otros detalles están en juego. Primero, si tanto el objeto principal como la clase que se extiende javafx.application.Application tienen el mismo nombre, el compilador Scala generará un archivo de clase que ambos extienden Application y tiene un public static void main(...). Eso significa que el comportamiento especial descrito anteriormente se activará y el lanzador Java intentará cargar el tiempo de ejecución de Javafx como un módulo. Dado que SBT no tiene ninguna idea de los módulos, el tiempo de ejecución de Javafx no estará en la ruta del módulo y la llamada a findModule(...) fallará.

Por otro lado, si el objeto principal tiene un nombre diferente de la clase principal, el compilador SCALA colocará public static void main(...) en una clase que no extienda la aplicación, que a su vez significa que el método principal () se ejecutará normalmente .

Antes de continuar, debemos señalar que, si bien SBT no puso el tiempo de ejecución de Javafx en la ruta del módulo, de hecho, lo puso en la carrera. Eso significa que las clases Javafx son visibles para JVM, simplemente no se pueden cargar como módulo. Después de todo

Un archivo de jar modular es como un archivo JAR ordinario en todas las formas posibles, excepto que también incluye un archivo Module-info.Class en su directorio raíz.

(de el estado del sistema del módulo)

Sin embargo, si se le ocurra un método, digamos Application.launch(...), Java cargará felizmente javafx.application.Application de la carrera. Application.launch(...) de manera similar tendrá acceso al resto de Javafx y todo funciona.

Esa es también la razón por la cual dirigir una aplicación Javafx sin trabajos de bifurcación. En ese caso, SBT siempre invocará public static void main(...) directamente, lo que significa que no se activan conductas especiales del lanzador de Java y se encontrará el tiempo de ejecución de Javafx en el Classpath.


Aquí hay un fragmento para ver el comportamiento anterior en acción:

Escala principal:

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
4
Delphi1024 25 may. 2020 a las 21:15