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

我已经完成了一些关于将Spring DI与Jav​​aFx集成的教程,但我已经碰到了一个简单的例子没有涉及的墙(我无法弄清楚)。

我想要在视图层和表示层之间进行清晰的分离。 我想使用fxml定义可组合视图,使用Spring将它们连接在一起。 这是一个具体的例子:

Dashboard.fxml:

     

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控件使用新实例加载,而不是从spring上下文加载。 它必须使用自己的FXMLLoader?

任何想法如何使自定义控件与Spring一起玩得很好? 我真的不想让控制器/演示者手动连接它们。

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

这是我使用本教程和我自己的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相关的东西准备就绪(即设置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(); } } } } 

你可以看到任何一个Controller(我只有一个,但它可以很多)用@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); } } 

此Controller被声明为@Bean,因此可以与任何其他@Beans(或服务,组件等)进行@Autowired。 现在,例如,您可以让它回答按钮按下并将其字段上执行的逻辑委托给@Service。 声明为Spring创建的控制器的任何组件都将由Spring管理,从而了解上下文。

配置非常简单直接。 如果您有任何疑问,请随时询问。