使用 Guice 将数据注入 JavaFX ViewController

Inject Data with Guice into JavaFX ViewController

今天我将 Guice 添加到我的 Java FX 应用程序中。主要目标是用注入替换我的单例并分解依赖关系。 到目前为止一切正常,这是我必须开始一个新场景的代码:

public class App extends Application{

public static void main(String[] args){
    launch(args);
}

@Override
public void start(Stage primaryStage) throws Exception {
    final String LANGUAGE_BUNDLE = "myBundlePath";
    final String FXML = "myFXMLPath";

    try {
        ResourceBundle resourceBundle = ResourceBundle.getBundle(LANGUAGE_BUNDLE, Locale.GERMAN, this.getClass().getClassLoader());
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(FXML), resourceBundle, new JavaFXBuilderFactory(), getGuiceControllerFactory());
        primaryStage.setScene(new Scene(fxmlLoader.load()));
        primaryStage.show();
    }catch (IOException ex) {
        ex.printStackTrace();
    }
}

private Callback<Class<?>, Object> getGuiceControllerFactory(){
    Injector injector = Guice.createInjector(new GuiceModule());
    return new Callback<Class<?>, Object>() {
        @Override
        public Object call(Class<?> clazz) {
            return injector.getInstance(clazz);
        }
    };
}
}

我的 Guice 模块如下所示:

public class GuiceModule extends AbstractModule {
@Override
protected void configure() {
    bind(ITester.class).to(Tester.class);
    bind(ISecondTest.class).to(SecondTest.class);
}
}

请注意,我替换了资源包和 fxml 文件的路径,因为它们会暴露我的身份。但是加载和一切正常,所以这应该不是问题 ;)

现在的问题是,我想通过在不同视图中单击按钮来实例化一个新视图。第二个视图应该显示视图 1 中数据的更详细版本。 我需要传递给第二个视图的所有内容都是整数(或 int),但我完全不知道如何执行此操作。

我有标准的 FX 设置:

View.fxml (with a reference to the ViewController)
ViewController.java
Model.java
ViewModel.java

然后 ViewController 绑定到 ViewModel 提供的属性。 我需要 int 来选择正确的型号。

我能找到的所有内容都是关于注释的@Named,但据我所知,这不能用于注入动态数据。

你能给我提示一下我想做的这个叫什么吗? 或者长话短说:如何在一秒钟内注入一个由不同视图选择的变量 ViewController,然后将其余部分保留在标准 FX 方式中?

感谢任何帮助并提前致谢!

此致,克里斯蒂安

经过多方尝试,看来我自己找到了解决办法! 但是,它 "feels" 我正在做的事情很丑陋,所以我想得到一些确认 ;)

首先是理论:Guice 支持 "AssistedInject"。这是当 class 不能由默认构造函数构造时。为了能够使用 AssistedInject,您必须下载扩展程序(我从 maven repository 下载了 jar)。

AssistedInject 为您做的是它允许您指定一个工厂来为您构建变量。所以这就是我所做的:

首先,为稍后要使用的 class 创建一个接口,在我的例子中:

public interface IViewController {
}

其次,为工厂创建一个接口。重要提示:您不必实施工厂

public interface IControllerFactory {

    ViewController create(@Assisted int myInt);

}

第三,将具有相应参数的构造函数添加到您稍后要实例化的class,并让它实现您创建的接口,如下所示:

public class ViewController implements IViewController{

@AssistedInject
public ViewController(@Assisted int i){
    final String LANGUAGE_BUNDLE = "languageBundle";
    final String FXML = "View.fxml";

    try{
        ResourceBundle resourceBundle = ResourceBundle.getBundle(LANGUAGE_BUNDLE, Locale.GERMAN, this.getClass().getClassLoader());
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(FXML), resourceBundle, new JavaFXBuilderFactory());
        fxmlLoader.setController(this);
        Stage second = new Stage();
        second.setScene(new Scene(fxmlLoader.load()));
        second.show();
    }catch (IOException e){
        e.printStackTrace();
    }
    System.out.println("ViewController constructor called with: " + i);
}

这里有几点需要注意:

  • 方法的注解“@AssistedInject”
  • 我们要对外提供的参数的注解“@Assisted”
  • 我们手动为装载机设置控制器(使用 fxmlLoader.setController(this);)
  • 我必须删除 fxml 文件中的控制器配置,所以 fxml 中没有 "fx:controller"!

接下来我们需要将一个变量添加到 class 从我们想要实例化另一个 class:

@Inject
IControllerFactory controllerFactory;

我们可以像这样在class中使用它:

controllerFactory.create(3)

注意:我们调用了 ViewController class 中从未实现过的方法 "create"! Guice 知道它必须调用构造函数 - magic

作为最后一步,我们需要在我们的 GuiceModule 中添加到上下文的连接,如下所示:

@Override
protected void configure(){
    install(new FactoryModuleBuilder()
            .implement(IPagingDirectoryViewController.class, PagingDirectoryViewController.class)
            .build(IPagingDirectoryControllerFactory.class));
}

注意 我收到错误:无法解析方法 'implement java.lang.Class<"The interface class">, java.lang.Class<"The implementing class">'。这是因为我忘记让我的控制器 class 实现接口。

好的,这就是我让它工作的方式。 然而,正如我所说,我很乐意听取一些意见!

此致,克里斯蒂安

在您的模块配置中,您可以简单地为 FXMLLoader 添加一个 Provider 方法,在该方法中您将 Guices 'injector.getInstance()' 指定为加载器的 ControllerFactory。

@Provides
public FXMLLoader getFXMLLoader(com.google.inject.Injector injector) {
    FXMLLoader loader = new FXMLLoader();

    loader.setControllerFactory(injector::getInstance);
    return loader;
}

您现在要做的就是在模块配置的 configure() 方法中绑定 ViewController。

    // for example:
    bind(ViewController.class);

并确保控制器 class 已正确绑定到您的 fxml 文件中。

fx:controller="your.package.ViewController"

现在您只需使用注入器来获取 FXMLLoader 的实例。