java 使用 Spring Boot 启动 JavaFX 2

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35048238/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 23:40:52  来源:igfitidea点击:

Launching JavaFX 2 with Spring Boot

javaspringspring-bootjavafx-2

提问by Infinito

I am trying to create new application with JavaFX 2 and Spring Boot, but so far my simple app (like hello world) isn't running because of "root is null" in MainPaneController.

我正在尝试使用 JavaFX 2 和 Spring Boot 创建新应用程序,但到目前为止我的简单应用程序(如 hello world)没有运行,因为MainPaneController.

MainPaneController class:

MainPaneController 类:

public class MainPaneController implements Initializable {

    public static final String VIEW = "/fxml/Scene.fxml";

    @FXML
    private Node root;

    @FXML
    private Label label;

    @PostConstruct
    public void init() {
    }

    public Node getRoot() {
        return root;
    }

    @FXML
    private void handleButtonAction(ActionEvent event) {
        System.out.println("You clicked me!");
        label.setText("Hello World!");
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }

}

Main class FxBootApplication:

主类 FxBootApplication:

@SpringBootApplication
public class FxBootApplication extends Application {

    private static String[] args;

    @Override
    public void start(final Stage stage) throws Exception {
        //Parent root = FXMLLoader.load(getClass().getResource("/fxml/Scene.fxml"));
        // Bootstrap Spring context here.
        ApplicationContext context = SpringApplication.run(FxBootApplication.class, args);

        MainPaneController mainPaneController = context.getBean(MainPaneController.class);

        Scene scene = new Scene((Parent) mainPaneController.getRoot()); // error here

        //Scene scene = new Scene(root);
        //scene.getStylesheets().add("/styles/Styles.css");
        stage.setTitle("JavaFX and Maven");
        stage.setScene(scene);
        stage.show();
    }

    /**
     * The main() method is ignored in correctly deployed JavaFX application.
     * main() serves only as fallback in case the application can not be
     * launched through deployment artifacts, e.g., in IDEs with limited FX
     * support. NetBeans ignores main().
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        FxBootApplication.args = args;
        launch(args);
    }

}

ApplicationConfiguration class:

应用配置类:

@Configuration
public class ApplicationConfiguration {

    @Bean
    public MainPaneController mainPaneController() throws IOException {
        MainPaneController mpc = (MainPaneController) loadController(MainPaneController.VIEW);
        return mpc;
    }

    public <T> T loadController(String url) throws IOException {
        try (InputStream fxmlStream = getClass().getResourceAsStream(url)) {
            FXMLLoader loader = new FXMLLoader(getClass().getResource(url));
            //FXMLLoader.load(url);
            loader.load(fxmlStream);
            return loader.getController();
        }
    }

}

Error is while I am trying to get root for Scene by controller.getRoot();

错误是当我试图通过以下方式为 Scene 获取 root 时controller.getRoot()

I followed the solution proposed here -> JavaFX fxml - How to use Spring DI with nested custom controls?but eventually is not working for me at all. Should I somehow initialize this root before?

我遵循了此处提出的解决方案 -> JavaFX fxml - 如何将 Spring DI 与嵌套的自定义控件一起使用?但最终根本不适合我。我之前应该以某种方式初始化这个根吗?

回答by dzim

Unfortunately I don't find the link to the solution, that works for me, anymore... But: I have the code, which I tested to some extend.

不幸的是,我找不到解决方案的链接,这对我有用,不再......但是:我有代码,我对其进行了某种程度的测试。

First you need your Application class:

首先你需要你的 Application 类:

package eu.dzim.yatafx;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import eu.dzim.yatafx.model.app.ApplicationModel;
import eu.dzim.yatafx.spring.service.FXMLLoaderService;
import eu.dzim.yatafx.util.Utils;
import eu.dzim.yatafx.util.res.StringResource;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class YataFXApplication extends Application implements CommandLineRunner {

    private static final Logger LOG = LogManager.getLogger(FileSyncFXApplication.class);

    @Override
    public void run(String... args) {
        // something to call prior to the real application starts?
    }

    private static String[] savedArgs;

    // locally stored Spring Boot application context
    private ConfigurableApplicationContext applicationContext;

    // we need to override the FX init process for Spring Boot
    @Override
    public void init() throws Exception {

        // set Thread name
        Thread.currentThread().setName("main");

        // LOG.debug("Init JavaFX application");
        applicationContext = SpringApplication.run(getClass(), savedArgs);
        applicationContext.getAutowireCapableBeanFactory().autowireBean(this);
    }

    // ... and close our context on stop of the FX part
    @Override
    public void stop() throws Exception {
        // LOG.debug("Stop JavaFX application");
        super.stop();
        applicationContext.close();
    }

    protected static void launchApp(Class<? extends FileSyncFXApplication> appClass, String[] args) {
        FileSyncFXApplication.savedArgs = args;
        Application.launch(appClass, args);
    }

    @Autowired
    private FXMLLoaderService mFXMLLoaderService;

    @Autowired
    private ApplicationModel mApplicationModel;

    @Override
    public void start(Stage primaryStage) {

        // set Thread name
        Thread.currentThread().setName("main-ui");

        try {
            FXMLLoader loader = mFXMLLoaderService.getLoader(Utils.getFXMLResource("Root"), StringResource.getResourceBundle());

            Pane root = loader.load();

            Scene scene = new Scene(root, 1200, 800);
            scene.getStylesheets().add("/eu/dzim/filesyncfx/ui/application.css");

            primaryStage.setScene(scene);
            primaryStage.setOnCloseRequest(windowEvent -> {

                LOG.debug("tear down JavaFX application");
                // mApplicationModel.setLoggedIn(!mLoginService.logout());

                // orderly shut down FX
                Platform.exit();

                // But: there might still be a daemon thread left over from OkHttp (some async dispatcher)
                // so assume everything is fine and call System.exit(0)
                System.exit(0);
            });

            primaryStage.show();

        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
        }
    }

    public static void main(String[] args) throws Exception {

        // SpringApplication.run(SampleSimpleApplication.class, args);

        savedArgs = args;
        Application.launch(FileSyncFXApplication.class, args);
    }
}

I used org.springframework.boot:spring-boot-starter-parent:1.3.3.RELEASEas the base.

我用作org.springframework.boot:spring-boot-starter-parent:1.3.3.RELEASE基地。

Note the FXMLLoaderServiceinterface I autowired here:

注意FXMLLoaderService我在这里自动连接的接口:

package eu.dzim.yatafx.spring.service;

import java.net.URL;
import java.util.ResourceBundle;

import javafx.fxml.FXMLLoader;

public interface FXMLLoaderService {

    FXMLLoader getLoader();

    FXMLLoader getLoader(URL location);

    FXMLLoader getLoader(URL location, ResourceBundle resourceBundle);
}

The implementation looks like this:

实现如下所示:

package eu.dzim.yatafx.spring.service.impl;

import java.net.URL;
import java.util.ResourceBundle;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import eu.dzim.yatafx.spring.service.FXMLLoaderService;
import javafx.fxml.FXMLLoader;
import javafx.util.Callback;

@Component
@Scope("singleton")
public class FXMLLoaderServiceImpl implements FXMLLoaderService {

    private static final Logger LOG = LogManager.getLogger(FXMLLoaderServiceImpl.class);

    @Autowired
    private ConfigurableApplicationContext context;

    @PostConstruct
    private void postConstruct() {
        LOG.debug("PostConstruct: set up " + getClass().getName());
    }

    @Override
    public FXMLLoader getLoader() {
        FXMLLoader loader = new FXMLLoader();
        loader.setControllerFactory(new Callback<Class<?>, Object>() {
            @Override
            public Object call(Class<?> param) {
                return context.getBean(param);
            }
        });
        return loader;
    }

    @Override
    public FXMLLoader getLoader(URL location) {
        FXMLLoader loader = new FXMLLoader(location);
        loader.setControllerFactory(new Callback<Class<?>, Object>() {
            @Override
            public Object call(Class<?> param) {
                return context.getBean(param);
            }
        });
        return loader;
    }

    @Override
    public FXMLLoader getLoader(URL location, ResourceBundle resourceBundle) {
        FXMLLoader loader = new FXMLLoader(location, resourceBundle);
        loader.setControllerFactory(new Callback<Class<?>, Object>() {
            @Override
            public Object call(Class<?> param) {
                return context.getBean(param);
            }
        });
        return loader;
    }

    @PreDestroy
    private void preDestroy() {
        LOG.debug("PreDestroy: tear down " + getClass().getName());
    }
}

The usage is already displayed in the Application class: Just @Autowire the service and create the sub-views from there. Since I rely almost exclusivly on FXML, this one is importand to me, since I want to use all that nice DI stuff in my Controllers.

用法已经显示在 Application 类中:只需 @Autowire 服务并从那里创建子视图。因为我几乎完全依赖 FXML,所以这个对我来说很重要,因为我想在我的控制器中使用所有那些好的 DI 东西。

Best example is the global Application, wich holds some JavaFX properties I attach to in the controller classes.

最好的例子是全局应用程序,其中包含我附加到控制器类中的一些 JavaFX 属性。

While the shown application is more a stub (the application and the FXML service), I had a fun project where I used this approach in concurrency to a parallel developed Web application, that was RESTing on a "Micro"service I developed at work.

虽然显示的应用程序更像是一个存根(应用程序和 FXML 服务),但我有一个有趣的项目,我在该项目中使用这种方法与并行开发的 Web 应用程序并发,这是我在工作中开发的“微”服务上的 REST。

Hope the code is enough an example to get it to work on your side. Please just ask, if you have more questions.

希望代码足以作为一个例子,让它在你身边工作。如果您有更多问题,请提问。

Cheers, Daniel

干杯,丹尼尔

Edit: I think the mistake in your code is simply the part in the FXML service. I have this injected ConfigurableApplicationContext which I use to create the controller from. You would need a ControllerFactory for this.

编辑:我认为您的代码中的错误只是 FXML 服务中的一部分。我有这个注入的 ConfigurableApplicationContext,我用它来创建控制器。为此,您需要一个 ControllerFactory。



There are some third party samples to assist with JavaFX and Spring Boot integration:

有一些第三方示例可以帮助集成 JavaFX 和 Spring Boot: