我使用JavaFX应用程序并使用FXML来实现MVC模式。我已经完成了概念验证,现在开始创建JavaFX用户界面。
在我之前使用spring MVC的经历中,通常会创建服务并通过注释将它们注入控制器类。但是对于JavaFX,我找不到任何建议如何做到这一点。另外我不确定我是否必须将服务放到控制器上,或者从控制器调用主类方法。第二个解决方案在主应用程序类中保存服务引用。
请注意我的应用程序在并发线程中运行服务类。所以它们都实现了Runnable接口
答案 0 :(得分:1)
我会避免让控制器需要引用主应用程序类,因为它引入了一个非常必要的额外依赖。因此,让每个控制器保持对服务对象的引用。
要向控制器提供服务,您基本上可以使用this question中列出的技术之一。
基本上有三种方法可以做到这一点:
创建控制器并直接在FXMLLoader
在此版本中,您不在FXML文件的根元素中使用fx:controller
属性(这样做会导致抛出异常)。
鉴于
public interface Service { ... }
和
public class SomeController {
private final Service service ;
public SomeController(Service service) {
this.service = service ;
}
// ...
}
然后您可以使用
加载FXML文件Service service = ... ;
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml/file.fxml"));
SomeController controller = new SomeController(Service.class);
loader.setController(controller);
Parent uiRoot = loader.load();
从FXMLLoader
检索控制器并设置服务
如果您希望能够使用fx:controller
属性,则您的控制器类必须具有无参数构造函数。在这种情况下,您可以在FXMLLoader
完成加载后在控制器上设置服务。这看起来像:
public class SomeController {
private Service service ;
public void initService(Service service) {
this.service = service ;
// update UI with values from service...
}
// ...
}
请注意,在这里您可能需要重构initialize()
方法中的某些代码,因为该代码可能依赖于服务,而在调用initialize()
时不会设置该服务。只需将任何此类代码移至initService(...)
方法即可。现在加载FXML文件看起来像
Service service = ... ;
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml/file.fxml"));
Parent uiRoot = loader.load();
SomeController controller = loader.getController();
controller.initService(service);
使用控制器工厂
第三种方法使用控制器工厂。这稍微复杂一些,但有一些优点。特别是,如果您的fxml文件使用fx:include
标记,则在加载包含的fxml文件时将重用控制器工厂,因此这些控制器也可以初始化服务对象。使用上述两种方法管理包含的fxml文件是可能的,但有点复杂。
控制器工厂本质上是一个将Class<?>
映射到应该使用的控制器的函数(可能是该类中的一个,尽管有这个要求)。默认控制器工厂只调用newInstance()
对象上的Class<?>
(这就是你需要一个无参数构造函数的原因)。这是一个通用控制器工厂实现,它调用构造函数,如果存在,则调用Service
参数,如果不存在,则调用no-arg构造函数。
Service service = ... ;
Callback<Class<?>, Object> controllerFactory = type -> {
try {
for (Constructor<?> c : type.getConstructors()) {
if (c.getParameterCount() == 1
&& c.getParameterTypes()[0] == Service.class) {
return c.newInstance(service);
}
}
return type.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
};
您可以创建一次并将其用于您加载的任何FXML(请注意它引用单个Service
实例):
FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml/file.fxml"));
loader.setControllerFactory(controllerFactory);
Parent uiRoot = loader.load();
这将与fx:controller
属性一起使用,即使它引用了带有Service
参数的构造函数的类(例如上面的第一个控制器示例)。
如果你习惯了依赖注入框架,你可能会对Adam Bien的afterburner.fx感兴趣。这可以通过设置一个控制器工厂来检查@Inject
注释的控制器类并在控制器上设置这些值,因此您所要做的就是在控制器中注释服务字段并遵循特定的afterburner.fx命名约定,一切都是自动发生的。
我还建议由Adam Bien推荐this article,它讨论了与控制器中的服务进行通信的一些策略(包括处理并发问题)。