带有 JavaFX 17 的 Leiningen Uberjar

Posted

技术标签:

【中文标题】带有 JavaFX 17 的 Leiningen Uberjar【英文标题】:Leiningen Uberjar with JavaFX 17 【发布时间】:2021-12-14 15:40:52 【问题描述】:

我在 2014 年有一个用 Clojure 和 JavaFX 编写的程序。最近修改了该程序的依赖项以使用 Java 17。简单地替换新版本的依赖项会产生与无法读取新类相关的错误文件格式。我想更新应用程序,但无法使用当前版本的 Java (17) 和 JavaFX (17.0.1) 生成 uberjar。

这里是 project.clj 和 SSCCE 的源文件。

(defproject sutest "0.1.0-SNAPSHOT"
  :description "Test for including JavaFX components in uberjar"
  :dependencies [[org.clojure/clojure "1.10.3"]
                 [org.openjfx/javafx-controls "17.0.1"]]
  :aot :all
  :main sutest.core)
(ns sutest.core
  (:gen-class
    :extends javafx.application.Application)
  (:import
    [javafx.application Application Platform]
    [javafx.event EventHandler]
    [javafx.geometry Insets Pos]
    [javafx.scene Scene]
    [javafx.scene.control Button Label]
    [javafx.scene.layout VBox]))

(defn -start [this stage]
  (let [hiLbl (Label. "Hello World!")
        exitBtn (Button. "Exit")
        root (VBox. 12.0)]
    (.setOnAction exitBtn (reify EventHandler (handle [_ _]
                                                (Platform/exit))))
    (.setPadding root (Insets. 0 10 0 10))
    (.addAll (.getChildren root) [hiLbl exitBtn])
    (.setAlignment root Pos/CENTER)
    (.setScene stage (Scene. root 250 150)))
  (.show stage))

(defn -main [& args]
  (Application/launch sutest.core args))

当直接使用lein run 或从 IntelliJ IDEA/Cursive “运行”配置执行时,程序按预期工作。运行 lein uberjar 完成且没有错误,但尝试使用

运行 uberjar
java -jar target/sutest-0.1.0-SNAPSHOT-standalone.jar

生产

Error: JavaFX runtime components are missing, and are required to run this application

我正在使用 Leiningen 2.9.6,因为 this issue 和 2.9.7。运行程序时,Leiningen 会在本地依赖库中构建一个包含所需 jar 的类路径。

我看到了一些关于将较新的 JavaFX 模块包含在 Java 的“胖”jar 中的问题,并尝试将它们包含在 Leiningen 构建中。很少与 Clojure 和 Java 相关。例如,请参阅 fn-fx 库的 project.clj。这诉诸一个特殊的“泄漏”配置文件来包含模块。由于某种原因,这对我不起作用。

我尝试将模块添加到 IDEA/Cursive 项目中。正确下载了项目信息中的模块,但仍然没有使用模块构建 uberjar。

我也摆弄过 IDEA/Cursive 项目的“Artifacts”部分。但这并不成功。

Gluon 网站上有一些教程介绍了如何制作一个包含 JavaFX 组件的“胖”jar,但这些都是针对 Java 项目的。

可以使用 Leiningen 创建包含依赖项的 uberjar 吗?

如果不是 Leiningen,tools.deps 或 boot 怎么样?

有没有人通过调整 Gluon 指令成功地退回到简单的 Maven 构建?

【问题讨论】:

请编辑您的问题以显示您的 jar 的内容。 Leiningen 支持classifiers for dependencies。如果您检查您的 jar 内容并且它不包含所需的本机组件,您应该能够通过为这些本机组件创建额外的依赖项来添加它们,就像在执行 shared maven build 的相同任务时所做的那样,至少这就是对我来说很有意义,尽管我不确定,因为我不熟悉您的工具链选择。 【参考方案1】:

在@jewelsea 的建议下,我查看了原始问题中创建的 uberjar 的内容。我用过

jar tvf jar tvf target/sutest-0.1.0-SNAPSHOT-standalone.jar

生成页面和课程列表页面。

靠近顶部的是线条:

...
   306 Sat Oct 23 15:23:46 EDT 2021 javafx-controls-17.0.1.jar
   306 Sat Oct 23 15:23:46 EDT 2021 javafx-graphics-17.0.1.jar
   302 Sat Oct 23 15:23:46 EDT 2021 javafx-base-17.0.1.jar
