JavaFX软件devise

在JavaFX应用程序中,必须将javafx.application.Application分类,并且必须从此派生类中调用inheritance的launch()方法(尽pipe它是公共的),否则会引发exception。 launch()方法然后使用reflection来实例化派生类,使得在启动时难以为类成员设置值而不会丢失它们。 所有这一切对我来说都是不寻常的,我想知道为什么启动一个JavaFX应用程序是如此复杂,如果这种软件devise(devise模式?)有一个名称,或者它只是糟糕的devise?

编辑:

更具体地说,我想使用观察者模式,所以我的Java应用程序在文档加载时得到通知,如下所示:

public class MyDocumentLoader extends Application { private ChangeListener<Worker.State> changeListener; public void setChangeListener(ChangeListener<Worker.State> changeListener) { this.changeListener = changeListener; } ... public void loadDocument(String url) { webEngine.getLoadWorker().stateProperty().addListener(changeListener); webEngine.load(url); } ... } 

我需要在几个方法中的callback成员,理想情况下,我可以有多个加载文档的类的实例,所以我可以为不同的URL设置不同的ChangeListeners。

JavaFX支持大量的部署和打包策略,参考。 https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/toc.html ,并有一个标准化的生命周期入口和出口点,简化了所有这些策略的支持。

如果您正在努力初始化您的主应用程序类,由于它被JavaFX启动程序实例化,您最好的select是使用Application.init()和Application.stop()方法,正如James_D指出的那样。

我的猜测是,这个devise是由错误编写的(大量的)Swing应用程序驱动的,“主要” JFrame被实例化并显示在错误的线程上(即不在AWT事件调度线程中)。 我的猜测是,许多Swing应用程序被错误地编写,他们不得不防守地编码框架以避免错误的使用,并且他们想要避免JavaFX的这种情况。

一个FX应用程序强制(好吧,几乎强制,有黑客)开始这种方式使得更难以类似的方式写一个错误的应用程序。 launch方法(以及等效的Oracle JVM启动过程,如果您的Application子类没有main方法和调用launch )会执行相当多的样板工作:启动FX工具包,实例化Application子类并调用其init()方法,然后在FX应用程序线程上实例化主要Stage并将其传递给Application子类的start(...)方法。 这然后确保一切正在正确的线程上运行。

您应该基本上将JavaFX应用程序中的start(...)方法视为在“传统”Java应用程序中replacemain(...)方法,并理解它是在FX Application Thread上调用的。

我的build议是, Application子类应尽可能less; 它应该只是委托给其他东西来实际创build用户界面,然后应该把它放在初级阶段并显示它。 包含一个main方法,除了调用launch(...)作为非JavaFX感知型JVM的后备之外什么都不做。 您应该只有一个Application子类的实例存在于任何JVM中。 这样你的Application子类就没有设置类的成员,所以你所描述的问题根本就不会出现。

如果你使用FXML,这实际上是相当自然的: start(...)方法本质上只是代表FXML控制器对来完成真正的工作。 如果你不使用FXML,创build一个单独的类来做实际的布局等,并委托给它。 看到这个相关的问题得到了同样的想法。

还要注意你的陈述

inheritance的launch()方法虽然是公共的,但必须从这个派生类中调用

并不完全准确,因为有一个重载forms的launch(...)方法 ,您可以在其中指定应用程序的子类。 所以,如果你真的需要的话,你可以创build一个启动FX工具包的存根:

 public class FXStarter extends Application { @Override public void start(Stage primaryStage) { // no-op } } 

现在你可以做:

 public class MyRegularApplication { public static void main(String[] args) { // start FX toolkit: new Thread(() -> Application.launch(FXStarter.class)).start(); // other stuff here... } } 

请注意,直到FX工具包closures, launch才会返回,因此将此调用放在另一个线程中是非常重要的。 这可能会造成竞争条件,在launch(...)之前您可能会尝试做一些需要FX工具包的事情launch(...)已经实际初始化了它,所以您应该防范:

 public class FXStarter extends Application { private static final CountDownLatch latch = new CountDownLatch(1); public static void awaitFXToolkit() throws InterruptedException { latch.await(); } @Override public void init() { latch.countDown(); } @Override public void start(Stage primaryStage) { // no-op } } 

接着

 public class MyRegularApplication { public static void main(String[] args) throws InterruptedException { // start FX toolkit: new Thread(() -> Application.launch(FXStarter.class)).start(); FXStarter.awaitFXToolkit(); // other stuff here... } } 

SSCCE(我只是使用内部类来处理所有事情,所以这很便于运行,但在现实生活中,这些都是独立的类):

 import java.util.Random; import java.util.concurrent.CountDownLatch; import javafx.application.Application; import javafx.application.Platform; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.VBox; import javafx.stage.Stage; public class BackgroundProcessDrivenApp { public static void main(String[] args) throws InterruptedException { Platform.setImplicitExit(false); new Thread(() -> Application.launch(FXStarter.class)).start(); FXStarter.awaitFXToolkit(); new MockProcessor().doStuff() ; } public static class FXStarter extends Application { private static final CountDownLatch latch = new CountDownLatch(1); @Override public void init() { latch.countDown(); } public static void awaitFXToolkit() throws InterruptedException { latch.await(); } @Override public void start(Stage primaryStage) { } } public static class MockProcessor { private final int numEvents = 10 ; public void doStuff() { Random rng = new Random(); try { for (int event = 1 ; event <= numEvents; event++) { // just sleep to mimic waiting for background service... Thread.sleep(rng.nextInt(5000) + 5000); String message = "Event " + event + " occurred" ; Platform.runLater(() -> new Messager(message).showMessageInNewWindow()); } } catch (InterruptedException exc) { Thread.currentThread().interrupt(); } finally { Platform.setImplicitExit(true); } } } public static class Messager { private final String message ; public Messager(String message) { this.message = message ; } public void showMessageInNewWindow() { Stage stage = new Stage(); Label label = new Label(message); Button button = new Button("OK"); button.setOnAction(e -> stage.hide()); VBox root = new VBox(10, label, button); root.setAlignment(Pos.CENTER); Scene scene = new Scene(root, 350, 120); stage.setScene(scene); stage.setAlwaysOnTop(true); stage.show(); } } }