Swing、JavaFX 和 Clojure 在类路径方面玩得并不好。



也许这是Clojure中的错误。。。我的问题是…

为什么当我在下面的代码中创建一个新的JLabel时,我在创建JavaFXStage时会得到一个ClassNotFoundException,但当我不创建JLabel的时候,创建JavaFXstage(并找到类)就很好了?

为了重现这一点,用lein new app class-path-fail创建一个新的leiningen项目,并用以下代码替换core.clj:

(ns class-path-fail.core
  (:gen-class)
  (:import (javafx.stage Stage)
           (javafx.application Platform)
           (javax.swing JLabel SwingUtilities)
           (javafx.embed.swing JFXPanel)))
(JFXPanel.)
(SwingUtilities/invokeAndWait #(new JLabel "ha"))
(defn simple-fn-will-fail []
  (let [form `(fn [] (new Stage))]
    (eval form)))
(Platform/runLater #(simple-fn-will-fail))
(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (println "Hello, World!"))

你可以通过运行它来验证这是否有效…但是。。。评论JLabel的创建并观察它的工作原理!!

我使用的是Clojure 1.8.0。

这是我的项目。clj:

(defproject class-path-fail "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.8.0"]]
  :main ^:skip-aot class-path-fail.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all}})

我发现,如果前两行代码颠倒,那么就会在类路径中找到Stage并加载它。新的前两行看起来像:

(SwingUtilities/invokeAndWait #(new JLabel "ha"))
(JFXPanel.)

我遇到过类似的问题(尽管是在不同的上下文中),我怀疑这是由于JavaFX应用程序线程的类加载器配置,而不一定是Clojure特有的。例如,请参阅以下类似问题:API可用于Java应用程序,但不能用于JavaFX,并且:https://community.oracle.com/thread/2564617?start=0&tstart=0

也就是说,为什么要使用这么多级别的间接性,以及这么多不同的机制来安排未来发生的事情?在普通的Clojure代码中很少需要eval函数。

(Platform/runLater #(simple-fn-will-fail))可以简化:不需要匿名函数,因为simple-fn-will-fail已经是一个不带参数的函数。因此,这条线是完全等效的(并且以完全相同的方式失败):(Platform/runLater simple-fn-will-fail)

此外,simple-fn-will-fail实际上并没有尝试创建新的Stage,它只是返回一个将执行此操作的函数(该函数被丢弃,从未调用),因此,如果您不是通过调用eval来调用编译器,则不会出现此问题。

如果我们能够退一步,更好地了解您实际想要实现的目标,那么可能有一种方法可以解决JavaFX应用程序线程上的类加载器的问题。

不管怎样,这些都是一些想法。也许我们会从对JavaFX和Clojure编译有更深入理解的人那里听到更多。

在下面发表评论后,我又做了一些修改,发现即使我在-main中提取了副作用表单,在尝试评估simple-fn-will-fail中的表单时也会抛出同样的异常。因此,这个问题更加强烈地指向JavaFX类加载器。

然而,通过确保在正常的Clojure线程上进行编译,然后使用Platform/runLater运行生成的函数(我认为这可能是您无论如何都想做的),我能够让一切正常运行。这是我的调整版本:

(ns class-path-fail.core
  (:gen-class)
  (:import (javafx.stage Stage)
           (javafx.application Platform)
           (javax.swing JLabel SwingUtilities)
           (javafx.embed.swing JFXPanel)))
(defn simple-fn-will-fail []
  (let [form `(fn [] (new Stage))]
    (eval form)))
(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (JFXPanel.)
  (SwingUtilities/invokeAndWait #(new JLabel "ha"))
  (let [stage-creator (simple-fn-will-fail)]
    (Platform/runLater stage-creator))
  (println "Hello, World!"))

有了这个版本,我可以毫无问题地调用(-main)

最新更新