這篇會把 Java 版的 HelloFX 改寫成 ABCL 版本:先將 JavaFX SDK 的 JAR 檔案載入 ABCL,再用jnew-runtime-class在執行時建立繼承 JavaFX Application 的類別,最後把完整程式存成main.lisp,透過 ABCL 載入執行。
我們會以HelloFX為基準,改成使用ABCL+JavaFX的實現方式。這種實現方式的好處是不需要JDK環境,只需要JRE環境即可。
為了載入JavaFX SDK,需要將jar檔案加入CLASSPATH路徑:
;; add classpath
(ignore-errors
(let (libs)
(setf libs
;; (make-pathname :name :wild :directory '(:relative "lib") :type "jar")
(loop for file in (directory "lib/*.jar")
do
(setf file (namestring file))
(print file)
collect file))
(add-to-classpath libs)))
你可以像我一樣直接將SDK目錄複製到執行目錄下,然後透過ABCL程式直接將jar檔案遍歷加進CLASSPATH裡。然後需要建立一個動態的JAVA類別:
(let ((HelloFX (jnew-runtime-class "HelloFX"
:superclass "javafx.application.Application"
:methods `(("main" :void ()
(lambda (this)
(jstatic "launch" +application+ (jobject-class this) (jnew-array "java.lang.String" 1))))
("start" :void ("javafx.stage.Stage")
(lambda (this stage)
(prog* ((java-version (jstatic "getProperty" "java.lang.System" "java.version"))
(javafx-version (jstatic "getProperty" "java.lang.System" "javafx.version"))
(l (jnew +label+ (format nil "Hello, JavaFX ~A, running on Java ~A." javafx-version java-version)))
(scene (jnew +scene+ (jnew +StackPane+ (jarray-from-list `(,l))) 640 480)))
(jcall "setScene" stage scene)
(jcall "show" stage)))))
:access-flags '(:public))))
(jcall "main" (jnew HelloFX)))
上面這一段程式碼可以一行一行對照Java版本的HelloFX。不同的是ABCL的使用不經過javac工具的編譯,因此建立的JAVA類別是屬於運行時建立的runtime-class。
public class HelloFX extends Application
↑
對應
↓
(jnew-runtime-class "HelloFX" :superclass "javafx.application.Application" :access-flags '(:public)
public static void main(String[] args) {
launch();
}
↑
對應
↓
:methods `(("main" :void ()
(lambda (this)
(jstatic "launch" +application+ (jobject-class this) (jnew-array "java.lang.String" 1))))
@Override
public void start(Stage stage) {
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");
Label l = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".");
Scene scene = new Scene(new StackPane(l), 640, 480);
stage.setScene(scene);
stage.show();
}
↑
對應
↓
("start" :void ("javafx.stage.Stage")
(lambda (this stage)
(prog* ((java-version (jstatic "getProperty" "java.lang.System" "java.version"))
(javafx-version (jstatic "getProperty" "java.lang.System" "javafx.version"))
(l (jnew +label+ (format nil "Hello, JavaFX ~A, running on Java ~A." javafx-version java-version)))
(scene (jnew +scene+ (jnew +StackPane+ (jarray-from-list `(,l))) 640 480)))
(jcall "setScene" stage scene)
(jcall "show" stage)))))
不過要是這麼直接執行了,會發生錯誤,因爲還有一些別名常數尚未定義:
(define-symbol-macro +application+ (jclass "javafx.application.Application"))
(define-symbol-macro +scene+ (jclass "javafx.scene.Scene"))
(define-symbol-macro +label+ (jclass "javafx.scene.control.Label"))
(define-symbol-macro +StackPane+ (jclass "javafx.scene.layout.StackPane"))
(define-symbol-macro +stage+ (jclass "javafx.stage.Stage"))
在定義完成別名常數後,在嘗試建立一次動態的HelloFX類別並執行,就會得到與JAVA版本的相同結果。如果關閉視窗會重新回到REPL的互通介面。遺憾的是JavaFX在同一次執行的環境裡,無法建立多個Application,因此也無法在執行一次。不過可以將輸入的內容儲存成一份腳本程式main.lisp,完整內容如下:
;; add classpath
(ignore-errors
(let (libs)
(setf libs
;; (make-pathname :name :wild :directory '(:relative "lib") :type "jar")
(loop for file in (directory "lib/*.jar")
do
(setf file (namestring file))
(print file)
collect file))
(add-to-classpath libs)))
(define-symbol-macro +application+ (jclass "javafx.application.Application"))
(define-symbol-macro +scene+ (jclass "javafx.scene.Scene"))
(define-symbol-macro +label+ (jclass "javafx.scene.control.Label"))
(define-symbol-macro +StackPane+ (jclass "javafx.scene.layout.StackPane"))
(define-symbol-macro +stage+ (jclass "javafx.stage.Stage"))
(let ((HelloFX (jnew-runtime-class "HelloFX"
:superclass "javafx.application.Application"
:methods `(("main" :void ()
(lambda (this)
(jstatic "launch" +application+ (jobject-class this) (jnew-array "java.lang.String" 1))))
("start" :void ("javafx.stage.Stage")
(lambda (this stage)
(prog* ((java-version (jstatic "getProperty" "java.lang.System" "java.version"))
(javafx-version (jstatic "getProperty" "java.lang.System" "javafx.version"))
(l (jnew +label+ (format nil "Hello, JavaFX ~A, running on Java ~A." javafx-version java-version)))
(scene (jnew +scene+ (jnew +StackPane+ (jarray-from-list `(,l))) 640 480)))
(jcall "setScene" stage scene)
(jcall "show" stage)))))
:access-flags '(:public))))
(jcall "main" (jnew HelloFX)))
這樣當我們想要執行ABCL版本的HelloFX,就可以直接載入:
abcl --load main.lisp --eval '(quit)'
執行後應該會看到與 Java 版本相同外觀的 HelloFX 視窗,畫面中央顯示 Java 與 JavaFX 的版本資訊:

關閉視窗後,程式會回到終端機。這表示 ABCL 已經能在執行時建立 JavaFX 應用程式。接下來程式碼開始變長,下一篇將介紹如何使用 Common Lisp 的 ASDF 工程文件,把程式拆分成多個檔案,讓各部分更容易管理。