解决方案 1:通过应用程序传递对向下的引用。HostServices
这可能类似于你所期望的“相当痛苦”的方法。但基本上你会做这样的事情:
public void start(Stage primaryStage) throws Exception {
    FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml"));
    Parent root = loader.load();
    MainController controller = loader.getController();
    controller.setHostServices(getHostServices());
    primaryStage.setScene(new Scene(root));
    primaryStage.show();
}
然后在:MainController
public class MainController {
    private HostServices hostServices ;
    public HostServices getHostServices() {
        return hostServices ;
    }
    public void setHostServices(HostServices hostServices) {
        this.hostServices = hostServices ;
    }
    @FXML
    private void showDialog() {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("dialog.fxml"));
        Parent dialogRoot = loader.load();
        DialogController dialogController = loader.getController();
        dialogController.setHostServices(hostServices);
        Stage dialog = new Stage();
        dialog.setScene(new Scene(dialogRoot));
        dialog.show();
    }
}
当然看起来像这样:DialogController
public class DialogController {
    @FXML
    private Hyperlink hyperlink ;
    private HostServices hostServices ;
    public HostServices getHostServices() {
        return hostServices ;
    }
    public void setHostServices(HostServices hostServices) {
        this.hostServices = hostServices ;
    }
    @FXML
    private void openURL() {
        hostServices.openDocument(hyperlink.getText());
    }
}
解决方案 2:使用控制器工厂将主机服务推送到控制器。
这是上述内容的更清晰版本。不是获取控制器并调用方法来初始化它们,而是通过controrFactory配置它们的创建,并通过将对象传递给控制器的构造函数来创建控制器(如果它具有合适的构造函数):HostServices
public class HostServicesControllerFactory implements Callback<Class<?>,Object> {
    private final HostServices hostServices ;
    public HostServicesControllerFactory(HostServices hostServices) {
        this.hostServices = hostServices ;
    }
    @Override
    public Object call(Class<?> type) {
        try {
            for (Constructor<?> c : type.getConstructors()) {
                if (c.getParameterCount() == 1 && c.getParameterTypes()[0] == HostServices.class) {
                    return c.newInstance(hostServices) ;
                }
            }
            return type.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
现在,在加载 FXML 时使用控制器工厂:
public void start(Stage primaryStage) throws Exception {
    FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml"));
    loader.setControllerFactory(new HostServicesControllerFactory(getHostServices()));
    Parent root = loader.load();
    primaryStage.setScene(new Scene(root));
    primaryStage.show();
}
并定义要用作构造函数参数的控制器:HostServices
public class MainController {
    private final HostServices hostServices ;
    public MainController(HostServices hostServices) {
        this.hostServices = hostServices ;
    }
    @FXML
    private void showDialog() {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("dialog.fxml"));
        loader.setControllerFactory(new HostServicesControllerFactory(hostServices));
        Parent dialogRoot = loader.load();
        Stage dialog = new Stage();
        dialog.setScene(new Scene(dialogRoot));
        dialog.show();
    }    
}
当然
public class DialogController {
    @FXML
    private Hyperlink hyperlink ;
    private final HostServices hostServices ;
    public DialogController(HostServices hostServices) {
        this.hostServices = hostServices ;
    }
    @FXML
    private void openURL() {
        hostServices.openDocument(hyperlink.getText());
    }
}
解决方案3:这是一个非常丑陋的解决方案,我强烈建议不要使用它。我只是想包含它,这样我就可以表达出来,而不会在发布时冒犯其他人。将主机服务存储在静态字段中。
public class MainApp extends Application {
    private static HostServices hostServices ;
    public static HostServices getHostServices() {
        return hostServices ;
    }
    public void start(Stage primaryStage) throws Exception {
        hostServices = getHostServices();
        Parent root = FXMLLoader.load(getClass().getResource("main.fxml"));
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }
}
那么你只是做
MainApp.getHostServices().showDocument(hyperlink.getText());
随时随地满足您的需求。此处的问题之一是,对于需要访问主机服务的所有控制器,您都引入了对应用程序类型的依赖关系。
解决方案 4定义单例 。这比解决方案3更好,但仍然不是一个好的解决方案imo。HostServicesProvider
public enum HostServicesProvider {
    INSTANCE ;
    private HostServices hostServices ;
    public void init(HostServices hostServices) {
        if (this.hostServices != null) {
            throw new IllegalStateException("Host services already initialized");
        }
        this.hostServices = hostServices ;
    }
    public HostServices getHostServices() {
        if (hostServices == null) {
            throw new IllegalStateException("Host services not initialized");
        }
        return hostServices ;
    }
}
现在你只需要
public void start(Stage primaryStage) throws Exception {
    HostServicesProvider.INSTANCE.init(getHostServices());
    // just load and show main app...
}
和
public class DialogController {
    @FXML
    private Hyperlink hyperlink ;
    @FXML
    private void openURL() {
        HostServicesProvider.INSTANCE.getHostServices().showDocument(hyperlink.getText());
    }
}
解决方案 5使用依赖关系注入框架。这可能不适用于您当前的用例,但可能会让您了解这些(相对简单)框架的强大程度。
例如,如果您使用的是afterburner.fx,则只需执行
Injector.setModelOrService(HostServices.class, getHostServices());
在您的应用程序或方法中,然后start()init()
public class DialogPresenter {
    @Inject
    private HostServices hostServices ;
    @FXML
    private Hyperlink hyperlink ;
    @FXML
    private void showURL() {
        hostServices.showDocument(hyperlink.getText());
    }
}
这里是使用Spring的一个例子。