在 JavaFX 中,FXMLLoader
使用 0-arg 构造函数在 .fxml 文件中自动创建指定的控制器。FXMLLoader#setController
它还提供了使用或手动设置/创建控制器的方法FXMLoader#setControllerFactory
。
我现在的问题是它们之间有什么区别?是否存在一种情况比另一种更好?
这两种方法的文档并没有真正回答我的问题。
在 JavaFX 中,FXMLLoader
使用 0-arg 构造函数在 .fxml 文件中自动创建指定的控制器。FXMLLoader#setController
它还提供了使用或手动设置/创建控制器的方法FXMLoader#setControllerFactory
。
我现在的问题是它们之间有什么区别?是否存在一种情况比另一种更好?
这两种方法的文档并没有真正回答我的问题。
load(...)
当您使用中定义的方法之一加载 FXML 文件时FXMLLoader
,在默认设置中,将创建两个重要的对象FXMLLoader
:root
于 FXML 结构的根元素controller
是一个对象(特定实例),root
通过 以各种方式连接到FXMLLoader
。因为您无法使用 XML 属性的字符串值(即提供给 的值)指定实际对象,所以默认的工作方式是在属性中指定控制器类
fx:controller="..."
的名称。默认情况下,通过反射调用指定类的零参数构造函数来创建控制器实例。fx:controller
FXMLLoader
将FXML 文件中带注释的元素注入到其创建的控制器实例中的匹配字段中
FXMLLoader
。@FXML
完成此操作后,它会调用该initialize()
方法(如果有),从而允许相当直观地初始化控制器和关联视图的属性。值得注意的一些后果(并非所有后果都与这个问题相关):
FXMLLoader.load(...)
或其变体,将创建控制器类的多个实例。这(几乎?)总是您想要的,但值得注意的是,状态不会从一个控制器实例持续到另一个控制器实例。希望以某种形式向控制器提供数据是很常见的。例如,在类似 MVC/MVP/MVVM 的架构中,控制器(或等效物)通常需要访问模型对象。您可以通过在加载 FXML 后检索对控制器的引用并传递它,使用默认设置来实现此目的:
虽然这种方法是可行的,但它有几个缺点:
initialize()
调用该方法后)才会提供模型。这意味着必须执行的任何依赖于模型的初始化都必须作为调用的结果来完成setModel()
,而不是在更直观的地方(例如方法initialize()
)。final
控制器类中(这通常是自然且理想的),并且没有优雅的方法来强制仅设置模型一次。前两个缺点可以通过
setController(...)
在加载 FXMLFXMLLoader
之前调用来弥补。然而,这也带来了新的缺点:请注意,在这种情况下,我们能够将模型实例传递给控制器构造函数。这意味着模型可以是最终的,并且在控制器实例化后即可使用。所以控制器可以看起来像:
当我们调用时会发生的情况
setController(...)
是,我们完全替换了创建控制器的默认机制,而是提供了我们选择的控制器实例。因此,我们现在无法fx:controller
在 FXML 文件中指定属性;它充其量是多余的,并且可能是矛盾的,如果我们这样做,就会抛出异常。上面的最后两个缺点(视图和控制器类之间代码的冗长和显式连接)仍然存在,尽管我们已经解决了其他两个可能更重要的问题。然而,无法在 FXML 文件中指定控制器类意味着我们引入了一个新的缺点:
相比之下,控制器工厂允许我们使用创建控制器实例的默认机制(即在属性中指定类
fx:controller
并让FXMLLoader
提供实例),但允许我们配置实例的创建方式。控制器工厂是 a ,它本质上是一个将 a (类型)映射到将用作控制器的对象的
Callback<Class<?>, Object>
函数。Class<?>
它应该是所提供的类的实例,但没有对此进行实际检查。前面的例子可以重写
在这种情况下,我们可以(并且必须)将其包含
fx:controller="com.example.MyController"
在 FXML 文件中。我们的控制器看起来与以前的版本完全相同,final
模型实例可以在initialize()
方法中访问。我们的开发工具“知道”哪个类用于控制器,因此可以检查方法和字段名称等。请注意,我们仍然在加载代码中硬编码控制器类名称。我们可以使用反射来制作一个通用的控制器工厂。此实现查看提供的控制器类,尝试查找采用模型实例的构造函数,并使用该构造函数(如果有)。如果找不到这样的构造函数,它会尝试使用默认的无参数构造函数。如果两者都不存在,则会抛出异常。显然,这里的逻辑是任意的,可以是您选择的任何逻辑。
这有点冗长,但我们可以编写一次并在应用程序中重复使用它。FXML 文件是“自然的”,看起来与默认设置中的完全相同,并且控制器可以看起来像此处发布的那样(带有采用模型实例的构造函数等)。可以说,使用反射在性能和缺乏编译时类型检查方面存在普遍的缺点,但
FXMLLoader
已经使用了如此多的反射,因此这里的额外成本可能很小。值得注意的一点是对于嵌套的 FXML 文件,
<fx:include>
在 FXML 中使用。当加载包含的 FXML 文件时,将使用与显式加载的包含该文件的 FXML 文件相同的控制器工厂。因此,如果您使用控制器工厂,它必须足够灵活,以便为“主”FXML 和任何包含的 FXML 提供正确的控制器。如果我没记错的话,控制器工厂是在 2.1 版本中引入的,以响应想要使用依赖项注入框架来管理 JavaFX 控制器的人的请求。(可能是 Adam Bien,当时他正在编写现已无人维护的afterburner 框架,但我的记忆力不太好。)
控制器工厂在这种情况下工作得很好。该网站上有几个 Spring-JavaFX 示例,但总体思路是:
本质上,我们只是告诉
FXMLLoader
从 spring 应用程序上下文中获取控制器。基于 Spring 的控制器看起来像
此处可能重复:JavaFX:需要帮助理解 setControllerFactory
正如副本中所解释的,
setControllerFactory
当您使用依赖项注入框架并希望使用自定义工厂来注入依赖项时,这非常有用。如果不使用任何依赖注入的框架,使用setController
.