JavaFXFXML如何将Spring DI与嵌套的自定义控件一起使用?

 2023-02-16    383  

问题描述

我已经经过了许多教程,将春季di与javafx集成,但我已经撞到了一堵墙,简单的示例不涵盖(我无法弄清楚).

我想要在视图和演示层之间进行清洁分离.我想使用FXML来定义可组合的视图,然后弹簧将其全部连接在一起.这是一个具体示例:

JavaFXFXML如何将Spring DI与嵌套的自定义控件一起使用?

dashboard.fxml:

<GridPane fx:id="view"
          fx:controller="com.scrub.presenters.DashboardPresenter"
          xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml">
   <children>
      <TransactionHistoryPresenter fx:id="transactionHistory"  />
   </children>
</GridPane>

main.java:

public void start(Stage primaryStage) throws Exception{
    try {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppFactory.class);
        SpringFxmlLoader loader = context.getBean(SpringFxmlLoader.class);
        primaryStage.setScene(new Scene((Parent)loader.load("/views/dashboard.fxml")));
        primaryStage.setTitle("Hello World");
        primaryStage.show();
    } catch(Exception e) {
        e.printStackTrace();
    }
}

springfxmlloader.java:

public class SpringFxmlLoader {

    @Autowired
    ApplicationContext context;

    public Object load(String url) {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource(url));
            loader.setControllerFactory(new Callback<Class<?>, Object>() {
                @Override
                public Object call(Class<?> aClass) {
                    return context.getBean(aClass);
                }
            });
            return loader.load();
        } catch(Exception e) {
            e.printStackTrace();
            throw new RuntimeException(String.format("Failed to load FXML file '%s'", url));
        }
    }
}

因此,当 dashboardPresenter 加载时,springfxmlloader正确地向控制器注入了loader.setControllerFactory.

但是,自定义 TransActionHistoryPresenter 控制装置有新实例,而不是来自春季上下文.它必须使用自己的fxmlloader?

有什么想法如何使自定义控件与春季发挥好作用?我真的不想沿着控制器/主持人手动将它们接线的路线.

推荐答案

这里的主要问题是确保在Javafx应用程序的同一线程上初始化Spring.这通常意味着必须在Javafx应用程序线程上执行弹簧代码.当然可以通过自己的线程执行其他耗时的工作.

这是我使用这个教程和我自己对 Spring Boot :

@SpringBootApplication
@ImportResource("classpath:root-context.xml")
public class JavaFXSpringApplication extends Application {

  private static final Logger log = LoggerFactory.getLogger(JavaFXSpringApplication.class);

  private Messages messages;

  private static String[] args;

  @Override
  public void start(final Stage primaryStage) {

    // Bootstrap Spring context here.
    ApplicationContext context = SpringApplication.run(JavaFXSpringApplication.class, args);
    messages = context.getBean(Messages.class);
    MainPaneController mainPaneController = context.getBean(MainPaneController.class);

    // Create a Scene
    Scene scene = new Scene((Parent) mainPaneController.getRoot());
    scene.getStylesheets().add(getClass().getResource("/css/application.css").toExternalForm());

    // Set the scene on the primary stage
    primaryStage.setScene(scene);
    // Any other shenanigans on the primary stage...
    primaryStage.show();
  }

  public static void main(String[] args) {

    JavaFXSpringApplication.args = args;

    launch(args);
  }
}

此类既是JAVAFX应用程序入口点,又是Spring Boot初始化入口点,因此围绕Varargs的传递.导入外部配置文件使得在准备其他与春季相关的东西的过程中保持主班的整齐(即设置Spring Data JPA,资源束,安全…)

在Javafx” start”方法上,主要的应用程序context是初始化并生存的.此时使用的任何BEAN都必须通过applicationContext.getBean()检索,但是所有其他注释的bean(前提是在此主类的后代包中)都可以一如既往地访问.

特别是在此其他类中声明控制器:

@Configuration
@ComponentScan
public class ApplicationConfiguration {

  @Bean
  public MainPaneController mainPaneController() throws IOException {
    return (MainPaneController) this.loadController("path/to/MainPane.fxml");
  }

  protected Object loadController(String url) throws IOException {
    InputStream fxmlStream = null;
    try {
      fxmlStream = getClass().getResourceAsStream(url);
      FXMLLoader loader = new FXMLLoader();
      loader.load(fxmlStream);
      return loader.getController();
    } finally {
      if (fxmlStream != null) {
        fxmlStream.close();
      }
    }
  }
}

您可以看到任何控制器(我只有一个,但可以很多)用@bean注释,整个类都是配置.

最后,这里是mainpanecontroller.

public class MainPaneController {

  @Autowired
  private Service aService;

  @PostConstruct
  public void init() {
    // ...stuff to do with components...
  }

  /*
   * FXML Fields
   */
  @FXML
  private Node root;

  @FXML
  private TextArea aTextArea;

  @FXML
  private TextField aTextField;

  @FXML
  private void sayButtonAction(ActionEvent event) {
    aService.doStuff(aTextArea, aTextField);
  }
}

该控制器被声明为@bean,因此可以与其他任何@beans(或服务,组件等)一起@Autowed.现在,例如,您可以将其回答按钮按下,并在其字段上执行的逻辑委托到@Service.声明为弹簧创建的控制器的任何组件都将通过弹簧管理,从而意识到上下文.

这都是非常简单而直接的配置.随时问您是否有疑问.

其他推荐答案

这是可能的.
创建可提供弹簧豆的自定义BuilderFactory.然后将其分配给FXMLLoader fxmlLoader.setBuilderFactory(beanBuilderFactory);

@Component
public class BeanBuilderFactory implements BuilderFactory {

  @Autowired
  private ConfigurableApplicationContext context;

  public BeanBuilderFactory() {

  }

  @Override
  public Builder<?> getBuilder(Class<?> type) {
    try {
      Object bean = this.context.getBean(type);
      if (bean.getClass().isAssignableFrom(type))
        return new Builder() {
          @Override
          public Object build() {
            return bean;
          }
        };
      else
        return null;
    } catch (BeansException e) {
      return null;
    }
  }
}

以上所述是小编给大家介绍的JavaFXFXML如何将Spring DI与嵌套的自定义控件一起使用?,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对77isp云服务器技术网的支持!

原文链接:https://77isp.com/post/34018.html

=========================================

https://77isp.com/ 为 “云服务器技术网” 唯一官方服务平台,请勿相信其他任何渠道。