如何在JavaFX控制器中使用Guice?

7

我有一个JavaFX应用程序,想要引入Guice,因为我的代码现在充满了工厂,仅用于测试目的。

我有一个使用案例,在这个案例中,我有一个特定视图的控制器类。这个控制器类有一个视图模型,并通过控制器类的构造函数将模型传递给视图模型。

在控制器类中,我有一个联系人服务对象,提供编辑/保存/删除操作。目前,我有该对象的接口并提供实现和模拟。可以通过Factory.getInstance()方法检索此对象。

我想做的是像这样的事情:

public class NewContactController implements Initializable {
    // ------------------------------------------------------------------------
    // to inject by guice
    // ------------------------------------------------------------------------
    private ContactService contactService;

    @Inject
    public void setContactService(ContactService srv) {
        this.contactService = srv;
    }
    // edit window
    public NewContactController(Contact c) {
        this.viewModel = new NewContactViewModel(c);
    }
    // new window
    public NewContactController() {
        this.viewModel = new NewContactViewModel();
    }

    @FXML
    void onSave(ActionEvent event) {
        //do work like edit a contcat, 
        contactService.editContact(viewModel.getModelToSave());
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        // bind to viewmodel---------------------------------------------------
    }
}

我该如何实现这个功能呢?做类似的事情是一个好主意吗? 当我在寻找解决方案时,我发现了fx-guice和类似的框架,但是如何将它们结合起来呢? 特别是如何让这些字段被注入并自己实例化控制器,或者至少给它一些构造函数参数?
4个回答

18

我没有使用Guice,但最简单的方法似乎是只需在FXMLLoader上使用控制器工厂。您可以创建一个控制器工厂,指示FXMLLoader使用Guice来初始化您的控制器:

final Injector injector = Guice.createInjector(...);
FXMLLoader loader = new FXMLLoader(getClass().getResource(...));
loader.setControllerFactory(new Callback<Class<?>, Object>() {
   @Override
   public Object call(Class<?> type) {
       return injector.getInstance(type);
   }
});
// In Java 8, replace the above with 
// loader.setControllerFactory(injector::getInstance);
Parent root = loader.<Parent>load();

1
好的,按照定义,我必须让任何框架来实例化控制器吗?我能否将任何参数传递给构造函数中的控制器呢?使用Afterburner、Guice或Spring,都可以吗?我的意思是,如果我的控制器从一个框架类继承,就像在Roboguice中一样,那么我需要如何编写代码呢? - simonides
3
很棒的答案,只想提一下在Java 8中可以将其作为一行代码完成:loader.setControllerFactory(injector::getInstance); - uesports135
请明确一下,每个控制器类都需要一个新的加载器吗? - Evvo

2

有一个很好的JavaFX DI框架叫做afterburner.fx。去看一下吧,我认为这是你正在寻找的工具。


1
afterburner.fx非常出色,如果您还没有完全承诺于Guice的话。 - James_D
1
afterburner.fx的最新版本已经将近4年没有更新了(2016年4月)。尽管Guice距离上次发布已经有1年左右的时间,但它也拥有更多的社区支持。 - Evvo
@Evvo 我不能代表这个库的创作者Adam说话,但我认为他会欢迎任何外部贡献。 - Alex

0

假设您可以手动/使用Guice实例化控制器而不是从FXML中实例化,如果需要将任何非DIable参数传递给构造函数,则可以使用https://github.com/google/guice/wiki/AssistedInject。然后,您将使用.setController(this)手动设置控制器到FXMLLoader中,并在控制器的构造函数中加载FXML文件。

不确定是否存在任何缺点,但这种系统似乎对我有效 :)


0

如何使用JavaFxGuice

  1. 扩展javafx.application.Application并从主方法调用launch方法。这是应用程序的入口点。
  2. 实例化您选择的依赖注入容器。例如Google Guice或Weld。
  3. 在应用程序的start方法中,实例化FXMLLoader并将其控制器工厂设置为从容器中获取控制器。最好使用提供程序从容器本身获取FXMLLoader,然后给它一个.fxml文件资源。这将创建新创建窗口的内容。
  4. 将在上一步中实例化的Parent对象提供给Stage对象(通常称为primaryStage),作为start(Stage primaryStage)方法的参数。
  5. 通过调用其show()方法显示primaryStage。

代码示例 MyApp.java

public class MyApp extends Application {
    @Override
    public void start(Stage primaryStage) throws IOException {
        Injector injector = Guice.createInjector(new GuiceModule());
        //The FXMLLoader is instantiated the way Google Guice offers - the FXMLLoader instance
        // is built in a separated Provider<FXMLLoader> called FXMLLoaderProvider.
        FXMLLoader fxmlLoader = injector.getInstance(FXMLLoader.class);

        try (InputStream fxmlInputStream = ClassLoader.getSystemResourceAsStream("fxml\\login.fxml")) {
            Parent parent = fxmlLoader.load(fxmlInputStream);
            primaryStage.setScene(new Scene(parent));
            primaryStage.setTitle("Access mini Stock App v1.1");
            primaryStage.show();
            primaryStage.setOnCloseRequest(e -> {
                System.exit(0);
            });
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public static void main(String[] args) {

        launch();
    }

}

然后是FXMLLoaderProvider.java

public class FXMLLoaderProvider implements Provider<FXMLLoader> {
    
        @Inject Injector injector;
    
        @Override
        public FXMLLoader get() {
            FXMLLoader loader = new FXMLLoader();
            loader.setControllerFactory(p -> {
                return injector.getInstance(p);
            });
            return loader;
        }
    
    }

感谢 Pavel Pscheidl 先生在 Integrating JavaFX 8 with Guice 中提供给我们这个智能代码。


我担心你从Pavel的帖子中复制了错误的代码。你需要设置控制器工厂的代码。你上面的例子没有将Guice添加到FXML加载器或控制器中。 - Peter Paul Kiefer
@PeterPaulKiefer,我没有注意到这一点,现在我已经用自己的工作代码编辑了示例,非常感谢你。 - Ismail

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接