我已经完成了许多关于将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应用程序线程上执行;其他耗时的作业当然可以在自己的线程上执行。
@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();
}
}
}
}
您可以看到任何控制器(我只有一个,但可以有很多)都用@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,因此它可以与任何其他@ Bean(或服务、组件等)自动连接。).例如,现在您可以让它响应按钮的按下,并将对其字段执行的逻辑委托给@Service。Spring创建的控制器中声明的任何组件都将由Spring管理,因此知道上下文。
配置起来非常简单明了。如果你有任何疑问,请随时提问。
这是可能的。创建交付spring beans的定制BuilderFactory。然后赋给< code > fxml loader fxml loader . 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;
}
}
}