在JavaFX应用程序中,必须对javafx.application.Application进行子类化,并且必须从此派生类中调用继承的launch()方法,尽管它是公共的,否则抛出异常.然后,launch()方法使用反射来实例化派生类,这使得在启动时很难为类成员设置值而不会丢失它们.所有这些对我来说都是不寻常的,我想知道为什么启动JavaFX应用程序是如此复杂,如果那种软件设计(设计模式?)有一个名字,或者它只是糟糕的设计?
编辑:
更具体地说,我想使用观察者模式,因此我的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);
}
...
}
Run Code Online (Sandbox Code Playgroud)
我需要几个方法中的回调成员,理想情况下我可以有多个加载文档的类实例,因此我可以为不同的URL设置不同的ChangeListener.
我的猜测是,这个设计是由(大量)错误编写的Swing应用程序激发的,其中"主要" JFrame被实例化并显示在错误的线程上(即不在AWT事件调度线程上).我的猜测是,如此多的Swing应用程序被错误地编写,他们必须针对不正确的使用情况进行防御性编码,并且他们希望避免使用JavaFX这种情况.
强制(好吧,几乎强迫,有黑客攻击)FX应用程序以这种方式启动使得以类似的方式错误地编写应用程序变得更加困难.该launch方法(以及等效的Oracle JVM启动过程,如果你有一个Application没有main方法和调用的子类launch)做了相当多的样板工作:它启动FX工具包,实例化子Application类并调用其init()方法,然后在FX应用程序上线程它实例化主要Stage并将其传递给Application子类的start(...)方法.然后确保一切都在正确的线程上运行.
您应该基本上start(...)将JavaFX应用程序中的main(...)方法视为"传统"Java应用程序中方法的替代,并理解它是在FX应用程序线程上调用的.
我的建议是Application子类应尽可能小; 它应该委托给其他人来实际创建UI,然后将它放在主要阶段并显示它.包括一个main除了调用launch(...)非JavaFX感知JVM的回退之外什么都不做的方法.您应该只有一个Application子类的一个实例存在于任何JVM中.这样,您的Application子类就没有要设置的类成员,因此您不会出现所描述的问题.
如果你使用FXML,这实际上是相当自然的:该start(...)方法基本上只委托FXML控制器对来完成实际工作.如果您不使用FXML,请创建一个单独的类来执行实际布局等,并委托给它.看到这个相关的问题,它有同样的想法.
另请注意您的陈述
继承的launch()方法虽然是公共的,但必须在此派生类中调用
并不完全准确,因为有一个重载形式的launch(...)方法,您可以在其中指定应用程序子类.所以,如果你真的需要,你可以创建一个存根来启动FX工具包:
public class FXStarter extends Application {
@Override
public void start(Stage primaryStage) {
// no-op
}
}
Run Code Online (Sandbox Code Playgroud)
现在你可以这样做:
public class MyRegularApplication {
public static void main(String[] args) {
// start FX toolkit:
new Thread(() -> Application.launch(FXStarter.class)).start();
// other stuff here...
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,launch在FX工具包关闭之前不会返回,因此必须将此调用放在另一个线程中.这可能会创建竞争条件,您可能会在launch(...)实际初始化之前尝试执行需要FX工具包的操作,因此您应该防范:
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
}
}
Run Code Online (Sandbox Code Playgroud)
然后
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...
}
}
Run Code Online (Sandbox Code Playgroud)
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();
}
}
}
Run Code Online (Sandbox Code Playgroud)