JavaFX的。服务类参考在哪里?控制器或主应用程序入门级?

时间:2015-06-13 11:16:36

标签: java javafx fxml

我使用JavaFX应用程序并使用FXML来实现MVC模式。我已经完成了概念验证,现在开始创建JavaFX用户界面。

在我之前使用spring MVC的经历中,通常会创建服务并通过注释将它们注入控制器类。但是对于JavaFX,我找不到任何建议如何做到这一点。另外我不确定我是否必须将服务放到控制器上,或者从控制器调用主类方法。第二个解决方案在主应用程序类中保存服务引用。

请注意我的应用程序在并发线程中运行服务类。所以它们都实现了Runnable接口

1 个答案:

答案 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,它讨论了与控制器中的服务进行通信的一些策略(包括处理并发问题)。