簡體   English   中英

JavaFX fxml - 如何將 Spring DI 與嵌套的自定義控件一起使用?

[英]JavaFX fxml - How to use Spring DI with nested custom controls?

我已經閱讀了許多關於將 Spring DI 與 JavaFx 集成的教程,但我遇到了簡單示例沒有涵蓋的牆(我無法弄清楚)。

我希望視圖層和表示層之間完全分離。 我想使用 fxml 來定義可組合視圖和 Spring 將它們連接在一起。 這是一個具體的例子:

儀表板.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>

主.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控件加載了一個新實例,而不是來自 spring 上下文。 它必須使用自己的 FXMLLoader 嗎?

任何想法如何使自定義控件與 Spring 一起玩得很好? 我真的不想走讓控制器/演示者手動連接它們的道路。

這里的主要問題是確保 Spring 在 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 初始化入口點,因此傳遞了可變參數。 導入外部配置文件可以更輕松地保持主類整潔,同時准備好其他與 Spring 相關的東西(即設置 Spring Data JPA、資源包、安全性......)

在 JavaFX 的“start”方法中,主 ApplicationContext 被初始化並存在。 此時使用的任何 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(或服務、組件等)進行@Autowired。 例如,現在您可以讓它響應按鈕按下並將在其字段上執行的邏輯委托給@Service。 任何聲明到 Spring 創建的控制器中的組件都將由 Spring 管理,從而了解上下文。

配置起來非常簡單和直接。 如果您有任何疑問,請隨時提問。

有可能的。 創建提供 spring bean 的自定義 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;
    }
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM