Clojure can't import JavaFX classes with static initializers

早过忘川 提交于 2019-12-10 13:27:32

问题


I'm playing around with Clojure (1.6) and JavaFX 8, and right at the start I've run into a problem. For example, this very simple code fails:

(ns xxyyzz.core)

(gen-class :name "xxyyzz.core.App"
           :extends javafx.application.Application
           :prefix "app-")

(defn app-start [app stage]
  (let [button (javafx.scene.control.Button.)]))

(defn launch []
  (javafx.application.Application/launch xxyyzz.core.App (into-array String [])))

(defn -main []
  (launch))

This is the last part of the stack trace that seems relevant:

Caused by: java.lang.ExceptionInInitializerError
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:340)
        at clojure.lang.RT.classForName(RT.java:2070)
        at clojure.lang.Compiler$HostExpr.maybeClass(Compiler.java:969)
        at clojure.lang.Compiler$HostExpr.access$400(Compiler.java:747)
        at clojure.lang.Compiler$NewExpr$Parser.parse(Compiler.java:2494)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6560)
        ... 48 more
Caused by: java.lang.IllegalStateException: Toolkit not initialized
        at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:276)
        at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:271)
        at com.sun.javafx.application.PlatformImpl.setPlatformUserAgentStylesheet(PlatformImpl.java:562)
        at com.sun.javafx.application.PlatformImpl.setDefaultPlatformUserAgentStylesheet(PlatformImpl.java:524)
        at javafx.scene.control.Control.<clinit>(Control.java:81)
        ... 55 more

I don't speak Java at all, but researching this it seems that the problem lies with Clojure and the way it imports Java classes. If I understand correctly, at import time it runs the class static initializer, and for some JavaFX classes (Button in my case) that crashes.

Guess I have two questions: is my understanding of this error correct? And second, is there a way to work around this problem somehow? I've tried pulling the imports inside functions instead at the (ns) declaration, but it still doesn't work.

If there's no Clojure fix, could this be possibly fixed with some additional Java code?

Any tips and pointers are welcome!


回答1:


I couldn't find a way to alter Clojure's import behavior, but I did find a couple of hacks to do what I need.

First, JavaFX provides builder classes, so the cleanest way in this particular case would be to use ButtonBuilder to create new Buttons.

Second way would be to write a simple Java class that wraps the Button, and then from Clojure's side import that wrapping class. It's an OK solution when working with smaller number of problematic classes.

Third way would be to import at runtime, something like this (thanks to guys at #clojure for helping out with this):

(defn import-at-runtime [name]
  (.importClass (the-ns *ns*)
                (clojure.lang.RT/classForName name)))

(import-at-runtime "javafx.scene.control.Button")

(let [button (eval `(new ~(symbol "javafx.scene.control.Button") ~"Button Text"))

In the end, this seems like a ugly wart in Clojure's Java interop, it would be great if it could be fixed in the future.


UPDATE: There's also clojure.lang.RT/classForNameNonLoading, but unfortunately, it's not public as of Clojure 1.6. It's easy to re-implement it in Clojure, though:

(fn [^String class-name]
  (Class/forName class-name false (clojure.lang.RT/baseLoader)))

Later, the class can be instantiated with clojure.lang.Reflector/invokeConstructor.



来源:https://stackoverflow.com/questions/23365409/clojure-cant-import-javafx-classes-with-static-initializers

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!