746012 Sat Oct 23 15:23:46 EDT 2021 javafx-base-17.0.1-mac.jar
2545243 Sat Oct 23 15:23:48 EDT 2021 javafx-controls-17.0.1-mac.jar
4852153 Sat Oct 23 15:23:48 EDT 2021 javafx-graphics-17.0.1-mac.jar
...

lein run 命令构建的类路径中使用的大小相同的相同 jar。

因此,所需的 JavFX 模块 jar 已包含在 uberjar 中。

这引发了关于java.base 模块的sun.launcher.LauncherHelper 类对JavaFX 应用程序的特殊处理的message thread 的模糊记忆。 (如果您有兴趣,该类的 Java 17 版本的来源是 here。)启动器对正在启动的主类进行特殊检查。如果它扩展了javafx.application.Application,JavaFX 模块必须存在于模块路径上,否则会显示原始问题中的错误消息并中止启动。上面提到的消息线程和this *** answer给出了更好的解释。

最终结果是您不能让 JavaFX“胖”jar 的主程序条目从 javafx.application.Application 类扩展。 相反,您可以使用“普通”主类,然后调用程序的 JavaFX 部分。

下面的项目清单是对原始问题中的项目的轻微修改,并实施了所描述的解决方案。

这是修改后的project.clj。只有带有程序入口点的:main类的名称发生了变化。

(defproject sutest "0.1.0-SNAPSHOT"
  :description "Test for including JavaFX components in uberjar"
  :dependencies [[org.clojure/clojure "1.10.3"]
                 [org.openjfx/javafx-controls "17.0.1"]]
  :aot :all
  :main sutest.launcher)

这是用于启动 JavaFX 部分的新类:

(ns sutest.launcher
  (:gen-class)
  (:require [sutest.core :as sc]))

(defn -main [& args]
  (sc/start-it args))

它使用新的start-it 函数调用sutest.core 命名空间

这里是原始 JavaFX 程序的修订版。 start-it 函数启动事物,而不是之前使用的 -main 函数。请注意对args 参数的更改。它不再具有& rest 签名。它必须是一个(可能是空的)vector 字符串。标签和按钮的变量名称已更改,以反映 Clojure 变量命名约定。

(ns sutest.core
  (:gen-class
    :extends javafx.application.Application)
  (:import
    [javafx.application Application Platform]
    [javafx.event EventHandler]
    [javafx.geometry Insets Pos]
    [javafx.scene Scene]
    [javafx.scene.control Button Label]
    [javafx.scene.layout VBox]))

(defn -start [_ stage]
  (let [hi-lbl (Label. "Hello World!")
        exit-btn (Button. "Exit")
        root (VBox. 12.0)]
    (.setOnAction exit-btn (reify EventHandler (handle [_ _]
                                                (Platform/exit))))
    (.setPadding root (Insets. 0 10 0 10))
    (.addAll (.getChildren root) [hi-lbl exit-btn])
    (.setAlignment root Pos/CENTER)
    (.setScene stage (Scene. root 250 150)))
  (.show stage))

(defn start-it [args]
  (Application/launch sutest.core args))

就是这样。现在您可以构建一个 uberjar 并从命令行运行它。此外,它仍然可以使用 lein run 命令直接从 Leiningen 运行,也可以在 IDE 中运行(在使用运行配置中的入口点更改类的名称之后。)

这是一个肮脏的黑客攻击

它基于对启动器内部的详细了解,而不是使用模块。启动时,程序仍然会发出警告,例如:

WARNING: Unsupported JavaFX configuration: classes were loaded from 'unnamed module @25b2b199'

希望它能够工作足够长的时间来弄清楚如何在 Clojure 中的“胖”罐子中使用 Java 模块。

【讨论】:

以上是关于带有 JavaFX 17 的 Leiningen Uberjar的主要内容,如果未能解决你的问题,请参考以下文章

为 Windows 查找 javafx jar 文件

带有 VSCode 的 JavaFX-11

带有 javaFX 的 MVC

leiningen - 如何为本地 jar 添加依赖项?

安装 Leiningen 以与 Clojure 一起使用

Leiningen 安装